Combine 2 SELECT statements with filled gaps - mysql

I have this SELECT:
SELECT
m.`maschine-name` AS 'byMaschine',
q.`mname` AS 'byMName'
FROM
`qualitaet` q
INNER JOIN
maschinen m ON m.maschine = q.maschine
WHERE
q.`status`='1'
GROUP BY
concat(q.maschine, q.mname)
and get this result:
| maschine-name | mname |
| TYP 1 | 0 |
| TYP 2 | 3 |
| TYP 2 | 4 |
| TYP 3 | 0 |
| TYP 4 | 0 |
see SQL Fiddle here
Then i have nearly the same SELECT with additional COUNT and Datefilter:
SELECT
m.`maschine-name` AS 'byMaschine',
q.`mname` AS 'byMName',
COUNT(*) AS 'total'
FROM
`qualitaet` q
INNER JOIN
maschinen m ON m.maschine = q.maschine
WHERE
q.`created`>=NOW() - INTERVAL 2 DAY
AND
q.`status`='1'
GROUP BY
concat(q.maschine, q.mname)
and get this result:
| maschine-name | mname | total |
| TYP 2 | 3 | 1 |
| TYP 3 | 0 | 2 |
see SQL Fiddle here
The 2nd SELECT doesn't give me all information. I need a mix from both SELECTS
The 2nd SELECT should look like this result:
| maschine-name | mname | total |
| TYP 1 | 0 | 0 |
| TYP 2 | 3 | 1 |
| TYP 2 | 4 | 0 |
| TYP 3 | 0 | 2 |
| TYP 4 | 0 | 0 |
Is it possible to RIGHT JOIN with 2 SELECTS? Or is there another way to get the result?

Use conditional aggregation:
SELECT m.`maschine-name` AS byMaschine, q.`mname` AS byMName,
sum(q.created >= NOW() - INTERVAL 2 DAY) as Total
FROM qualitaet q INNER JOIN
maschinen m
ON m.maschine = q.maschine
WHERE q.status = '1'
GROUP BY q.maschine, q.mname;
Other suggestions:
There is no need to concatenate the grouping columns in the GROUP BY -- unless you really, really intend to do this (which I doubt).
If status is numeric, don't use single quotes for the constant.
Don't use single quotes for column aliases. In fact, your aliases don't need any quotes at all.

Related

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.

Using left join with min

I am trying to connect two tables with left join and a date.
My SQL Query
SELECT
ord.`ordernumber` bestellnummer,
his.`change_date` zahldatum
FROM
`s_order` ord
LEFT JOIN
`s_order_history` his ON ((ord.`id`=his.`orderID`) AND (ord.`cleared`=his.`payment_status_id`)) #AND MIN(his.`change_date`)
WHERE
ord.`ordertime` >= \''.$dateSTART.'\' AND ord.`ordertime` <= \''.$dateSTOP.'\'' ;
s_order
+----+---------------------+---------+-------------+
| id | ordertime | cleared | ordernumber |
+----+---------------------+---------+-------------+
| 1 | 2014-08-11 19:53:43 | 2 | 123 |
| 2 | 2014-08-15 18:33:34 | 2 | 125 |
+----+---------------------+---------+-------------+
s_order_history
+----+-------------------+-----------------+---------+---------------------+
| id | payment_status_id | order_status_id | orderID | orderID change_date |
+----+-------------------+-----------------+---------+---------------------+
| 1 | 1 | 5 | 1 | 2014-08-11 20:53:43 |
| 2 | 2 | 5 | 1 | 2014-08-11 22:53:43 |
| 3 | 2 | 7 | 1 | 2014-08-12 19:53:43 |
| 4 | 1 | 5 | 2 | 2014-08-15 18:33:34 |
| 5 | 1 | 6 | 2 | 2014-08-16 18:33:34 |
| 6 | 2 | 6 | 2 | 2014-08-17 18:33:34 |
+----+-------------------+-----------------+---------+---------------------+
Wanted result:
+-------------+---------------------+
| ordernumber | change_date |
+-------------+---------------------+
| 123 | 2014-08-11 22:53:43 |
| 125 | 2014-08-17 18:33:34 |
+-------------+---------------------+
The problem I have is getting only the date, where the cleared/payment_status_id value has been changed in s_order. I currently get all dates where the payment_status_id matches the current cleared value, but I only need the one, where it happend first.
This is only an excerpt of the actually query, since the original is a lot longer (mostly more left joins and a lot more tables).
You can group data by ordernumber
SELECT
ord.`ordernumber` bestellnummer,
MIN(his.`min_change_date`) as zahldatum
FROM
`s_order` ord
LEFT JOIN
`s_order_history` his ON ((ord.`id`=his.`orderID`) AND (ord.`cleared`=his.`payment_status_id`)) #AND MIN(his.`change_date`)
WHERE
ord.`ordertime` >= \''.$dateSTART.'\' AND ord.`ordertime` <= \''.$dateSTOP.'\''
GROUP BY
ord.`ordernumber`;
or you can group data in a subquery:
SELECT
ord.`ordernumber` bestellnummer,
his.`min_change_date` zahldatum
FROM
`s_order` ord
LEFT JOIN (
SELECT
orderID, payment_status_id, MIN(change_date) as min_change_date
FROM
s_order_history
GROUP BY
orderID, payment_status_id
) his ON (ord.`id` = his.`orderID` AND ord.`cleared` = his.`payment_status_id`)
WHERE
ord.`ordertime` >= \''.$dateSTART.'\' AND ord.`ordertime` <= \''.$dateSTOP.'\'';
Try this:
select s_order.ordernumber, min(s_order_history.change_date)
from s_order left join s_order_history
on s_order.id = s_order_history.orderID
and s_order.cleared = s_order_history.payment_status_id
group by s_order.order_id
SELECT ord.`ordernumber` bestellnummer,
MIN( his.`change_date` ) zahldatum
...
GROUP BY ord.`ordernumber`
MIN is an aggregate function so you can't use it in a JOIN straight up like you've tried above. You also are not comparing it to a value in your JOIN.
You'll want to do something like:
his.`change_date` = (SELECT MIN(his.`change_date`) FROM s_order_history where ord.`id` = his.`orderID`)
in your JOIN.

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.

