Node.js mysql2 Wrap joined table into array of objects - mysql

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;

Related

How to join two tables in mysql with matching data and group in array

I need to join two tables (products and orders) in mysql with a matching data (order id) and different data (products ids) that should be in a array.
I tried with a simple JOIN but data are not being grouped. Does anyone can give me a light?
SELECT Order.id, Order.userId, Product.id AS productsIds
FROM Orders AS Order
JOIN Products AS Product
ON Order.id = Product.orderId
Result:
[
{
"id": 1,
"userId": 1,
"productsIds": 2
},
{
"id": 1,
"userId": 1,
"productsIds": 5
},
{
"id": 1,
"userId": 1,
"productsIds": 6
},
{
"id": 3,
"userId": 2,
"productsIds": 4
},
{
"id": 2,
"userId": 3,
"productsIds": 3
}
]
Expected:
[
{
"id": 1,
"userId": 1,
"productsIds": [2, 5, 6]
},
{
"id": 3,
"userId": 2,
"productsIds": 4
},
{
"id": 2,
"userId": 3,
"productsIds": 3
}
]
You need to use GROUP BY clause and GROUP_CONCAT in select statement, will give the expected result.
Following is the query.
SELECT Order.id, Order.userId, GROUP_CONCAT(Product.id) AS productsIds FROM Orders AS Order JOIN Products AS Product ON Order.id = Product.orderId GROUP BY Order.id, Order.userId ;

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

Fetching N records excluding joined ones?

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