need to get description of id from another table which are pipe separated - sql

I have two tables table1 and table2
in table1 there is a column with name typeids in which ids are pipe separated
ex: 2|3|4 --> these ids are the primary key in table2
table2 contains Id, Description which has data like
2-text1
3-text2
4-text3
now I need to get the table1 contents but 2|3|4 will be replaced by
text1|text2|text3

This is a really poor design of your database and as others have said you should do your level best to get it changed.
That said, this is possible. It is just ugly as sin and I am sure performs like a dog, but you can blame that on your database designer. In short, you need to split your id string on the | character, join each element to your table2 and then concatenate them all back together using for xml. As you are using SQL Server 2016 you can use STRING_SPLIT instead of the function I have used below, though as I don't currently have access to a 2016 box here we are (Working example):
create function dbo.StringSplit
(
#str nvarchar(4000) = ' ' -- String to split.
,#delimiter as nvarchar(1) = ',' -- Delimiting value to split on.
,#num as int = null -- Which value to return.
)
returns table
as
return
(
-- Start tally table with 10 rows.
with n(n) as (select n from (values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n(n))
-- Select the same number of rows as characters in isnull(#str,'') as incremental row numbers.
-- Cross joins increase exponentially to a max possible 10,000 rows to cover largest isnull(#str,'') length.
,t(t) as (select top (select len(isnull(#str,'')) a) row_number() over (order by (select null)) from n n1,n n2,n n3,n n4)
-- Return the position of every value that follows the specified delimiter.
,s(s) as (select 1 union all select t+1 from t where substring(isnull(#str,''),t,1) = #delimiter)
-- Return the start and length of every value, to use in the SUBSTRING function.
-- ISNULL/NULLIF combo handles the last value where there is no delimiter at the end of the string.
,l(s,l) as (select s,isnull(nullif(charindex(#delimiter,isnull(#str,''),s),0)-s,4000) from s)
select rn as ItemNumber
,Item
from(select row_number() over(order by s) as rn
,substring(isnull(#str,''),s,l) as item
from l
) a
where rn = #num -- Return a specific value where specified,
or #num is null -- Or everything where not.
)
go
declare #t1 table (id varchar(10));
insert into #t1 values
('2|3|4')
,('5|6|7');
declare #t2 table (id varchar(1), description varchar(10));
insert into #t2 values
('2','text1')
,('3','text2')
,('4','text3')
,('5','text4')
,('6','text5')
,('7','text6')
;
select t1.id
,stuff((select '|' + t2.description
from #t1 as t1a
cross apply dbo.StringSplit(t1a.id,'|',null) as s
join #t2 as t2
on s.Item = t2.id
where t1.id = t1a.id
for xml path('')
),1,1,''
) as t
from #t1 as t1;
Output:
+-------+-------------------+
| id | t |
+-------+-------------------+
| 2|3|4 | text1|text2|text3 |
| 5|6|7 | text4|text5|text6 |
+-------+-------------------+

Related

Joining sql tables with no common columns without ordering

I have my data in a form of 2 coma separated strings
DECLARE #ids nvarchar(max) = '1,2,3'
DECLARE #guids nvarchar(max) =
'0000000-0001-0000-0000-000000000000,
`0000000-0022-0000-0000-000000000000`,
`0000000-0013-0000-0000-000000000000'`
I need them in a table as separate columns based on their position in the string
Table1
| Id | Guid |
| 1 | 0000000-0001-0000-0000-000000000000 |
| 2 | 0000000-0022-0000-0000-000000000000 |
| 3 | 0000000-0013-0000-0000-000000000000 |
I can split both strings into separate tables by using
DECLARE #split_ids
(value nvarchar(max))
DECLARE #xml xml
SET #xml = N'<root><r>' + replace(#ids, ',' ,'</r><r>') + '</r></root>'
INSERT INTO #split_ids(Value)
SELECT r.value('.','nvarchar(max)')
FROM #xml.nodes('//root/r') as records(r)
I've tried
SELECT t1.*, t2.*
FROM (SELECT t1.*, row_number() OVER (ORDER BY [Value]) as seqnum
from cte_Ids t1
) t1 FULL OUTER JOIN
(SELECT t2.*, row_number() OVER (ORDER BY [Value]) as seqnum
from cte_barcodes t2
) t2
ON t1.seqnum = t2.seqnum;
But that orders the tables by Value and my data is random and can't be ordered.
Is there a way of joining tables based on their row numbers without ordering them first?
Or is there another way of inserting data from a string to a table?
You do not need to split and/or insert the input data into separate tables. In this situation you simply need to parse the input strings and get the substrings and their ordinal positions (an XML-based approach or a splitter function are possible solutions).
But if you use SQL Server 2016+, a JSON-based approach is also an option. The idea is to transform the strings into valid JSON arrays (1,2,3 into [1,2,3]), parse the arrays with OPENJSON() and join the tables returned from OPENJSON() calls. As is explained in the documentation, the columns that OPENJSON() function returns (when the default schema is used) are key, value and type and in case of JSON array, the key column holds the index of the element in the specified array.
DECLARE #ids nvarchar(max) = N'1,2,3'
DECLARE #guids nvarchar(max) = N'0000000-0001-0000-0000-000000000000,0000000-0022-0000-0000-000000000000,0000000-0013-0000-0000-000000000000'
SELECT j1.[value] AS Id, j2.[value] AS Guid
FROM OPENJSON(CONCAT('[', #ids, ']')) j1
JOIN OPENJSON(CONCAT('["', REPLACE(#guids, ',', '","'), '"]')) j2 ON j1.[key] = j2.[key]
Result:
Id Guid
1 0000000-0001-0000-0000-000000000000
2 0000000-0022-0000-0000-000000000000
3 0000000-0013-0000-0000-000000000000
You need row numbering over initial order, this means that you should use some constant expression in window function order_by clause.
SQL server does not allow use constants directly, but over(order_by (select 1)) is allowed:
SELECT t1.*, t2.*
FROM (SELECT t1.*, row_number() OVER (ORDER BY (select 1)) as seqnum
from cte_Ids t1
) t1 FULL OUTER JOIN
(SELECT t2.*, row_number() OVER (ORDER BY (select 1)) as seqnum
from cte_barcodes t2
) t2
ON t1.seqnum = t2.seqnum;
Note that this doesn't guarantee initial order (it will be unspecified), but often it behaves correctly :)
One of solutions is to parse your comma separated values in a loop (using WHILE) from both variables. Then you could insert those extracted in the same iteration values at once as one row to a table.
One solution uses recursive CTEs:
with cte as (
select cast(null as nvarchar(max)) as id, cast(null as nvarchar(max)) as guid, #ids + ',' as rest_ids, #guids + ',' as rest_guids, 0 as lev
union all
select left(rest_ids, charindex(',', rest_ids) - 1),
left(rest_guids, charindex(',', rest_guids) - 1),
stuff(rest_ids, 1, charindex(',', rest_ids), ''),
stuff(rest_guids, 1, charindex(',', rest_guids), ''),
lev + 1
from cte
where rest_ids <> ''
)
select id, guid
from cte
where lev > 0;
Here is a db<>fiddle.

How to SELECT string between second and third instance of ",,"?

I am trying to get string between second and third instance of ",," using SQL SELECT.
Apparently functions substring and charindex are useful, and I have tried them but the problem is that I need the string between those specific ",,"s and the length of the strings between them can change.
Can't find working example anywhere.
Here is an example:
Table: test
Column: Column1
Row1: cat1,,cat2,,cat3,,cat4,,cat5
Row2: dogger1,,dogger2,,dogger3,,dogger4,,dogger5
Result: cat3dogger3
Here is my closest attempt, it works if the strings are same length every time, but they aren't:
SELECT SUBSTRING(column1,LEN(LEFT(column1,CHARINDEX(',,', column1,12)+2)),LEN(column1) - LEN(LEFT(column1,CHARINDEX(',,', column1,20)+2)) - LEN(RIGHT(column1,CHARINDEX(',,', (REVERSE(column1)))))) AS column1
FROM testi
Just repeat sub-string 3 times, each time moving onto the next ",," e.g.
select
-- Substring till the third ',,'
substring(z.col1, 1, patindex('%,,%',z.col1)-1)
from (values ('cat1,,cat2,,cat3,,cat4,,cat5'),('dogger1,,dogger2,,dogger3,,dogger4,,dogger5')) x (col1)
-- Substring from the first ',,'
cross apply (values (substring(x.col1,patindex('%,,%',x.col1)+2,len(x.col1)))) y (col1)
-- Substring from the second ',,'
cross apply (values (substring(y.col1,patindex('%,,%',y.col1)+2,len(y.col1)))) z (col1);
And just to reiterate, this is a terrible way to store data, so the best solution is to store it properly.
Here is an alternative solution using charindex. The base idea is the same as in Dale K's an answer, but instead of cutting the string, we specify the start_location for the search by using the third, optional parameter, of charindex. This way, we get the location of each separator, and could slip each value off from the main string.
declare #vtest table (column1 varchar(200))
insert into #vtest ( column1 ) values('dogger1,,dogger2,,dogger3,,dogger4,,dogger5')
insert into #vtest ( column1 ) values('cat1,,cat2,,cat3,,cat4,,cat5')
declare #separetor char(2) = ',,'
select
t.column1
, FI.FirstInstance
, SI.SecondInstance
, TI.ThirdInstance
, iif(TI.ThirdInstance is not null, substring(t.column1, SI.SecondInstance + 2, TI.ThirdInstance - SI.SecondInstance - 2), null)
from
#vtest t
cross apply (select nullif(charindex(#separetor, t.column1), 0) FirstInstance) FI
cross apply (select nullif(charindex(#separetor, t.column1, FI.FirstInstance + 2), 0) SecondInstance) SI
cross apply (select nullif(charindex(#separetor, t.column1, SI.SecondInstance + 2), 0) ThirdInstance) TI
For transparency, I saved the separator string in a variable.
By default the charindex returns 0 if the search string is not present, so I overwrite it with the value null, by using nullif
IMHO, SQL Server 2016 and its JSON support in the best option here.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, Tokens VARCHAR(500));
INSERT INTO #tbl VALUES
('cat1,,cat2,,cat3,,cat4,,cat5'),
('dogger1,,dogger2,,dogger3,,dogger4,,dogger5');
-- DDL and sample data population, end
WITH rs AS
(
SELECT *
, '["' + REPLACE(Tokens
, ',,', '","')
+ '"]' AS jsondata
FROM #tbl
)
SELECT rs.ID, rs.Tokens
, JSON_VALUE(jsondata, '$[2]') AS ThirdToken
FROM rs;
Output
+----+---------------------------------------------+------------+
| ID | Tokens | ThirdToken |
+----+---------------------------------------------+------------+
| 1 | cat1,,cat2,,cat3,,cat4,,cat5 | cat3 |
| 2 | dogger1,,dogger2,,dogger3,,dogger4,,dogger5 | dogger3 |
+----+---------------------------------------------+------------+
It´s the same as #"Yitzhak Khabinsky" but i think it looks clearer
WITH CTE_Data
AS(
SELECT 'cat1,,cat2,,cat3,,cat4,,cat5' AS [String]
UNION
SELECT 'dogger1,,dogger2,,dogger3,,dogger4,,dogger5' AS [String]
)
SELECT
A.[String]
,Value3 = JSON_VALUE('["'+ REPLACE(A.[String], ',,', '","') + '"]', '$[2]')
FROM CTE_Data AS A

Split a column with comma delimiter

I have a table with 3 columns with the data given below.
ID | Col1 | Col2 | Status
1 8007590006 8002240001,8002170828 I
2 8002170828 8002000004 I
3 8002000001 8002240001 I
4 8769879809 8002000001 I
5 8769879809 8002000001 I
Col2 can contain multiple comma delimited values. I need to update status to C if there is a value in col2 that is also present in col1.
For example, for ID = 1, col2 contains 8002170828 which is present in Col1, ID = 2. So, status = 'C'
From what I tried, I know it won't work where there are multiple values as I need to split that data and get individual values and then apply update.
UPDATE Table1
SET STATUS = 'C'
WHERE Col1 IN (SELECT Col2 FROM Table1)
If you are using SQL Server 2016 or later, then STRING_SPLIT comes in handy:
WITH cte AS (
SELECT ID, Col1, value AS Col2
FROM Table1
CROSS APPLY STRING_SPLIT(Col2, ',')
)
UPDATE t1
SET Status = 'C'
FROM Table1 t1
INNER JOIN cte t2
ON t1.Col1 = t2.Col2;
Demo
This answer is intended as a supplement to Tim's answer
As you don't have the native string split that came in 2016 we can make one:
CREATE FUNCTION dbo.STRING_SPLIT
(
#List NVARCHAR(MAX),
#Delimiter NVARCHAR(255)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
SELECT y.i.value('(./text())[1]', 'nvarchar(4000)') as value
FROM
(
SELECT x = CONVERT(XML, '<i>'
+ REPLACE(#List, #Delimiter, '</i><i>')
+ '</i>').query('.')
) AS a CROSS APPLY x.nodes('i') AS y(i)
);
GO
--credits to sqlserverperfomance.com for the majority of this code - https://sqlperformance.com/2012/07/t-sql-queries/split-strings
Now Tim's answer should work out for you, so I won't need to repeat it here
I chose an xml based approach because it performs well and your data seems sane and won't have any xml chars in it. If it ever will contain xml chars like > that will break the parsing they should be escaped then unescaped after split
If you aren't allowed to make functions you can extract everything between the RETURNS and the GO, insert it into Tim's query,tweak the variable names to be column names and it'll still work out

SQL How to return column value based in other substring position delimited by commas

My first post! I hope that you help me :)
I'm working in SQL 2017 and I have a table like this:
+----+------------------+------------------+
| ID | Col1 | Col2 |
+-----+------------------+------------------+
| 110 | 450,2,50,110,600 | 3,45,30,901,1001 |
| 250 | 2,250,300,1 | 1,33,540,900 |
| 45 | 1,45,320 | 200,444,600 |
+-----+------------------+------------------+
The logic is to find the ID position in Col1 and return based in that position the substring in Col2.
Example:
ID 110 match 4th position in Col1 so should return 901 value in Col2.
ID 250 match 2nd position in Col1 so should return 33 value in Col2.
ID 45 match 2nd position in Col1 so should return 400 value in Col2.
I made different attempts without any success, probably I'm in wrong direction.
Can you please help with this?
The output that I want is the specific values from Col2.
Thanks!
For SQL Server 2016+ (I'm not going to do one for earlier because of STRING_SPLIT support
DECLARE #BadDesign table (ID int, Col1 varchar(200), Col2 varchar(200));
INSERT #BadDesign VALUES
(110,'450,2,50,110,600', '3,45,30,901,1001'),
(250,'2,250,300,1', '1,33,540,900'),
(45 ,'1,45,320', '200,444,600')
SELECT
*
FROM
#BadDesign B
CROSS APPLY
(SELECT
rn = ROW_NUMBER() OVER (ORDER BY (SELECT 1)), value
FROM
STRING_SPLIT(B.Col1, ',')
) b1
CROSS APPLY
(SELECT
rn = ROW_NUMBER() OVER (ORDER BY (SELECT 1)), value
FROM
STRING_SPLIT(B.Col2, ',')
) b2
WHERE
B.ID = b1.value AND b1.rn = b2.rn
No guarantees on ROW_NUMBER consistency over the output of STRING_SPLIT.
Edit: also requires database compatibility to be 130 or above (SQL Server 2016)
The STRING_SPLIT function is available only under compatibility level
130. If your database compatibility level is lower than 130, SQL Server will not be able to find and execute STRING_SPLIT function. You
can change a compatibility level of database using the following
command: ALTER DATABASE DatabaseName SET COMPATIBILITY_LEVEL = 130
Using a Custom String Split Function (this answer is using one written by Aaron Bertrand), so not restricting the use on SQL2016+
CREATE FUNCTION dbo.SplitStringsOrdered (
#List NVARCHAR(2000)
, #Delimiter NVARCHAR(32)
)
RETURNS TABLE
AS
RETURN (
SELECT
rn = ROW_NUMBER() OVER (ORDER BY Number)
, Item
FROM
(
SELECT
Number
, Item = LTRIM(RTRIM(SUBSTRING(
#List
, Number
, CHARINDEX(#Delimiter, #List + #Delimiter, Number) - Number
)
)
)
FROM
(
SELECT ROW_NUMBER() OVER (ORDER BY [object_id])
FROM
sys.all_objects
) AS n(Number)
WHERE
Number <= CONVERT(INT, LEN(#List))
AND SUBSTRING(#Delimiter + #List, Number, LEN(#Delimiter)) = #Delimiter
) AS y
);
GO
And amending the query created by #gbn in his/her answer - is this allowed on SO?
DECLARE #BadDesign table (ID int, Col1 varchar(200), Col2 varchar(200));
INSERT #BadDesign VALUES
(110,'450,2,50,110,600', '3,45,30,901,1001'),
(250,'2,250,300,1', '1,33,540,900'),
(45 ,'1,45,320', '200,444,600')
SELECT
B.*, Col1Value=b1.Item, Cal2Value = B2.Item
FROM
#BadDesign B
CROSS APPLY
(SELECT
rn = ROW_NUMBER() OVER (ORDER BY (SELECT 1)), F.Item
FROM
dbo.SplitStringsOrdered(B.Col1, ',') F
) b1
CROSS APPLY
(SELECT
rn = ROW_NUMBER() OVER (ORDER BY (SELECT 1)), F1.Item
FROM
dbo.SplitStringsOrdered(B.Col2, ',') F1
) b2
WHERE
b1.rn = b2.rn

how to extract a particular id from the string using sql

I want to extract a particular ids from the records in a table.For example i have a below table
Id stringvalue
1 test (ID 123) where another ID 2596
2 next ID145 and the condition I(ID 635,897,900)
I want the result set as below
ID SV
1 123,2596
2 145,635,897,900
i have tried the below query which extracts only one ID from the string:
Select Left(substring(string,PATINDEX('%[0-9]%',string),Len(string)),3) from Table1
I seriously don't encourage the T-SQL approach (as SQL is not meant to do this), however, a working version is presented below -
Try this
DECLARE #T TABLE(ID INT IDENTITY,StringValue VARCHAR(500))
INSERT INTO #T
SELECT 'test (ID 123) where another ID 2596' UNION ALL
SELECT 'next ID145 and the condition I(ID 635,897,900)'
;WITH SplitCTE AS(
SELECT
F1.ID,
X.SplitData
,Position = PATINDEX('%[0-9]%', X.SplitData)
FROM (
SELECT *,
CAST('<X>'+REPLACE(REPLACE(StringValue,' ',','),',','</X><X>')+'</X>' AS XML) AS XmlFilter
FROM #T F
)F1
CROSS APPLY
(
SELECT fdata.D.value('.','varchar(50)') AS SplitData
FROM f1.xmlfilter.nodes('X') AS fdata(D)) X
WHERE PATINDEX('%[0-9]%', X.SplitData) > 0),
numericCTE AS(
SELECT
ID
,AllNumeric = LEFT(SUBSTRING(SplitData, Position, LEN(SplitData)), PATINDEX('%[^0-9]%', SUBSTRING(SplitData, Position, LEN(SplitData)) + 't') - 1)
FROM SplitCTE
)
SELECT
ID
,STUFF(( SELECT ',' + c1.AllNumeric
FROM numericCTE c1
WHERE c1.ID = c2.ID
FOR XML PATH(''),TYPE)
.value('.','NVARCHAR(MAX)'),1,1,'') AS SV
FROM numericCTE c2
GROUP BY ID
/*
Result
ID SV
1 123,2596
2 145,635,897,900
*/
However, I completely agree with #Giorgi Nakeuri. It is better to use some programming language (if you have that at your disposal) and use regular expression for the same. You can figure out that, I have used REPLACE function two times, first to replace the blank space and second to replace the commas(,).
Hope you will get some idea to move on.