MySQL Dynamic inner join for combining multiple rows into one? - mysql

I am creating a database with the tables below, where shop_id in hours refers to a an id in shop.
Preferably I would have a query to return all data in one row, in stead of needing to post-process a lot of rows to "merge" the result from hours so the end result looks like this.
+-------------------+---------+------+---------+----------------+----------+---------------+---------------+-----+-----+-----+-----+-----+-----+-----+
| name | address | zip | city | municipal | phone | lat | lng | day | day | day | day | day | day | day |
+-------------------+---------+------+---------+----------------+----------+---------------+---------------+-----+-----+-----+-----+-----+-----+-----+
| Coop Marked Budal | false | 7298 | Budalen | Midtre gauldal | 72436410 | 62.8837013245 | 10.4836997986 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
+-------------------+---------+------+---------+----------------+----------+---------------+---------------+-----+-----+-----+-----+-----+-----+-----+
Right now I have come to the query below, wich I feel there must be a better alternative to. So my question is: Is there really another solution to this?
Also, I've seen concat, but I want to avoid having to split strings later on when processing the data.
select shop.name, shop.address, shop.zip, shop.city, shop.municipal, shop.phone, shop.lat, shop.lng,
h.day, hh.day, hhh.day, hhhh.day, hhhhh.day, hhhhhh.day, hhhhhhh.day
from shop
/**
As it requires a unique table name, this was the solution I found.
Could this be shortened?
**/
inner join hours h on shop.id = h.shop_id and h.day = 1
inner join hours hh on shop.id = hh.shop_id and hh.day = 2
inner join hours hhh on shop.id = hhh.shop_id and hhh.day = 3
inner join hours hhhh on shop.id = hhhh.shop_id and hhhh.day = 4
inner join hours hhhhh on shop.id = hhhhh.shop_id and hhhhh.day = 5
inner join hours hhhhhh on shop.id = hhhhhh.shop_id and hhhhhh.day = 6
inner join hours hhhhhhh on shop.id = hhhhhhh.shop_id and hhhhhhh.day = 7;
Tables
shop
+----+-------------------+---------+------+---------+----------------+----------+---------------+---------------+----------+-----------+
| id | name | address | zip | city | municipal | phone | lat | lng | chain_id | county_id |
+----+-------------------+---------+------+---------+----------------+----------+---------------+---------------+----------+-----------+
| 1 | Test | false | 1234 | Test | Test | 12341234| 0.0000 | 0.0000 | 3 | 16 |
+----+-------------------+---------+------+---------+----------------+----------+---------------+---------------+----------+-----------+
hours
+-----+----------+----------+---------+
| day | open | close | shop_id |
+-----+----------+----------+---------+
| 1 | 09:00:00 | 18:00:00 | 1 |
| 2 | 09:00:00 | 18:00:00 | 1 |
| 3 | 09:00:00 | 18:00:00 | 1 |
| 4 | 09:00:00 | 18:00:00 | 1 |
| 5 | 09:00:00 | 18:00:00 | 1 |
| 6 | 09:00:00 | 20:00:00 | 1 |
| 7 | 14:00:00 | 20:00:00 | 1 |
+-----+----------+----------+---------+

You can also use a case .. when to do the pivot, and then group by the shop fields and use an aggregate function to process the day.
select
shop.NAME, shop.address, shop.zip, shop.city, shop.municipal, shop.phone, shop.lat, shop.lng,
MAX(CASE WHEN h.DAY = 1 THEN h.DAY ELSE 0 END) AS Day1,
MAX(CASE WHEN h.DAY = 2 THEN h.DAY ELSE 0 END) AS Day2,
MAX(CASE WHEN h.DAY = 3 THEN h.DAY ELSE 0 END) AS Day3,
MAX(CASE WHEN h.DAY = 4 THEN h.DAY ELSE 0 END) AS Day4,
MAX(CASE WHEN h.DAY = 5 THEN h.DAY ELSE 0 END) AS Day5,
MAX(CASE WHEN h.DAY = 6 THEN h.DAY ELSE 0 END) AS Day6,
MAX(CASE WHEN h.DAY = 7 THEN h.DAY ELSE 0 END) AS Day7
from shop
INNER JOIN HOURS h ON shop.id = h.shop_id
group by
shop.NAME, shop.address, shop.zip, shop.city, shop.municipal, shop.phone, shop.lat, shop.lng;
Just a note about what you want displayed in the day columns:
AFAIK if any of the hours rows for a shop : day is missing, your current query will drop the whole row? If you want this behaviour repeated, you will need to also add in a where clause.

