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.
Related
I am struggling to find a way to get a GRAND TOTAL count of the number of rows matched in a LEFT JOIN.
This is the current query:
SELECT * FROM renewal
LEFT JOIN appointment ON appointment.renewalid=renewal.id
I have tried:
SELECT *, COUNT(app.id) AS appcount FROM renewal
LEFT JOIN appointment ON appointment.renewalid=renewal.id
But that is obviously not the right way, as it only returns the total appointments for each renewal row returned.
I have also tried a subquery:
SELECT customer.*, app.totalcount FROM renewal
LEFT JOIN (SELECT COUNT(id) AS appcount FROM appointment) AS app ON app.renewalid=renewal.id
This also is not working.
Currently, I can get it to return the total for each customer, but not a GRAND TOTAL.
The reason I am not just querying the appointments table alone, is because I need it only to return a grand total of appointments linked to customers which follow the specified OUTER 'WHERE' statement.
Disclaimer: The queries above are a much more simplified version, just for readability sakes.
Here is the full query:
SELECT
renewal.id AS renid,
renewal.personid,
renewal.enddate,
renewal.assettype,
renewal.producttype,
renewal.vrm,
renewal.make,
renewal.model,
renewal.submodel,
renewal.derivative,
renewal.complete,
person.forename,
person.surname,
person.company,
appointment.id AS appid,
COUNT(appointment.renewalid) AS appointedcount,
appointment.renewalid,
n.latestnote,
(
SELECT
COUNT(complete)
FROM
renewal
WHERE
complete = 1 && enddate BETWEEN '2020-01-01' AND '2020-01-30' && dealershipid = '1' && assettype = 'N' && producttype NOT LIKE '%CH%' && complete = 1
) AS renewedcount
FROM
renewal
LEFT JOIN person ON person.id = renewal.personid
LEFT JOIN appointment ON appointment.renewalid = renewal.id
LEFT JOIN(
SELECT
note AS latestnote,
TIMESTAMP,
renewalid
FROM
renewal_note
ORDER BY
TIMESTAMP
DESC
) AS n
ON
n.renewalid = renewal.id
WHERE
enddate BETWEEN '2020-01-01' AND '2020-01-30' && renewal.dealershipid = '1' && assettype = 'N' && producttype NOT LIKE '%CH%'
GROUP BY
renid
ORDER BY
enddate ASC
This is what is being outputted with the Full query (Removed cluttering columns which arent relevant to this question):
| renid | appid | appointedcount | renewedcount | |
|--------|--------|-----------------|---------------|--|
| 60177 | 1096 | 6 | 5 | |
| 64704 | 2470 | 6 | 5 | |
| 43057 | | 0 | 5 | |
| 64626 | | 0 | 5 | |
| 11123 | | 0 | 5 | |
| 72469 | | 0 | 5 | |
| 76055 | 2879 | 7 | 5 | |
| 76001 | 2546 | 3 | 5 | |
| 72171 | 2769 | 6 | 5 | |
| 76073 | | 0 | 5 | |
| 73183 | 2093 | 8 | 5 | |
| 73114 | 2834 | 6 | 5 | |
| 43088 | | 0 | 5 | |
| 732 | | 0 | 5 | |
| 11157 | | 0 | 5 | |
| 60207 | | 0 | 5 | |
| 73103 | 2015 | 3 | 5 | |
| 75982 | | 0 | 5 | |
| 43076 | | 0 | 5 | |
It seems like the highest appointedcount value is 8, which if you count the number of rows with an appid (an appointment), adds up to 8.
I believe I am heading in the right direction, as it is returning 8 (at a random renewal row), but can't seem to get past this point.
Can someone please guide me in the right direction?
I think you want a window function:
SELECT *, COUNT(a.id) OVER () AS total_appcount
FROM renewal r LEFT JOIN
appointment a
ON a.renewalid = r.id ;
In older versions of MySQL, you can use a correlated subquery:
SELECT *,
(SELECT COUNT(*)
FROM renewal r JOIN
appointment a
ON a.renewalid = r.id
) AS total_appcount
FROM renewal r LEFT JOIN
appointment a
ON a.renewalid = r.id ;
Note that for the subquery, you don't need an outer join, because you only want matches.
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.
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.
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
I have two tables that looks like this:
Table: items
id | itemId
---|------
0 | 1
1 | 2
2 | 3
Table: item_specs
id | itemId | key | values
---|--------|---------------
0 | 1 | itemreceived | 2012-06-01
1 | 1 | modelyear | 1992
2 | 1 | model | 2
3 | 2 | itemreceived | 2012-06-05
4 | 2 | modelyear | 2003
5 | 2 | model | 1
6 | 3 | itemreceived | 2012-07-05
7 | 3 | modelyear | 2000
8 | 3 | model | 3
My current query looks like this:
SELECT items.*, item_specs.* FROM item_specs
INNER JOIN item_specs ON items.itemId = item_specs.itemId
WHERE itemId IN(1,2,3)
How can I order the result by a key value, for example: model?
The result I'm looking for is something like this: (if I order by model)
id | itemId | key | values
---|--------|---------------
3 | 2 | itemreceived | 2012-06-05
4 | 2 | modelyear | 2003
5 | 2 | model | 1
0 | 1 | itemreceived | 2012-06-01
1 | 1 | modelyear | 1992
2 | 1 | model | 2
6 | 3 | itemreceived | 2012-07-05
7 | 3 | modelyear | 2000
8 | 3 | model | 3
The content that is returned is ordered by the value that is that has the key model
You need the model number for every row. You can do that with a join:
SELECT items.*, item_specs.*
FROM item_specs
INNER JOIN item_specs ON items.itemId = item_specs.itemId
INNER JOIN item_specs aux ON (aux.key = 'model' and aux.itemID = item_specs.itemId)
WHERE item_specs.itemId IN(1,2,3)
ORDER BY aux.values/*this is the model*/, item_specs.id;
or with a subselect:
SELECT items.*,
item_specs.*,
(select aux.values
from item_specs aux
where aux.key = 'model' and aux.itemID = item_specs.itemId
) as model
FROM item_specs
INNER JOIN item_specs ON items.itemId = item_specs.itemId
WHERE item_specs.itemId IN(1,2,3)
ORDER BY model, item_specs.id;
SELECT * FROM `table` WHERE `key` = 'model' ORDER BY `values` ASC
You have to manually specify a table type/storage engine. That can't be seen in the structure you provided.
Read more here.
It seems you want to use an order by clause. This will order by the columns you need. You can also do sneaky things here, like insert a true/false value for what you order by first.
SELECT * FROM `table`
Order by (case When Key='model' then 0 else 1 end), values
See, for instance, http://blog.sqlauthority.com/2007/07/17/sql-server-case-statement-in-order-by-clause-order-by-using-variable/
SELECT * FROM `table`
WHERE `key` = 'model'
ORDER BY `values`;