Create one json per one table row - sql

I would like to create jsons from the data in the table.
Table looks like that:
|code |
+------+
|D5ABX0|
|MKT536|
|WAEX44|
I am using FOR JSON PATH which is nice:
SELECT [code]
FROM feature
FOR JSON PATH
but the return value of this query are three concatenated jsons in one row:
|JSON_F52E2B61-18A1-11d1-B105-00805F49916B |
+----------------------------------------------------------+
1 |[{"code":"D5ABX0"},{"code":"MKT536"},{"code":"WAEX44"}]|
I need to have each row to be a separate json, like that:
|JSON_return |
+---------------------+
1 |{"code":"D5ABX0"} |
2 |{"code":"MKT536"} |
3 |{"code":"WAEX44"} |
I was trying to use splitting function (CROSS APPLY) which needs to have a separator as a parameter but this is not a robust solution as the json could be more expanded or branched and this could separate not the whole json but the json inside the json:
;WITH split AS (
SELECT [json] = (SELECT code FROM feature FOR JSON PATH)
)
SELECT
T.StringElement
FROM split S
CROSS APPLY dbo.fnSplitDelimitedList([json], '},{') T
The output is:
|StringElement |
+---------------------+
1 |[{"code":"D5ABX0" |
2 |"code":"MKT536" |
3 |"code":"WAEX44"}] |
Is there a way to force sqlserver to create one json per row?

You'll need to use as subquery to achieve this; FOR JSON will create a JSON string for the entire returned dataset. This should get you what you're after:
CREATE TABLE #Sample (code varchar(6));
INSERT INTO #Sample
VALUES ('D5ABX0'),
('MKT536'),
('WAEX44');
SELECT (SELECT Code
FROM #Sample sq
WHERE sq.code = S.code
FOR JSON PATH)
FROM #Sample S;
DROP TABLE #Sample;

CREATE TABLE #Temp
(
ID INT IDENTITY(1, 1) ,
StringValue NVARCHAR(100)
);
INSERT INTO #Temp
( StringValue )
VALUES ( N'D5ABX0' -- StringValue - nvarchar(100)
),
( 'MKT536' ),
( 'WAEX44' );
SELECT ID,'[{"code:":'''''+StringValue+'''''}]' AS JSON_return FROM #Temp
DROP TABLE #Temp

Related

Sort by value in json array in SQL Server

I have table called foo like this :
create table foo
(
Id Int,
Description Nvarchar(Max)
)
I have json in array in Description like this:
[
{
"name":"a",
"date":"2021-03-01"
},
{
"name":"b",
"date":"2021-03-02"
}
]
I want sort foo table by max date in json list - something like this:
select *
from foo
order by json_value(Description, '$.date')
but this query does not work.
How can I fix this?
One possible approach is to parse the stored JSON with OPENJSON() and find the max date:
Table:
CREATE TABLE foo (
Id Int,
Description Nvarchar(Max)
)
INSERT INTO foo (Id, Description)
VALUES
(1, N'[{"name":"a","date":"2021-03-01"},{"name":"b","date":"2021-03-02"}]'),
(2, N'[{"name":"a","date":"2021-03-11"},{"name":"b","date":"2021-03-12"}]')
Statement:
SELECT Id, Description
FROM (
SELECT *
FROM foo f
OUTER APPLY (
SELECT MAX([date]) AS [Date]
FROM OPENJSON(f.Description) WITH ([date] nvarchar(10) '$.date')
) j
) t
ORDER BY TRY_CONVERT(date, [Date], 23)
Result:
Id Description
1 [{"name":"a","date":"2021-03-01"},{"name":"b","date":"2021-03-02"}]
2 [{"name":"a","date":"2021-03-11"},{"name":"b","date":"2021-03-12"}]

Adding hyphen in a column in sql table

