Reputation: 149
Suppose I have 2 variables that look like an array:
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';
I need to find if @code
contains 10490
(for example) and if it does, I need to find a corresponding value (by its index) in @value
variable which would be Digital
since 10490
is the 4th element in @code
array and 4th element of @value
array is Digital
(note that the 2nd element of the @value
array is NULL.
Disclaimer:
@code
array will ALWAYS contain unique values. It's not possible to have more than 1 10490
for example.
@code
array will always start and end with ','.
Number of elements in @code
and @value
will always be the same if you take 1st and last comma off the @code
variable.
I cannot use functions or stored procedures, so everything needs to be done as part of 1 query.
Upvotes: 1
Views: 16979
Reputation: 7918
This should be quite simple. If performance is important I would suggest splitting the strings using DelimitedSplit8K. Here's a simple, high-performing solution:
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;
Results:
code item
---------- ------------
10490 Digital
Upvotes: 0
Reputation: 67311
I think you know, that this is a very bad design... If you can change this, you really should. But this can be solved:
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';
--The query will cast both strings to a splittable XML
--The query('/x[text()]')
will remove empty entries (leading and trailing comma)
--(...assuming there will never be an empty entry in @code
)
--Then it will read a derived numbered list from both
--finally it will join both lists on their 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
The result
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
Just add a simple WHERE
to reduce this to the one value you need.
Disclaimer: it is not guaranteed, that the numbering with ROW_NUMBER
and ORDER BY (SELECT NULL)
will ever return the correct sequence, but for a better chance you'd need SQL Server 2016+. For more details: read this link and the other contributions there
Upvotes: 1
Reputation: 1959
You may need to add some code for when @tofind is not found
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
Upvotes: 1
Reputation: 740
Here are two possibilities. In your case I would even try to merge it into one WHILE
loop.
(compatibility level 130 and up) you can use built in function 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
)
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)
Upvotes: 1