I received a valid JSON string from client side, it contains an array of integer values:
declare #JSON nvarchar(max) = N'{"Comments": "test", "Markets": [3, 151]}'
How to select the market IDs correctly?
If I use a query like this: select * from openjson(#JSON) j, it returns
The type of Markets is 4, which means an object,
but the query below returns null value:
select j.Markets from openjson(#JSON) with(Markets nvarchar(max)) j
My goal is to update Market table based on these IDs, eg:
update Market set Active = 1 where MarketID in (3, 151)
Is there a way to do this?
Any built-in function compatible with SQL server 2016 can be used.
Note:
Thanks to #johnlbevan
SELECT VALUE FROM OPENJSON(#JSON, '$.Markets') works perfectly for this problem.
Just for the completeness, here is how I created the JSON integer array ("Markets": [3, 151]) from SQL server.
Since there is no array_agg function out of the box in 2016, I did this:
SELECT (
JSON_QUERY('[' + STUFF(( SELECT ',' + CAST(MarketID AS VARCHAR)
FROM Market
FOR XML PATH('')),1,1,'') + ']' ) AS Markets)
To expand the Markets array alongside other columns you can do this:
SELECT Comments, Market
FROM OPENJSON('{"Comments": "test", "Markets": [3, 151]}')
WITH (Comments nvarchar(32), Markets NVARCHAR(MAX) AS JSON) AS a
CROSS APPLY OPENJSON (a.Markets) WITH (Market INT '$') AS b
Convert the string to json
Map the first field returned to the Comments column with type nvarchar(32)
Map the second field to Markets column with type nvarchar(max), then use as json to say that the contents is json (see https://learn.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql#arguments for a more detailed description - search the page for as json; the key paragraph starts at the 4th occurrence)
Use a cross apply to apply the OPENJSON function to the Markets column so we can fetch values from that property.
Finally use the WITH statement to map the name Market to the returned value, and assign it a data type of INT.
However, to just get the list of values needed to do the update, you can do this:
UPDATE Market
SET Active = 1
WHERE MarketID IN
(
SELECT value
FROM OPENJSON('{"Comments": "test", "Markets": [3, 151]}','$.Markets')
);
Again OPENJSON lets us query the string as JSON
However this time we specify a path to point at the Markets value directly (see https://learn.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql)
We now return the values returned and filter our UPDATE on those, as we would were we dealing with any other subquery.
Related
I would love the option to turn the event_params nested BQ field into a JSON field?
My desired output should look like this:
{"sessionId":123456789,"version":"1.005"}
Consider below
select *, (
select '{' || string_agg(format('%s:%s',
json_extract(kv, '$.key'),
json_extract(kv, '$.string_value')
)) || '}'
from unnest(json_extract_array(to_json_string(event_params))) kv
) json
from `project.dataset.table`
if applied to sample data in your question - output is
Update: I realized you changed/fixed data sample - so see updated query below
select *, (
select '{' || string_agg(format('%s:%s',
json_extract(kv, '$.key'),
json_extract(kv, '$.value.string_value')
)) || '}'
from unnest(json_extract_array(to_json_string(event_params))) kv
) json
from `project.dataset.table`
with output
I made a version where you can define number fields in the JSON object with proper format, and you can filter for certain keys to end up in the JSON object:
with t as (
-- fake example data with same format
select * from unnest([
struct([
struct('session_id' as key, struct('123' as string_value) as value),
('timestamp', struct('1234567')),
('version', struct('2.23.65'))
] as event_params)
,struct([struct('session_id',struct('645')),('timestamp',struct('7653365')),('version',struct('3.675.34'))])
])
)
-- actual query
select
event_params, -- original data for comparison
format('{ %s }', -- for each row create one json object:
(select -- string_agg will return one string with all key-value pairs comma-separated
string_agg( -- within aggregation create key-value pairs
if(key in ('timestamp','session_id'), -- if number fields
format('"%s" : %s',key,value.string_value), -- then number format
format('"%s" : "%s"',key,value.string_value)) -- else string format
, ', ')
from unnest(event_params) -- unnest turns array into a little table per row, so we can run SQL on it
where key in ('session_id','version') -- filter for certain keys
) -- subquery end
) as json
from t
I have the following JSON,
And I have no clue as to how to parse "Value" (When you run it in SQL you get 3 columns key, value and type)
Any ideas?
DECLARE #json NVARCHAR(MAX);
SET #json = N'[
{"salesDate":"2020-03-02","storeSales":[
{"storeId":"104","sales":[
{"productId":"20002","salesVolume":0.700,"salesQuantity":2,"lastChanged":{"date":"2020-03-03","time":"07:28:06"}},
{"productId":"74301","salesVolume":0.750,"salesQuantity":1,"lastChanged":{"date":"2020-03-03","time":"07:28:06"}},
{"productId":"642401","salesVolume":0.750,"salesQuantity":1,"lastChanged":{"date":"2020-03-03","time":"07:28:06"}},
{"productId":"784001","salesVolume":2.100,"salesQuantity":3,"lastChanged":{"date":"2020-03-03","time":"07:28:06"}},
{"productId":"1013801","salesVolume":1.500,"salesQuantity":2,"lastChanged":{"date":"2020-03-03","time":"07:28:06"}},
{"productId":"1202801","salesVolume":0.750,"salesQuantity":1,"lastChanged":{"date":"2020-03-03","time":"07:28:06"}},
{"productId":"1209901","salesVolume":0.700,"salesQuantity":1,"lastChanged":{"date":"2020-03-03","time":"07:28:06"}},
{"productId":"1282201","salesVolume":0.750,"salesQuantity":1,"lastChanged":{"date":"2020-03-03","time":"07:28:06"}},
{"productId":"3317301","salesVolume":1.500,"salesQuantity":2,"lastChanged":{"date":"2020-03-03","time":"07:28:06"}},
{"productId":"4801301","salesVolume":0.700,"salesQuantity":1,"lastChanged":{"date":"2020-03-03","time":"07:28:06"}},
{"productId":"5780106","salesVolume":6.000,"salesQuantity":2,"lastChanged":{"date":"2020-03-03","time":"07:28:06"}},
{"productId":"7964902","salesVolume":0.375,"salesQuantity":1,"lastChanged":{"date":"2020-03-03","time":"07:28:06"}},
{"productId":"10785001","salesVolume":0.750,"salesQuantity":1,"lastChanged":{"date":"2020-03-03","time":"07:28:06"}},
{"productId":"11037501","salesVolume":1.500,"salesQuantity":2,"lastChanged":{"date":"2020-03-03","time":"07:28:06"}}]}]}]';
SELECT *
FROM OPENJSON(#json)
Assuming you wanted to get the sales data try this (although I am not 100% sure if this query offers the best performance)
SELECT [SalesDate], [StoreId], [ProductId], [SalesVolume], [SalesQuantity]
FROM OPENJSON(#json) WITH (
[SalesDate] DATETIME '$.salesDate',
[StoreSales] NVARCHAR(MAX) '$.storeSales' AS JSON
)
OUTER APPLY OPENJSON(StoreSales) WITH (
[StoreId] INT '$.storeId',
[Sales] NVARCHAR(MAX) '$.sales' AS JSON
)
OUTER APPLY OPENJSON(Sales) WITH (
[ProductId] INT '$.productId',
[SalesVolume] DECIMAL(18, 4) '$.salesVolume',
[SalesQuantity] INT '$.salesQuantity'
)
Value is the first object in your JSON array, which is itself another JSON object. SO you will have to read it using other SQL Server JSON utilities. For example:
SELECT JSON_VALUE(Value, '$.salesDate')
FROM OPENJSON(#json)
I have following query in ORACLE:
SELECT *
FROM "Supplier" s
OUTER APPLY(
SELECT JSON_ARRAYAGG(JSON_OBJECT(p."Id", p."Description", p."Price")) as "Products"
FROM "Products" p
WHERE p."SupplierId" = s."Id"
) sp
In OUTER APPLY subquery I am creating json from columns I need and then aggregating those objects into json array. I need those two functions because sometimes I use only one of them. The same operation I would like to do in SqlServer. This is solution I managed so far:
SELECT *
FROM "Supplier" as s
OUTER APPLY(
SELECT p."Id", p."Description", p."Price"
FROM "Products" as p
WHERE p."SupplierId" = s."Id"
FOR JSON PATH
) as sp("Products")
The problem is that SqlServer executing those two functions at once (this is purpose for FOR JSON PATH statement). So here are my questions:
1) Is there possible to create json object without wrapping it into array (oracle-like syntax)?
2) Is there possible to aggregate json objects into an array?
UPDATE
I am using SqlServer version 2019 15.0.2000.5
Expected result (single record of products in json format)
"Products":{
"Id":"FEB0646B709B45B5A306E10599716F28",
"Description":"Database Manager",
"Price":149.99
}
If I understand the question correctly, the following statements are possible soltion (of course, they are based on the example data and statements in the question):
How to create a single JSON object:
If you want to generate one single JSON object, you need to use FOR JSON PATh for each row in the OUTER APPLY statement with the appropriate path expression. JSON_QUERY() is needed, because it returns a valid JSON and FOR JSON doesn't escape special characters.
Tables:
CREATE TABLE Supplier (
Id int,
Description varchar(50),
DateStart date
)
CREATE TABLE Products (
Id varchar(5),
SupplierId int,
Description varchar(100),
Price numeric(10, 2)
)
INSERT INTO Supplier (Id, Description, DateStart)
VALUES (1, 'Oracle', '19900505')
INSERT INTO Products (Id, SupplierId, Description, Price)
VALUES ('11111', 1, 'Database Manager', 149.99)
INSERT INTO Products (Id, SupplierId, Description, Price)
VALUES ('22222', 1, 'Chassi', 249.99)
Statement:
SELECT *
FROM "Supplier" s
OUTER APPLY(
SELECT Products = JSON_QUERY((
SELECT
p."Id" AS 'Product.Id',
p."Description" AS 'Product.Description',
p."Price" AS 'Product.Price'
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
))
FROM "Products" as p
WHERE p."SupplierId" = s."Id"
) sp ("Products")
Result:
Id Description DateStart Products
1 Oracle 1990-05-05 {"Product":{"Id":"11111","Description":"Database Manager","Price":149.99}}
1 Oracle 1990-05-05 {"Product":{"Id":"22222","Description":"Chassi","Price":249.99}}
How to aggregate JSON objects into an array:
By default FOR JSON creates a JSON array with one JSON object for each row. You only need to set a root key:
Statement:
SELECT *
FROM "Supplier" s
OUTER APPLY(
SELECT p."Id", p."Description", p."Price"
FROM "Products" p
WHERE p."SupplierId" = s."Id"
FOR JSON PATH
) sp("Products")
Result:
Id Description DateStart Products
1 Oracle 1990-05-05 [{"Id":"11111","Description":"Database Manager","Price":149.99},{"Id":"22222","Description":"Chassi","Price":249.99}]
DECLARE #data varchar(max)
DECLARE #LIST NVARCHAR(MAX)
DECLARE #Temp TABLE (YourColumnName VARCHAR(MAX) NULL);
INSERT INTO #Temp SELECT DISTINCT columnName FROM YourTableName WHERE(Id > 1000);
SELECT #LIST = STRING_AGG(CONVERT(NVARCHAR(max), ISNULL(YourColumnName, 'N/A')), ',') FROM #Temp
SET #data =(select #LIST as Name1,#LIST as Name2 For Json PATH)
select #data
I have a table with about 900,000 rows. One of the columns has a 7 character value that I need to search for client-side validation of user input. I'm currently using ajax as the user types, but most of the users can out-run the ajax round trips and end up having to wait until the all the validation calls return. So I want to shift the wait time to the initial load of the app and take advantage of browser caching. So I'll bundle minify and gzip the json file with webpack. I'll probably make it an entry that I can then require/ensure as the app loads.
To make the validation super fast on the client side I want to produce a json file that has a single json structure with the first two characters of the 7 character column as the key to the object with an array of all values that start with the first two characters as an array in the value for said key (see example below). I can then use indexOf to find the value within this segmented list and it will be very quick.
As mentioned above I'm currently using ajax as the user types.
I'm not going to show my code because it's too complex. But I basically keep track of the pending ajax requests and when the request that started with the last value the user entered returns (the value currently sitting in the text box), then I can show the user if the entry exists or not. That way if the request return out of order, I'm not giving false positives.
I'm using SQL Server 2016 so I want to use for json to produce my desired output. But here's what I want to produce:
{
"00": [ "0000001", "0000002", ... ],
"10": [ "1000000", "1000001", ... ],
//...
"99": [ "9900000", "9900001", ...]
}
So far I have been unable to figure out how to use substring( mySevenDigitCol, 1, 2 ) as the key in the json object.
I'm not sure if this can be done using FOR JSON AUTO (["0000001", "0000002", ... ] is the difficult part), but next approach based on string manipulation is one possible solution to your problem:
Input:
CREATE TABLE #Data (
SevenDigitColumn varchar(7)
)
INSERT INTO #Data
(SevenDigitColumn)
VALUES
('0000001'),
('0000002'),
('0000003'),
('0000004'),
('0000005'),
('1000001'),
('1000002'),
('9900001'),
('9900002')
T-SQL:
;WITH JsonData AS (
SELECT
SUBSTRING(dat.SevenDigitColumn, 1, 2) AS [Key],
agg.StringAgg AS [Values]
FROM #Data dat
CROSS APPLY (
SELECT STUFF(
(
SELECT CONCAT(',"', SevenDigitColumn, '"')
FROM #Data
WHERE SUBSTRING(SevenDigitColumn, 1, 2) = SUBSTRING(dat.SevenDigitColumn, 1, 2)
FOR XML PATH('')
), 1, 1, '') AS StringAgg
) agg
GROUP BY SUBSTRING(dat.SevenDigitColumn, 1, 2), agg.StringAgg
)
SELECT CONCAT(
'{',
STUFF(
(
SELECT CONCAT(',"', [Key], '": [', [Values], ']')
FROM JsonData
FOR XML PATH('')
), 1, 1, ''),
'}')
Output:
{"00": ["0000001","0000002","0000003","0000004","0000005"],"10": ["1000001","1000002"],"99": ["9900001","9900002"]}
Notes:
With SQL Server 2017+ you can use STRING_AGG() function:
SELECT
CONCAT(
'{',
STRING_AGG(KeyValue, ','),
'}'
)
FROM (
SELECT CONCAT(
'"',
SUBSTRING(dat.SevenDigitColumn, 1, 2),
'": [',
STRING_AGG('"' + SevenDigitColumn + '"', ','),
']'
) AS KeyValue
FROM #Data dat
GROUP BY SUBSTRING(dat.SevenDigitColumn, 1, 2)
) JsonData
Notes:
If your column data is not only digits, you should use STRING_ESCAPE() with 'json' as second parameter to escape special characters.
Say I have two JSON strings as follows:
[{"RowId":102787,"UserId":1,"Activity":"This is another test","Timestamp":"2017-11-25T14:37:30.3700000"}]
[{"RowId":102787,"UserId":2,"Activity":"Testing the Update function","Timestamp":"2017-11-25T14:37:30.3700000"}]
Both have the same properties but two of the properties in the second string have different values than the first (UserId and Activity). Is it possible, in Azure SQL Database T-SQL, to generate a third JSON string that contains the values in the second string that are different from the first? In other words, I'd like a string returned that looks like this:
[{"UserId":2,"Activity":"Testing the Update function"}]
Also, the solution should assume that the properties in the JSON strings are not known. I need this to be a generic solution for any two JSON strings.
Have not tried this on Azure, but it seems to work on SQL Server 2017
There is probably a more elegant way to get to the final JSON string other than through string manipulation, perhaps we can update the answer as better ways are found.
-- Expected : [{"UserId":2,"Activity":"Testing the Update function"}]
DECLARE #jsonA NVARCHAR(MAX) = '[{"RowId":102787,"UserId":1,"Activity":"This is another test","Timestamp":"2017-11-25T14:37:30.3700000"}]'
,#jsonB NVARCHAR(MAX) = '[{"RowId":102787,"UserId":2,"Activity":"Testing the Update function","Timestamp":"2017-11-25T14:37:30.3700000"}]'
,#result NVARCHAR(MAX) = ''
SELECT #jsonA = REPLACE(REPLACE(#jsonA, ']', ''), '[', '')
,#jsonB = REPLACE(REPLACE(#jsonB, ']', ''), '[', '')
;WITH DSA AS
(
SELECT *
FROM OPENJSON(#jsonA)
)
,DSB AS
(
SELECT *
FROM OPENJSON(#jsonB)
)
SELECT #result += CONCAT (
'"', B.[key], '":'
,IIF(B.[type] = 2, B.[value], CONCAT('"', B.[value], '"')) -- havent checked types other than 1 and 2; think there's a bool type?
,','
)
FROM DSA A
JOIN DSB B ON A.[key] = B.[key]
WHERE A.[value] != B.[value]
SELECT CONCAT('[{', LEFT(#result, LEN(#result) - 1), '}]')