MySQL combine multiple rows into one and maintain column names - mysql

The Tables
The tables and some dummy data to illustrate the issue.
members
Stores basic information about the members.
------------------------------------
| member_id | email |
------------------------------------
| 1 | 1#a.com |
------------------------------------
| 2 | 2#a.com |
------------------------------------
| 3 | 3#a.com |
------------------------------------
| 4 | 4#a.com |
------------------------------------
| 5 | 4#a.com |
------------------------------------
member_data
Stores some extra meta data for each member
----------------------------------------
| member_id | name | surname | company |
----------------------------------------
| 1 | A | A | A |
----------------------------------------
| 2 | B | B | B |
----------------------------------------
| 3 | C | C | C |
----------------------------------------
| 4 | D | D | D |
----------------------------------------
| 5 | E | E | E |
----------------------------------------
categories
Different categories that are within the system.
------------------------------------
| cat_id | cat_name |
------------------------------------
| 1 | Cars |
------------------------------------
| 2 | Bikes |
------------------------------------
| 3 | Boats |
------------------------------------
licences
A member must have a licence to be able to access a category.
-----------------------------------------------------------------------
| id | subid | catid | start_date | end_date | description |
-----------------------------------------------------------------------
| 1 | 1 | 1 | 2014-01-01 | 2020-12-31 | Premium |
-----------------------------------------------------------------------
| 2 | 1 | 2 | 2014-01-01 | 2015-12-31 | Premium |
-----------------------------------------------------------------------
| 3 | 1 | 3 | 2014-01-01 | 2018-12-31 | Premium |
-----------------------------------------------------------------------
| 4 | 2 | 1 | 2014-01-01 | 2016-12-31 | Premium |
-----------------------------------------------------------------------
| 7 | 3 | 1 | 2014-01-01 | 2014-01-02 | Premium |
-----------------------------------------------------------------------
| 8 | 3 | 2 | 2014-01-01 | 2014-01-02 | Premium |
-----------------------------------------------------------------------
| 9 | 3 | 3 | 2014-01-01 | 2020-01-31 | Premium |
-----------------------------------------------------------------------
| 10 | 5 | 1 | 2014-01-01 | 2014-01-02 | Premium |
-----------------------------------------------------------------------
| 11 | 5 | 2 | 2014-01-01 | 2014-01-02 | Premium |
-----------------------------------------------------------------------
| 12 | 5 | 3 | 2014-01-01 | 2014-01-02 | Premium |
-----------------------------------------------------------------------
About the data
Member 1 has a licence to categories 1,2 and 3. They are all active and valid.
Member 2 has a licence to category 1 only. It is active.
Member 3 has a licence to 1,2 and 3. Only the licence for category 3 is valid.
Member 4 has no licences.
Member 5 has licences for categories 1,2 and 3 but they are all expired.
What I'm Trying To Acheive
I want to get the records for each members' licence, with their respective member_data and category. A licence must exist and be valid for a category for the member to have data returned for that licence.
Furthermore, I want each of the licences that are returned to come back as one row, which contains all of the data required in the following format:
Output format
I want to output the members who hold valid licences, and return either their expiry date for a category or no output if they don't have a licence for a category but do hold one for another. I.e.:
----------------------------------------------------------------------------------
| Company | Name | LicenceType | Cars | Bikes | Boats |
----------------------------------------------------------------------------------
| A |A A | Premium |2020-12-31 | 2015-12-31 | 2018-12-21 |
----------------------------------------------------------------------------------
| B |B B | Premium |2016-12-31 | | |
----------------------------------------------------------------------------------
| C |C C | Premium | | | 2020-01-31 |
----------------------------------------------------------------------------------
What I've Tried
SELECT
md.company as Company,
CONCAT(md.name,' ', md.surname) as Name,
l.description as LicenceType,
(CASE WHEN (c.cat_name='Cars') THEN l.end_date ELSE '' END)AS Cars,
(CASE WHEN (c.cat_name='Bikes') THEN l.end_date ELSE '' END)AS Bikes,
(CASE WHEN (c.cat_name='Boats') THEN l.end_date ELSE '' END)AS Boats
FROM
licences as l
JOIN
categories as c ON c.cat_id=l.catid
JOIN
member_data as md ON md.member_id=l.subid
WHERE
l.end_date>='2014-12-17'
AND
(l.description='Premium')
ORDER BY Company ASC
Currently outputs
This is how the data currently appears:
----------------------------------------------------------------------------------
| Company | Name | LicenceType | Cars | Bikes | Boats |
----------------------------------------------------------------------------------
| A |A A | Premium |2020-12-31 | | |
----------------------------------------------------------------------------------
| A |A A | Premium | | 2015-12-31 | |
----------------------------------------------------------------------------------
| A |A A | Premium | | | 2018-12-21 |
----------------------------------------------------------------------------------
| B |B B | Premium |2016-12-31 | | |
----------------------------------------------------------------------------------
| C |C C | Premium | | | 2020-01-31 |
----------------------------------------------------------------------------------
The issue is, as you can see for Company A's record, showing as three distinct rows. I would like to have each of the three rows returned as just a single row, as per the output format shown above.
I'd appreciate any ideas on how to achieve this. Thank you.

