Extract Values from key and array on JSON - mysql

How could I extract the values in keys and array on json
json
{
"key1": "US",
"key2": "545644566",
"car": ["HONDA","TOYOTA","FORD"]
}
The resulted expected:
key
value
key1
US
key2
545644566
car
HONDA
car
TOYOTA
car
FORD
I tried using:
SELECT JSON_EXTRACT(u.json, CONCAT('$.','"',g.field_name,'"')),g.*
FROM table_json u
LEFT JOIN table_column g
ON JSON_CONTAINS(JSON_KEYS(u.json), JSON_QUOTE(g.field_name), '$')

You can use a dictionary table such as information_schema.tables along with iterating index values for some variables in order to generate rows for the elements of the arrays. The following code also works for non-array elements without need of a conditional. Apply JSON_KEYS() function to derive the key elements in the first subquery, and then use JSON_EXTRACT() for the respective indexes to derive the corresponding values of the arrays in the last subquery such as
WITH t1 AS
(
SELECT #i := #i + 1 AS i,
JSON_UNQUOTE(JSON_EXTRACT(kys, CONCAT('$[', #i - 1, ']'))) AS `key`,
JSON_EXTRACT(json,
CONCAT('$.',
JSON_EXTRACT(kys, CONCAT('$[', #i - 1, ']'))
)
) AS value, kys
FROM t
JOIN (SELECT #i := 0, JSON_KEYS(json) AS kys FROM t) AS k
JOIN information_schema.tables
)
SELECT #k := IF(#j=`key`,#k + 1,0) AS `index`,
#j := `key` AS `key`,
JSON_UNQUOTE(JSON_EXTRACT(value, CONCAT('$[', #k, ']'))) AS value
FROM t1
JOIN (SELECT #k := 0 ) AS k
LEFT JOIN information_schema.tables
ON #k < JSON_LENGTH(value) - 1
WHERE value IS NOT NULL
Demo

Related

select distinct values from json arrays in mysql database

I have table like this
my_table
id my_json
1 ['1','2','3']
2 ['2']
3 ['2','3']
...
12000 ....
I want to find all distinct values in json's arrays
like this
result
'1'
'2'
'3'
I have this code but i need split values to rows
set #items = (SELECT
GROUP_CONCAT(
REPLACE(REPLACE(lower(my_json), ']', ''), '[', '')
SEPARATOR ','
)
FROM my_table);
SELECT CONCAT ('[',#items,']') AS jarray;
result is
[1,2,3]
probably somebody have ideas?
You can use json_table:
select json_arrayagg(t2.v) from (select distinct t1.v from tbl t
cross join json_table(t.my_json, '$[*]' columns( v int path '$')) t1) t2
See fiddle.

JOIN multiple unions using a loop

I'm facing the following issue:
I have a JSON array such as this:
[{"from":"09:25","to":"14:00"},{"from":"15:05","to":"16:10"},{"from":"17:40","to":"17:50"},{"from":"19:00","to":"19:10"},{"from":"19:30","to":"19:50"}]
And I want to have a MySQL query that returns a row for each of the intervals, containing the 'from' and 'to' as columns.
So far I have tried this:
SELECT
idx,
REPLACE(JSON_EXTRACT(JSON_EXTRACT(json, CONCAT('$[', idx, ']')), CONCAT('$.from')), '"', '') AS 'from',
REPLACE(JSON_EXTRACT(JSON_EXTRACT(json, CONCAT('$[', idx, ']')), CONCAT('$.to')), '"', '') AS 'to',
json
FROM test.json
JOIN (
SELECT 0 AS idx UNION
SELECT 1 AS idx UNION
SELECT 2 AS idx UNION
SELECT 3 AS idx UNION
SELECT 4
) AS indexes
And it does work. I get the following result:
this is the desired output. The problem is the set number of SELECTS in the join.
The issue is that I have to do this:
SELECT 0 AS idx UNION
SELECT 1 AS idx UNION
SELECT 2 AS idx UNION
SELECT 3 AS idx UNION
SELECT 4
to insert as many 'idx' as there are items in the JSON array. Is there any way to create do this with a loop? The count of the items will be stored in a separate column, 'howmany' in the table that contains the JSON.
This is how the table I extract data from looks like:
I've tried iterating with a while:
declare counter int unsigned default 0;
SELECT
idx,
REPLACE(JSON_EXTRACT(JSON_EXTRACT(json, CONCAT('$[', idx, ']')), CONCAT('$.from')), '"', '') AS 'from',
REPLACE(JSON_EXTRACT(JSON_EXTRACT(json, CONCAT('$[', idx, ']')), CONCAT('$.to')), '"', '') AS 'to',
json
FROM test.json
JOIN (
(while counter < howmany do
SELECT counter AS idx UNION
set counter=counter+1;
end WHILE)
) AS indexes
and it fails. I am 100% certain that the way I tried is not the way to do it, but I am out of ideas.
Edit: I think it's worth mentioning that we cannot use JSON_TABLE as our MariaDB version is a slightly earlier one than when JSON_TABLE was introduced.
Edit2: I'm using Apache XAMPP's MySQL server.
Database client version: libmysql - mysqlnd 5.0.12-dev - 20150407 - $Id: 7cc7cc96e675f6d72e5cf0f267f48e167c2abb23 $
MariaDB version 10.3.32
WITH RECURSIVE
cte AS (
SELECT id,
jsonvalue,
0 num,
JSON_UNQUOTE(JSON_EXTRACT(jsonvalue, CONCAT('$[', 0, '].from'))) `from`,
JSON_UNQUOTE(JSON_EXTRACT(jsonvalue, CONCAT('$[', 0, '].to'))) `to`
FROM test
UNION ALL
SELECT id,
jsonvalue,
1 + num,
JSON_UNQUOTE(JSON_EXTRACT(jsonvalue, CONCAT('$[', 1 + num, '].from'))),
JSON_UNQUOTE(JSON_EXTRACT(jsonvalue, CONCAT('$[', 1 + num, '].to')))
FROM cte
WHERE JSON_EXTRACT(jsonvalue, CONCAT('$[', 1 + num, '].from')) IS NOT NULL
)
SELECT id,
`from`,
`to`
FROM cte;
DEMO

Store values in different variables in SQL, separated by (Comma) ","

I need to separate values and store them in different variables in SQL,
for example
a='3100,3101,3102,....'
And the output should be
x=3100
y=3101
z=3102
.
.
.
create function [dbo].[udf_splitstring] (#tokens varchar(max),
#delimiter varchar(5))
returns #split table (
token varchar(200) not null )
as
begin
declare #list xml
select #list = cast('<a>'
+ replace(#tokens, #delimiter, '</a><a>')
+ '</a>' as xml)
insert into #split
(token)
select ltrim(t.value('.', 'varchar(200)')) as data
from #list.nodes('/a') as x(t)
return
end
GO
declare #cad varchar(100)='3100,3101,3102'
select *,ROW_NUMBER() over (order by token ) as rn from udf_splitstring(#cad,',')
token rn
3100 1
3101 2
3102 3
The results of the Parse TVF can easily be incorporated into a JOIN, or an IN
Declare #a varchar(max)='3100,3101,3102'
Select * from [dbo].[udf-Str-Parse](#a,',')
Returns
RetSeq RetVal
1 3100
2 3101
3 3102
The UDF if needed (much faster than recursive, loops, and xml)
CREATE FUNCTION [dbo].[udf-Str-Parse] (#String varchar(max),#Delimiter varchar(25))
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 a,cte1 b,cte1 c,cte1 d) A ),
cte3(N) As (Select 1 Union All Select t.N+DataLength(#Delimiter) From cte2 t Where Substring(#String,t.N,DataLength(#Delimiter)) = #Delimiter),
cte4(N,L) As (Select S.N,IsNull(NullIf(CharIndex(#Delimiter,#String,s.N),0)-S.N,8000) From cte3 S)
Select RetSeq = Row_Number() over (Order By A.N)
,RetVal = LTrim(RTrim(Substring(#String, A.N, A.L)))
From cte4 A
);
--Orginal Source http://www.sqlservercentral.com/articles/Tally+Table/72993/
--Much faster than str-Parse, but limited to 8K
--Select * from [dbo].[udf-Str-Parse-8K]('Dog,Cat,House,Car',',')
--Select * from [dbo].[udf-Str-Parse-8K]('John||Cappelletti||was||here','||')
I suggest you to use following query, it's much faster than other functions like cross apply and udf.
SELECT
Variables
,S_DATA
FROM (
SELECT
Variables
,CASE WHEN LEN(LIST2)>0 THEN LTRIM(RTRIM(SUBSTRING(LIST2, NUMBER+1, CHARINDEX(',', LIST2, NUMBER+1)-NUMBER - 1)))
ELSE NULL
END AS S_DATA
,NUMBER
FROM(
SELECT Variables
,','+COMMA_SEPARETED_COLUMN+',' LIST2
FROM Tb1
)DT
LEFT OUTER JOIN TB N ON (N.NUMBER < LEN(DT.LIST2)) OR (N.NUMBER=1 AND DT.LIST2 IS NULL)
WHERE SUBSTRING(LIST2, NUMBER, 1) = ',' OR LIST2 IS NULL
) DT2
WHERE S_DATA<>''
and also you should create a table 'NUMBER' before running the above query.
CREATE TABLE TB (Number INT)
DECLARE #I INT=0
WHILE #I<1000
BEGIN
INSERT INTO TB VALUES (#I)
SET #I=#I+1
END

Mysql Split line to INSERT

I am having a table1:
id - values
12 - 124,145,135
16 - 254,33,11,456,78
...
With SQL, how can I split the values to insert into another table to get:
INSERT INTO table2 (id,cat) VALUES 12, 124;
INSERT INTO table2 (id,cat) VALUES 12, 145;
INSERT INTO table2 (id,cat) VALUES 12, 135;
INSERT INTO table2 (id,cat) VALUES 16, 254;
...
Thank you!
Here's how you could do it in MySQL:
INSERT INTO table2
SELECT id,
REPLACE(substring(substring_index(vals, ',', i),
length(substring_index(vals, ',', i - 1)) + 1), ',', '')
FROM (
SELECT a.*, #i := if(#id = a.id, #i + 1, 1) i, #id := a.id
FROM (
SELECT *
FROM table1 a
INNER JOIN information_schema.global_status b ON 1 = 1
ORDER BY a.id
) a
INNER JOIN (SELECT #i := 0, #id := NULL) x
) a
WHERE i <= LENGTH(vals) - LENGTH(REPLACE(vals, ',', '')) + 1
Obviously, the main issue was to convert the second column to rows.
Here's how it works (you should start reading it from the inner queries):
INSERT INTO table2
SELECT id,
/* Get i-th CSV value from vals (where i ranges between 1
and number of generated rows) */
REPLACE(substring(substring_index(vals, ',', i),
length(substring_index(vals, ',', i - 1)) + 1), ',', '')
FROM (
SELECT a.*,
/* Generate an index for each row. The index values will
go from 1 to n (the number of generated rows) for each row
in table1 */
#i := if(#id = a.id, #i + 1, 1) i,
/* We keep a reference to the previous table1.id, so that
we can reset the counter when we move to the next row
in table1 */
#id := a.id
FROM (
/* Generate sufficient rows
You should have at least: Count(table1.*) x MAX_CSV_VALUES_PER_ROW rows */
SELECT *
FROM table1 a
/* I used information_schema.global_status, but you may use any other table
or even create your own temporary table:
SELECT 1 UNION ALL SELECT 2 ... UNION ALL SELECT n */
INNER JOIN information_schema.global_status b ON 1 = 1
ORDER BY a.id
) a
INNER JOIN (SELECT #i := 0, #id := NULL) x
) a
WHERE
/* Keep only the relevant values; the other values are duplicates.
We're going to keep only those values with indices that are less
or equal to the number of CSV values in vals */
i <= LENGTH(vals) - LENGTH(REPLACE(vals, ',', '')) + 1;
Does this help you? Split strings using mysql
In case the number of concatenated values in the "values" column is unknown or varying I would go for the answer which uses a separate function (SPLIT_STR). Otherwise it's getting really complicated.

Using CROSS APPLY for more than one column

Day #3 with SQL Server.
I am trying to combine 2 columns of delimited data into one output from a Table Valued Function. Here is my data:
I would like the data to be processed and placed into a table in the following format:
I am currently trying to use this CROSS APPLY TSQL statement, but I don't know what I'm doing.
USE [Metrics]
INSERT INTO dbo.tblSplitData(SplitKey, SplitString, SplitValues)
SELECT d.RawKey, c.*, e.*
FROM dbo.tblRawData d
CROSS APPLY dbo.splitstringcomma(d.DelimitedString) c, dbo.splitstringcomma(d.DelimitedValues) e
My research on CROSS APPLY has broad context, and I don't understand how it should be applied in this scenario. Do I need a subquery with an additional CROSS APPLY and a join to combine the returns from the two Table Valued Functions?
Here is the split function I was using originally (I can't remember the author to credit them):
CREATE FUNCTION [dbo].[splitstring] ( #stringToSplit VARCHAR(MAX), #Delimiter CHAR(1))
RETURNS
#returnList TABLE ([Name] [nvarchar] (500))
AS
BEGIN
DECLARE #name NVARCHAR(255)
DECLARE #pos INT
WHILE CHARINDEX(#Delimiter, #stringToSplit) > 0
BEGIN
SELECT #pos = CHARINDEX(#Delimiter, #stringToSplit)
SELECT #name = SUBSTRING(#stringToSplit, 1, #pos-1)
INSERT INTO #returnList
SELECT #name
SELECT #stringToSplit = SUBSTRING(#stringToSplit, #pos+1, LEN(#stringToSplit)-#pos)
END
INSERT INTO #returnList
SELECT #stringToSplit
RETURN
END
Edit & Revised Query
USE [Metrics]
INSERT INTO dbo.tblSplitData(SplitKey, SplitString, SplitValues)
SELECT s.RawKey, s.SplitString, v.SplitValues
FROM (
SELECT d.RawKey, d.DelimitedString,
c.item SplitString, c.rn
FROM dbo.tblRawData d
CROSS APPLY dbo.splitstring(d.DelimitedString, ',') c
) s
INNER JOIN
(
SELECT d.RawKey, d.DelimitedValues,
c.item SplitValues, c.rn
FROM dbo.tblRawData d
CROSS APPLY dbo.splitstring(d.DelimitedValues, ',') c
) v
on s.RawKey = v.RawKey
and s.rn = v.rn;
It might be easier to answer this if we could see your split string function. My answer is using a version of my split function that I have.
I would include in your split function a row number that you can use to JOIN the split string and the split values.
Split function:
CREATE FUNCTION [dbo].[Split](#String varchar(MAX), #Delimiter char(1))
returns #temptable TABLE (items varchar(MAX), rn int)
as
begin
declare #idx int
declare #slice varchar(8000)
declare #rn int = 1 -- row number that increments with each value in the delimited string
select #idx = 1
if len(#String)<1 or #String is null return
while #idx!= 0
begin
set #idx = charindex(#Delimiter,#String)
if #idx!=0
set #slice = left(#String,#idx - 1)
else
set #slice = #String
if(len(#slice)>0)
insert into #temptable(Items, rn) values(#slice, #rn)
set #String = right(#String,len(#String) - #idx)
set #rn = #rn +1
if len(#String) = 0 break
end
return
end;
Then if you have multiple columns to split, you could use a query similar to the following:
INSERT INTO dbo.tblSplitData(SplitKey, SplitString, SplitValues)
select s.rawkey,
s.splitstring,
v.splitvalues
from
(
SELECT d.RawKey, d.delimitedstring, d.delimitedvalues,
c.items SplitString,
c.rn
FROM dbo.tblRawData d
CROSS APPLY dbo.Split(d.DelimitedString, ',') c
) s
inner join
(
SELECT d.RawKey, d.delimitedstring, d.delimitedvalues,
c.items SplitValues,
c.rn
FROM dbo.tblRawData d
CROSS APPLY dbo.Split(d.DelimitedValues, ',') c
) v
on s.rawkey = v.rawkey
and s.delimitedstring = v.delimitedstring
and s.rn = v.rn;
See SQL Fiddle with Demo
This uses two subqueries that generate the list of split values, then they are joined using the row number created by the split function.
Since you are on Sql Server 2008 .You can do this without a UDF using XML.
;WITH CTE1 AS
(
SELECT *
,RN= Row_Number() OVER( Partition BY DelemitedString,DelimitedValues,RawKey,TableID ORDER BY TableID)
FROM
(
SELECT *
,DelimitedStringXML = CAST('<d>'+REPLACE(DelemitedString,',','</d><d>')+'</d>' AS XML)
,DelimitedValueXML = CAST('<d>'+REPLACE(DelimitedValues,',','</d><d>')+'</d>' AS XML)
FROM dbo.tblRawData
) as t
Cross Apply
(
SELECT y.value('.', 'VARCHAR(30)') AS SplitString FROM DelimitedStringXML.nodes('//d') as x(y)
) as b
)
,CTE2 AS
(
SELECT *
,RN= Row_Number() OVER( Partition BY DelemitedString,DelimitedValues,RawKey,TableID ORDER BY TableID)
FROM
(
SELECT *
,DelimitedStringXML = CAST('<d>'+REPLACE(DelemitedString,',','</d><d>')+'</d>' AS XML)
,DelimitedValueXML = CAST('<d>'+REPLACE(DelimitedValues,',','</d><d>')+'</d>' AS XML)
FROM dbo.tblRawData
) as t
CROSS APPLY
(
SELECT h.value('.', 'VARCHAR(30)') AS SplitValue FROM DelimitedValueXML.nodes('//d') as g(h)
) as c
)
SELECT a.RawKey,a.SplitString,b.SplitValue
FROM CTE1 as a
INNER JOIN CTE2 as b
on a.TableID= b.TableID
AND a.RN = b.RN
Here is SQLFiddle Demo