I was looking for help with a query which will produce the output in JSON that I could use to populate a Menu, thus the JSON needs to create the menu structure.
Here are the two tables that I have, and the best query I've been able to write (which does not produce the output I need) is:
SELECT
c.ID As CategoryID,
c.Name As CategoryName,
p.ProductName as ProductName
p.ProductID as ProductID
FROM Category c, Product p
WHERE
c.ID = p.CategoryID
FOR JSON PATH, ROOT('Menu')
Table: Category
ID | Name
---------------
1 | Fruit
2 | Vegetable
Table: Product
ProductID | CategoryID | ProductName
----------------------------------------
1 | 1 | Apple
2 | 1 | Orange
3 | 2 | Celery
4 | 2 | Carrot
5 | 1 | Banana
The desired output for this query would be the following
menu:
[
{name: 'Fruit', ID: '1', Items:
[
{productname: 'Apple', productid: '1'},
{productname: 'Orange', productid: '2'},
{productname: 'Banana', productid: '5'}
]
},
{name: 'Vegetable', ID: '2', Items:
[
{productname: 'Celery', productid: '3'},
{productname: 'Carrot', productid: '4'}
]
}
]
Is anyone able to explain the steps in which I could change my query to achieve this?
You may choose between the following options:
FOR JSON AUTO and an appropriate join. The output is automatically generated based on the structure of the SELECT statement.
A combination of two nested FOR JSON AUTO statements:
Table:
CREATE TABLE Category (ID int, Name varchar(50))
INSERT INTO Category (ID, Name)
VALUES
(1, 'Fruit'),
(2, 'Vegetable')
CREATE TABLE Product (ProductID int, CategoryID int, ProductName varchar(50))
INSERT INTO Product (ProductID, CategoryID, ProductName)
VALUES
(1, 1, 'Apple'),
(2, 1, 'Orange'),
(3, 2, 'Celery'),
(4, 2, 'Carrot'),
(5, 1, 'Banana')
Statement (using FOR JSON AUTO and an appropriate join and SELECT structure):
SELECT
c.Name AS name, c.ID as id,
items.ProductName AS productname, items.ProductId AS productid
FROM Category c
JOIN Product items ON (c.ID = items.CategoryId)
ORDER BY c.ID, items.ProductId
FOR JSON AUTO, ROOT ('menu')
Statement (using two FOR JSON AUTO statements):
SELECT
c.Name AS name, c.ID as id,
items = (
SELECT p.ProductName AS productname, p.ProductID AS productid
FROM Product p
WHERE p.CategoryID = c.ID
FOR JSON AUTO
)
FROM Category c
FOR JSON AUTO, ROOT ('menu')
Result:
{
"menu":[
{
"name":"Fruit",
"id":1,
"items":[
{"productname":"Apple","productid":1},
{"productname":"Orange","productid":2},
{"productname":"Banana","productid":5}
]
},
{
"name":"Vegetable",
"id":2,
"items":[
{"productname":"Celery","productid":3},
{"productname":"Carrot","productid":4}
]
}
]
}
Try this:
DECLARE #Category TABLE
(
[ID] TINYINT
,[Name] VARCHAR(12)
);
INSERT INTO #Category ([ID], [Name])
VALUES (1, 'Fruit')
,(2, 'Vegetable');
DECLARE #Product TABLE
(
[ProductID] TINYINT
,[CategoryID] TINYINT
,[ProductName] VARCHAR(12)
);
INSERT INTO #Product ([ProductID], [CategoryID], [ProductName])
VALUES (1, 1, 'Apple')
,(2, 1, 'Orange')
,(3, 2, 'Celery')
,(4, 2, 'Carrot')
,(5, 1, 'Banana');
SELECT
c.ID As CategoryID,
c.Name As CategoryName,
Items.ProductName as ProductName,
Items.ProductID as ProductID
FROM #Category c
INNER JOIN #Product Items
ON
c.ID = Items.CategoryID
FOR JSON AUTO, ROOT('Menu')
Related
I have a JSON_ARRAY of ids in the form of [1,3,...]. Each value represents an id to a value in another table.
Table: pets
id | value
1 | cat
2 | dog
3 | hamster
Table: pet_owner
id | pets_array
1 | [1, 3]
2 | [2]
3 | []
What I want to get when I query pet_owners is the following result:
Table: pet_owner
id | pets_array
1 | ["cat", "hamster"]
2 | ["dog"]
3 | []
How do I run a sub-select on each array element to get its value?
As JSON goes, it is always a pain to handle
When you need also all that have no pets, you must left Join the owner table
CREATE TABLE pet_owner (
`id` INTEGER,
`pets_array` JSON
);
INSERT INTO pet_owner
(`id`, `pets_array`)
VALUES
('1', '[1, 3]'),
('2', '[2]'),
('3', '[]');
CREATE TABLE pets (
`id` INTEGER,
`value` VARCHAR(7)
);
INSERT INTO pets
(`id`, `value`)
VALUES
('1', 'cat'),
('2', 'dog'),
('3', 'hamster');
SELECT
t1.id,
JSON_ARRAYAGG(
p.`value`
) AS pets_array
FROM(
SELECT *
FROM pet_owner ,
JSON_TABLE(
pet_owner.pets_array , "$[*]"
COLUMNS(IDs int PATH "$" NULL ON ERROR DEFAULT '0' ON EMPTY )
) AS J_LINK ) t1
LEFT JOIN pets p ON p.id =t1.IDs
GROUP BY
t1.id
;
id | pets_array
-: | :-----------------
1 | ["cat", "hamster"]
2 | ["dog"]
db<>fiddle here
A normalized Table would spare you to convert the data into usable columns.
You can join on json_contains(), then re-aggregate:
select po.id, json_arrayagg(p.value) as owners
from pet_owner po
left join pets p on json_contains(po.pets_array, cast(p.id as char))
group by po.id
Note that, unlike most (if not all!) other databases, MySQL does not guarantee the ordering of elements in an array generated by json_arrayagg(): that's just a fact we have to live with as of the current version.
I have two table, rates and criterias. parent_id in criterias refers to id in rates.
I need to select the rates where ALL children rows in table criterias WHERE criteria_1 AND criteria_2 equal to NULL.
In the example below, only flat rate should be selected
rates
id | name
--------------------
1 | summer rate
2 | flat rate
3 | student rate
conditions
id | parent_id | criteria_1 | criteria_2
------------------------------------------------------
1 | 1 | 523 | 563
2 | 1 | null | null
3 | 2 | null | null
4 | 2 | null | null
5 | 3 | 777 | null
I tried NOT EXIST, but it return it return any rate where one children have two null criteria
try using this subquery with inner join.
select * from
(select * from rates where name = 'flat rate') t1
inner join
(select * from criterias where coalesce(criteria_1, 0) = 0 and coalesce(criteria_2, 0) = 0) t2
on t2.parent_id = t1.id
Please see the following query it should work. You need to compare 2 result set to find rate with ALL null childrens.
SELECT
a.parent_id
FROM(
SELECT
parent_id,
COUNT(*) AS total_count
FROM criterias c
WHERE c. criteria_1 IS NULL AND c.criteria_2 IS NULL
GROUP BY 1
) a
INNER JOIN (
SELECT
parent_id,
COUNT(*) AS total_count
FROM criterias c
GROUP BY 1
)b ON a.parent_id = b.parent_id AND a.total_count = b.total_count
I would use some aggregate function with an having clause grouped by parent_id.
Using a min or max would return a numerical value if there is at least one non-null value per parent_id but will be null if there are only null. So just need to use an having min(<field>) is null to find a parent_id with only null value.
select *
from rates r
where id in(
select parent_id
from criterias
group by parent_id
having min(criteria_1) is null
and min(criteria_2) is null
);
or With an inner join (if you prefer)
select *
from rates r
inner join (
select parent_id
from criterias
group by parent_id
having min(criteria_1) is null
and min(criteria_2) is null
) c ON c.parent_id = r.id;
Validated with :
create table rates(
id int,
name varchar(20)
);
create table criterias (
id int,
parent_id int,
criteria_1 int null,
criteria_2 int null
);
insert into rates values (1, 'summer rate');
insert into rates values (2, 'flate rate');
insert into rates values (3, 'student rate');
insert into rates values (4, 'old rate');
insert into rates values (5, 'any rate');
insert into criterias values (1, 1, 523, 563);
insert into criterias values (2, 1, null, null);
insert into criterias values (3, 2, null, null);
insert into criterias values (4, 2, null, null);
insert into criterias values (5, 1, 777, null);
insert into criterias values (6, 4, null, null);
insert into criterias values (7, 5, null, null);
insert into criterias values (8, 5, null, null);
/*insert into criterias values (9, 5, 1, null);*/
select *
from rates r
where id in(
select parent_id
from criterias
group by parent_id
having min(criteria_1) is null
and min(criteria_2) is null
);
Result:
id name
2 flate rate
4 old rate
5 any rate
I have two tables in my database:
create table category (id integer, name text, parent_id integer);
create table product (id integer, name text, category integer, description text);
insert into category
values
(1, 'Category A', null),
(2, 'Category B', null),
(3, 'Category C', null),
(4, 'Category D', null),
(5, 'Subcategory Of 1', 1),
(6, 'Subcategory Of 5', 5),
(7, 'Subcategory Of 5', 5),
(8, 'Subcategory of D', 4)
;
insert into product
values
(1, 'Product One', 5, 'Our first product'),
(2, 'Product Two', 6, 'Our second product'),
(3, 'Product Three', 8, 'The even better one');
How can I return like this:
product_id | product_name | root_category | category_path
-----------+--------------+---------------+-----------------------------
1 | Product One | 1 | /Category A/Subcategory Of 1
2 | Product Two | 1 | /Category A/Subcategory of 5/Subcategory of 6
I use "WITH RECURSIVE" in categories table but can't find the way to combine product table with 1 time query.
I use example from here
What's the best way to do this ?
Here you go, assumming you have MariaDB 10.2 or newer:
with recursive pt (root_id, id, path) as (
select id, id, concat('/', name) from category where parent_id is null
union all
select pt.root_id, c.id, concat(pt.path, '/', c.name)
from pt join category c on c.parent_id = pt.id
)
select p.id, p.name, pt.root_id, pt.path
from pt
join product p on pt.id = p.category;
Result:
id name root_id path
-- -------------- ------- ---------------------------------------------
1 Product One 1 /Category A/Subcategory Of 1
2 Product Two 1 /Category A/Subcategory Of 1/Subcategory Of 5
3 Product Three 4 /Category D/Subcategory of D
I have a query to search data by multiple tags, I got this idea from this question Multiple tags search query. I have 3 table such as books(id, title....). tags(id,name), books_tag(id,book_id,tag_id). So I use my query to select the books by tags but it is not giving any results.
Books Table
id : title
1 : maths
2 : science
3 : HP
Tag Table
id : name
1 : a
2 : b
3 : c
Boook_tag Table
id : book_id : tag_id
1 : 1 : 2
2 : 1 : 3
3 : 2 : 1
4 : 3 : 1
5 : 3 : 2
So if I search book tag c, the result should be book_id 1 (maths) OR search by c and a, the result should be book_id 1,2,3 (maths, science, HP)
This is my query
SELECT books_tag.book_id, books_pre.title
FROM books_tag
JOIN books_pre ON books_tag.book_id = books_pre.id
JOIN tags ON books_tag.tag_id = tags.id
WHERE tags.name IN ('a', 'd')
GROUP BY books_tag.book_id
HAVING COUNT(books_tag.tag_id) = 2
The query you are trying is for AND criteria like get those books which have tag a AND tag d, As per your question you don't need that criteria, just remove the group by and having clause and you will be fine
SELECT DISTINCT b.*
FROM books_pre b
JOIN books_tag bt ON bt.book_id = b.id
JOIN tags t ON bt.tag_id = t.id
WHERE t.name IN ('a', 'b')
Demo
You should use count(distinct books_tag.tag_id)
SELECT books_tag.book_id, books_pre.title
FROM books_tag
JOIN books_pre ON books_tag.book_id = books_pre.id
JOIN tags ON books_tag.tag_id = tags.id
WHERE tags.name IN ('a', 'd')
GROUP BY books_tag.book_id
HAVING COUNT(distinct books_tag.tag_id) = 2
Try this short query:
Sample data:
create table Books(id int, title varchar(10));
insert into Books values
(1, 'maths'),
(2, 'science'),
(3, 'HP');
create table Tag(id int, name char(1));
insert into Tag values
(1, 'a'),
(2, 'b'),
(3, 'c');
create table Book_tag(id int, book_id int, tag_id int);
insert into Book_tag values
(1, 1, 2),
(2, 1, 3),
(3, 2, 1),
(4, 3, 1),
(5, 3, 2);
T-SQL:
select distinct book_id from Book_tag bt
where exists(select 1 from Tag
where id = bt.tag_id
and name in ('c', 'a'));
in part name in (...) you specify tags you want to search for.
This question is best explained with an example. So, if you have 2 tables category and event in PostgreSQL as follows: -
create table category (
id integer primary key,
type varchar(255),
label varchar (255),
parent_id integer
);
insert into category (id, type, label, parent_id)
values (1, 'organisation', 'Google', null),
(2, 'product', 'Gmail', 1),
(3, 'organisation', 'Apple', null),
(4, 'product', 'iPhone', 3),
(5, 'product', 'Mac', 3);
create table event (
id integer primary key,
name varchar (255),
category_id integer
);
insert into event (id, name, category_id)
values (1, 'add', 4),
(2, 'delete', 5),
(3, 'update', 2);
As you can see, the category table is quite dynamic and a hierarchy of categories can be defined.
What I'm trying to achieve is selecting entries of the event table and join it with the categories but flatten it to a JSON structure. I can illustrate using the following query: -
select e.*,
jsonb_build_object(
c1.type, c1.label,
c2.type, c2.label
) as categories
from event e
left join category c2 on c2.id = e.category_id
left join category c1 on c1.id = c2.parent_id
This will return: -
+----+--------+-------------+------------------------------------------------+
| id | name | category_id | categories |
+----+--------+-------------+------------------------------------------------+
| 1 | add | 4 | {"organisation": "Apple", "product": "iPhone"} |
| 2 | delete | 5 | {"organisation": "Apple", "product": "Mac"} |
| 3 | update | 2 | {"organisation": "Google", "product": "Gmail"} |
+----+--------+-------------+------------------------------------------------+
However, this approach only works when an event.category_id column references a child category which has precisely 1 parent (2 levels). Really what I'm looking for is to generate categories, regardless if (a) it doesn't have a parent category (i.e. a 1 level category) OR (b) has more than 1 parent (e.g. 3 levels). For example, if I add the following rows to the event and category tables: -
insert into category (id, type, label, parent_id)
values (6, 'module', 'Mobile', 5), /* has 2 parents */
(7, 'organisation', 'AirBNB', null); /* has no parents */
insert into event (id, name, category_id)
values (4, 'event1', 6),
(5, 'event2', 7);
... and run the query from above it will return: -
ERROR: argument 1: key must not be null
SQL state:
My gut feeling is a recursive CTE could solve this.
Update 1
create or replace function category_array(category_parent_id int) returns setof jsonb as $$
select case
when count(x) > 0 then
jsonb_agg(f.x) || jsonb_build_object (
c.type, c.label
)
else jsonb_build_object (
c.type, c.label
)
end as category_pair
from category c
left join category_array (c.parent_id) as f(x) on true
where c.id = category_parent_id
group by c.id, c.type, c.label;
$$ language sql;
... and call using this SQL ...
select *,
category_array(category_id)
from event;
... will return the following ...
+----+--------+-------------+--------------------------------------------------------------------------+
| id | name | category_id | categories |
+----+--------+-------------+--------------------------------------------------------------------------+
| 1 | add | 4 | [{"organisation": "Apple"}, {"product": "iPhone"}] |
| 2 | delete | 5 | [{"organisation": "Apple"}, {"product": "Mac"}] |
| 3 | update | 2 | [{"organisation": "Google"}, {"product": "Gmail"}] |
| 4 | event1 | 6 | [[{"organisation": "Apple"}, {"product": "Mac"}], {"module": "Mobile"}] |
| 5 | event2 | 7 | {"organisation": "AirBNB"} |
+----+--------+-------------+--------------------------------------------------------------------------+
Pretty close but not quite there just yet!
Use the concatenation operator || to build cumulative jsonb objects:
with recursive events as (
select
e.id, e.name, e.category_id as parent_id,
jsonb_build_object(c.type, c.label) as categories
from event e
left join category c on c.id = e.category_id
union all
select
e.id, e.name, c.parent_id,
categories || jsonb_build_object(c.type, c.label)
from events e
join category c on c.id = e.parent_id
)
select id, name, categories
from events
where parent_id is null
order by id;
Note that the query is not protected against circular dependencies, so you need to be sure that all paths in the table are ended with nulls.
Test the query on DbFiddle.
Alternative solution:
create or replace function get_categories(int)
returns jsonb language sql as $$
select case
when parent_id is null then
jsonb_build_object (type, label)
else
jsonb_build_object (type, label) || get_categories(parent_id)
end as categories
from category
where id = $1
$$;
select id, name, get_categories(category_id)
from event
order by id;
DbFiddle.