Use aggregation:
SELECT
md.company as Company,
CONCAT(md.name,' ', md.surname) as Name,
l.description as LicenceType,
MAX(CASE WHEN (c.cat_name='Cars') THEN l.end_date ELSE '' END)AS Cars,
MAX(CASE WHEN (c.cat_name='Bikes') THEN l.end_date ELSE '' END)AS Bikes,
MAX(CASE WHEN (c.cat_name='Boats') THEN l.end_date ELSE '' END)AS Boats
FROM
licences as l
JOIN
categories as c ON c.cat_id=l.catid
JOIN
member_data as md ON md.member_id=l.subid
WHERE
l.end_date>='2014-12-17'
AND
(l.description='Premium')
GROUP BY Company, Name, l.description
ORDER BY Company ASC;

Related

MySQL : How to do a Matrix combining three tables

I have the three following SQL Tables :
+-------------------------+
| SHOP |
+---------+---------------+
| shop_id | shop_label |
+---------+---------------+
| 1 | Shop Paris |
| 2 | Shop Madrid |
| 3 | Shop New York |
| 4 | Shop Tokyo |
+---------+---------------+
+----------------------------+
| PRODUCT |
+------------+---------------+
| product_id | product_label |
+------------+---------------+
| 1 | Pen |
| 2 | Workbook |
| 3 | Smartphone |
| 4 | Computer |
| 5 | Chair |
+------------+---------------+
+-------------------------------------------------------+
| COMMAND LINE |
+---------+------------+----------+---------------------+
| fk_shop | fk_product | quantity | date |
+---------+------------+----------+---------------------+
| 1 | 1 | 10 | 2021-10-20 12:10:59 |
| 4 | 3 | 1 | 2021-10-23 12:11:07 |
| 2 | 2 | 3 | 2021-10-29 12:12:07 |
| 1 | 2 | 8 | 2021-10-30 12:12:37 |
| 1 | 1 | 5 | 2021-11-03 13:10:07 |
+---------+------------+----------+---------------------+
And now I'm trying to make a matrix of all the products as COLUMNS and all the shops as ROWS showing how much product were buyed. I would like to make a query to retrieve this result :
+------------+------------+-------------+---------------+------------+
| | Shop Paris | Shop Madrid | Shop New York | Shop Tokyo |
+------------+------------+-------------+---------------+------------+
| Pen | 15 | 8 | | |
| Workbook | | 3 | | |
| Smartphone | | | | 1 |
| Computer | | | | |
| Chair | | | | |
+------------+------------+-------------+---------------+------------+
Do you know a way to do something like this ? I saw a long time ago a query with the WITH operator to create matrix, but I don't remember it really well...
Thanks for your help.
in oracle or sqlserver there is PIVOT() to use , but in MYSQL its not.
So something like this can be done:
select p.product_label ,
sum(case when s.shop_id = 1 then cl.quantity else 0 end) Shop_Paris ,
sum(case when s.shop_id = 2 then cl.quantity else 0 end) Shop_madrid ,
sum(case when s.shop_id = 3 then cl.quantity else 0 end) Shop_newyork ,
sum(case when s.shop_id = 4 then cl.quantity else 0 end) Shop_tokyo
from command_line cl
inner join product p on p.product_id = cl.fk_product
inner join shop s on s.shop_id = cl.fk_shop
group by p.product_label

