Fetching N records excluding joined ones? - mysql

I have three tables (details omitted for brevity):
create table products (
id,
name
)
create table tags (
id,
name
)
create table product_tags (
product_id,
tag_id
)
These tables are populated as follows:
--------
products
--------
+----+------+
| id | name |
+----+------+
| 1 | Rice |
| 2 | Bean |
| 3 | Milk |
+----+------+
----
tags
----
+----+-------+
| id | name |
+----+-------+
| 1 | Eat |
| 2 | Drink |
| 3 | Seeds |
| 4 | Cow |
+----+-------+
When fetching products I want the output to be formatted as:
[{
id: 1,
name: 'Rice',
tags: [
{
id: 1,
name: 'Eat'
},
{
id: 3,
name: 'Seeds'
},
]
},
{
id: 2,
name: 'Bean',
tags: [
{
id: 1,
name: 'Eat'
},
{
id: 3,
name: 'Seeds'
},
]
},
{
id: 3,
name: 'Milk',
tags: [
{
id: 2,
name: 'Drink'
},
{
id: 4,
name: 'Cow'
},
]
}]
To accomplish this, what I'm doing is:
select
products.*,
tags.id as tag_id, tags.name as tag_name
from products
left join product_tags map on map.product_id = products.id
left join tags on map.tag_id = tags.id
The output of which is:
[{
id: 1,
name: 'Rice',
tag_id: 1,
tag_name: 'Eat',
},{
id: 1,
name: 'Rice',
tag_id: 3,
tag_name: 'Seeds',
},{
id: 2,
name: 'Bean',
tag_id: 1,
tag_name: 'Eat',
},{
id: 2,
name: 'Bean',
tag_id: 3,
tag_name: 'Seeds',
},{
id: 3,
name: 'Milk',
tag_id: 2,
tag_name: 'Drink',
},{
id: 3,
name: 'Milk',
tag_id: 4,
tag_name: 'Cow',
}]
Which I parse by hand and aggregate each product instance with an array of zero or more tag objects that it is associated with.
Question
When doing the select above, the output is 6 rows. However, there are only 3 products. Is it possible to use a limit clause that would apply to just the products?
For example, if Product.id => 1 had 10 tags associated with it, doing a LIMIT 5 would only select 5 of the tags. What I want to do is select 5 products along with all tags associated with it.
The only way I can think of achieving this is to select just the products, then do an unbounded SELECT with just the product IDs from the previous query.
Bonus question
Is there a more efficient way to do this JOIN such that the output is aggregated as above?

Use a subquery to select 5 products, then join with tags:
SELECT p.name as product, t.name AS tag
FROM (
SELECT id, name
FROM products
ORDER by name
LIMIT 5) AS p
JOIN product_tags AS pt ON pt.product_id = p.id
JOIN tags AS t ON pt.tag_id = t.id

Related

Node.js mysql2 Wrap joined table into array of objects

I have faced the problem when I have two tables with relation, let's say users and posts, and in my DB I have one user with three posts:
Users table:
id
name
1
John
Posts table:
id
user_id
1
1
2
1
3
1
When i try to retrieve my data with the next SQL query
this.connection.query(SELECT * FROM users U LEFT JOIN posts.user_id P on U.id = P.user.id)
i receive three rows for each post with the user:
{ id: 1, name: 'John', posts.id: 1, user_id: 1 }
{ id: 1, name: 'John', posts.id: 2, user_id: 1 }
{ id: 1, name: 'John', posts.id: 2, user_id: 1 }
I want to map all joined data into an array of objects, just like this:
[
{
id: 1,
name: 'John',
posts: [
{ id: 1, user_id: 1 },
{ id: 2, user_id: 1 },
{ id: 3, user_id: 1 },
]
}
]
I already implemented my own mapper via the javascript reduce method, but I hope this could be done underhood.
During my investigation, I found Mysql aggregate functions documentation. In my case, JSON_ARRAYAGG combined with JSON_OBJECT did the job for me and returned the result:
[
{
id: 1,
name: 'John',
posts: [
{ id: 1, user_id: 1 },
{ id: 2, user_id: 1 },
{ id: 3, user_id: 1 },
]
}
]
Query, I used to retrieve data:
SELECT
user.id,
user.name,
user.age,
JSON_ARRAYAGG(JSON_OBJECT(‘id’, post.id, 'label', post.label)) AS posts
FROM users U
LEFT JOIN posts.user_id P on U.id = P.user.id
GROUP BY U.id
Also, this might be useful for Postgres users:
SELECT
user.id,
user.name,
user.age,
json_agg(json_build_object('id', post.id, 'label', post.label, ..... )) AS posts
FROM ...
JOIN ...
GROUP BY user.id, user.name, user.age;