Related

One row result with multiple join in MySQL

I have 3 tables like the following.
Table "mansioni":
id_mansione | desc_mansione
1 | production
2 | office
3 | transport
Table "dipendente": store id, name and surname:
id_dip | nome_dip | cognome_dip
1 | piero | rossi
2 | marco | rossi
Table dipendenti_iddip: store the association between "dipendente" and table "mansioni"
iddip_mansione | num_mansione | id_mansione
1 | 1 | 1
1 | 2 | 2
2 | 1 | 2
2 | 2 | 3
Now I need a query that give me a result like this:
id_dip | nome_dip | cognome_dip | mansione1 | mansione2 | mansione3
1 | piero | rossi | production| office |
2 | marco | rossi | office | transport |
I arrived to the following query but with this I can only see the "id_mansione" and not the "desc mansione" field
select i.id_dip,
i.nome_dip,
i.cognome_dip,
max(case when t.num_mansione='1' then t.id_mansione end) Mansione1,
max(case when t.num_mansione='2' then t.id_mansione end) Mansione2,
max(case when t.num_mansione='3' then t.id_mansione end) Mansione3
from dipendente i
left join dipendenti_iddip t
on i.id_dip = t.iddip_mansione
group by i.id_dip, i.nome_dip, i.cognome_dip
How can I arrive to my result?
Thanks...
Add join on mansioni and replace t.id_mansione with m.desc_mansione
select i.id_dip,
i.nome_dip,
i.cognome_dip,
max(case when t.num_mansione = '1' then m.desc_mansione end) Mansione1,
max(case when t.num_mansione = '2' then m.desc_mansione end) Mansione2,
max(case when t.num_mansione = '3' then m.desc_mansione end) Mansione3
from dipendente i
join dipendenti_iddip t
on i.id_dip = t.iddip_mansione
join mansioni m on m.id_mansione = t.id_mansione
group by i.id_dip

subtract 2 DB rows from each other for each unique partner_ID? MYSQL

I have a MYSQL table where I need to get to subtract values from 2 different rows.
This is my DB table:
Tablename: ext_partnertotals
| Partner_ID | Partnername | Month | Year | Total |
|------------|-------------|-------|------|-------|
| 1 | Partner 1 | 1 | 2018 | 10 |
| 1 | Partner 1 | 2 | 2018 | 12 |
| 2 | Partner 2 | 1 | 2018 | 18 |
| 2 | Partner 2 | 2 | 2018 | 12 |
It should get this with a query:
| Partner_ID | Partnername | up/down |
|------------|-------------|---------|
| 1 | Partner 1 | +2 |
| 2 | Partner 2 | -6 |
I need to get the Subtract value of 2 different months for each Partner.
Every Partner has a tablerow for each month and a value for that month.
Now I need to get If they went up or went down in value since the month before.
Can someone write me a query?
Since you're unable to improve your terrible schema, I recommend you use a (very ugly/hard to maintain and very slow) correlated subquery:
SELECT Partner_ID, Partnername, Year, Month, Total - (
SELECT Total
FROM ext_partnertotals AS prev
WHERE prev.Partner_ID = cur.Partner_ID AND CASE cur.Month
WHEN 1 THEN prev.Year = cur.Year - 1 AND prev.Month = 12
ELSE prev.Year = cur.Year AND prev.Month = cur.Month - 1
END
) AS `up/down` FROM ext_partnertotals AS cur
See it on sqlfiddle.