Joins with two tables conditionally based on the value of ONE column

I have following tables to manage purchase and issues of items.
item Table
+---------+-----------+
| item_id | item_name |
+---------+-----------+
| 500 | A4 |
| 501 | A5 |
| 502 | B5 |
| 503 | B4 |
| 504 | A3 |
+---------+-----------+
supplier Table
+-------------+---------------+
| sup_id | supplier_name |
+-------------+---------------+
| 1 | ABC Suppliers |
| 2 | DEF Suppliers |
| 3 | GHI Suppliers |
+-------------+---------------+
officer Table
+------------+--------------+
| officer_id | officer_name |
+------------+--------------+
| 1 | Jhon |
| 2 | William |
| 3 | Ken |
| 4 | Robert |
+------------+--------------+
purchase / issue table
+-----------+---------+---------+---------+------------+-----+-------------+
| update_id | bill_no | sup_id | item_id | date | qty | type |
+-----------+---------+---------+---------+------------+-----+-------------+
| 1000 | 10 | 1 | 500 | 2018-11-01 | 50 | purchase |
| 1001 | 40 | 1 | 500 | 2018-11-02 | 25 | purchase |
| 1002 | 32 | 2 | 500 | 2018-11-04 | 10 | issue |
| 1003 | 25 | 3 | 500 | 2018-11-05 | 12 | issue |
| 1004 | 14 | 1 | 500 | 2018-11-08 | 22 | purchase |
| 1005 | 55 | 2 | 501 | 2018-11-09 | 10 | purchase |
| 1006 | 30 | 2 | 502 | 2018-11-10 | 40 | purchase |
+-----------+---------+---------+---------+------------+-----+-------------+
02) purchase / issue table holds the purchase details and issue details by mentioning the "type" at the end of table.
03) I need to get the following output.
+-----------+------------------------------+------------+-----+------------+
| item_name | supplier_name / officer_name | date | qty |type |
+-----------+------------------------------+------------+-----+------------+
| A4 | ABC Suppliers | 2018-11-01 | 50 | purchase |
| A4 | ABC Suppliers | 2018-11-02 | 25 | purchase |
| A4 | William | 2018-11-04 | 10 | issue |
| A4 | Ken | 2018-11-05 | 12 | issue |
| A4 | ABC Suppliers | 2018-11-08 | 22 | purchase |
| A5 | DEF Suppliers | 2018-11-09 | 10 | purchase |
| B5 | DEF Suppliers | 2018-11-10 | 40 | purchase |
+-----------+------------------------------+------------+-----+------------+
03) I used the following query
select item.item_name,
supplier.supplier_name,
purchase.date,
purchase.qty,
purchase.type
from purchase
join item on item.item_id = purchase.item_id
where supplier.sup_id in (select sup_id from supplier)
04) But I did't get the desired output. I can not understand what I am going wrong. Can anyone help me ?
In your SELECT clause, you are referring to supplier table, without joining to that table. Now, it seems that you want to show officer_name when the purchase type is issue, else supplier_name when it is purchase.
We can do a LEFT JOIN to both the tables, and use CASE .. WHEN to determine the respective name.
I have removed the WHERE .. IN subquery, which does not seem to serve any purpose here.
select item.item_name,
CASE purchase.type
WHEN 'purchase' THEN supplier.supplier_name
WHEN 'issue' THEN officer.officer_name
END AS `supplier_name_officer_name`
purchase.date,
purchase.qty,
purchase.type
from purchase
join item on item.item_id = purchase.item_id
left join supplier on purchase.sup_id = supplier.sup_id
left join officer on purchase.sup_id = officer.officer_id
You need to join the supplier just as you did for the item.
And I see no point in the where clause at all.

Get quantity per product in each order using MySQL

