Parse JSON array inside an object in T-SQL - sql

I have a json array like this:
{
"TagsDictionary": [
{
"key" : "property1",
"value" : "property1Value"
},
{
"key" : "property2",
"value" : "property2Value"
}
]
}
I need to query it in T-SQL. I want this result:
property1 | property2
property1Value | property2Value
I read docs. But I can't achieve result with my structure. Unfortunately the JSON is stored in the database and I can't change it, because other modules depends on it.
I'm using SQL Server 2016.

Not 100% sure this is what you are looking for
Example
Declare #YourTable table (ID int,JSON varchar(max))
Insert Into #YourTable values
(1,'{"TagsDictionary": [{"key" : "property1","value" : "property1Value"},{"key" : "property2","value" : "property2Value"}]}')
;with cte as (
Select A.ID
,RN = row_number() over (partition by id,Indx order by B.[Key])
,CN = Indx+1
,B.[value]
From #YourTable A
Cross Apply (
Select Indx = B.[key]
,C.[key]
,C.[value]
From OpenJSON(A.JSON) A
Cross Apply OpenJSON(A.Value) B
Cross Apply OpenJSON(B.Value) C
) B
)
Select *
From cte
Pivot ( max(value) for CN in ([1],[2]) ) pvt
Returns
ID RN 1 2
1 1 property1 property2
1 2 property1Value property2Value

Related

Is there any way in MariaDB to search for less than value from array of json objects

Here's my json doc:
[
{
"ID":1,
"Label":"Price",
"Value":399
},
{
"ID":2,
"Label":"Company",
"Value":"Apple"
},
{
"ID":2,
"Label":"Model",
"Value":"iPhone SE"
},
]
Here's my table:
+----+------------------------------------------------------------------------------------------------------------------------------------+
| ID | Properties |
+----+------------------------------------------------------------------------------------------------------------------------------------+
| 1 | [{"ID":1,"Label":"Price","Value":399},{"ID":2,"Label":"Company","Value":"Apple"},{"ID":3,"Label":"Model","Value":"iPhone SE"}] |
| 2 | [{"ID":1,"Label":"Price","Value":499},{"ID":2,"Label":"Company","Value":"Apple"},{"ID":3,"Label":"Model","Value":"iPhone X"}] |
| 3 | [{"ID":1,"Label":"Price","Value":699},{"ID":2,"Label":"Company","Value":"Apple"},{"ID":3,"Label":"Model","Value":"iPhone 11"}] |
| 4 | [{"ID":1,"Label":"Price","Value":999},{"ID":2,"Label":"Company","Value":"Apple"},{"ID":3,"Label":"Model","Value":"iPhone 11 Pro"}] |
+----+------------------------------------------------------------------------------------------------------------------------------------+
Here's what I want to search on search query:
SELECT *
FROM mobiles
WHERE ($.Label = "Price" AND $.Value < 400)
AND ($.Label = "Model" AND $.Value = "iPhone SE")
Above mentioned query is just for illustration purpose only. I just wanted to convey what I want to perform.
Also I know the table can be normalized into two. But this table is also a place holder table and let's just say it is going to stay the same.
I need to know if it's possible to query the given json structure for following operators: >, >=, <, <=, BETWEEN AND, IN, NOT IN, LIKE, NOT LIKE, <>
Since MariaDB does not support JSON_TABLE(), and JSON_PATH supports only member/object selector, it is not so straightforward to filter JSON here. You can try this query, that tries to overcome that limitations:
with a as (
select 1 as id, '[{"ID":1,"Label":"Price","Value":399},{"ID":2,"Label":"Company","Value":"Apple"},{"ID":3,"Label":"Model","Value":"iPhone SE"}]' as properties union all
select 2 as id, '[{"ID":1,"Label":"Price","Value":499},{"ID":2,"Label":"Company","Value":"Apple"},{"ID":3,"Label":"Model","Value":"iPhone X"}]' as properties union all
select 3 as id, '[{"ID":1,"Label":"Price","Value":699},{"ID":2,"Label":"Company","Value":"Apple"},{"ID":3,"Label":"Model","Value":"iPhone 11"}]' as properties union all
select 4 as id, '[{"ID":1,"Label":"Price","Value":999},{"ID":2,"Label":"Company","Value":"Apple"},{"ID":3,"Label":"Model","Value":"iPhone 11 Pro"}]' as properties
)
select *
from a
where json_value(a.properties,
/*Get path to Price property and replace property name to Value*/
replace(replace(json_search(a.properties, 'one', 'Price'), '"', ''), 'Label', 'Value')
) < 400
and json_value(a.properties,
/*And the same for Model name*/
replace(replace(json_search(a.properties, 'one', 'Model'), '"', ''), 'Label', 'Value')
) = "iPhone SE"
| id | properties
+----+------------
| 1 | [{"ID":1,"Label":"Price","Value":399},{"ID":2,"Label":"Company","Value":"Apple"},{"ID":3,"Label":"Model","Value":"iPhone SE"}]
db<>fiddle here.
I would not use string functions. What is missing in MariaDB is the ability to unnest the array to rows - but it has all the JSON accessors we need to access to the data. Using these methods rather than string methods avoids edge cases, for example when the values contain embedded double quotes.
You would typically unnest the array with the help of a table of numbers that has at least as many rows as there are elements in the biggest array. One method to generate that on the fly is row_number() against a table with sufficient rows - say sometable.
You can unnest the arrays as follows:
select t.id,
json_unquote(json_extract(t.properties, concat('$[', n.rn, '].Label'))) as label,
json_unquote(json_extract(t.properties, concat('$[', n.rn, '].Value'))) as value
from mytable t
inner join (select row_number() over() - 1 as rn from sometable) n
on n.rn < json_length(t.properties)
The rest is just aggregation:
select t.id
from (
select t.id,
json_unquote(json_extract(t.properties, concat('$[', n.rn, '].Label'))) as label,
json_unquote(json_extract(t.properties, concat('$[', n.rn, '].Value'))) as value
from mytable t
inner join (select row_number() over() - 1 as rn from sometable) n
on n.rn < json_length(t.properties)
) t
group by id
having
max(label = 'Price' and value + 0 < 400) = 1
and max(label = 'Model' and value = 'iPhone SE') = 1
Demo on DB Fiddle

