SQL - 从数组中获取特定元素

SQL - Get specific element from an array

假设我有 2 个看起来像数组的变量:

declare @code nvarchar(200) = 
',10501,10203,10491,10490,10091,10253,10008,10020,10570,10499,';
declare @value nvarchar(200) = 
'True~~100000006~Digital~0~0~~1388.76~Completed~True';

我需要查找 @code 是否包含 10490(例如),如果包含,我需要在 @value 变量中找到相应的值(通过其索引)将是 Digital,因为 10490@code 数组中的第 4 个元素,而 @value 数组的第 4 个元素是 Digital(注意 [= 的第二个元素14=] 数组为 NULL。

免责声明@code 数组将始终包含唯一值。例如,不可能有超过 1 个 10490@code 数组总是以','开始和结束。 如果您从 @code 变量中去掉第一个和最后一个逗号,@code@value 中的元素数量将始终相同。 我不能使用函数或存储过程,所以一切都需要作为 1 个查询的一部分完成。

这里有两种可能。在你的情况下,我什至会尝试将它合并到一个 WHILE 循环中。

SQL服务器2016及以上

(兼容级别 130 及以上)您可以使用内置函数 STRING_SPLIT

DECLARE @code nvarchar(200) = 
',10501,10203,10491,10490,10091,10253,10008,10020,10570,10499,';
DECLARE @value nvarchar(200) = 
'True~~100000006~Digital~0~0~~1388.76~Completed~True';

DECLARE @valuetosearch nvarchar(200) = '10490'

SELECT value FROM 
(
  SELECT value ,ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS 'idx'
  FROM STRING_SPLIT ( @value , '~' )
) AS x2
WHERE x2.idx =
  (
    SELECT idx-1 FROM
    ( 
      SELECT value ,ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS 'idx'
      FROM STRING_SPLIT ( @code , ',' ) 
    ) AS x1
  WHERE x1.[value] = @valuetosearch
)

对于 SQL 服务器的早期版本:

DECLARE @code nvarchar(200) = 
',10501,10203,10491,10490,10091,10253,10008,10020,10570,10499,';
DECLARE @value nvarchar(200) = 
'True~~100000006~Digital~0~0~~1388.76~Completed~True';

DECLARE @valuetosearch nvarchar(200) = '10490'

DECLARE @codetbl AS TABLE (idx int IDENTITY(1,1)
  ,code nvarchar(200))
DECLARE @valuetbl AS TABLE (idx int IDENTITY(1,1)
  ,value nvarchar(200))

DECLARE @name nvarchar(200)
DECLARE @pos int

WHILE CHARINDEX(',', @code) > 0
 BEGIN
  SELECT @pos  = CHARINDEX(',', @code)
  SELECT @name = SUBSTRING(@code, 1, @pos-1)

  INSERT INTO @codetbl
  SELECT @name

  SELECT @code = SUBSTRING(@code, @pos+1, LEN(@code)-@pos)
END

INSERT INTO @codetbl
SELECT @code


WHILE CHARINDEX('~', @value) > 0
 BEGIN
  SELECT @pos  = CHARINDEX('~', @value)
  SELECT @name = SUBSTRING(@value, 1, @pos-1)

  INSERT INTO @valuetbl
  SELECT @name

  SELECT @value = SUBSTRING(@value, @pos+1, LEN(@value)-@pos)
END

INSERT INTO @valuetbl
SELECT @value


SELECT value FROM @valuetbl
WHERE idx = (SELECT idx-1 FROM @codetbl WHERE code = @valuetosearch)

当找不到@tofind 时,您可能需要添加一些代码

declare @code nvarchar(200) = 
',10501,10203,10491,10490,10091,10253,10008,10020,10570,10499,';
declare @value nvarchar(200) = 
'True~~100000006~Digital~0~0~~1388.76~Completed~True';

declare @tofind nvarchar(200) = '10490';
--select left(@code,CHARINDEX(@tofind,@code)) 
--select len(left(@code,CHARINDEX(@tofind,@code))) -  LEN( REPLACE(  left(@code,CHARINDEX(@tofind,@code)) , ',', ''))
declare @nth int;
set @nth =  len(left(@code,CHARINDEX(@tofind,@code))) -  LEN( REPLACE(  left(@code,CHARINDEX(@tofind,@code)) , ',', ''))

declare @SplitOn nvarchar = '~';
declare @RowData nvarchar(200) = @value + '~';

declare @Cnt int = 1
    While (Charindex(@SplitOn,@RowData)>0) and @Cnt < @nth 
    Begin
        Set @RowData = Substring(@RowData,Charindex(@SplitOn,@RowData)+1,len(@RowData))
        Set @Cnt = @Cnt + 1
    End
Select --Data = ltrim(rtrim(@RowData)),
Case when ltrim(rtrim(@RowData)) = '' then null else
    LEFT(ltrim(rtrim(@RowData)) , CHARINDEX('~',ltrim(rtrim(@RowData))) -1)
end as Result

我想你知道,这是一个非常糟糕的设计......如果你能改变它,你真的应该。但这可以解决:

declare @code nvarchar(200) = 
',10501,10203,10491,10490,10091,10253,10008,10020,10570,10499,';
declare @value nvarchar(200) = 
'True~~100000006~Digital~0~0~~1388.76~Completed~True';

--查询会将两个字符串转换为可拆分的 XML
-- query('/x[text()]') 将删除空条目(前导和尾随逗号)
--(...假设 @code 中永远不会有空条目)
--然后它将从两个
中读取一个派生的编号列表 -- 最后它将加入两个列表的 PartIndex

WITH Casted AS
(
    SELECT CAST('<x>' + REPLACE(@code,',','</x><x>') + '</x>' AS XML).query('/x[text()]') AS CodeXml
          ,CAST('<x>' + REPLACE(@value,'~','</x><x>') + '</x>' AS XML) AS ValueXml
)
,CodeDerived AS
(
    SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS PartIndex
          ,x.value('text()[1]','nvarchar(max)') AS CodePart
    FROM Casted
    CROSS APPLY CodeXml.nodes('/x') A(x)
)
,ValueDerived AS
(
    SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS PartIndex
          ,x.value('text()[1]','nvarchar(max)') AS ValuePart
    FROM Casted
    CROSS APPLY ValueXml.nodes('/x') A(x)
)
SELECT cd.PartIndex
      ,CodePart
      ,ValuePart
FROM CodeDerived cd
INNER JOIN ValueDerived vd ON cd.PartIndex=vd.PartIndex

结果

inx     CodePart    ValuePart
1       10501       True
2       10203       NULL
3       10491       100000006
4       10490       Digital
5       10091       0
6       10253       0
7       10008       NULL
8       10020       1388.76
9       10570       Completed
10      10499       True

只需添加一个简单的 WHERE 即可将其减少到您需要的值。

免责声明:不能保证 ROW_NUMBERORDER BY (SELECT NULL) 的编号将永远 return 正确的顺序,但为了更好的机会,您需要 SQL 服务器 2016+。

这应该很简单。如果性能很重要,我建议使用 DelimitedSplit8K 拆分字符串。这是一个简单的高性能解决方案:

DECLARE @searchFor INT = 10490;

SELECT code = s1.item, s2.item
FROM dbo.DelimitedSplit8K(@code,',')  s1
JOIN dbo.DelimitedSplit8K(@value,'~') s2 ON s2.ItemNumber = s1.ItemNumber-1
WHERE s1.Item = @searchFor;

结果:

code       item
---------- ------------
10490      Digital