Subquery mysql with where clause

Hello, Im stuck with mysql subquery, this is the table I have
table detail_order
==============================
id_detail | id_order | id_toko
1 | 1 | 1
2 | 1 | 2
3 | 1 | 3
4 | 1 | 4
table ket_detail
==================================
id_ket | id_detail | id_size | qty
1 | 1 | 7 | 3
2 | 1 | 9 | 1
3 | 1 | 5 | 2
4 | 2 | 7 | 8
table size
=================================
id_size | size | id_color | stock
7 | 40 | 6 | 30
9 | 42 | 6 | 20
5 | 39 | 5 | 30
table color
==========================
id_color | color
6 | green
5 | red
Im trying in subquery to show qty on table ket_detail with where clause, but when Im try it subquery return more than one row.
this is my query
SELECT dt.id_detail,
SUM(tk.qty) AS tot_order,
COUNT(dm.color) AS tot_color,
(SELECT ket.qty FROM ket_detail AS ket, t_size AS u
WHERE u.id_size=ket.id_size AND u.size = 40) AS size_40
FROM detail_order AS dt
LEFT JOIN ket_detail AS tk ON tk.id_detail=dt.id_detail
LEFT JOIN t_size AS u ON u.id_size = tk.id_size
LEFT JOIN t_color AS dm ON dm.id_color=u.id_color
WHERE dt.id_order = 1
GROUP BY dt.id_detail
but when I change size to 39 the data like this
id_detail | tot_order | tot_color | size_40
============================================
1 | 6 | 2 | 2
2 | 8 | 1 | 2
3 | NULL | 0 | 2
4 | NULL | 0 | 2
what do I want is the data like this
id_detail | tot_order | tot_color | size_40
============================================
1 | 6 | 2 | 3
2 | 8 | 1 | 8
3 | NULL | 0 | NULL
4 | NULL | 0 | NULL
You don't need a subquery to get the size = 39 or size = 40 data. You can use conditional aggregation instead:
SELECT dt.id_detail,
SUM(tk.qty) AS tot_order,
COUNT(dm.color) AS tot_color,
SUM(CASE
WHEN u.size = 39 THEN tk.qty
ELSE 0
END) AS size_39,
SUM(CASE
WHEN u.size = 40 THEN tk.qty
ELSE 0
END) AS size_40
FROM detail_order AS dt
LEFT JOIN ket_detail AS tk ON tk.id_detail=dt.id_detail
LEFT JOIN t_size AS u ON u.id_size = tk.id_size
LEFT JOIN t_color AS dm ON dm.id_color=u.id_color
WHERE dt.id_order = 1
GROUP BY dt.id_detail;
Demo here
The proper way to do it with a subquery is:
SELECT dt.id_detail,
SUM(tk.qty) AS tot_order,
COUNT(dm.color) AS tot_color,
(SELECT SUM(ket.qty)
FROM ket_detail AS ket
JOIN t_size AS u ON u.id_size=ket.id_size
WHERE ket.id_detail = dt.id_detail AND u.size = 40) AS size_40
FROM detail_order AS dt
LEFT JOIN ket_detail AS tk ON tk.id_detail=dt.id_detail
LEFT JOIN t_size AS u ON u.id_size = tk.id_size
LEFT JOIN t_color AS dm ON dm.id_color=u.id_color
WHERE dt.id_order = 1
GROUP BY dt.id_detail;
Demo here
If you need to select the total quantity for more than one sizes, then you have to repeat the subquery for each required size. Hence, I think, the first query provides a solution that is cleaner, easier to extend and more efficient.

Joining and nesting queries in mysql

