Alright. I have a query that looks like this:
SELECT
SUM(`order_items`.`quantity`) as `count`,
`menu_items`.`name`
FROM
`orders`,
`menu_items`,
`order_items`
WHERE
`orders`.`id` = `order_items`.`order_id` AND
`menu_items`.`id` = `order_items`.`menu_item_id` AND
`orders`.`date` >= '2008-11-01' AND
`orders`.`date` <= '2008-11-30'
GROUP BY
`menu_items`.`id`
The purpose of this query is to show the amount of items sold in a given date range. Although this works, I now need it to show a count
of 0
if a particular item has no sales in the date range. I tried using COALESCE
around the SUM
but that didn't do the trick, and I didn't really expect it to. Anyhow, does anyone know how I would go about accomplishing this? I'm having one of those moments where I feel like I should know this but I can't think of it.
Cheers
This can be done without any subqueries, if one puts the date conditions in the JOIN
clause.
Below is code I tested on MySQL 5.0.
SELECT m.name, COALESCE(SUM(oi.quantity), 0) AS count
FROM menu_items AS m
LEFT OUTER JOIN (
order_items AS oi JOIN orders AS o
ON (o.id = oi.order_id)
) ON (m.id = oi.menu_item_id
AND o.`date` BETWEEN '2008-11-01' AND '2008-11-30')
GROUP BY m.id;
Output:
+--------+-------+
| name | count |
+--------+-------+
| bread | 2 |
| milk | 1 |
| honey | 2 |
| cheese | 0 |
+--------+-------+
Here's the DDL and setup code, in the MySQL flavor:
DROP TABLE IF EXISTS menu_items;
CREATE TABLE menu_items (
id INT PRIMARY KEY,
name VARCHAR(10)
) TYPE=InnoDB;
DROP TABLE IF EXISTS orders;
CREATE TABLE orders (
id INT PRIMARY KEY,
`date` DATE
) TYPE=InnoDB;
DROP TABLE IF EXISTS order_items;
CREATE TABLE order_items (
order_id INT,
menu_item_id INT,
quantity INT,
PRIMARY KEY (order_id, menu_item_id),
FOREIGN KEY (order_id) REFERENCES orders(id),
FOREIGN KEY (menu_item_id) REFERENCES menu_items(id)
) TYPE=InnoDB;
INSERT INTO menu_items VALUES
(1, 'bread'),
(2, 'milk'),
(3, 'honey'),
(4, 'cheese');
INSERT INTO orders VALUES
(1, '2008-11-02'),
(2, '2008-11-03'),
(3, '2008-10-29');
INSERT INTO order_items VALUES
(1, 1, 1),
(1, 3, 1),
(2, 1, 1),
(2, 2, 1),
(2, 3, 1),
(3, 4, 10);
Randy's answer is close, but the where statement removes any mention of those items not part of any orders in that date range.
Note that "left join" is different to linking tables in the where clause in the manner you have done (i.e. inner joins). I suggest you read up on the different types of SQL joins (inner, outer, cross).
In essense, you need to join the data you get from Randy's query against your source list of items. Using a subselect will do this:
SELECT
name
, nvl(count, 0) as count
FROM
menu_items items
LEFT JOIN (
SELECT
menu_items.id
, SUM(order_items.quantity) as count
FROM
menu_items
LEFT JOIN order_items ON menu_items.id = order_items.menu_item_id
LEFT JOIN orders ON orders.id = order_items.order_id
WHERE
"date" between to_date('2008-11-01','YYYY-MM-DD') and to_date('2008-11-30','YYYY-MM-DD')
GROUP BY
menu_items.id
) counts on items.id = counts.id;
This is in Oracle 10g BTW. I doubt you're using Oracle, so you'll need to convert to your own database.
Running a test shows the following:
SQL> create table menu_items ( id number, name varchar2(10));
create table order_items (order_id number, menu_item_id number, quantity number);
create table orders (id number, "date" date);
Table created.
SQL>
Table created.
SQL>
Table created.
SQL>
insert into menu_items values (1, 'bread');
insert into menu_items values (2, 'milk');
insert into menu_items values (3, 'honey');
insert into menu_items values (4, 'cheese');
SQL>
1 row created.
SQL>
1 row created.
SQL>
1 row created.
SQL>
1 row created.
SQL>
insert into orders values (1, to_date('2008-11-02', 'YYYY-MM-DD'));
insert into orders values (2, to_date('2008-11-03', 'YYYY-MM-DD'));
insert into orders values (3, to_date('2008-10-29', 'YYYY-MM-DD'));SQL>
1 row created.
SQL>
1 row created.
SQL>
insert into order_items values (1, 1, 1);
insert into order_items values (1, 3, 1);
1 row created.
SQL>
1 row created.
SQL>
insert into order_items values (2, 1, 1);
insert into order_items values (2, 2, 1);
insert into order_items values (2, 3, 1);
insert into order_items values (3, 4, 10);
1 row created.
SQL>
1 row created.
SQL>
1 row created.
SQL>
1 row created.
SQL> SQL>
1 row created.
SQL>
SELECT
name
, nvl(count, 0) as count
FROM
menu_items items
LEFT JOIN (
SELECT
menu_items.id
, SUM(order_items.quantity) as count
FROM
menu_items
LEFT JOIN order_items ON menu_items.id = order_items.menu_item_id
LEFT JOIN orders ON orders.id = order_items.order_id
WHERE
"date" between to_date('2008-11-01','YYYY-MM-DD') and to_date('2008-11-30','YYYY-MM-DD')
GROUP BY
menu_iteSQL> 2 3 4 5 6 7 ms.id
) counts on items.id = counts.id; 8 9 10 11 12 13 14 15 16 17 18
NAME COUNT
---------- ----------
bread 2
milk 1
honey 2
cheese 0
SQL>
drop table menu_items;
drop table order_items;
drop table orders;SQL>
Table dropped.
SQL>
Table dropped.
SQL>
Table dropped.
SQL>
PS: It's bad practice to use 'date' as a column name as it is (in most cases) a type name and can cause problems to queries and parses.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With