MySQL query to select from different tables and group data [duplicate] - mysql

This question already has answers here:
How do I generate nested json objects using mysql native json functions?
(4 answers)
Closed 6 years ago.
I have some tables about tv shows. Also I have one of the pages where I show where user can see some seasons and episodes.
CREATE TABLE `tv` (
`tv_id` int(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
`title` VARCHAR(30),
`rating` float(2,1) DEFAULT NULL,
`total_seasons` tinyint UNSIGNED,
)
CREATE TABLE `tv_player_episode_mapping` (
`tv_id` int(11) UNSIGNED,
`season` tinyint(11) UNSIGNED,
`episode` tinyint(11) UNSIGNED,
`player_id` tinyint(11) UNSIGNED,
`file_name` TEXT
);
CREATE TABLE `player` (
`player_id` int(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
`hostname` VARCHAR(30),
UNIQUE KEY `name` (`hostname`)
);
To select a data from a table I use this query:
SELECT tpe.season, tpe.episode, p.hostname, tpe.file_name, t.rating, t.total_seasons
FROM tv_player_episode_mapping tpe
INNER JOIN player p ON p.player_id = tpe.player_id
INNER JOIN tv t ON t.tv_id = tpe.tv_id
WHERE tpe.tv_id = 1;
Below is the result of query and here the problem - some data like total_seasons and ratins repeated:
[
{
file_name:"19891-molodoy-papa.html",
hostname:"kinoclub.cc",
season: 1,
episode: 1,
total_seasons: 1,
rating: 8.5,
},
{
file_name:"19891-molodoy-papa.html",
hostname:"kinoclub.cc",
season: 1,
episode: 2,
total_seasons: 1,
rating: 8.5,
}
]
I try to change my query to get somethink like this (I am novice in databases and I think this is good structure for response from the server):
{
total_seasons: 1,
rating: 8.5,
players: [
[ // Here first element of array `playes` is array for season 1
[ // Episode 1
{
file_name:"1-1-the-young-pope.html",
hostname:"my-player.io",
},
{
file_name:"1-1-the-young-pope.html",
hostname:"another-one-player.io",
}
],
[ // Episode 2
{
file_name:"1-2-the-young-pope.html",
hostname:"my-player.io",
},
{
file_name:"1-2-the-young-pope.html",
hostname:"another-one-player.io",
}
],
],
[] // Season 2
]
};
But because I am little experienced in databases I stop trying with this query and can't find the way:
SELECT tpe.season, tpe.episode,
JSON_OBJECT(
'total_seasons', t.total_seasons,
'players', JSON_OBJECT('hostname', p.name, 'file_name', tpe.file_name))
FROM tv_player_episode_mapping tpe
INNER JOIN player p ON p.player_id = tpe.player_id
INNER JOIN tv t ON t.tv_id = tpe.tv_id
WHERE tpe.tv_id = 1;
How can I change this query to solve my problem?
UPD:
I wrote some query using example and I am good with it, but now result of my query looks strange.
New query:
select json_object(
'rating', t.rating,
'total_seasons', t.total_seasons,
'players', json_array(
(select GROUP_CONCAT(
json_object(
'hostname', p.hostname,
'file_name', tpe.file_name,
'season', tpe.season,
'episode', tpe.episode
)
)
FROM tv_player_episode_mapping tpe
INNER JOIN player p ON p.player_id = tpe.player_id
INNER JOIN tv t ON t.tv_id = tpe.tv_id
where tpe.tv_id = 52)
)
)
from tv t WHERE tv_id=52;
Result of query:
{
"json_object( 'rating', t.rating, 'total_seasons', t.total_seasons, 'players', json_array( (select GROUP_CONCAT( json_object( 'hostname', p.hostname, 'file_name', tpe.file_name, 'season', tpe.season, 'episod":
"{\"players\": [\"{\\\"season\\\": 1, \\\"episode\\\": 1, \\\"hostname\\\": \\\"kinoclub.cc\\\", \\\"file_name\\\": \\\"19891-molodoy-papa.html\\\"},{\\\"season\\\": 1, \\\"episode\\\": 2, \\\"hostname\\\": \\\"kinoclub.cc\\\", \\\"file_name\\\": \\\"19891-molodoy-papa.html\\\"},{\\\"season\\\": 1, \\\"episode\\\": 1, \\\"hostname\\\": \\\"kinoclub.cc\\\", \\\"file_name\\\": \\\"123.html\\\"}\"], \"rating\": 8.5, \"total_seasons\": 1, \"rating\": 8.300000190734863}"
}
What can I do to format result like this?
{
total_seasons: 1,
rating: 8.5,
players: []
};

The short answer is no, but you still can partially implement JSON like structure by using group_concat function.
Example:
SELECT x, y, concat('[', group_concat(z), ']') as z FROM tbl group by x, y;
Will give you answer like:
x y z
1 2 [3,4,5]
6 7 [8,9,10]

Related

Nested SELECT statements and reading in nested JSON file in SQL Server

The discussed problem has been solved partly in here:
Read in nested JSON file in SQL Server
but now the JSON file was extended with more objects of different format.
Declare #json nvarchar(max)
SELECT #json =
N'{
"Model": {
"Energy-X/A": {
"x": 1,
"y": 2,
"z": 3
},
"Energy-X/B": {
"x": 4,
"y": 5,
"z": 6
}
},
"Energy":
{
"Energy-X/A": [
[
100.123456, null
],
[
101.123456, null
]
],
"Energy-X/B": [
[
102.123456, null
],
[
103.123456, null
]
]
}
}'
select * from openjson(#json, '$.Model')
with (x [int] '$."Energy-X/A".x',
y [int] '$."Energy-X/A".y',
z [int] '$."Energy-X/A".z',
x [int] '$."Energy-X/B".x',
y [int] '$."Energy-X/B".y',
z [int] '$."Energy-X/B".z'
);
select commaDelimited.* from openjson (#json)
with (energyXA nvarchar(max) '$.Energy."Energy-X/A"' as json,
energyXB nvarchar(max) '$.Energy."Energy-X/B"' as json
) as energy
cross apply (
select
(select string_agg(isnull(value, 'null'), ',') from openjson(energyXA, '$[0]')),
(select string_agg(isnull(value, 'null'), ',') from openjson(energyXB, '$[0]'))
union all
select
(select string_agg(isnull(value, 'null'), ',') from openjson(energyXA, '$[1]')),
(select string_agg(isnull(value, 'null'), ',') from openjson(energyXB, '$[1]'))
) commaDelimited ([Energy-X/A], [Energy-X/B]);
The solution works and the values can be extracted but now I want to combine both SELECT statements into one query and construct a correlated subquery. The columns should appear when "Energy-X/A" and Energy-X/B" match like:
Energy-X/A
Energy-X/A
x
y
z
100.123456, null
101.123456, null
1
2
3
Energy-X/B
Energy-X/B
x
y
z
102.123456, null
103.123456, null
4
5
6
or also better output would be to sum up the values of Energy-X/A and Energy-X/B in one, separate column (using a delimiter such as semicolon):
Energy-X/A
x
y
z
100.123456, null ; 101.123456, null
1
2
3
Energy-X/B
x
y
z
102.123456, null ; 103.123456, null
1
2
3
I am grateful for any help!
Since you changed your expected results significantly, I've completely re-written your query.
Start by unpivoting the A and B values into separate rows using a (values) table and json_query.
Then read those columns using openjson.
In the case of Energy you need two levels of aggregation also, in order to get your second expected result.
select
commaDelimited.*,
model.*
from (values
(json_query(#json, '$.Model.BCS'), json_query(#json, '$.Energy."Energy-X/A"')),
(json_query(#json, '$.Model.BCSA'), json_query(#json, '$.Energy."Energy-X/B"'))
) j(model, energy)
outer apply openjson(j.model)
with (
x int,
y int,
z int
) model
outer apply (
select
Energy = string_agg(c.Energy, ' ; ')
from openjson(j.energy) energy
cross apply (
select
Energy = string_agg(isnull(Xinner.value, 'null'), ', ')
from openjson(energy.value) Xinner
) c
) commaDelimited;
db<>fiddle

OPENJSON to Parse Field getting Subquery returned more than 1 value error

I have an object in a database which I believe can be parsed using OPENJSON. If there is a better solution, I am definitely open to it. The values below are fictional.
TABLE
DROP TABLE IF EXISTS #temp;
CREATE TABLE #temp (
ID INT,
Object VARCHAR(MAX)
);
INSERT INTO #temp
(
ID,
Object
)
VALUES
( 1, '{ "Country": "US", "CountryName": "United States of America", "Inflation": 5.0 }'),
( 2, '{ "Country": "MX", "CountryName": "Mexico", "Inflation": 6.0 }'),
( 3, '{ "Country": "CA", "CountryName": "Canada, "Inflation": 5.5 }');
ATTEMPTED SOLUTION
SELECT *
FROM OPENJSON((SELECT Object FROM #temp))
WITH (
Country CHAR(2) '$.Country',
CountryName VARCHAR(MAX) '$.CountryName',
Inflation DECIMAL(2,1) '$.Inflation'
)
RESULT
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
DESIRED RESULT
ID
Country
Country Name
Inflation
1
US
United States of America
5.0
2
MX
Mexico
6.0
1
CA
Canada
5.5
Any assistance would be greatly appreciated!
After you add the " after Canada ... Use a CROSS APPLY
Select A.ID
,B.*
From #temp A
Cross Apply (
SELECT *
FROM OPENJSON(Object)
WITH (
Country CHAR(2) '$.Country',
CountryName VARCHAR(MAX) '$.CountryName',
Inflation DECIMAL(2,1) '$.Inflation'
)
) B
Results
EDIT
OR - If your JSON is that simple, the following is a nudge more performant.
Select A.ID
,Country = JSON_VALUE(Object,'$.Country')
,CountryName = JSON_VALUE(Object,'$.CountryName')
,Inflation = JSON_VALUE(Object,'$.Inflation')
From #Temp A

Aggregating subquery keys to build a complex object

I have a table that describes a list of products made for a given order, in this every row on this table has to have a an id of the product and the reason why it was purchased,
I would like to build a json response that amounts to an array of locations the ordered product is for, within that, an array of unique product codes and with that an array of reasons why that unique product was ordered.
I've only been able to get the topmost part of my query defined but the join and sub-select nature of the request is actually getting me in a bit of a fiddle. Is this kind of thing actually possible in plpgsql?
Additionally I'd like to join product_order.reason on product_order_reason.id and retrieve the longform_text inside the table associated with this row but I figure the bigger thing is to get the return at all and that's where i've been left stumped.
product
id |name |cost |cost_rate|
--------|------------------------|------|---------|
WALLC |Wall Clock | 15.00|SINGLE |
MIRR |Mirror | 25.00|SINGLE |
KEY |Door Keys | 5.00|SINGLE |
KEYFOB |Key Fob | 40.00|SINGLE |
product_order
product_id|quantity|location |quote_detail_quote_id |is_primary_order|reason|
----------|--------|----------|------------------------------------|----------------|------|
MIRR | 2|floor_0 |C7D33FED-CB15-5796-DC7D-A7BCEA8923C5|true | 1|
KEYF | 3|floor_0 |C7D33FED-CB15-5796-DC7D-A7BCEA8923C5|true | 2|
WALLC | 3|floor_1 |C7D33FED-CB15-5796-DC7D-A7BCEA8923C5|true | 1|
WALLC | 3|floor_1 |C7D33FED-CB15-5796-DC7D-A7BCEA8923C5|true | 3|
product_order_reason
------------------------------------------------
id (varchar, pk) | shortform_text(varchar) | longform_text(varchar)
------------------------------------------------
id|shortform_text |longform_text |
--|-------------------------------------|-----------------------------------------------------|
1|Employee Room |Standard employee room with no window |
2|Meeting Room |Standard Meeting Room |
3|Mirror |Additional Mirror Request |
create
or replace
function get_breakdown_v1_0_0(p_quote_id character varying,
p_location character varying,
p_product_code character varying) returns json language plpgsql as $function$ declare row_count smallint := 0;
begin
raise notice 'Location: %',
p_location;
raise notice 'Product: %',
p_product_code;
-- Perform santiy check on quote_id so that the json does not include a null result.
select
count(*) into
strict row_count
from
quote_detail
where
quote_id = p_quote_id;
if row_count = 0 then raise 'Quote ID % not found',
p_quote_id
using ERRCODE = '02000';
-- SQL standard no_data
elseif row_count > 1 then raise 'Too many rows returned for ID %',
p_quote_id
using ERRCODE = 'P0003';
-- PL/pgSQL too_many_rows
end if;
-- Returns an object comprised of unique values for locations, where not null and their associated products
return (
select
jsonb_build_object ('locations',jsonb_agg( jsonb_build_object( 'area', location, 'items', items)))
from
(
select
location,
jsonb_agg(jsonb_build_object ('code', product_id, 'reasons', reason)) as items
from
product_order
where
(quote_detail_quote_id = p_quote_id)
and (location = p_location
or p_location is null)
and (product_id = p_product_code
or p_product_code is null)
group by
location) a );
end $function$ ;
Desired response;
{
"area": "floor_0",
"items": [
{
"code": "WALLC",
"reasons": [
{
"quantity": 2,
"reason_code": "Standard Employee Room"
},
{
"quantity": 2,
"reason_code": "Standard Cubicle"
}
]
},
{
"code": "MIRR",
"reasons": [
{
"quantity": 3,
"reason_code": "Meeting Room"
}
]
}
]
}]
Alright I think I have something for you. The idea is to build one of the arrays at a time and carry the necessary remaining info to the outer queries for further array building. You can add your constraints for quote_detail_quote_id , location, and product_id to the innermost query's WHERE clause.
SQLFiddle to show it in action.
This may take some studying:
SELECT json_build_object('area', t3.location, 'items', t3.code_json)
FROM
(
SELECT t2.location
, array_to_json(array_agg(jsonb_build_object('code', t2.product_id, 'reasons', t2.qty_reason_json))) AS code_json
FROM
(
SELECT t.location
, t.product_id
, array_to_json(array_agg(jsonb_build_object('quantity', t.quantity, 'reason_code', t.longform_text))) AS qty_reason_json
FROM
(
SELECT po.product_id
, po.quantity
, po.location
, po.reason
, por.longform_text
FROM product_order po
JOIN product_order_reason por ON (por.id = po.reason)
WHERE quote_detail_quote_id = 'C7D33FED'
) t
GROUP BY t.location, t.product_id
) t2
GROUP BY t2.location
) t3
;

SQL Server Tree Query

I need some help is MS SQL Server Query. I’m not much of a DBA. I have an application with an Organization Table which is made up of a parent-child relationship:
CREATE TABLE [dbo].[Organizations](
[OrgPK] [int] IDENTITY(1,1) NOT NULL,
[OrgParentFK] [int] NULL,
[OrgName] [varchar](200) NOT NULL,
CONSTRAINT [PK__Organizations] PRIMARY KEY CLUSTERED
Sample data looks like this:
OrgPK, OrgParentFK, OrgName
1, 0, Corporate
2, 1, Department A
3, 1, Department B
4, 2, Division 1
5, 2, Division 2
6, 3, Division 1
7, 6, Section 1
8, 6, Section 2
I'm trying to generate a query that returns an org path based on a given OrgPK. Example if given OrgPK = 7 the query would return 'Corporation/Department B/Division 1/Section 1'
If give OrgPk = 5 the return string would be 'Corporation/Department A/Division 2'
Thank you for your assistance.
WITH OrganizationsH (OrgParentFK, OrgPK, OrgName, level, Label) AS
(
SELECT OrgParentFK, OrgPK, OrgName, 0, CAST(OrgName AS VARCHAR(MAX)) As Label
FROM Organizations
WHERE OrgParentFK IS NULL
UNION ALL
SELECT o.OrgParentFK, o.OrgPK, o.OrgName, level + 1, CAST(h.Label + '/' + o.OrgName VARCHAR(MAX)) As Label
FROM Organizations o JOIN OrganizationsH h ON o.OrgParentFK = h.OrgPK
)
SELECT OrgParentFK, OrgPK, OrgName, level, Label
FROM OrganizationsH
WHERE OrgPK = 5
h/t to marc_s
It can also be solved by creating a scalar valued function:
-- SELECT [dbo].[ListTree](5)
CREATE FUNCTION [dbo].[ListTree](#OrgPK int)
RETURNS varchar(max)
AS
BEGIN
declare #Tree varchar(MAX)
set #Tree = ''
while(exists(select * from dbo.Organizations where OrgPK=#OrgPK))
begin
select #Tree=OrgName+'/'+#Tree,
#OrgPK=OrgParentFK
from dbo.Organizations
where OrgPK=#OrgPK
end
return left(#Tree,len(#Tree)-1)
END

MySQL select from view with user variables - Unexpected result

I have 2 tables and a view. In product_oper I have some products that I receive (when id_dest is 1) and that I sell (when id_src is 1). The table product_doc contains the date when the operation took place.
CREATE TABLE product_doc (
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
doc_date date NOT NULL,
doc_no char(16) NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB;
INSERT INTO product_doc (id,doc_date,doc_no) VALUES
(1,'2009-10-07','1'),
(2,'2009-10-14','2'),
(3,'2009-10-28','4'),
(4,'2009-10-21','3');
CREATE TABLE product_oper (
id bigint(12) unsigned NOT NULL AUTO_INCREMENT,
id_document bigint(20) unsigned NOT NULL,
prod_id bigint(12) unsigned NOT NULL DEFAULT '0',
prod_quant decimal(16,4) NOT NULL DEFAULT '1.0000',
prod_value decimal(18,2) NOT NULL DEFAULT '0.00',
id_dest bigint(20) unsigned NOT NULL,
id_src bigint(20) unsigned NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB;
INSERT INTO product_oper (id,id_document,prod_id,prod_quant,prod_value,id_dest,id_src)
VALUES
(10,1,1,'2.0000', '5.00',1,0),
(11,3,1,'0.5000', '1.20',0,1),
(12,1,2,'3.0000','26.14',1,0),
(13,2,2,'0.5000','10.20',0,1),
(14,3,2,'0.3000', '2.60',0,1),
(15,4,2,'1.0000', '0.40',1,0);
In the view I want to see all the operations and the dates.
CREATE VIEW product_oper_view AS
SELECT product_oper.*, product_doc.doc_date AS doc_date, product_doc.doc_no AS doc_no
FROM product_oper JOIN product_doc ON product_oper.id_document = product_doc.id
WHERE 1;
Now I want to see the operations of a single product, and the amount and value at a specific date.
SET #amount=0.000, #balance=0.00;
SELECT product_oper_view.*,
IF(id_dest<>0, prod_quant, NULL) AS q_in,
IF(id_dest<>0, prod_value, NULL) AS v_in,
IF(id_src<>0, prod_quant, NULL) AS q_out,
IF(id_src<>0, prod_value, NULL) AS v_out,
#amount:=#amount + IF(id_dest<>0, 1, -1)*prod_quant AS q_amount,
#balance:=#balance + IF(id_dest<>0, 1, -1)*prod_value AS v_balance
FROM product_oper_view
WHERE prod_id=2 AND (id_dest=1 OR id_src=1)
ORDER BY doc_date;
The result I get is strange:
id, id_ prod_ prod_ id_ id_ doc_date, q_in, v_in, q_ v_
doc, quant,value,dest,src, q_out, v_out, amount, balance
12, 1, 3.0000, 26.14, 1, 0, '2009-10-07', 3.0000, 26.14, NULL , NULL, 3.000, 26.14
13, 2, 0.5000, 10.20, 0, 1, '2009-10-14', NULL , NULL, 0.5000, 10.20, 2.500, 15.94
15, 4, 1.0000, 0.40, 1, 0, '2009-10-21', 1.0000, 0.40, NULL , NULL, 3.200, 13.74
14, 3, 0.3000, 2.60, 0, 1, '2009-10-28', NULL , NULL, 0.3000, 2.60, 2.200, 13.34
The amount starts from zero,
at row 1: +3 => 3 (ok)
at row 2: -0.5 => 2.5 (ok)
at row 3: +1 => 3.2 (???)
at row 4: -0.3 => 2.2 (???)
It seems that MySQL doesn't take the order of rows specified in the ORDER BY clause when executing the statement, and it looks after the id: See that document with id 4 is before document with id 3 ('2009-10-21' < '2009-10-28')
Am I doing something wrong, or is it a bug of MySQL?
If I'm not totally wrong the ORDER-operation is one of the last things done when preparing the result set. Therefore your calculations are done before ordering the results. The correct way to circumvent this problem should be to use a subselect:
SET #amount=0.000, #balance=0.00;
SELECT p.*,
#amount:=#amount + IF(p.id_dest <> 0, 1, -1) * p.prod_quant AS q_amount,
#balance:=#balance + IF(p.id_dest <> 0, 1, -1) * p.prod_value AS v_balance
FROM (
SELECT product_oper_view.*,
IF(product_oper_view.id_dest <> 0, product_oper_view.prod_quant, NULL) AS q_in,
IF(product_oper_view.id_dest <> 0, product_oper_view.prod_value, NULL) AS v_in,
IF(product_oper_view.id_src <> 0, product_oper_view.prod_quant, NULL) AS q_out,
IF(product_oper_view.id_src <> 0, product_oper_view.prod_value, NULL) AS v_out
FROM product_oper_view
WHERE product_oper_view.prod_id = 2
AND (product_oper_view.id_dest = 1 OR product_oper_view.id_src = 1)
ORDER BY product_oper_view.doc_date
) AS p