Currently, I'm using this nice query:
select
users.name,
sum(race_results.winnings) as total_winnings,
count(CASE WHEN race_results.place=1 THEN 1 ELSE 0 END) AS times_won_first_place
from users
inner join race_results
where race_results.userid = users.id and race_results.place = 1
group by users.id
order by total_winnings desc
to get this
************************************************
| name | total_winnings | times_won_first_place |
| Bob | 4000 | 4 |
| John | 1000 | 1 |
************************************************
the race_results table looks like this
*******************************************
| id | raceid | userid | place | winnings |
| 1 | 1 | 1 | 1 | 1000 |
| 2 | 1 | 2 | 5 | 50 |
| 3 | 1 | 3 | 6 | 50 |
| 4 | 2 | 1 | 1 | 1000 |
| 5 | 2 | 2 | 3 | 250 |
*******************************************
I would like to include four three more columns for something like this
***************************************************************************
| name | total_winnings | total_races | 1st_place | 2nd_place | 3rd_place |
| Bob | 4000 | 5 | 4 | 0 | 0 |
| John | 1000 | 5 | 1 | 1 | 1 |
***************************************************************************
If I were to do separate queries for the new columns, I'd use
select count(raceid) from race_results where userid = 1
select count(raceid) from race_results where userid = 1 and place = 1
select count(raceid) from race_results where userid = 1 and place = 2
select count(raceid) from race_results where userid = 1 and place = 3
to do separate queries would be easy but with the existing query I had to use CASE just to get the count of times a user won 1st place. (using
count(CASE WHEN race_results.place=2 THEN 1 ELSE 0 END)
returns the same results).
How would I nest these or join them into my existing query to get what I want?
You can do it this way:
select
users.name,
sum(race_results.winnings) as total_winnings,
count(*) AS total_races,
sum(race_results.place = 1) AS times_won_first_place ,
sum(race_results.place = 2) AS times_won_second_place,
sum(race_results.place = 3) AS times_won_third_place
from users
inner join race_results
where race_results.userid = users.id
group by users.id
order by total_winnings desc;
With ANSI standard SQL you could use case expressions inside the sum function but since MySQL (and some other databases) evaluate boolean expressions to 1 for true you can replace the case expression with the just the condition to evaluate and then just sum them.
So instead of CASE WHEN race_results.place=1 THEN 1 ELSE 0 END you can do sum(race_results.place=1) and save some space and typing :)
See this SQL Fiddle for an example.

Mysql inner join query

