Extracting a single value from a json array in sql server - sql

I am using MS SQL server to get a search result in json format there is only ever 1 row returned in my use case but they designed this as a search tool so you can return more than one value hence the array. The issue I am having is extracting the id value from the array that is returned.
json #response (Array):
{"hits":[{"id":1320172,"email":"xyz#domain.eu","first_name":"IMA","last_name":"TESTERTOO","created":"2018-12-12T11:52:58+00:00","roles":["Learner"],"status":true}],"total":1}
I have tried a number of things but I can't seem to get the path right.
SET #MyUserid = JSON_QUERY(#Reponse, '$.hits[0].id')
SET #MyUserid =JSON_VALUE(#Reponse,'$.hits[0].id')
SET #MyUserid = JSON_QUERY(#Reponse, '$.id')
On most examples I have found the json is not a single line array so I feel like I am missing something there. I'm inexperienced with working with json so any help would be greatly appreciated.

You can try this
DECLARE #json NVARCHAR(MAX)=
N'{"hits":[{"id":1320172,"email":"xyz#domain.eu","first_name":"IMA","last_name":"TESTERTOO","created":"2018-12-12T11:52:58+00:00","roles":["Learner"],"status":true}],"total":1}';
--This will return just one selected value
SELECT JSON_VALUE(#json,'$.hits[0].id')
--This will return the whole everything:
SELECT A.total
,B.*
FROM OPENJSON(#json)
WITH(hits nvarchar(max) AS JSON, total int) A
CROSS APPLY OPENJSON(A.hits)
WITH(id int
,email nvarchar(max)
,first_name nvarchar(max)
,last_name nvarchar(max)
,created nvarchar(max)
,roles nvarchar(max) AS JSON
,[status] bit) B

Related

Can't expand JSON file in SQL Server using OPENJSON beyond 1st level

I am working with JSON that has been inserted into a SQL table and I have been trying to expand the dataset. So far I have been unable to expand beyond a single.
The data looks like this in the database. A single record with JSON.
I have been able to expand the data with the following query:
DECLARE #json NVARCHAR(MAX);
SET #json = (Select [JSON] FROM TableLocation)
SELECT *
FROM OPENJSON (#json)
I have confirmed that all the records are there, however, I haven't been able to expand it beyond this level. Most of the documentation I have found online doesn't reference if the hierarchy is blank. Any assistance would be great.
I have tried reference the ID column (or any other columns), however if I do I get a column of nulls.
DECLARE #json NVARCHAR(MAX);
SET #json = (Select [JSON] FROM TableLocation)
SELECT *
FROM OPENJSON (#json)
WITH (
ID nvarchar(4000) '$.id')

I'm having trouble with the sql language functions

I have been working in the SQL language for 1 month. That's why I may not understand things. But I always get an error when creating these functions. I wrote the codes down there. The error message is as follows:
Only one expression can be specified in the select list when
the subquery is not introduced with EXISTS.
CREATE FUNCTION deneme(
#ID int
)
RETURNS nvarchar(max)
AS
BEGIN
DECLARE #value nvarchar(max)
SET #value = (
SELECT * FROM information
WHERE #ID = Person_id
)
RETURN #value
END
You cannot assign all columns values into one variable like that, and since you're passing an ID of the person and you want your function to returns the info of that person
CREATE FUNCTION dbo.deneme
(
#ID int
)
RETURNS NVARCHAR(300)
AS
BEGIN
DECLARE #Value NVARCHAR(300) = N'';
SELECT #Value = CONCAT(I.FirstName, N' ', I.LastName)
FROM Information I
WHERE I.PersonId = #ID;
RETURN #Value;
END
As others have pointed out you are trying to place multiple columns/fields in a single column/field.
#ID is a single column. "Select *" is presumably returning more than a single column or else it wouldn't be much help!
In order to change this and make it work as you are trying here, you would need to concat the columns you are trying to return. This is almost surely not the best way to accomplish this but sometimes concating names (for example) is fine.
The other issue you may be running into is even if you changed this to "Select ID" but still have errors it may be because the query returns more than one row matching that criteria. You can work around this (it is a work around most of the time) by limiting the number of rows returned with "TOP 1". But be careful as this may not return the information you want. You can use an order by statement to help ensure it is the correct information (such as order by Time_entered).
The code below with "TOP 1" and concatenating multiple columns (and casting as the same type) will always work.
Again, these are not Best Practices and shouldn't be used to sanitize data in production... but it does show you why you are getting these errors and how to prevent them.
CREATE FUNCTION deneme(
#ID int
)
RETURNS nvarchar(max)
AS
BEGIN
DECLARE #value nvarchar(max)
SET #value = (
SELECT TOP 1 cast(First_name as nvarchar) + N' ' + cast(Last_name as nvarchar) FROM information
WHERE #ID = Person_id
Order by Time_entered desc
)
RETURN #value
END

parse openxml and get a name value collection for given node

I have a xml document which contains some custom fields which i wont know the names of. i want to generate a select statement which will list the contents in a name value style.
All examples I have found sofar require me to know the names of the nodes.
i.e.
declare #idoc int
declare #doc nvarchar(max); set
#doc = '<user>
<additionalfields>
<Account__Manager>Fred Dibner</Account__Manager>
<First__Aider>St Johns Ambulance</First__Aider>
</additionalfields>
</user>'
EXEC sp_xml_preparedocument #idoc OUTPUT, #doc;
SELECT * FROM OPENXML (#idoc, 'user/additionalfields/',1)
is it possible to achieve this?
well i found the answer after a fair amount more experimenting.(incidentally the double underscore replace is due to the output format of some of the database field names.)
SELECT replace(name,'__',' ') as name, value
FROM OPENXML (#idoc, '/user/additionalfields/*',1)
WITH (
Name nvarchar(4000) '#mp:localname',
value nvarchar(4000) './text()'
)

How to perform a WHERE similar to "NOT IN" with a list of value coming from a HTTP FORM?

I have web application developed in C#, where a page is showing the output of a SQL query; the query is a linq call to a store procedure and I am using SQL Server 2008.
One of the column display the tags associated with the result line; on the same page a list of check-boxes is displayed, each check-box correspond to a tag, and if the user turn on or off one or multiple check-box I want to filter the query.
My simplest SQL solution would be to us "NOT IN" like the following:
select * from [mytable] where [tags] not in ('tag1','tag2, etc...)
Given I convert the FORM POST to a string with comma separated values.
Example:
string tags = ParseFormAndConvertCheckBoxToCSV(Page.Request.Form);
string sqlcmd = "select * from [mytable] where [tags] not in (" + tags + ")";
But I don't want to dynamically build the SQL because as far as I know that would be bad.
I can imagine few way of splitting the string with comma separated list of values in SQL nd store the values into a in-memory table:
declare #tagscsv nvarchar(MAX)
declare #notags TABLE(Value nvarchar(50))
set #tagscsv = 'taxi,ivf'
Declare #x XML
select #x = cast('<A>'+ replace(#tagscsv,',','</A><A>')+ '</A>' as xml)
insert into #notags
select t.value('.', 'nvarchar(50)') as v from #x.nodes('/A') as x(t)
select * from [mytable] where [tags] not in (select * from #notags)
...but this too sounds like a dirty trick.
To me it looks like this should be a very common situation, and I figure out a lot of people out there faced this problem in the past, but searching on google I could not find an elegant solution.
Anyone can help?
Edit: Missed the stored procedure part.
With stored procedures you do not have a lot of options. I usually do what you did, split the comma seperated string and save the values to a temp table (or table variable).
CREATE PROCEDURE SplitAndStuff
#List nvarchar(MAX) = NULL,
#SplitOn nvarchar(5) = ',' --This can be replaced with a literal if it is always a comma.
AS
BEGIN
DECLARE #Pos int;
DECLARE #SplitOnLength int;
DECLARE #Results TABLE (value nvarchar(MAX))
SET #SplitOnLength = DATALENGTH(#SplitOn) / 2;
IF (RIGHT(#List, #SplitOnLength) <> #SplitOn) SET #List = #List + #SplitOn; --Add trailling split string if there is not one already.
SET #Pos = CHARINDEX(#SplitOn, #List, 1) --Initalize for loop. (The starting position returned is 1-based, not 0-based.)
WHILE #Pos > 0
BEGIN
INSERT INTO #Results (value) SELECT CAST(SUBSTRING(#List,1,#Pos-1)AS int);
SET #List = SUBSTRING(#List, #Pos+#SplitOnLength, DATALENGTH(#List) / 2);
SET #Pos = CHARINDEX(#SplitOn, #List, 1);
END
END
GO
If your stored procedure is not doing anything else (besides the look up) you could use my original LINQ answer below:
Since you are using LINQ you need to split tagscsv and use the resulting array to perform an "where in" query:
string[] tags = tagscsv.Split(',');
var output = from q in db.Table where !tags.Contains(q) select q;
See also:
http://msdn.microsoft.com/en-us/library/ms132407.aspx
Using LINQ to Perform "WHERE IN (Value1,Value2)" Queries
The NOT IN clause in LINQ to SQL

TSQL Statement IN

I am having a small problem with the IN SQL statement. I was just wondering if anyone could help me?
#Ids = "1,2,3,4,5"
SELECT * FROM Nav WHERE CONVERT(VARCHAR,NavigationID) IN (CONVERT(VARCHAR,#Ids))
This is coming back with the error below, I am sure this is pretty simple!
Conversion failed when converting the varchar value '1,' to data type int.
The SQL IN clause does not accept a single variable to represent a list of values -- no database does, without using dynamic SQL. Otherwise, you could use a Table Valued Function (SQL Server 2000+) to pull the values out of the list & return them as a table that you can join against.
Dynamic SQL example:
EXEC('SELECT *
FROM Nav
WHERE NavigationID IN ('+ #Ids +')')
I recommend reading The curse and blessings of dynamic SQL before using dynamic SQL on SQL Server.
Jason:
First create a function like this
Create FUNCTION [dbo].[ftDelimitedAsTable](#dlm char, #string varchar(8000))
RETURNS
--------------------------------------------------------------------------*/
/*------------------------------------------------------------------------
declare #dlm char, #string varchar(1000)
set #dlm=','; set #string='t1,t2,t3';
-- tHIS FUNCION RETUNRS IN THE ASCENDING ORDER
-- 19TH Apr 06
------------------------------------------------------------------------*/
--declare
#table_var TABLE
(id int identity(1,1),
r varchar(1000)
)
AS
BEGIN
declare #n int,#i int
set #n=dbo.fnCountChars(#dlm,#string)+1
SET #I =1
while #I <= #N
begin
insert #table_var
select dbo.fsDelimitedString(#dlm,#string,#i)
set #I= #I+1
end
if #n =1 insert #TABLE_VAR VALUES(#STRING)
delete from #table_var where r=''
return
END
And then
set quoted_identifier off
declare #ids varchar(max)
select #Ids = "1,2,3,4,5"
declare #nav table ( navigationid int identity(1,1),theother bigint)
insert #nav(theother) select 10 union select 11 union select 15
SELECT * FROM #Nav WHERE CONVERT(VARCHAR,NavigationID) IN (select id from dbo.ftDelimitedAsTable(',',#Ids))
select * from dbo.ftDelimitedAsTable(',',#Ids)
What you're doing is not possible with the SQL IN statement. You cannot pass a string to it and expect that string to be parsed. IN is for specific, hard-coded values.
There are two ways to do what you want to do here.
One is to create a 'dynamic sql' query and execute it, after substituting in your IN list.
DECLARE #query varchar(max);
SET #query = 'SELECT * FROM Nav WHERE CONVERT(VARCHAR,NavigationID) IN (' + #Ids + ')'
exec (#query)
This can have performance impacts and other complications. Generally I'd try to avoid it.
The other method is to use a User Defined Function (UDF) to split the string into its component parts and then query against that.
There's a post detailing how to create that function here
Once the function exists, it's trivial to join onto it
SELECT * FROM Nav
CROSS APPLY dbo.StringSplit(#Ids) a
WHERE a.s = CONVERT(varchar, Nav.NavigationId)
NB- the 'a.s' field reference is based on the linked function, which stores the split value in a column named 's'. This may differ based on the implementation of your string split function
This is nice because it uses a set based approach to the query rather than an IN subquery, but a CROSS JOIN may be a little complex for the moment, so if you want to maintain the IN syntax then the following should work:
SELECT * FROM Nav
WHERE Nav.NavigationId IN
(SELECT CONVERT(int, a.s) AS Value
FROM dbo.StringSplit(#Ids) a