Best method to write a recursive SQL view - sql-server

I am looking for the cleanest SQL query to attain the following. Performance is not as important because my dataset is small.
Sample table:
Letter field holding: A, B, C, D, E,
Location field holding: UAE, CANADA, BOSTON, BAHRAIN, FRANCE
And I am looking for a result that lists every letter/location with letter in it combination, so the following result set:
A-UAE
A-CANADA
A-BAHRAIN
A-FRANCE
B-BOSTON
B-BAHRAIN
C-CANADA
C-FRANCE
D-CANADA
E-UAE
E-FRANCE

This is yet another solution:
DECLARE #Letter TABLE (
letter CHAR(1) PRIMARY KEY
);
DECLARE #Country TABLE (
name VARCHAR(100) PRIMARY KEY
);
INSERT INTO #Letter (letter)
VALUES ('A'), ('B'), ('C'), ('D'), ('E');
INSERT INTO #Country (name)
VALUES ('UAE'), ('CANADA'), ('BOSTON'), ('BAHRAIN'), ('FRANCE');
SELECT CONCAT(L.letter, ' - ', C.name)
FROM #Letter AS L
INNER JOIN #Country AS C
ON C.name LIKE '%' + L.letter + '%'
ORDER BY L.letter, C.name;
Result:
A - BAHRAIN
A - CANADA
A - FRANCE
A - UAE
B - BAHRAIN
B - BOSTON
C - CANADA
C - FRANCE
D - CANADA
E - FRANCE
E - UAE
Hopefull this outputs what you'd expect.
You can run this query on Stack Exchange Data: https://data.stackexchange.com/stackoverflow/query/622821
Alternatively, if performance becomes issue, you could create a seperate table which would store each country name and its' unique letters, so you could make a simple join instead of LIKEing to compare things:
DECLARE #CountrySplit TABLE (
letter CHAR(1)
, name VARCHAR(100)
, PRIMARY KEY (letter, name)
);
INSERT INTO #CountrySplit (letter, name)
SELECT DISTINCT SUBSTRING(C.name, v.number + 1, 1), C.name
FROM #Country AS C
INNER JOIN master..spt_values AS V
ON V.number < LEN(C.name)
WHERE V.type = 'P';
SELECT CONCAT(L.letter, ' - ', CS.name) AS Result
FROM #CountrySplit AS CS
INNER JOIN #Letter AS L
ON L.letter = CS.letter;
This is query on Stack Exchange Data:
https://data.stackexchange.com/stackoverflow/query/622841
Credits to this answer for string split: T-SQL Split Word into characters