I'm using two tables in the database.
The first contains data related to the successful and unsuccessful payments while the second table contains data regarding the status of services.
The result of the query should combine both tables and as a result list the successful and unsuccessful payments grouped by the days as well as the status of services grouped by days.
First table looks like:
id | charged | date
-----------------------------
8 | OK | 2011-12-03
7 | OK | 2011-12-03
9 | NO | 2011-12-03
11 | OK | 2011-12-04
14 | NO | 2011-12-04
The second table looks like:
id | status | date
--------------------------
8 | 1 | 2011-12-03
9 | 1 | 2011-12-03
11 | 0 | 2011-12-04
12 | 0 | 2011-12-04
14 | 1 | 2011-12-04
The correct query result should be:
date | not_charged | charged | status_1 | status_0
-----------------------------------------------------------
2011-12-04 | 1 | 1 | 1 | 2
2011-12-03 | 1 | 2 | 2 | 0
The query that I've tried looks like this:
SELECT i.date, SUM(
CASE WHEN i.charged = 'NO'
THEN 1 ELSE 0 END ) AS not_charged, SUM(
CASE WHEN i.charged = 'OK'
THEN 1 ELSE 0 END ) AS charged, SUM(
CASE WHEN s.status = '1'
THEN 1 ELSE 0 END ) AS status_1, SUM(
CASE WHEN s.status = '0' THEN 1 ELSE 0 END ) AS status_0
FROM charge i INNER JOIN status s ON s.date = i.date
GROUP BY i.date
But I get the wrong result that looks like this
date | not_charged | charged | status_1 | status_0
---------------------------------------------------------
2011-12-04 | 3 | 3 | 2 | 4
2011-12-03 | 2 | 4 | 6 | 0
What I'm doing wrong and how can I get the correct result?
Thanks for all suggestions.
Try this one -
SELECT date,
SUM(IF(charged = 'NO', 1, 0)) not_charged,
SUM(IF(charged = 'OK', 1, 0)) charged,
SUM(IF(status = 1, 1, 0)) status_1,
SUM(IF(status = 0, 1, 0)) status_0
FROM (
SELECT date, charged, NULL status FROM charge
UNION ALL
SELECT date, NULL charged, status FROM status
) t
GROUP BY date DESC;
+------------+-------------+---------+----------+----------+
| date | not_charged | charged | status_1 | status_0 |
+------------+-------------+---------+----------+----------+
| 2011-12-04 | 1 | 1 | 1 | 2 |
| 2011-12-03 | 1 | 2 | 2 | 0 |
+------------+-------------+---------+----------+----------+
This assumes the ID columns related that service status and payment status together...
SELECT
COALESCE(charge.date, status.date) AS date,
SUM(CASE WHEN charge.charged = 'NO' THEN 1 ELSE 0 END) AS not_charged,
SUM(CASE WHEN charge.charged = 'OK' THEN 1 ELSE 0 END) AS charged,
SUM(CASE WHEN status.status = '0' THEN 1 ELSE 0 END) AS status_0,
SUM(CASE WHEN status.status = '1' THEN 1 ELSE 0 END) AS status_1
FROM
charge
FULL OUTER JOIN
status
ON charge.id = status.id
GROUP BY
COALESCE(charge.date, status.date)
Note, I'm note sure how you want to deal with 7 (No status record) and 12 (no charge record). This currently just counts what is there.
Alternatively, if you don't want to related the records by ID, you can still relate by date but you need to change your logic.
At present you're getting this, because you only relate by date...
id | charged | date id | status | date
----------------------------- --------------------------
8 | OK | 2011-12-03 8 | 1 | 2011-12-03
8 | OK | 2011-12-03 9 | 1 | 2011-12-03
7 | OK | 2011-12-03 8 | 1 | 2011-12-03
7 | OK | 2011-12-03 9 | 1 | 2011-12-03
9 | NO | 2011-12-03 8 | 1 | 2011-12-03
9 | NO | 2011-12-03 9 | 1 | 2011-12-03
11 | OK | 2011-12-04 11 | 0 | 2011-12-04
11 | OK | 2011-12-04 12 | 0 | 2011-12-04
11 | OK | 2011-12-04 14 | 1 | 2011-12-04
14 | NO | 2011-12-04 11 | 0 | 2011-12-04
14 | NO | 2011-12-04 12 | 0 | 2011-12-04
14 | NO | 2011-12-04 14 | 1 | 2011-12-04
Instead you need to consolidate the data down to 1 per date per table, then join...
SELECT
COALESCE(charge.date, status.date) AS date,
charge.not_charged,
charge.charged,
status.status_0,
status.status_1
FROM
(
SELECT
date,
SUM(CASE WHEN charged = 'NO' THEN 1 ELSE 0 END) AS not_charged,
SUM(CASE WHEN charged = 'OK' THEN 1 ELSE 0 END) AS charged
FROM
charge
GROUP BY
date
)
AS charge
FULL OUTER JOIN
(
SELECT
date,
SUM(CASE WHEN charged = '0' THEN 1 ELSE 0 END) AS status_0,
SUM(CASE WHEN charged = '1' THEN 0 ELSE 1 END) AS status_1
FROM
status
GROUP BY
date
)
AS status
ON charge.date = status.date
There are other methods, but hopefully this explains a bit for you.
I suggest using a UNION ALL:
select date,
coalesce(sum(not_charged),0) not_charged,
coalesce(sum(charged),0) charged,
coalesce(sum(status_1),0) status_1,
coalesce(sum(status_0),0) status_0
from (select date,
case charged when 'NO' then 1 end not_charged,
case charged when 'OK' then 1 end charged,
0 status_1,
0 status_0
from charge
union all
select date,
0 not_charged,
0 charged,
case status when '1' then 1 end status_1,
case status when '0' then 1 end status_0
from status) sq
group by date