I need help understanding how to add a hyphen to a column where the values are as follows,
8601881, 9700800,2170
The hyphen is supposed to be just before the last digit. There are multiple such values in the column and the length of numbers could be 5,6 or more but the hyphen has to be before the last digit.
Any help is greatly appreciated.
The expected output should be as follows,
860188-1,970080-0,217-0
select concat(substring(value, 1, len(value)-1), '-', substring(value, len(value), 1)) from data;create table data(value varchar(100));
Here is the full example:
create table data(value varchar(100));
insert into data values('6789567');
insert into data values('98765434');
insert into data values('1234567');
insert into data values('876545');
insert into data values('342365');
select concat(substring(value, 1, len(value)-1), '-', substring(value, len(value), 1)) from data;
| (No column name) |
| :--------------- |
| 678956-7 |
| 9876543-4 |
| 123456-7 |
| 87654-5 |
| 34236-5 |
In case OP meant there can be multiple numbers in the column value here is the solution:
create table data1(value varchar(100));
insert into data1 values('6789567,5467474,846364');
insert into data1 values('98765434,6474644,76866,68696');
insert into data1 values('1234567,35637373');
select t.value, string_agg(concat(substring(token.value, 1, len(token.value)-1), '-',
substring(token.value, len(token.value), 1)), ',') as result
from data1 t cross apply string_split(value, ',') as token group by t.value;
value | result
:--------------------------- | :-------------------------------
1234567,35637373 | 123456-7,3563737-3
6789567,5467474,846364 | 678956-7,546747-4,84636-4
98765434,6474644,76866,68696 | 9876543-4,647464-4,7686-6,6869-6
Using SQL SERVER 2017, you can leverage STRING_SPLIT, STUFF, & STRING_AGG to handle this fairly easily.
DECLARE #T TABLE (val VARCHAR(100)) ;
INSERT INTO #T (val) VALUES ('8601881,9700800,2170') ;
SELECT t.val,
STRING_AGG(STUFF(ss.value, LEN(ss.value), 0, '-'), ',') AS Parsed
FROM #T AS t
CROSS APPLY STRING_SPLIT(t.val, ',') AS ss
GROUP BY t.val ;
Returns
8601881,9700800,2170 => 860188-1,970080-0,217-0
STRING_SPLIT breaks them into individual values, STUFF inserts the hyphen into each individual value, STRING_AGG combines them back into a single row per original value.
You can use LEN and LEFT/RIGHT method to get your desired output. Logic are given below:
Note: this will work for any length's value.
DECLARE #T VARCHAR(MAX) = '8601881'
SELECT LEFT(#T,LEN(#T)-1)+'-'+RIGHT(#T,1)
If you have "dash/hyphen" in your data, and you have to store it in varchar or nvarchar just append N before the data.
For example:
insert into users(id,studentId) VALUES (6,N'12345-1001-67890');

SELECT COL HIVE SQL VALUE WHERE VALUES <5000

I'm learning about HIVE and I have come across a question I cannot seem to find a workable answer for. I have to extract all of the numeric columns that ONLY contain integer values <5000 from a table and create a space separated text file. I am familiar with creating text files and selecting rows but selecting columns that meet a specific parameter I am not familiar with, any help or guidance will be appreciated! Below I've listed the structure of the table. Also, there is an image attached showing the data in table format. For OUTPUT I need to go through ALL the COLUMNS and RETURN ONLY the the COLUMNS that meet the parameter of integer values LESS THAN 5000.
create table lineorder (
lo_orderkey int,
lo_linenumber int,
lo_custkey int,
lo_partkey int,
lo_suppkey int,
lo_orderdate int,
lo_orderpriority varchar(15),
lo_shippriority varchar(1),
lo_quantity int,
lo_extendedprice int,
lo_ordertotalprice int,
lo_discount int,
lo_revenue int,
lo_supplycost int,
lo_tax int,
lo_commitdate int,
lo_shipmode varchar(10)
)
Data in tbl format
Conditional columns selecting is a terrible, horrible, no good, very bad idea.
Being that said, here is a demo.
with t as
(
select stack
(
3
,10 ,100 ,1000 ,'X' ,null
,20 ,null ,2000 ,'Y' ,200000
,30 ,300 ,3000 ,'Z' ,300000
) as (c1,c2,c3,c4,c5)
)
select regexp_replace
(
printf(concat('%s',repeat(concat(unhex(1),'%s'),field(unhex(1),t.*,unhex(1))-2)),*)
,concat('([^\\x01]*)',repeat('\\x01([^\\x01]*)',field(unhex(1),t.*,unhex(1))-2))
,c.included_columns
) as record
from t
cross join (select ltrim
(
regexp_replace
(
concat_ws(' ',sort_array(collect_set(printf('$%010d',pos+1))))
,concat
(
'( ?('
,concat_ws
(
'|'
,collect_set
(
case
when cast(pe.val as int) >= 5000
or cast(pe.val as int) is null
then printf('\\$%010d',pos+1)
end
)
)
,'))|(?<=\\$)0+'
)
,''
)
) as included_columns
from t
lateral view posexplode(split(printf(concat('%s',repeat(concat(unhex(1),'%s'),field(unhex(1),*,unhex(1))-2)),*),'\\x01')) pe
) c
+---------+
| record |
+---------+
| 10 1000 |
| 20 2000 |
| 30 3000 |
+---------+
I don't think hive supports variable substitution in the function. So you would have to write a shell scripts that executes the first query which returns the required columns.Then you can assign it to a variable in shell script and then create a new query for creating files in local directory and run it via hive -e from bash.
create table t1(x int , y int) ; // table used for below query
Sample bash script :
cols =hive -e 'select concat_ws(',', case when min(x) > 5000 then 'x' end , case when min(y) > 5000 then 'y' end) from t1'
query ="INSERT OVERWRITE LOCAL DIRECTORY <directory name> ROW FORMAT DELIMITED FIELDS TERMINATED BY ' ' select $cols from t1 "
hive -e query

Simple SQL path query?

I am working through an intro SQL textbook and am confused by the following problem, where we are given the table and values:
CREATE TABLE LineageTable (
parent INT,
id INT,
genus_name VARCHAR(30),
PRIMARY KEY (id)
);
INSERT INTO LineageTable VALUES
(3, 1, 'FamilyA'),
(2, 4, 'FamilyB'),
(7, 2, 'FamilyC');
And I want to write a function that will return a text string representing the path from the a given name to the desired root
My Attempt:
CREATE FUNCTION LineageTable (input VARCHAR(50))
RETURNS TABLE (input VARCHAR(50))
AS $$
BEGIN
RETURN QUERY
SELECT input
FROM LineageTable1
INNER JOIN LineageTable ON LineageTable.parent = LineageTable.id
WHERE LineageTable1.genus_name = LineageTable1.genus_name;
END $$
However, I am confused as how to iterate through this table multiple times to string the path together properly. Any ideas? Thanks all!
On Postgres you can use a RECURSIVE query:
WITH RECURSIVE Rec as
(
SELECT id, parent_id, Name
FROM Hierarchy
WHERE Name = 'Sirenia'
UNION ALL
SELECT Hierarchy.id, Hierarchy.parent_id, Hierarchy.Name
FROM Hierarchy
INNER JOIN Rec
ON Hierarchy.id = Rec.parent_Id
)
SELECT string_agg(Name, '->') path
FROM Rec;
| path |
|:---------------------------------:|
| Sirenia->Paenungulata->Afrotheria |
Rextester here

How to get result of LEFT join into another table as one field

Not sure how to describe this so I will show example:
table PAGES
id int
parent int
name nvarchar
status tinyint
table PAGES_MODULES
id int
id_parent int
module_type nvarchar
module_id int
status int
One page can have more than one linked modules. Example records:
id parent name status
1 -1 Xyz 1
2 -1 Yqw 1
id id_parent module_type module_id status
1 1 ARTICLE 1 1
2 1 GALLERY 2 1
3 2 CATEGORY 3 1
What I need is to create select which will not return 2 results if I do select left join page_modules.
I would like to have select which returns linked modules as this:
id parent name status modules
1 -1 Xyz 1 ARTICLE GALLERY
2 -1 Yqw 1 CATEGORY
Is that possible?
Thanks.
UPDATE
I have tried COALESE, CROSS APPLY and SELECT within SELECT methods and came to these conclusions:
http://blog.feronovak.com/2011/10/multiple-values-in-one-column-aka.html
Hope I can publish these here, not meaning to spam or something.
You'd need to create a custom aggregate function that could concatenate the strings together, there is no built-in SQL Server function that does this.
You can create a custom aggregate function (assuming your using the latest version of SQL) using a .Net assembly. Here's the MS reference on how to do this (the example in the article is actually for a CONCATENATE function just like you require): http://msdn.microsoft.com/en-us/library/ms182741.aspx
Use group_concat() to smoosh multiple rows' worth of data into a single field like that. Note that it does have a length limit (1024 chars by default), so if you're going to have a zillion records being group_concatted, you'll only get the first few lines worth unless you raise the limit.
SELECT ..., GROUP_CONCAT(modules SEPARATOR ' ')
FROM ...
GROUP BY ...
Note that it IS an aggregate function, so you must have a group-by clause.
-- ==================
-- sample data
-- ==================
declare #pages table
(
id int,
parent int,
name nvarchar(max),
status tinyint
)
declare #pages_modules table
(
id int,
id_parent int,
module_type nvarchar(max),
module_id int,
status int
)
insert into #pages values (1, -1, 'Xyz', 1)
insert into #pages values (2, -1, 'Yqw', 1)
insert into #pages_modules values (1, 1, 'ARTICLE', 1, 1)
insert into #pages_modules values (2, 1, 'GALLERY', 2, 1)
insert into #pages_modules values (3, 2, 'CATEGORY', 3, 1)
-- ==================
-- solution
-- ==================
select
*,
modules = (
select module_type + ' ' from #pages_modules pm
where pm.id_parent = p.id
for xml path('')
)
from #pages p
You need to join both tables and then GROUP BY by pages.id, pages.parent, pages.status, pages.name and pages.status. Your modules field in your resultset is then a string aggregate function, i.e in Oracle LISTAGG(pages_modules.modules, ' ') as modules.