With the help of a Parse/Split UDF and a Cross Apply.
I added an ID to demonstrate that this can be run for the entire table
Example
Declare #YourTable table (ID int,Letter varchar(50),Location varchar(50))
Insert Into #YourTable values
(1,'A, B, C, D, E,','UAE, CANADA, BOSTON, BAHRAIN, FRANCE')
Select A.ID
,B.*
From #YourTable A
Cross Apply (
Select NewValue = B1.RetVal+'-'+B2.RetVal
From [dbo].[udf-Str-Parse](A.Letter,',') B1
Join [dbo].[udf-Str-Parse](A.Location,',') B2
on charindex(B1.RetVal,B2.RetVal)>0
) B
Returns
ID NewValue
1 A-UAE
1 A-CANADA
1 A-BAHRAIN
1 A-FRANCE
1 B-BOSTON
1 B-BAHRAIN
1 C-CANADA
1 C-FRANCE
1 D-CANADA
1 E-UAE
1 E-FRANCE
The UDF if needed
CREATE FUNCTION [dbo].[udf-Str-Parse] (#String varchar(max),#Delimiter varchar(10))
Returns Table
As
Return (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace((Select replace(#String,#Delimiter,'§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
);
--Thanks Shnugo for making this XML safe
--Select * from [dbo].[udf-Str-Parse]('Dog,Cat,House,Car',',')
--Select * from [dbo].[udf-Str-Parse]('John Cappelletti was here',' ')
--Select * from [dbo].[udf-Str-Parse]('this,is,<test>,for,< & >',',')
EDIT - Option without a UDF
Declare #YourTable table (ID int,Letter varchar(50),Location varchar(50))
Insert Into #YourTable values
(1,'A, B, C, D, E,','UAE, CANADA, BOSTON, BAHRAIN, FRANCE')
Select A.ID
,B.*
From #YourTable A
Cross Apply (
Select NewValue = B1.RetVal+'-'+B2.RetVal
From (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace((Select replace(A.Letter,',','§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
) B1
Join (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace((Select replace(A.Location,',','§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
) B2
on charindex(B1.RetVal,B2.RetVal)>0
) B

Related

Select all records when multiple value parameter is blank

I'm creating a report in SSRS and want to have a multiple value parameter but allow blank values (''), and display all records when blank.
The gist of it is:
SELECT *
FROM Products p
JOIN ProductCategories c on c.ProductId = p.Id
WHERE (c.Name IN (#Categories) OR #Categories = '')
Which works when blank, and works with 1 category, but errors out with 2 categories. We got around this by using a temp table, but that solution seemed sort of hacky, so I wanted to see if there was a better way to resolve this.
The temp table workaround we built was this:
CREATE TABLE #temp (ProductId INT, Category NVARCHAR(MAX))
INSERT INTO #temp
SELECT p.Id, c.Name
FROM Products p
JOIN ProductCategories c on c.ProductId = p.Id
WHERE c.Name IN (#Categories)
IF ((SELECT COUNT(*) FROM #temp) = 0)
BEGIN
INSERT INTO #temp
SELECT p.Id, c.Name
FROM Products p
JOIN ProductCategories c on c.ProductId = p.Id
WHERE c.Name LIKE '%'
END
SELECT * FROM #temp
Thanks in advance!
If you don't have a split/parse function
Example
...
Where #Categories = ''
or
C.Name in (
Select RetVal = ltrim(rtrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace((Select replace(#Categories,',','§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
)
Here is a dbFiddle ... You'll notice Poultry was excluded, then try it when #Categories=''

SQL select Split column and then find in other split select

Hi I have a SQL Server table that one column has comma separated values:
12323,234322,1112,99323.....
And I have a parameter #values nvarchar(500) that will also have comma separated values.
In my query I need to check if anything from the parameter exists in my table field.
Something like this>
...
WHERE
(#values = '' OR select s from dbo.Split(',',t.Data) in ( select s from dbo.Split(',',#values )))
Of course the above gives me errors.
Any clue?
Join both tables that you got out of the split
SELECT *
...
FROM (SELECT s FROM dbo.Split(',',t.Data)) X
INNER JOIN (SELECT s FROM dbo.Split(',',#values)) Y
ON X.s = Y.s
...
EXISTS is your friend here.
WHERE
(#values = '' OR EXISTS (select a.value from string_split(t.Data, ',') a inner join ( select value from string_split(#values, ',')) b ON a.value = b.value))
Try this below code it may helps you
IF OBJECT_ID('Tempdb..#Temp') IS NOT NULL
Drop table #Temp
Declare #SearchVariable varchar(1000)='12323,234322,1112,99323,22222,4545,656565,8989,1111,22222'--Varibale Contains these values to search
CREATE TABLE #Temp (CommaValue Varchar(100))-- This is the table having comma separted value columns
INSERT INTO #Temp
SELECT '12323,234322,1112,99323' Union all
SELECT '12323,656565,1112,4545'
Declare #VariableSearch TABLE (ValueName varchar(1000))
Insert into #VariableSearch
SELECT #SearchVariable
;With cte
AS
(
SELECT Split.a.value('.', 'VARCHAR(1000)') AS TablesData
FROM (
SELECT CAST('<S>' + REPLACE(CommaValue, ',', '</S><S>') + '</S>' AS XML) AS TablesData
FROM #Temp
) AS A
CROSS APPLY TablesData.nodes('/S') AS Split(a)
)
SELECT DISTINCT ROW_NUMBER()Over(Order by (SELECT 1)) AS Rno, * from cte C Inner join
(
SELECT Split.a.value('.', 'VARCHAR(1000)') AS VariableSeachData
FROM (
SELECT CAST('<S>' + REPLACE(ValueName, ',', '</S><S>') + '</S>' AS XML) AS VariableSeachData
FROM #VariableSearch
) AS A
CROSS APPLY VariableSeachData.nodes('/S') AS Split(a)
)DT
On C.TablesData=DT.VariableSeachData
OutPut
Rno TablesData VariableSeachData
---------------------------------
1 1112 1112
2 1112 1112
3 12323 12323
4 12323 12323
5 234322 234322
6 4545 4545
7 656565 656565
8 99323 99323
Not quite sure, but maybe this can give you an idea.
using Outer Apply and EXISTS operator.
SELECT x.value
FROM Table T
OUTER APPLY ( SELECT value
FROM dbo.Split(t.data)
) X
WHERE EXISTS ( SELECT 1
FROM dbo.Split(#values) S
WHERE s.value = x.value )

Querying json key name in SQL Server

Given json like this...
{"setting1":"A","setting2":"B","setting3":"C"}
I would like to see results like...
+----------+-------+
| name | value |
+----------+-------+
| setting1 | A |
| setting2 | B |
| setting3 | C |
+----------+-------+
My struggle is I'm trying to find out how to extract the key's name (i.e., "setting1", "setting2", "setting3", etc.)
I could do something like the following query, but I don't know how many settings there will be and what their names will be, so I'd like something more dynamic.
SELECT
B.name,
B.value
FROM OPENJSON(#json) WITH
(
setting1 varchar(50) '$.setting1',
setting2 varchar(50) '$.setting2',
setting3 varchar(50) '$.setting3'
) A
CROSS APPLY
(
VALUES
('setting1', A.setting1),
('setting2', A.setting2),
('setting3', A.setting3)
) B (name, value)
With XML, I could do something simple like this:
DECLARE #xml XML = '<settings><setting1>A</setting1><setting2>B</setting2><setting3>C</setting3></settings>'
SELECT
A.setting.value('local-name(.)', 'VARCHAR(50)') name,
A.setting.value('.', 'VARCHAR(50)') value
FROM #xml.nodes('settings/*') A (setting)
Any way to do something similar with SQL Server's json functionality?
Aaron Bertrand has written about json key value in Advanced JSON Techniques
SELECT x.[Key], x.[Value]
FROM OPENJSON(#Json, '$') AS x;
Return
Key Value
------------------
setting1 A
setting2 B
setting3 C
Option Using a Table
Declare #YourTable table (ID int,JSON_String varchar(max))
Insert Into #YourTable values
(1,'{"setting1":"A","setting2":"B","setting3":"C"}')
Select A.ID
,C.*
From #YourTable A
Cross Apply (values (try_convert(xml,replace(replace(replace(replace(replace(JSON_String,'"',''),'{','<row '),'}','"/>'),':','="'),',','" '))) ) B (XMLData)
Cross Apply (
Select Name = attr.value('local-name(.)','varchar(100)')
,Value = attr.value('.','varchar(max)')
From B.XMLData.nodes('/row') as C1(r)
Cross Apply C1.r.nodes('./#*') as C2(attr)
) C
Returns
ID Name Value
1 setting1 A
1 setting2 B
1 setting3 C
Option Using a String Variable
Declare #String varchar(max) = '{"setting1":"A","setting2":"B","setting3":"C"}'
Select C.*
From (values (try_convert(xml,replace(replace(replace(replace(replace(#String,'"',''),'{','<row '),'}','"/>'),':','="'),',','" '))) ) A (XMLData)
Cross Apply (
Select Name = attr.value('local-name(.)','varchar(100)')
,Value = attr.value('.','varchar(max)')
From A.XMLData.nodes('/row') as C1(r)
Cross Apply C1.r.nodes('./#*') as C2(attr)
) C
Returns
Name Value
setting1 A
setting2 B
setting3 C
If you are open to a TVF.
The following requires my Extract UDF. This function was created because I was tired of extracting string (patindex,charindex,left,right, etc). It is a modified tally parse which accepts two non-like delimiters.
Example
Declare #YourTable table (ID int,JSON_String varchar(max))
Insert Into #YourTable values
(1,'{"setting1":{"global":"A","type":"1"},"setting2":{"global":"B","type":"1"},"setting3":{"global":"C","type":"1"}} ')
Select A.ID
,B.Setting
,C.*
From #YourTable A
Cross Apply (
Select Setting = replace(replace(B1.RetVal,'"',''),'{','')
,B2.RetVal
From [dbo].[udf-Str-Extract](A.JSON_String,',',':{') B1
Join [dbo].[udf-Str-Extract](A.JSON_String,':{','}') B2
on B1.RetSeq=B2.RetSeq
) B
Cross Apply (
Select Name = C1.RetVal
,Value = C2.RetVal
From [dbo].[udf-Str-Extract](','+B.RetVal,',"','":') C1
Join [dbo].[udf-Str-Extract](B.RetVal+',',':"','",') C2
on C1.RetSeq=C2.RetSeq
) C
Returns
ID Setting Name Value
1 setting1 global A
1 setting1 type 1
1 setting2 global B
1 setting2 type 1
1 setting3 global C
1 setting3 type 1
The UDF if Interested
CREATE FUNCTION [dbo].[udf-Str-Extract] (#String varchar(max),#Delimiter1 varchar(100),#Delimiter2 varchar(100))
Returns Table
As
Return (
with cte1(N) As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
cte2(N) As (Select Top (IsNull(DataLength(#String),0)) Row_Number() over (Order By (Select NULL)) From (Select N=1 From cte1 N1,cte1 N2,cte1 N3,cte1 N4,cte1 N5,cte1 N6) A ),
cte3(N) As (Select 1 Union All Select t.N+DataLength(#Delimiter1) From cte2 t Where Substring(#String,t.N,DataLength(#Delimiter1)) = #Delimiter1),
cte4(N,L) As (Select S.N,IsNull(NullIf(CharIndex(#Delimiter1,#String,s.N),0)-S.N,8000) From cte3 S)
Select RetSeq = Row_Number() over (Order By N)
,RetPos = N
,RetVal = left(RetVal,charindex(#Delimiter2,RetVal)-1)
From (
Select *,RetVal = Substring(#String, N, L)
From cte4
) A
Where charindex(#Delimiter2,RetVal)>1
)
/*
Max Length of String 1MM characters
Declare #String varchar(max) = 'Dear [[FirstName]] [[LastName]], ...'
Select * From [dbo].[udf-Str-Extract] (#String,'[[',']]')
*/

SQL Server : extract hashtags from column of text

I understand that this answer here: How to extract hashtags from a string in T-SQL
explains how to extract hashtags from a declared string variable, but how do I apply this operation to an entire column of strings?
Using a CROSS APPLY. Just for fun, remove the final WHERE, and see what happens
Example
Declare #YourTable table (ID int,SomeText varchar(max))
Insert into #YourTable values
(1, '#want to extract all #hastag out of this string, #delhi #Traffic')
,(2, '#bunny #hastag #donetodeath')
Select A.ID
,B.*
From #YourTable A
Cross Apply (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace((Select replace(replace(A.SomeText,char(13),' '),' ','§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
) B
Where B.RetVal like '#%'
Returns
ID RetSeq RetVal
1 1 #want
1 5 #hastag
1 10 #delhi
1 11 #Traffic
2 1 #bunny
2 2 #hastag
2 3 #donetodeath

Display rows based on column value in sql server 2008

i have column col1 and col2
Col1 col2
abc,def xyz,xyz
abc1,def1 xyz1,xyz1
i need output as below
Col1 col2
abc xyz,xyz
def xyz,xyz
abc1 xyz1,xyz1
def1 xyz1,xyz1
that is if col1 contains 2 value (abc,def) and col2 contains 2 value(xyz,xyz) then i need 4 rows. likewise col1 and col2 contains 2 values then i need 9 rows.
please help me to get the output in sql server
A little XML and and a CROSS APPLY
Option 1: Without a Split/Parse Function
Declare #YourTable table (Col1 varchar(25),col2 varchar(25))
Insert Into #YourTable values
('abc,def','xyz,xyz'),
('abc1,def1','xyz1,xyz1')
Select col1 = B.RetVal
,col2 = A.col2
From #YourTable A
Cross Apply (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace((Select replace(A.Col1,',','§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as X
Cross Apply x.nodes('x') AS B(i)
) B
Returns
col1 col2
abc xyz,xyz
def xyz,xyz
abc1 xyz1,xyz1
def1 xyz1,xyz1
Option 2: with a Split/Parse Function
Select col1 = B.RetVal
,col2 = A.col2
From #YourTable A
Cross Apply [dbo].[udf-Str-Parse](A.col1,',') B
The UDF if interested
CREATE FUNCTION [dbo].[udf-Str-Parse] (#String varchar(max),#Delimiter varchar(10))
Returns Table
As
Return (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace((Select replace(#String,#Delimiter,'§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as X
Cross Apply x.nodes('x') AS B(i)
);
--Thanks Shnugo for making this XML safe
--Select * from [dbo].[udf-Str-Parse]('Dog,Cat,House,Car',',')
--Select * from [dbo].[udf-Str-Parse]('John Cappelletti was here',' ')
--Select * from [dbo].[udf-Str-Parse]('this,is,<test>,for,< & >',',')
Here is another example:
;WITH A(Col1,col2)AS(
SELECT 'abc,def','xyz,xyz' UNION all
SELECT 'abc1,def1','xyz1,xyz1'
)
SELECT d.n,a.col2 FROM a
CROSS APPLY(VALUES(CONVERT(XML,'<n>'+REPLACE(col1,',','</n><n>')+'</n>'))) c(x)
CROSS APPLY(SELECT y.n.value('.','varchar(10)') FROM c.x.nodes('n') y(n)) d(n)
n col2
---------- ---------
abc xyz,xyz
def xyz,xyz
abc1 xyz1,xyz1
def1 xyz1,xyz1

Resources