Get first value or default from json object in T-SQL - sql-server

I have tried this code:
DECLARE #json_doc nvarchar(4000) = '{"Name1":"Value1", "Name2":"Value2"}';
SELECT
CASE WHEN (SELECT COUNT(*) FROM OPENJSON(#json_doc)) > 0
THEN JSON_VALUE(#json_doc,'$.' + (SELECT TOP(1) [key] FROM OPENJSON(#json_doc)))
ELSE NULL END
But it gives me this error:
Msg 13610 Level 16 State 1 Line 3
The argument 2 of the "JSON_VALUE or JSON_QUERY" must be a string literal.
Anyone know how to get this element?

I think you are looking for something like this:
DECLARE #json_doc nvarchar(4000) = '{"Name1":"Value1", "Name2":"Value2"}';
WITH CTE AS
(
SELECT [Value],
-- row number is ordered by the keys first appearence in the json source.
ROW_NUMBER() OVER(ORDER BY CHARINDEX('"' + [Key] +'":', #json_doc)) As rn
FROM OPENJSON(#Json_doc)
)
-- Get the value if json_doc contains any value
SELECT [Value] As ValueOrDefault
FROM CTE
WHERE rn = 1
UNION
-- Get null if not
SELECT NULL
WHERE NOT EXISTS (SELECT 1 FROM CTE);
The result of this query would be Value1 for that json.
However, if the #json_doc would be empty (set #json_doc = '{}';) it will return NULL.
DB<>Fiddle

This should work for you:
DECLARE #json_doc nvarchar(4000) = '{"Name1":"Value1", "Name2":"Value2"}';
DECLARE #sql nvarchar(max) = '
DECLARE #json_doc nvarchar(4000) = ''' + #json_doc + ''';
SELECT
CASE WHEN (SELECT COUNT(*) FROM OPENJSON(#json_doc)) > 0
THEN JSON_VALUE(#json_doc,''$.' + (SELECT TOP(1) [key] FROM OPENJSON(#json_doc)) + ''')
ELSE NULL END
';
EXECUTE (#sql);

Related

SQL - Get specific element from an array

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.
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
Here are two possibilities. In your case I would even try to merge it into one WHILE loop.
SQL Server 2016 and above
(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
)
For earlier versions of SQL Server:
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)
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
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

Sql FREETEXTTABLE with list searchkey

it looks FREETEXTTABLE just allow searchkey parameter as variable, like this:
FROM FREETEXTTABLE(dbo.SampleTable, SampleColumn, #searchKey)
I have a list searchKey values, about 50000 records, how to "JOIN" searchKey table with FREETEXTTABLE
I've used WHILE but it's very slow
DECLARE #results as table(
Key NVARCHAR(100),
Rank INT
)
declare #numberOfRows INT = SELECT MAX(Id) FROM dbo.SearchKeyTable)
declare #step int = (SELECT MIN(Id) FROM dbo.SearchKeyTable)
WHILE (#step <= #numberOfRows)
BEGIN
declare #searchKey NVARCHAR(500) = (Select TOP(1) '''' + REPLACE([Description], '''', '"') + ''' OR ''' + REPLACE([itemname], '''', '"') + '''' From dbo.SearchKeyTable where id = #step ORDER BY Id)
insert into #results
select [Key] as KeyId, SUM([Rank]) as TotalRank FROM FREETEXTTABLE(dbo.SampleTable, SampleColumn, #searchKey) GROUP BY [Key]
SET #step = #step + 1
END
select * from #results
My dbo.SampleTable table is also large, about 16 millions record
Current solution: use c# multi-thread to replace WHILE

how to remove duplicates from a comma seperated string in sql server

how to remove duplicate values from the comma seperated string in sql server. Without using functions
Declare #data varchar(max) = '34.22,768.55,34.22,123.34,12,999.0,999.0'
My expected result should be
34.22,768.55,123.34,12,999.0
i tried this query but it doesn't remove duplicates from the variable.
Declare #data varchar(max) = '34.22,768.55,34.22,123.34,12,999.0,999.0'
set #data= (select '' + cast(cast('<d>'+replace(#data, ', ',',</d><d>')+'</d>' as xml).query('distinct-values(/d)') as varchar(max)) +'')
Please try this -
DECLARE #x AS XML=''
Declare #finalstring varchar(max) = ''
DECLARE #Param AS VARCHAR(100) = '34.22,768.55,34.22,123.34,12,999.0,999.0'
SET #x = CAST('<A>'+ REPLACE(#Param,',','</A><A>')+ '</A>' AS XML)
select #finalstring = #finalstring + value + ',' from (
SELECT t.value('.', 'VARCHAR(10)') Value FROM #x.nodes('/A') AS x(t))p
GROUP BY value
PRINT SUBSTRING(#finalstring,0,LEN(#finalstring))
OUTPUT
12,123.34,34.22,768.55,999.0
For sql 2016+
Declare #data varchar(max) = '34.22,768.55,34.22,123.34,12,999.0,999.0'
Declare #finalstring varchar(max) = ''
select #finalstring = #finalstring + value + ',' from string_split(#data,',')
GROUP BY value
PRINT SUBSTRING(#finalstring,0,LEN(#finalstring))
OUTPUT
12,123.34,34.22,768.55,999.0
Try this
Declare #data varchar(max) = '34.22,768.55,34.22,123.34,12,999.0,999.0'
SELECT STUFF(
(
SELECT DISTINCT ',' + UniqNum FROM
(
SELECT CAST('<d>'+replace(#data, ',','</d><d>')+'</d>' AS XML) AS numberXml
) as t1
CROSS APPLY
(
SELECT my_Data.D.value('.','varchar(50)') as UniqNum
FROM t1.numberXml.nodes('d') as my_Data(D)
) t2
FOR XML PATH('')
), 1, 1, '')
Result
UniqNumber
---------------------------
12,123.34,34.22,768.55,999.0
Try This
Declare #data varchar(max) = '34.22,768.55,34.22,123.34,12,999.0,999.0'
;WITH CTE
AS
(
SELECT
MyStr = SUBSTRING(#data,CHARINDEX(',',#Data)+1,LEN(#data)),
Val = SUBSTRING(#data,1,CHARINDEX(',',#data)-1)
UNION ALL
SELECT
MyStr = CASE WHEN CHARINDEX(',',MyStr)>0
THEN SUBSTRING(MyStr,CHARINDEX(',',MyStr)+1,LEN(MyStr))
ELSE NULL END,
Val = CASE WHEN CHARINDEX(',',MyStr)>0
THEN SUBSTRING(MyStr,1,CHARINDEX(',',MyStr)-1)
ELSE MyStr END
FROM CTE
WHERE ISNULL(REPLACE(MyStr,',',''),'')<>''
)
SELECT
Val = SUBSTRING(List,1,LEN(List)-1)
FROM
(
SELECT
DISTINCT Val+','
FROM CTE
WHERE ISNULL(MyStr ,'')<>''
FOR XML PATH('')
)Q(List)
My Result
12,123.34,34.22,768.55,999.0
Just an another simple way of doing it.
Declare #data Nvarchar(max) = N'34.22,768.55,34.22,123.34,12,999.0,999.0'
, #data2 Nvarchar(max)='';
SELECT #data = N'SELECT #DATA_DIST= #DATA_DIST+VAL+'',''
FROM (SELECT '''+replace(#data,',',''' AS VAL UNION SELECT ''')+''')A';
EXECUTE sp_executesql #data,N'#DATA_DIST varchar(MAX) OUTPUT',#DATA_DIST=#data2 OUTPUT;
SELECT LEFT(#data2,LEN(#data2)-1);
Result:
12,123.34,34.22,768.55,999.0

Showing "Invalid object name " in sqlServer?

While Executing the Following query it showing the Invalid object name '#temp1'. can any body knows the error occurred due to which reason this is my orginal code i used to fetch code , her differnt tables are formed i need to get the sum of the each row of each table
DECLARE #t TABLE (
id int IDENTITY(1,1),
BranchName nvarchar(max)
)
DECLARE #n int = 0,
#i int = 1,
#BranchName nvarchar(max),
#sql nvarchar(max),
#columns nvarchar(max)
INSERT INTO #t
SELECT DISTINCT BranchName
FROM ALX_Branches
SELECT #n = ##ROWCOUNT
WHILE #n >= #i
BEGIN
SELECT #BranchName = BranchName
FROM #t
WHERE id = #i
SELECT #columns = (
SELECT DISTINCT ','+QUOTENAME([SubInventory])
FROM #MyTempTable
WHERE [BranchName] = #BranchName
FOR XML PATH('')
)
SELECT #sql = N'--
SELECT * into #temp1
FROM (
SELECT [BranchID],
[SubInventory],
[Product],
[Stock]
FROM #MyTempTable
WHERE [BranchName] = ''' +#BranchName +'''
) as t
PIVOT (
MAX([Stock]) FOR [SubInventory] IN ('+STUFF(#columns,1,1,'')+')
) as pvt'
EXEC sp_executesql #sql
select * from #temp1
Firstly, there is no need for creating #temp1 table before.
because you are using "Select * into" that already create table within it.
Suppose type this note as a comment, but I don't have enough reputation score.
The reason of
Invalid object name '#temp1'
is: the variable #sql is NULL because #temp1 is not created yet via "Select * into" clause.
so append selecting from #temp1 within dynamic sql as the following:
SELECT #sql = N'--
SELECT * into #temp1
FROM (
SELECT [BranchID],
[SubInventory],
[Product],
[Stock]
FROM #MyTempTable
WHERE [BranchName] = ''' +#BranchName +'''
) as t
PIVOT (
MAX([Stock]) FOR [SubInventory] IN ('+STUFF(#columns,1,1,'')+')
) as pvt
select * from #temp1 '
EXEC sp_executesql #sql

SQL - How to insert multiple records as one record without pivot?

i want to create a query from multiple records as one record , but i don't want to use Pivot, is there any solutions?
here's the table :
ID Element_Name Value
1 Parmitha 100
2 Anggun 200
3 Chandra 300
4 BagusofTerror 400
and i want the result is like this :
paramitha , anggun, chandra , bagusofterror
100 , 200, 300, 400
You can use for xml path ('') to transpose the values of a column.
For example, you could write
select Element_Name + ', '
from TheTable
for xml path ('');
To get Parmitha, Anggun, Chandra, BagusofTerror,
Here's a live demo: http://www.sqlfiddle.com/#!3/71f88/24
You can also use COALESCE to pivot a results set of columns into a varchar variable:
CREATE TABLE #Pivot
(ID int, Element_Name varchar(50), Value int)
INSERT #Pivot values (1,'Parmitha',100)
INSERT #Pivot values (2,'Anggun',200)
INSERT #Pivot values (3,'Chandra',300)
INSERT #Pivot values (4,'BagusofTerror',400)
DECLARE #titles VARCHAR(1000)
DECLARE #values VARCHAR(1000)
SET #titles = ''
SET #values = ''
SELECT #titles = #titles + COALESCE(Element_Name + ',' , '')
FROM #Pivot ORDER BY ID
SELECT #values = #values + COALESCE(convert(varchar, Value) + ',' , '')
FROM #Pivot ORDER BY ID
SELECT #titles
UNION ALL
SELECT #values
Gives:
Parmitha,Anggun,Chandra,BagusofTerror,
100,200,300,400,
Try this :-
Select
MAX(CASE WHEN colID = 1 THEN value ELSE NULL END) AS [Parmitha],
MAX(CASE WHEN colID = 2 THEN value ELSE NULL END) AS [Anggun],
MAX(CASE WHEN colID = 3 THEN value ELSE NULL END) AS [Chandra],
MAX(CASE WHEN colID = 4 THEN value ELSE NULL END) AS [BagusofTerror]
FROM
(
SELECT ROW_NUMBER() OVER (ORDER BY ID) AS colID,
ID,
Element_Name,
value
FROM Sample
) AS d
SQL DEMO
Taking Wolf's answer into consideration ,using dynamic query and xml path
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ','
+ convert(varchar(max), Element_Name, 120)
from Sample
FOR XML PATH(''), TYPE
).value('.', 'varchar(MAX)')
,1,1,'')
set #query = 'SELECT' + #cols + ' from
(
select value, Element_Name
from Sample
) x
pivot
(
max(value)
for Element_Name in (' + #cols + ')
) p '
execute(#query);
Demo
By the way y don't u use PIVOT .Using PIVOT the same result can be achieved
Select [Parmitha],[Anggun],[Chandra],[BagusofTerror]
FROM
(
Select value,element_name from Sample
)src
pivot
(
max(value)
for Element_Name in ([Parmitha],[Anggun],[Chandra],[BagusofTerror])
)pvt

Resources