postgresql How to generate nested json objects by one table

I want to aggregate the data from one table to a complex json. The elements should be grouped by id column. And nested json should be grouped by section column
I have a table with data:
-----------------------------
| id | section | subsection |
----------------------------
| 1 | s_1 | ss_1 |
----------------------------
| 1 | s_1 | ss_2 |
----------------------------
| 1 | s_2 | ss_3 |
----------------------------
| 2 | s_3 | ss_4 |
----------------------------
I want to create json like that:
[
{
"id": 1,
"sections": [
{
"section": "s_1",
"subsections": [
{
"subsection": "ss_1"
},
{
"subsection": "ss_2"
}
]
},
{
"section": "s_2",
"subsections": [
{
"subsection": "ss_3"
}
]
}
]
},
{
"id": 2,
"sections": [
{
"section": "s_3",
"subsections": [
{
"subsection": "ss_3"
}
]
}
]
}
]
I tried do it like that:
select
json_build_array(
json_build_object(
'id', a.id,
'sections', json_agg(
json_build_object(
'section', b.section,
'subsections', json_agg(
json_build_object(
'subsection', c.subsection
)
)
)
)
)
)
from table as a
inner join table as b on a.section = b.section
inner join table as c on b.subsection = c.subsection
group by a.id;
BUT there is a problem: Nested aggregate calls are not allowed
Is there any possible way to use nested aggregate calls? Or is there more elegant solution?
You'll need to use CTEs or nested queries for this. No JOINs necessary though:
SELECT json_agg(
json_build_object(
'id', a.id,
'sections', a.sections
)
)
FROM (
SELECT b.id, json_agg(
json_build_object(
'section', b.section,
'subsections', b.subsections
)
) AS sections
FROM (
SELECT c.id, c.section, json_agg(
json_build_object(
'subsection', c.subsection
)
) AS subsections
FROM table AS c
GROUP BY id, section
) AS b
GROUP BY id
) AS a;

mysql jsonarrayagg for comma seperated id

i have two table casetemp and medicication_master
patient has fields
id
name
age
sex
medicineid
1
xyz
23
M
1,2
2
abc
20
f
3
medicine has fields
id
medname
desc
1
crosin
fever tab
2
etzaa
shampoo
3
zaanta
painkiller
i want the mysql left join output as following :
[{
"id":"1",
"name":"xyz",
"age":"23",
"sex":"M",
"medicine_id":"1,2",
"medicine_Data":[
{
"id":"1"
"medname":"crosin",
"desc":"fever tab"
},
{
"id":"2"
"medname":"etzaa",
"desc":"shampoo"
}]
},
{
"id":"2",
"name":"abc",
"age":"20",
"sex":"F",
"medicine_id":"3",
"medicine_Data":[{
"id":"3"
"medname":"zaanta",
"desc":"pain killer"
}]
}]
the query i used is
SELECT json_object(
'id', b.id,
'name',b.name,
'age',b.age,
'sex',b.sex,
'medicine_id',b.medicine_id,
'medicine_data', json_arrayagg(json_object(
'id', pt.id,
'medname', pt.medname,
'desc',pt.desc,
))
)
FROM patient b LEFT JOIN medication_master pt
ON b.medicine_id = pt.id
where b.id=1
GROUP BY b.id;
thanks in advance

SQL Select to JSON for Menu Structure

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')

Retrieve running total over time of records in foreign table for each record in table

I have a table group and a table members. A group has many members. A single member record can belong to a single group. The members table has a foreign key/column group_id.
groups
+---------+---------+
| id | name |
+---------+---------+
| 1 | group01 |
+---------+---------+
members
+----+---------+------------+
| id | groupid | created_at |
+----+---------+------------+
| 9 | 1 | 2020-03-05 |
+----+---------+------------+
I need to retrieve, for each record in the groups table, a running total of its member count over time.
Here's an example of what the final data needs to look like, to make this more clear.
[
{ group: 'group01', count: 3, date: '2020-01-05' },
{ group: 'group01', count: 10, date: '2020-01-12' },
{ group: 'group01', count: 14, date: '2020-02-02' },
{ group: 'group02', count: 5, date: '2020-01-02' },
{ group: 'group02', count: 13, date: '2020-01-23' },
{ group: 'group02', count: 23, date: '2020-01-28' },
...
]
Does anyone know how this can be done?
In MySQL 8.0, you can just use window functions for this. You might be able to get the information without even looking at the groups table:
select
m.group_id,
count(*) over(partition by g.group_id order by date) cnt,
m.date
from members m
Or, if you want some data that is available in groups but no in members (say, the name of the group):
select
g.group_name,
count(*) over(partition by g.group_id order by date) cnt,
m.date
from members m
inner join groups g on g.group_id = m.group_id