SQL Server use column value as column names and convert to json array

Let's say I have the following table ProductValues:
ProductID
Name
Value
1
Market
A
1
Customer
B
2
Market
C
2
Customer
D
I'm able to group them by their ProductID and get these values as an array with the following code:
SELECT
(
SELECT Name, Value FROM ProductValues
WHERE P.ID = ProductID
FOR JSON PATH
)
FROM #ProductIDs P '#ProductIDs is a table containing the productIDs that Id like to retrieve'
This returns the following:
(No column name)
[{"Name":"Market","Value":"A"},{"Name":"Customer","Value":"B"}]
[{"Name":"Market","Value":"C"},{"Name":"Customer","Value":"D"}]
I would like to dynamically create key value pairs using Pivot. I want to achieve the following:
(No column name)
[{"Market":"A"},{"Customer":"B"}]
[{"Market":"C"},{"Customer":"D"}]
Looking at another answer, I tried the following, but this doesn't set the keys dynamically and won't execute (states that "Value" and "TechName" in the Pivot are undefined):
SELECT(
SELECT Market, Customer
FOR JSON PATH
)
FROM(
SELECT(
SELECT Name, Value FROM ProductValues
WHERE ProductID = P.ID
)
FROM #ProductIDs P
) t
PIVOT(
MAX(Value) '<--- "Value" Undefined'
FOR Name IN ( '<--- "Name" Undefined'
Market, Customer
)
) AS pvt
GROUP BY
Market, Customer
You can pivot with conditional aggregation, the convert to JSON:
select (
select
max(case when name = 'Market' then value end) as market,
max(case when name = 'Customer' then value end) as customer
from productvalues pv
where pv.productid = p.productid
for json path
) as js
from #ProductIDs p
Here is a demo on DB Fiddle.
SQL Server is declarative by design. If you are looking for dynamic columns, you will need DYNAMIC SQL.
Example
Declare #sql nvarchar(max) = stuff( (Select Distinct ','+QUOTENAME(Name) From ProductValues FOR XML PATH('')),1,1,'')
SET #sql = 'Select B.*
From (
SELECT '+#sql+'
FROM ProductValues
PIVOT (max([Value]) FOR [Name] IN ('+#sql+')) AS pvt
) A
Cross Apply ( (Select A.* for json path ) ) B (JSONData)'
exec(#sql)
Returns
JSONData
[{"Customer":"B","Market":"A"}]
[{"Customer":"D","Market":"C"}]

How to get recursivelevel using SQL Server 2012 hierarchyid?

I know its quite challenging, Is there any sql specialist please kindly help me to solve this.
I Have hierarchyID like below in NameHID column. Its representing /NameID/dadID/MomID/ respectively. Which means the students fatherid and the motherID. My Table Name is students.
this is my sample NAMEHID column
/2/8/5/
/5/11/12/
/8/7/9/
I need a output like
NameID | RecursiveLevel
2 0
5 1
7 2
8 1
9 2
11 2
12 2
From this Pic you can see the what is the RecursiveLevel. This tree representing something like ancestors of a specific node.
I need to show only the NameID and the Recursive level as out put.
if there Any other additional information need to do this?
Your data is not correct hierachyid.
You could do your job by using a split function and recursive cte
DECLARE #SampleData TABLE
(
NAMEHID varchar(30)
)
INSERT INTO #SampleData
(
NAMEHID
)
VALUES
('/2/8/5/'),('/5/11/12/'),('/8/7/9/')
;with temp AS
(
SELECT * FROM #SampleData sd
CROSS APPLY
(
SELECT * FROM [dbo].[SplitString](STUFF(LEFT(sd.NameHID,LEN(sd.NameHID) - 1),1,1,''),'/')
) cr
)
,cte AS
(
SELECT t.NameHID, t.[Value] , 0 AS Lvl FROM temp t
WHERE t.Pos = 1
AND NOT EXISTS (
SELECT * FROM temp t2
WHERE t2.[Value] = t.[Value] AND t2.Pos > 1
) -- root ID
UNION ALL
SELECT t2.NameHID, t2.[Value], cte.Lvl + 1
FROM cte
INNER JOIN temp t ON cte.[Value] = t.[Value] AND t.Pos = 1
INNER JOIN temp t2 ON t.NameHID = t2.NameHID AND t2.Pos > 1
)
SELECT cte.[Value] AS NameId, cte.Lvl
FROM cte
ORDER BY cte.Lvl, cte.[Value]
OPTION(MAXRECURSION 0)
Split function
CREATE FUNCTION [dbo].[SplitString] (#Text varchar(max),#Delimiter varchar(10))
Returns Table
As
Return (
Select Pos = Row_Number() over (Order By (Select null))
,Value = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>'+ Replace(#Text,#Delimiter,'</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
);
Demo link: http://rextester.com/KPX84657

SQL Server : How to get same "parent" value for all children

In my table I have 3 columns like below:
No. | Type | Amount
-------------------------------
110200014 | A | 19,259.00
110200014 | D | -802.46
110200014 | D | -1,604.92
Type A is for parent record, type D is for children record.
As a result in my request I would like all lines to show "19,259.00" in "amount" column. What is important for me is to get that "parent" value for all children
;
WITH cte
AS (
SELECT *
FROM TABLE
WHERE type = 'A'
)
SELECT cte.Amount, Table.*
FROM TABLE
INNER JOIN cte
ON TABLE.No = cte.no
WHERE TABLE.type = 'D'
Using the data generated by #ScubaManDan
The following works:
create table #tempTab(Num int,rtype char(1),amount decimal(8,2));
insert into #tempTab values(110200014,'A',19259.00)
insert into #tempTab values(110200014,'D',-802.46)
insert into #tempTab values(110200014,'D',-1604.92)
;WITH cte
AS (
SELECT *
FROM #tempTab
WHERE rtype = 'A'
)
SELECT cte.Amount, #tempTab.*
FROM #tempTab
INNER JOIN cte
ON #tempTab.Num = cte.Num
WHERE #tempTab.rtype = 'D'
Produces:
Amount Num rtype amount
------------------------------------
19259.00 110200014 D -802.46
19259.00 110200014 D -1604.92
How about something like this:
create table #tempTab(Num int,rtype char(1),amount decimal(8,2));
insert into #tempTab values
(110200014,'A',19259.00),
(110200014,'D',-802.46),
(110200014,'D',-1604.92)
select *
from #tempTab t
cross apply (select amount from #tempTab where rtype = 'A' and Num = t.Num) pa;

Ms Sql Trim comma , convert int , get data from another table

I have varchar Id like below,
'1','2','3' etc..
I split as comma after that i convert Id to int
SELECT FIRSAT_PERSONELLER_ID,
cast(LTRIM(C.value('n[1]','VARCHAR(50)')) as int )AS item1
FROM (SELECT *,
X = CAST('<myxml><nodes><n>' + REPLACE(FIRSAT_PERSONELLER_ID,',','</n></nodes><nodes> <n>') +
'</n></nodes></myxml>' AS XML)
FROM FIRSATLAR
)t
CROSS APPLY X.nodes('/myxml/nodes') Cols (C)
Result is like below:
item1:
1
2
3
i want to match above "item1" Id with my User Table.I tried below code however it did not work.I want to see User Names.
SELECT username
FROM UserTable
Where UserId=item1Id(İtem1Id does not appear)
Also i am not sure Id value is int or varchar.
Instead of above query , what i need to write ?
tSQL would look like this:
SELECT username
FROM UserTable a
join
(SELECT FIRSAT_PERSONELLER_ID,
cast(LTRIM(C.value('n[1]','VARCHAR(50)')) as int )AS item1
FROM (SELECT *,
X = CAST('<myxml><nodes><n>' + REPLACE(FIRSAT_PERSONELLER_ID,',','</n></nodes><nodes> <n>') +
'</n></nodes></myxml>' AS XML)
FROM FIRSATLAR
)t
CROSS APPLY X.nodes('/myxml/nodes') Cols (C)
) b on a.UserId = b.item1
hope this helps.