I have 3 tables, order, products, and orders_products and I'm trying to find out how many of each product was bought in each order. Is it possible to get all information in a single query?
My current query doesn't seem to work, whereas the product_id comes out as all the same.
SELECT orders.order_id, orders.username, COUNT( DISTINCT op.product_id ) product_id, SUM( op.quantity ) quantity
FROM `orders`
JOIN orders_products op ON op.order_id = orders.order_id
GROUP BY product_id, orders.order_id ORDER BY order_id, product_id
Which results in:
_______________________________________________
| order_id | username | product_id | quantity |
-----------------------------------------------
| 1 | bill | 1 | 3 |
-----------------------------------------------
| 1 | bill | 1 | 2 |
-----------------------------------------------
| 1 | bill | 1 | 5 |
-----------------------------------------------
| 2 | sally | 1 | 2 |
-----------------------------------------------
| 3 | jeff | 1 | 6 |
-----------------------------------------------
| 3 | jeff | 1 | 7 |
-----------------------------------------------
You can see the problem above in the product_id column which is always set to 1.
I'm trying to get something like this:
_______________________________________________
| order_id | username | product_id | quantity |
-----------------------------------------------
| 1 | bill | 1 | 5 |
-----------------------------------------------
| 1 | bill | 2 | 3 |
-----------------------------------------------
| 1 | bill | 3 | 2 |
-----------------------------------------------
| 2 | sally | 1 | 2 |
-----------------------------------------------
| 3 | jeff | 1 | 6 |
-----------------------------------------------
| 3 | jeff | 2 | 7 |
-----------------------------------------------
My tables:
-- Orders
_______________________
| order_id | username |
-----------------------
| 1 | bill |
-----------------------
| 2 | sally |
-----------------------
| 3 | jeff |
-----------------------
-- Products
___________________
| id | product |
-------------------
| 1 | Table |
-------------------
| 2 | Chair |
-------------------
| 3 | Mouse |
-------------------
-- Order Products
___________________________________________
| id | order_id | product_id | quantity |
-------------------------------------------
| 1 | 1 | 1 | 5 |
-------------------------------------------
| 2 | 1 | 2 | 3 |
-------------------------------------------
| 3 | 1 | 3 | 2 |
-------------------------------------------
| 4 | 2 | 1 | 2 |
-------------------------------------------
| 5 | 3 | 1 | 6 |
-------------------------------------------
| 6 | 3 | 2 | 7 |
-------------------------------------------
I think the query you want is:
SELECT o.order_id, o.username, op.product_id, SUM( op.quantity ) as quantity
FROM `orders` o JOIN
orders_products op
ON op.order_id = o.order_id
GROUP BY op.product_id, o.order_id
ORDER BY o.order_id, op.product_id;
In general, you do want all the columns in the GROUP BY in the SELECT. But (in general) they shouldn't be arguments to aggregation functions such as COUNT().
You can see the problem above in the product_id column which is always
set to 1
I think you are bit confused here, that's the data present in Order Products table for product_id column but in your query what you are trying to get is count of data COUNT( DISTINCT op.product_id ) product_id and since the count is 1, the result is showing the same.

MYSQL: Separate products into columns

