Related
I am trying to split a row when the ';' character appears. I have a list with projects, where one of the columns states which district the project belongs to. However some projects appears in multiple districts and is therefore written like "1;2;3" (District 1, 2 and 3). I want to create three rows form this and split on the ';'.
The error message says:
Msg 240, Level 16, State 1, Line 13
Types don't match between the anchor and the recursive part in column "DataItem" of recursive query "tmp".
I tried the split_string, but discovered my server is 2014 and lacks compatibility.
WITH tmp(Oppdragsnr, Kommune, DataItem, Kommunenr) AS
(
SELECT
Oppdragsnr,
Kommune,
LEFT(Kommunenr, CHARINDEX(';', Kommunenr + ';') - 1),
STUFF(Kommunenr, 1, CHARINDEX(';', Kommunenr + ';'), '')
FROM
oppdragene
UNION all
SELECT
Oppdragsnr,
Kommune,
LEFT(Kommunenr, CHARINDEX(';', Kommunenr + ';') - 1),
STUFF(Kommunenr, 1, CHARINDEX(';', Kommunenr + ';'), '')
FROM
tmp
WHERE
Kommunenr > ''
)
SELECT
Oppdragsnr,
Kommune,
DataItem
FROM
tmp
ORDER BY
Oppdragsnr
I would like the output to be a new table with new rows for every project that appears in multiple districts.
You should probably CAST to INT the DataItem column in the base and recursive part of the query, the following query should work for you
;WITH tmp(Oppdragsnr, Kommune, DataItem, Kommunenr) AS
(
SELECT
Oppdragsnr,
Kommune,
CAST(LEFT(Kommunenr, CHARINDEX(';', Kommunenr + ';') - 1) AS INT),
STUFF(Kommunenr, 1, CHARINDEX(';', Kommunenr + ';'), '')
FROM
oppdragene
UNION all
SELECT
Oppdragsnr,
Kommune,
CAST(LEFT(Kommunenr, CHARINDEX(';', Kommunenr + ';') - 1) AS INT),
STUFF(Kommunenr, 1, CHARINDEX(';', Kommunenr + ';'), '')
FROM
tmp
WHERE
Kommunenr > ''
)
SELECT
Oppdragsnr,
Kommune,
DataItem
FROM
tmp
ORDER BY
Oppdragsnr
I have the following table with following data as
Tab1
FutureMISBoundaryVersion CurrentMISBoundaryVersion FutureHAMBoundaryVersion CurrentHAMBoundaryVersion
2:21,5:50,4:55,7:80,9:33 2:12,5:40,4:35,7:60,9:87 2:52,5:90,4:75,7:30,9:57 2:42,5:60,4:95,7:70,9:37
This key value pair has to be split into and the value of each key has to be inserted into another table in the following fashion
FutureMIS-OAKVersion |FutureMIS-HAMVersion |FutureMIS-DURVersion | FutureMIS-BURVersion| FutureMIS-YRTVersion |DeviceMIS-OAKVersion|DeviceMIS-HAMVersion |DeviceMIS-DURVersion| DeviceMIS-BURVersion| DeviceMIS-YRTVersion
33 | 80 | 21 | 55 | 50 | 87 | 60 |12 |35 | 40
i,e: when it finds column 'FutureMISBoundaryVersion' in tab1 then its value
'2:21,5:50,4:55,7:80,9:33' will be split and its value is inserted in such a way that the corresponding value of key 2 i,e:21 will be inserted into FutureMIS-DURVersion column.
Similarly key 5's value 50 will be inserted into FutureMIS-BURVersion column and so on for other keys
when it finds column 'CurrentMISBoundaryVersion' then
'2:12,5:40,4:35,7:60,9:87' will be split and its value is inserted in such a way that the corresponding value of key 2 i,e:12 will be inserted into CurrentMIS-DURVersion column similarly key 5's 40 value will be inserted into DeviceMIS-YRTVersion column and so on for other columns of the source table.
The table structure may extend as I have shown only 4 source table column but logic for all the columns remain same
Very funky requirements to be honest.
Please note solution below will work only in SQL Server 2016+ as I'm using JSON to parse the data. However you can write your own parser, in this case code will work in almost all versions of SQL Server.
Parse function:
CREATE FUNCTION dbo.ParseIt(#Type NVARCHAR(255),#Value NVARCHAR(MAX))
RETURNS #Parsed TABLE (Code NVARCHAR(255),Value NVARCHAR(255))
AS
BEGIN
INSERT INTO #Parsed(Code,Value)
SELECT #Type + '-' + m.Code + 'Version' AS [Code],p.[1] AS [Value]
FROM (
SELECT j.[key] AS [ID],i.[key],i.value
FROM OPENJSON('["' + REPLACE(#Value,',','","') + '"]') j
CROSS APPLY OPENJSON('[' + REPLACE(j.[value],':',',') + ']') i
) a
PIVOT(MAX(a.value) FOR a.[key] IN ([0],[1])) p
INNER JOIN ( VALUES
(2,'DUR')
,(4,'BUR')
,(5,'YRT')
,(7,'HAM')
,(9,'OAK')
) m(ID, Code) ON m.ID = p.[0]
;
RETURN;
END
Initial data:
DECLARE #Table TABLE (FutureMISBoundaryVersion NVARCHAR(MAX), CurrentMISBoundaryVersion NVARCHAR(MAX),FutureHAMBoundaryVersion NVARCHAR(MAX),CurrentHAMBoundaryVersion NVARCHAR(MAX));
INSERT INTO #Table(FutureMISBoundaryVersion,CurrentMISBoundaryVersion,FutureHAMBoundaryVersion,CurrentHAMBoundaryVersion)VALUES
('2:21,5:50,4:55,7:80,9:33','2:12,5:40,4:35,7:60,9:87','2:52,5:90,4:75,7:30,9:57','2:42,5:60,4:95,7:70,9:37')
;
The code:
SELECT COALESCE(p.[FutureMIS-OAKVersion],'') AS [FutureMIS-OAKVersion]
,COALESCE(p.[FutureMIS-HAMVersion],'') AS [FutureMIS-HAMVersion]
,COALESCE(p.[FutureMIS-DURVersion],'') AS [FutureMIS-DURVersion]
,COALESCE(p.[FutureMIS-BURVersion],'') AS [FutureMIS-BURVersion]
,COALESCE(p.[FutureMIS-YRTVersion],'') AS [FutureMIS-YRTVersion]
,COALESCE(p.[DeviceMIS-OAKVersion],'') AS [DeviceMIS-OAKVersion]
,COALESCE(p.[DeviceMIS-HAMVersion],'') AS [DeviceMIS-HAMVersion]
,COALESCE(p.[DeviceMIS-DURVersion],'') AS [DeviceMIS-DURVersion]
,COALESCE(p.[DeviceMIS-BURVersion],'') AS [DeviceMIS-BURVersion]
,COALESCE(p.[DeviceMIS-YRTVersion],'') AS [DeviceMIS-YRTVersion]
FROM (
SELECT f.Code,f.Value FROM #Table t CROSS APPLY dbo.ParseIt('FutureMIS',t.FutureMISBoundaryVersion) f
UNION ALL
SELECT f.Code,f.Value FROM #Table t CROSS APPLY dbo.ParseIt('DeviceMIS',t.CurrentMISBoundaryVersion) f
) a
PIVOT(MAX(a.Value) FOR a.Code IN ([DeviceMIS-BURVersion],[DeviceMIS-DURVersion],[DeviceMIS-HAMVersion],[DeviceMIS-OAKVersion]
,[DeviceMIS-YRTVersion],[FutureMIS-BURVersion],[FutureMIS-DURVersion],[FutureMIS-HAMVersion],[FutureMIS-OAKVersion]
,[FutureMIS-YRTVersion])) p
;
The following query will parse the comma separated string 2:21,5:50,4:55,7:80,9:33 into individual component 2:21, 5:30 etc. From there you can use similar method to extract bb from aa:bb.
Since the key-value pair is in format aa:bb, you can use datepart(hour, 'aa:bb') and datepart(minute, 'aa:bb') to extract aa and bb
; with
Tab1 as
(
select val = '2:21,5:50,4:55,7:80,9:33'
)
select t.*, k1.k, k2.k, k3.k, k4.k, k5.k
from Tab1 t
cross apply
(
select i = charindex(',', t.val),
k = substring(t.val, 1, charindex(',', t.val + ',', 1) - 1)
) k1
cross apply
(
select i = charindex(',', t.val, k1.i + 1),
k = substring(t.val, k1.i + 1, charindex(',', t.val + ',', k1.i + 1) - k1.i - 1)
) k2
cross apply
(
select i = charindex(',', t.val, k2.i + 1),
k = substring(t.val, k2.i + 1, charindex(',', t.val + ',', k2.i + 1) - k2.i - 1)
) k3
cross apply
(
select i = charindex(',', t.val, k3.i + 1),
k = substring(t.val, k3.i + 1, charindex(',', t.val + ',', k3.i + 1) - k3.i - 1)
) k4
cross apply
(
select i = charindex(',', t.val, k4.i + 1),
k = substring(t.val, k4.i + 1, charindex(',', t.val + ',', k4.i + 1) - k4.i - 1)
) k5
This is a pain in SQL Server. You can do this with recursive CTEs:
with cte as (
select convert(varchar(max), left(FutureMISBoundaryVersion, charindex(',', FutureMISBoundaryVersion) - 1)) as FutureMISBoundaryVersion,
convert(varchar(max), left(CurrentMISBoundaryVersion, charindex(',', CurrentMISBoundaryVersion) - 1)) as CurrentMISBoundaryVersion,
convert(varchar(max), left(FutureHAMBoundaryVersion, charindex(',', FutureHAMBoundaryVersion) - 1)) as FutureHAMBoundaryVersion,
convert(varchar(max), left(CurrentHAMBoundaryVersion, charindex(',', FutureMISBoundaryVersion) - 1)) as CurrentHAMBoundaryVersion,
stuff(FutureMISBoundaryVersion, 1, charindex(',', FutureMISBoundaryVersion), '') + ',' as FutureMISBoundaryVersion_rest,
stuff(CurrentMISBoundaryVersion, 1, charindex(',', CurrentMISBoundaryVersion), '') + ',' as CurrentMISBoundaryVersion_rest,
stuff(FutureHAMBoundaryVersion, 1, charindex(',', FutureHAMBoundaryVersion), '') + ',' as FutureHAMBoundaryVersion_rest,
stuff(CurrentHAMBoundaryVersion, 1, charindex(',', CurrentHAMBoundaryVersion), '') + ',' as CurrentHAMBoundaryVersion_rest,
1 as lev
from t
union all
select convert(varchar(max), left(FutureMISBoundaryVersion_rest, charindex(',', FutureMISBoundaryVersion_rest) - 1)) as FutureMISBoundaryVersion,
convert(varchar(max), left(CurrentMISBoundaryVersion_rest, charindex(',', CurrentMISBoundaryVersion_rest) - 1)) as CurrentMISBoundaryVersion,
convert(varchar(max), left(FutureHAMBoundaryVersion_rest, charindex(',', FutureHAMBoundaryVersion_rest) - 1)) as FutureHAMBoundaryVersion,
convert(varchar(max), left(CurrentHAMBoundaryVersion_rest, charindex(',', CurrentHAMBoundaryVersion_rest) - 1)) as CurrentHAMBoundaryVersion,
stuff(FutureMISBoundaryVersion_rest, 1, charindex(',', FutureMISBoundaryVersion_rest), '') as FutureMISBoundaryVersion_rest,
stuff(CurrentMISBoundaryVersion_rest, 1, charindex(',', CurrentMISBoundaryVersion_rest), '') as CurrentMISBoundaryVersion_rest,
stuff(FutureHAMBoundaryVersion_rest, 1, charindex(',', FutureHAMBoundaryVersion_rest), '') as FutureHAMBoundaryVersion_rest,
stuff(CurrentHAMBoundaryVersion_rest, 1, charindex(',', CurrentHAMBoundaryVersion_rest), '') as CurrentHAMBoundaryVersion_rest,
lev + 1
from cte
where FutureMISBoundaryVersion_rest like '%,%'
)
select FutureMISBoundaryVersion, CurrentMISBoundaryVersion, FutureHAMBoundaryVersion, CurrentHAMBoundaryVersion, lev
from cte;
Here is a db<>fiddle.
In SQL Server I have a field that has delimited data (by space) in it.
E.g.
recid| Delimited data field
1| 1 2 3 4 5
2| 1 2 3 3 5
3| 1 1 1 1 1
I need to loop through all the records in the DB and interrogate the delimited data field and compare the third and fourth parts of data against each other and if they match, return the recid and the whole delimited field.
So from my example records 2 and 3 have matching data parts, so it would return:-
2|1 2 3 3 5
3|1 1 1 1 1
Because 3 3 matches, as does 1 1.
Thanks.
If it is always 1 digit and same format, you can try like following.
select * from #table
where SUBSTRING([data], 5, 1) = SUBSTRING([data], 7, 1)
If not (Numbers are not single digit), you can try like following.
;WITH cte
AS (SELECT F1.recid,
F1.[data],
O.splitdata,
Row_number()
OVER(
partition BY recid
ORDER BY (SELECT 1)) rn
FROM (SELECT *,
Cast('<X>' + Replace(F.data, ' ', '</X><X>') + '</X>' AS
XML)
AS
xmlfilter
FROM #table F)F1
CROSS apply (SELECT fdata.d.value('.', 'varchar(50)') AS
splitdata
FROM f1.xmlfilter.nodes('X') AS fdata(d)) O)
SELECT c1.recid,
c1.data
FROM cte c1
INNER JOIN cte c2
ON c1.recid = c2.recid
AND c1.rn = 3
AND c2.rn = 4
AND c1.splitdata = c2.splitdata
GROUP BY c1.recid,
c1.data
Online Demo
Need to split the data, give the row number and then compare.
Schema:
SELECT * INTO #TAB FROM (
SELECT 1, '1 2 3 4 5' UNION ALL
SELECT 2, '1 2 3 3 5' UNION ALL
SELECT 3, '1 1 1 1 1'
)A (recid , Delimited_data_field)
Solution :
;WITH CTE
AS (
SELECT recid
,Delimited_data_field
,ROW_NUMBER() OVER (PARTITION BY recid ORDER BY (SELECT 1)) RNO
,splt.X.value('.', 'INT') VAL
FROM (
SELECT recid
,Delimited_data_field
,CAST('<M>' + REPLACE(Delimited_data_field, ' ', '</M><M>') + '</M>' AS XML) DATA
FROM #TAB
) A
CROSS APPLY A.DATA.nodes('/M') splt(x)
)
SELECT C.recid
,C2.Delimited_data_field
FROM CTE C
INNER JOIN CTE C2 ON C.recid = C2.recid AND C.RNO = 3 AND C2.RNO = 4
AND C.VAL = C2.VAL
Result :
recid Delimited_data_field
2 1 2 3 3 5
3 1 1 1 1 1
Your question has two parts, find nth split and then compare. Your first approach should be to break the problem until you find built in functions that can do the job.
here is one method inner query return result after split and outer compares:
SELECT recid,Delimited from (
SELECT recid,Delimited, SUBSTRING(Delimited,
charindex(' ', Delimited, (charindex(' ', Delimited, 1))+2)+1,1)
third, SUBSTRING(Delimited, charindex(' ',Delimited,
(charindex(' ', Delimited, 1))+3)+1,1)
fourth FROM YourTable) tr
WHERE third = fourth
See simple substring and charindex can do the job.
Here is one more solution to that.
I tweaked the split function in this link (T-SQL: Opposite to string concatenation - how to split string into multiple records) a bit to make it usefule in your scenario.
Here is the function.
CREATE FUNCTION dbo.SplitAndGetNumberAt (#sep char(1), #s varchar(512), #pos int)
RETURNS INT
BEGIN
declare #val as varchar(10);
WITH Pieces(pn, start, stop) AS (
SELECT 1, 1, CHARINDEX(#sep, #s)
UNION ALL
SELECT pn + 1, stop + 1, CHARINDEX(#sep, #s, stop + 1)
FROM Pieces
WHERE stop > 0
)
SELECT #val = SUBSTRING(#s, start, CASE WHEN stop > 0 THEN stop-start ELSE 512 END)
FROM Pieces where pn = #pos;
RETURN #val
END
Now you can use this function to get 3rd and 4th position of numbers and compare easily.
select recid, deldata
from so1
where dbo.SplitAndGetNumberAt (' ', deldata, 3) = dbo.SplitAndGetNumberAt (' ', deldata, 4)
Hope it will help.
If you have SQL Server 2016 or higher, you may try one approach using OPENJSON() to split your input data. The important part here is the fact, that when OPENJSON parses a JSON array the indexes of the elements in the JSON text are returned as keys (0-based).
Input:
CREATE TABLE #Table (
RecId int,
Data varchar(max)
)
INSERT INTO #Table
(RecId, Data)
VALUES
(1, '1 2 3 4 5'),
(2, '1 2 3 3 5'),
(3, '1 1 1 1 1')
Statement:
SELECT
t.RecId,
t.Data
FROM #Table t
CROSS APPLY (SELECT [value] FROM OPENJSON('["' + REPLACE(t.Data,' ','","') + '"]') WHERE [key] = 2) j3
CROSS APPLY (SELECT [value] FROM OPENJSON('["' + REPLACE(t.Data,' ','","') + '"]') WHERE [key] = 3) j4
WHERE j3.[value] = j4.[value]
Output:
RecId Data
2 1 2 3 3 5
3 1 1 1 1 1
Just for fun, sort of crazy coding:
DECLARE #Table Table (
recid INT,
DelimitedDataField VARCHAR(32)
)
INSERT #Table (recid, DelimitedDataField)
VALUES
(1, '1 2 3 4 5'),
(2, '1 2 3 3 5'),
(3, '1 1 1 1 1')
SELECT *
FROM #Table
WHERE
SUBSTRING (
STUFF(
STUFF(
DelimitedDataField + ' - - -',
1,
CHARINDEX(' ', DelimitedDataField + ' - - -'),
''
),
1,
CHARINDEX(' ', STUFF(
DelimitedDataField + ' - - -',
1,
CHARINDEX(' ', DelimitedDataField + ' - - -'), '')
),
''),
1,
CHARINDEX(' ', STUFF(
STUFF(
DelimitedDataField + ' - - -',
1,
CHARINDEX(' ', DelimitedDataField + ' - - -'),
''
),
1,
CHARINDEX(' ', STUFF(
DelimitedDataField + ' - - -',
1,
CHARINDEX(' ', DelimitedDataField + ' - - -'), '')
),
'')
)
) =
SUBSTRING (
STUFF(
STUFF(
STUFF(
DelimitedDataField + ' - - -',
1,
CHARINDEX(' ', DelimitedDataField + ' - - -'),
''
),
1,
CHARINDEX(' ', STUFF(
DelimitedDataField + ' - - -',
1,
CHARINDEX(' ', DelimitedDataField + ' - - -'), '')
),
''),
1,
CHARINDEX(' ', STUFF(
STUFF(
DelimitedDataField + ' - - -',
1,
CHARINDEX(' ', DelimitedDataField + ' - - -'),
''
),
1,
CHARINDEX(' ', STUFF(
DelimitedDataField + ' - - -',
1,
CHARINDEX(' ', DelimitedDataField + ' - - -'), '')
),
'')
),
''
),
1,
CHARINDEX(' ', STUFF(
STUFF(
STUFF(
DelimitedDataField + ' - - -',
1,
CHARINDEX(' ', DelimitedDataField + ' - - -'),
''
),
1,
CHARINDEX(' ', STUFF(
DelimitedDataField + ' - - -',
1,
CHARINDEX(' ', DelimitedDataField + ' - - -'), '')
),
''),
1,
CHARINDEX(' ', STUFF(
STUFF(
DelimitedDataField + ' - - -',
1,
CHARINDEX(' ', DelimitedDataField + ' - - -'),
''
),
1,
CHARINDEX(' ', STUFF(
DelimitedDataField + ' - - -',
1,
CHARINDEX(' ', DelimitedDataField + ' - - -'), '')
),
'')
),
''
))
)
AND SUBSTRING (
STUFF(
STUFF(
DelimitedDataField + ' - - -',
1,
CHARINDEX(' ', DelimitedDataField + ' - - -'),
''
),
1,
CHARINDEX(' ', STUFF(
DelimitedDataField + ' - - -',
1,
CHARINDEX(' ', DelimitedDataField + ' - - -'), '')
),
''),
1,
CHARINDEX(' ', STUFF(
STUFF(
DelimitedDataField + ' - - -',
1,
CHARINDEX(' ', DelimitedDataField + ' - - -'),
''
),
1,
CHARINDEX(' ', STUFF(
DelimitedDataField + ' - - -',
1,
CHARINDEX(' ', DelimitedDataField + ' - - -'), '')
),
'')
)
) <>'-'
I have 2 columns of pipe delimited data that I need to break out into rows but the columns must stay together. Here's what my data looks like:
Plan Name: ABC|DEF|GHI|JKL
Plan Type: HMO|POS|HMO|PPO
I need to end up with 4 rows that look like this:
1 - ABC HMO
2 - DEF POS
3 - GHI HMO
4 - JKL PPO
I know how to separate each column individually using the STUFF function but how do I keep the first value from column 1 with the first value from column 2, etc? Don't know where to start. Appreciate any help!
p.s. - I am not on SQL Server 2016 so can't use STRING_SPLIT
One method is a recursive CTE:
with t as (
select *
from (values ('ABC|DEF|GHI|JKL', 'HMO|POS|HMO|PPO')) v(plannames, plantypes)
),
cte as (
select convert(varchar(max), left(plannames, charindex('|', plannames + '|') - 1)) as planname,
convert(varchar(max), left(plantypes, charindex('|', plantypes + '|') - 1)) as plantype,
convert(varchar(max), stuff(plannames, 1, charindex('|', plannames + '|'), '')) as planname_rest,
convert(varchar(max), stuff(plantypes, 1, charindex('|', plantypes + '|'), '')) as plantype_rest,
1 as lev
from t
union all
select convert(varchar(max), left(planname_rest, charindex('|', planname_rest + '|') - 1)) as planname,
convert(varchar(max), left(plantype_rest, charindex('|', plantype_rest + '|') - 1)) as plantype,
convert(varchar(max), stuff(planname_rest, 1, charindex('|', planname_rest + '|'), '')) as planname_rest,
convert(varchar(max), stuff(plantype_rest, 1, charindex('|', plantype_rest + '|'), '')) as plantype_rest,
lev + 1
from cte
where planname_rest <> ''
)
select *
from cte;
Here is a db<>fiddle.
Using delimitedsplit8k_lead you could do:
SELECT CONVERT(varchar(3), itemnumber) + ' - ' + PN.item + ' ' + PT.item
FROM YourTable YT
CROSS APPLY dbo.delimitedsplit8k_lead(YT.PlanName,'|') PN
CROSS APPLY dbo.delimitedsplit8k_lead(YT.PlanType,'|') PT
WHERE PN.ItemNumber = PT.ItemNumber;
This assumes PlanName and PlanType have the same number of elements.
How to do query on json array field.
my sql table field is personal data and the sample data is below
[{"Name":"A","Age":"12"},
{"Name":"B","Age":"22"},
{"Name":"C","Age":"22"}]
So the query needed is some thing like Name=A
This should put the JSON in a format that you can query normally:
select
SUBSTRING([name], CHARINDEX(':', [name]) + 1, 8000) as [name]
,SUBSTRING([age], CHARINDEX(':', [age]) + 1, 8000) as [name]
from
(select
SUBSTRING(value, 1, CHARINDEX(',', value) - 1) AS [name]
,SUBSTRING(value, CHARINDEX(':',value)+3,CHARINDEX(',',value)-1) as [age]
from
(select replace(replace(replace(replace(strTest, '[', ''), '}', ''), ']', ''), '"', '') as strMod
from [dbo].[tblTest]
) as a
cross apply string_split(strMod, '{')
where value <> ''
) as b