Joining tables but needs 0 for empty rows

I don't know how to explain the scenario using words. So am writing the examples:
I have a table named tblType:
type_id | type_name
---------------------
1 | abb
2 | cda
3 | edg
4 | hij
5 | klm
And I have another table named tblRequest:
req_id | type_id | user_id | duration
-------------------------------------------
1 | 4 | 1002 | 20
2 | 1 | 1002 | 60
3 | 5 | 1008 | 60
....
So what am trying to do is, fetch the SUM() of duration for each type, for a particular user.
This is what I tried:
SELECT
SUM(r.`duration`) AS `duration`,
t.`type_id`,
t.`type_name`
FROM `tblRequest` AS r
LEFT JOIN `tblType` AS t ON r.`type_id` = t.`type_id`
WHERE r.`user_id` = '1002'
GROUP BY r.`type_id`
It might return something like this:
type_id | type_name | duration
-------------------------------
1 | abb | 60
4 | hij | 20
It works. But the issue is, I want to get 0 as value for other types that doesn't have a row in tblRequest. I mean I want the output to be like this:
type_id | type_name | duration
-------------------------------
1 | abb | 60
2 | cda | 0
3 | edg | 0
4 | hij | 20
5 | klm | 0
I mean it should get the rows of all types, but 0 as value for those type that doesn't have a row in tblRequest
You could perform the aggregation on tblRequest and only then join it, using a left join to handle missing rows and coalesce to convert the nulls to 0s:
SELECT t.type_id, type_name, COALESCE(sum_duration, 0) AS duration
FROM tblType t
LEFT JOIN (SELECT type_id, SUM(duration) AS sum_duration
FROM tblRequest
WHERE user_id = '1002'
GROUP BY type_id) r ON t.type_id = r.type_id
Select a.type_id, isnull(sum(b.duration), 0)
From tblType a Left Outer Join tblRequest b
ON a.type_id = b.type_id and b.user_id = 1002
Group by a.type_id

Addition of the SUM of two independant tables

i have two tables
td_sell
|----------|----------------|------------------|
| id | user_id | price |
|----------------------------------------------|
| 1 | 2 | 10 |
|----------------------------------------------|
| 2 | 1 | 5 |
|----------------------------------------------|
| 3 | 2 | 3 |
|----------------------------------------------|
and td_commsion
|----------|----------------|------------------|
| id | user_id | price |
|----------------------------------------------|
| 1 | 1 | 3 |
|----------------------------------------------|
| 2 | 1 | 5 |
|----------------------------------------------|
| 3 | 2 | 3 |
|----------------------------------------------|
now i want a sql query like this
SELECT (SUM(td_sell.price) + SUM(td_comission.price)) AS his_earning
FROM td_sell, td_comission
WHERE td_sell.user_id='1'
AND td_comission.user_id='1'
but its showing abnormal result
the result should be 13, but its showing 29
This will work:
SELECT (SELECT SUM(s.price) FROM td_sell s WHERE s.user_id = 1)
+
(SELECT SUM(c.price) FROM td_comission c WHERE c.user_id = 1)
DEMO: SqlFiddle
You are getting the sum of the Cartesian join of the two tables.
http://en.wikipedia.org/wiki/Cartesian_product
SELECT sum(price)
FROM (
SELECT * FROM td_sell
UNION ALL
SELECT * FROM td_commission
) a
where a.user_id=1
Here's a SQL Fiddle:
Fiddle
You need to do the sum separately on each table, before combining the results. Here is one way:
select (sell + commission) as his_earning
from (select SUM(td_sell.price) as sell
from td_sell
where td_sell.user_id='1'
) s cross join
(select SUM(td_comission.price) as commission
from td_comission
where td_comission.user_id='1'
) c