This is in relation to my previous post MYSQL: Get quantity per product in each order.
Using the query below, it generates a table of:
SELECT o.order_id, o.username, op.product_id, SUM( op.quantity ) as quantity
FROM `orders` o JOIN
orders_products op
ON op.order_id = o.order_id
GROUP BY op.product_id, o.order_id
ORDER BY o.order_id, op.product_id;
_______________________________________________
| order_id | username | product_id | quantity |
-----------------------------------------------
| 1 | bill | 1 | 5 |
-----------------------------------------------
| 1 | bill | 2 | 3 |
-----------------------------------------------
| 1 | bill | 3 | 2 |
-----------------------------------------------
| 2 | sally | 1 | 2 |
-----------------------------------------------
| 3 | jeff | 1 | 6 |
-----------------------------------------------
| 3 | jeff | 2 | 7 |
-----------------------------------------------
As seen above, each new product_id is assigned its own row. Is it possible to have additional columns for each product_id instead of rows generating an output same as the one below?
_______________________________________________________________________
| order_id | username | product_qty_1 | product_qty_2 | product_qty_3 |
-----------------------------------------------------------------------
| 1 | bill | 5 | 3 | 2 |
-----------------------------------------------------------------------
| 2 | sally | 2 | 0 | 0 |
-----------------------------------------------------------------------
| 3 | jeff | 6 | 7 | 0 |
-----------------------------------------------------------------------
My tables:
-- Orders
_______________________
| order_id | username |
-----------------------
| 1 | bill |
-----------------------
| 2 | sally |
-----------------------
| 3 | jeff |
-----------------------
-- Products
___________________
| id | product |
-------------------
| 1 | Table |
-------------------
| 2 | Chair |
-------------------
| 3 | Mouse |
-------------------
-- Order Products
___________________________________________
| id | order_id | product_id | quantity |
-------------------------------------------
| 1 | 1 | 1 | 5 |
-------------------------------------------
| 2 | 1 | 2 | 3 |
-------------------------------------------
| 3 | 1 | 3 | 2 |
-------------------------------------------
| 4 | 2 | 1 | 2 |
-------------------------------------------
| 5 | 3 | 1 | 6 |
-------------------------------------------
| 6 | 3 | 2 | 7 |
-------------------------------------------
If you know the products in advance, you can use conditional aggregation:
SELECT o.order_id, o.username,
SUM(CASE WHEN op.product_id = 1 THEN op.quantity ELSE 0 END) as quantity1,
SUM(CASE WHEN op.product_id = 2 THEN op.quantity ELSE 0 END) as quantity2,
SUM(CASE WHEN op.product_id = 3 THEN op.quantity ELSE 0 END) as quantity3
FROM `orders` o JOIN
orders_products op
ON op.order_id = o.order_id
GROUP BY o.order_id
ORDER BY o.order_id;
If you don't know the product ids, then you would have to use dynamic SQL, or perhaps use GROUP_CONCAT() to create a string with the information you want.

Complex MySQL Query for Many-to-Many

I have searched and gone through the available topics similar to mine. But, failed to find that satisfies my requirements. Hence, posting it here.
I have four tables as follows:
"Organization" table:
--------------------------------
| org_id | org_name |
| 1 | A |
| 2 | B |
| 3 | C |
"Members" table:
----------------------------------------------
| mem_id | mem_name | org_id |
| 1 | mem1 | 1 |
| 2 | mem2 | 1 |
| 3 | mem3 | 2 |
| 4 | mem4 | 3 |
"Resource" table:
--------------------------------
| res_id | res_name |
| 1 | resource1 |
| 2 | resource2 |
| 3 | resource3 |
| 4 | resource4 |
"member-resource" table:
--------------------------------------------
| sl_no | mem_id | res_id |
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 1 |
| 4 | 4 | 3 |
| 5 | 3 | 4 |
| 6 | 2 | 3 |
| 7 | 4 | 3 |
I want to find out the total number of distinct resources according to organizations. Expected output is as follows:
| org_name | Total Resources |
| A | 3 |
| B | 1 |
| C | 1 |
I also want to find out the total number of shared resources according to organizations. Expected output is as follows:
| org_name | Shared Resources |
| A | 1 |
| B | 0 |
| C | 1 |
Any help in this regard will highly be appreciated.
Regards.
It is much simpler than you think, particularly because you don't even need the resource table:
SELECT o.org_name, COUNT(DISTINCT mr.res_id) TotalResources
FROM member_resource mr
JOIN members m ON mr.mem_id = m.mem_id
JOIN organization o ON m.org_id = o.org_id
GROUP BY o.org_id
Output:
| ORG_NAME | TOTALRESOURCES |
|----------|----------------|
| A | 3 |
| B | 1 |
| C | 1 |
Fiddle here.
Try this query below.
SELECT org_name, COUNT(DISTINCT res_id)
FROM organization, members, member-resource
WHERE members.mem_id = member-resource.mem_id
AND organization.org_id = members.org_id
GROUP BY org_id, org_name