mysql left join problem with SUM and WHERE clause - mysql

I have 2 tables in my database: item and category.
Items can be active, or inactive, and have a categoryID that relates to the id of a record in the category table.
i want to perform a query to show all the categories, with the total cost of active items for the category
So my goal is to return something looking like this:
+--------+------------+---------------+
| id | cat_name | total_cost |
+--------+------------+---------------+
| 1 | cat 1 | 12 |
| 2 | cat 2 | 0 |
| 3 | cat 3 | 45 |
+--------+------------+---------------+
My first query:
SELECT a.*,
SUM(b.cost) AS total_cost
FROM categories a LEFT JOIN items b
ON(a.id = b.category_id)
GROUP BY a.category_name
works ok, but it returns NULL items instead of 0, and uses all items regardless of active/inactive:
+--------+------------+---------------+
| id | cat_name | total_cost |
+--------+------------+---------------+
| 1 | cat 1 | 44 |
| 2 | cat 2 | NULL |
| 3 | cat 3 | 87 |
+--------+------------+---------------+
my second query adresses the NULL values:
SELECT a.*,
SUM(IF(b.cost IS NULL, 0, b.cost)) AS total_cost
FROM categories a LEFT JOIN items b
ON(a.id = b.category_id)
GROUP BY a.category_name
and turns out like so:
+--------+------------+---------------+
| id | cat_name | total_cost |
+--------+------------+---------------+
| 1 | cat 1 | 44 |
| 2 | cat 2 | NULL |
| 3 | cat 3 | 87 |
+--------+------------+---------------+
So in my tiny useless brain i try the following query, adding a WHERE clause on table b where active has to = 1 (true)
SELECT a.*,
SUM(IF(b.cost IS NULL, 0, b.cost)) AS total_cost
FROM categories a LEFT JOIN items b
ON(a.id = b.category_id)
WHERE b.active = 1
GROUP BY a.category_name
and i get the following:
+--------+------------+---------------+
| id | cat_name | total_cost |
+--------+------------+---------------+
| 1 | cat 1 | 12 |
| 3 | cat 3 | 45 |
+--------+------------+---------------+
so as you can se, i would like to return the entire range of categories, even when the right table returns no matching results... Any takes for a million imaginary cool points?

Use:
SELECT c.id,
c.cat_name,
COALESCE(SUM(i.cost), 0) AS total_cost
FROM CATEGORIES c
LEFT JOIN ITEMS i ON i.category_id = c.category_id
AND i.active = 1
GROUP BY c.id, c.cat_name

Try this:
SELECT a.*,
SUM(Case B.Active When 1 Then b.cost else 0 End) AS total_cost
FROM categories a
LEFT JOIN items b
ON b.category_id = a.id
GROUP BY a.category_name
or this:
SELECT a.*, SUM(b.cost) AS total_cost
FROM categories a
LEFT JOIN items b
ON b.category_id = a.id
And B.Active = 1
GROUP BY a.category_name

Related

Need MySQL query from three tables

I have three tables:
Items:
Id |Name | Price | Cat_Id
---- |-------|----------------
1 | Item1 | 50.00 | 1
2 | Item2 | 25.20 | 5
Category:
Id |Name |
---- |------|
1 | Cat1 |
2 | Cat2 |
Discount:
Id |Item_Id| Client_Id|Discount
---- |-------|----------------
1 | 1 | 1 | 10
2 | 1 | 2 | 15
3 | 2 | 2 | 6
I am trying to get all items with proper discount, which is different for every customer. In this example i have client with Client_Id 1, which have discount for Item1/10/ and he doesn`t have discount on Item2.
The result should look like this:
Id |Name | Price | Cat | Discount
---- |-------|----------------|----------
1 | Item1 | 50.00 | Cat1| 10
2 | Item2 | 25.20 | Cat5| 0
My question is what is the way to build the query. I join first two tables and need to filter the third, but should I use temp table or do query in query?
It's Simple SQL Query.
Select Id, Name, Price, Cat, Discount From Items
left join discount on Items.id=discount.Item_Id
left join category on Items.cat_id=id
Output Like:
Id |Name | Price | Cat | Discount
---- |-------|----------------|----------
1 | Item1 | 50.00 | Cat1| 10
2 | Item2 | 25.20 | Cat5| 0
Try this way:
select di.id,di.Client_Id,it.Name,it.Price,ca.Cat,ifnull(di.Discount,0)
from Discount di
right join Items it on di.Item_id=it.Id
left join Category ca on it.Cat_Id=ca.Id
order by di_Id,di.Client_id,It.Name
You get all rows from discount and then get all items and look for the category. If an item has no discount(null) you get a 0
To get the result you want,
following is the query...
select
i.id item_id,
d.id discount_id,
i.name,
i.price,
c.name cat_name,
d.discount
from items i
left join discount d on i.id = d.item_id
left join category c on i.cat_id = c.id
where d.client_id = 1 or d.client_id is null;
/*where condition added after update as OP required*/
UPDATE
As per OP's comment
select
i.id item_id,d.id discount_id,
i.name,i.price,c.name cat_name,d.discount
from discount d
left join items i on i.id = d.item_id
left join category c on i.cat_id = c.id
where d.client_id = 2;

Get default value from JOIN query if join field value doesn't exist in another table

I have two tables customers and reviews. The structure is like this:
customers:-
|---------------------------------|
| id | name |
|---------------------------------|
| 1 | Thutmekri |
|---------------------------------|
| 3 | Conan |
|---------------------------------|
reviews:-
|-------------------------------------------|
| id | business_id | customer_id |
|-------------------------------------------|
| 1 | 1 | 1 |
|-------------------------------------------|
| 2 | 1 | 2 |
|-------------------------------------------|
| 3 | 1 | 3 |
|-------------------------------------------|
customer_id of reviews is id of customer.
The join query,
SELECT customers.name, reviews.id as review_id
FROM customers, reviews
WHERE customers.id = reviews.customer_id
returns the dataset like this:-
|----------------------------------|
| review_id | name |
|----------------------------------|
| 1 | Thutmekri |
|----------------------------------|
| 3 | Conan |
|----------------------------------|
But I want it to return:-
|----------------------------------|
| review_id | name |
|----------------------------------|
| 1 | Thutmekri |
|----------------------------------|
| 2 | N/A |
|----------------------------------|
| 3 | Conan |
|----------------------------------|
For customer_id in reviews, which doesn't have any data in customers table, I want 'N/A' to be displayed. How can I do it?
The following query also didn't help
SELECT reviews.id as review_id, COALESCE( name, 'N/A' )
FROM customers, reviews
WHERE customers.id = reviews.customer_id
Use a left join and switch the order of the tables in the join:
SELECT
r.id AS review_id,
COALESCE(c.name, 'N/A') AS name
FROM reviews r
LEFT JOIN customers c
ON c.id = r.customer_id;
SQLFiddle
Try this :
SELECT r.id as review_id, COALESCE( c.name, 'N/A' ) FROM reviews r
LEFT JOIN customers c ON c.id = r.customer_id
Use the Right outer join to achieve this:
SELECT
reviews.id as review_id,
COALESCE(name, 'N/A' )
FROM customers
RIGHT JOIN reviews
ON customers.id = reviews.customer_id;
You can use below query
SELECT
R.id AS review_id,
COALESCE(C.name, 'N/A') AS name FROM reviews R LEFT JOIN customers C ON R.customer_id = C.id ;

MySQL count rows in a joint table

I would like to count the number of rows or in this case the number of products which are in the same category and return zero if there are no products in the category.
The tables in my query looking like this:
category category_lang media
------------ ------------- ---------
category_id | published category_id | name | alias media_id | category_id
----------------------- -------------------------- ----------------------
1 | 1 1 | One | one 1 | 1
2 | 1 2 | Two | two 2 | 2
3 | 1 3 | Three| three 3 | 3
media_lang product_category
------------------------------- ----------------
media_id | url | file_name product_id | category_id
------------------------------- ------------------------
1 | /images/ | file1.jpg 1 | 1
2 | /images/ | file2.jpg 2 | 1
3 | /images/ | file3.jpg 3 | 2
and I would like a result like this:
category_id | category_name | alias | media_id | url | file_name | count
1 | One | one | 1 | /images/ | file1.jpg | 2
2 | Two | two | 2 | /images/ | file2.jpg | 1
3 | Three | three | 3 | /images/ | file3.jpg | 0
My query currently looks like this
SELECT
c.`category_id`,
ca.`name`,
ca.`alias`,
m.`media_id`,
ma.`url`,
ma.`file_name`,
COUNT(p.`product_id`) AS `count`
FROM `category` c
LEFT JOIN `category_lang` ca ON (c.`category_id` = ca.`category_id`)
LEFT JOIN `media` m ON (c.`category_id` = m.`category_id`)
LEFT JOIN `media_lang` ma ON (m.`media_id` = ma.`media_id`)
LEFT JOIN `product_category` p ON (c.`category_id` = p.`category_id`)
WHERE c.`published` = 1
ORDER BY ca.`name`
my logic is obviously wrong because this query will return this:
category_id | category_name | alias | media_id | url | file_name | count
1 | One | one | 1 | /images/ | file1.jpg | 3
How can I achieve the desired result?
I think the most painless (and sane) way to approach this would be to just aggregate the product counts for each category from the product_category table in a separate subquery, and then just join this to what you already have:
SELECT
c.category_id,
ca.name,
ca.alias,
m.media_id,
ma.url,
ma.file_name,
COALESCE(t.cat_count, 0) AS cat_count
FROM category c
LEFT JOIN category_lang ca
ON c.category_id = ca.category_id
LEFT JOIN media m
ON c.category_id = m.category_id
LEFT JOIN media_lang ma
ON m.media_id = ma.media_id
LEFT JOIN
(
SELECT category_id, COUNT(*) AS cat_count
FROM product_category
GROUP BY category_id
) t
ON c.category_id = t.category_id
WHERE c.published = 1
ORDER BY ca.name
Note here that your product_category table does not have any category entries with no products. This is not a problem, because in the LEFT JOIN we can simply treat a NULL count as being zero. A NULL value would occur if a given category did not match to anything in the subquery.
Seems to me that it is where you are doing the count and that you doing have the criteria for doing the count. Adding an extra condition where product category id equals category id in your category table should fix the incorrect count.
SELECT
c.`category_id`,
ca.`name`,
ca.`alias`,
m.`media_id`,
ma.`url`,
ma.`file_name`,
COUNT(p.`product_id`) AS `count`
FROM `category` c
LEFT JOIN `category_lang` ca ON (c.`category_id` = ca.`category_id`)
LEFT JOIN `media` m ON (c.`category_id` = m.`category_id`)
LEFT JOIN `media_lang` ma ON (m.`media_id` = ma.`media_id`)
LEFT JOIN `product_category` p ON (c.`category_id` = p.`category_id`)
WHERE (c.`published` = 1) and (c.`category_id` = p.`category_id`)
ORDER BY ca.`name`

Joining two MySQL tables, but with additional conditions?

I have two tables:
Products:
+-------------------------------------------------+
| id | name | category | price |
+-------------------------------------- ----------+
| 1 | item1 | 1 | 0.99 |
| 2 | item2 | 2 | 1.99 |
| 3 | item3 | 3 | 2.95 |
| 4 | item4 | 4 | 2.50 |
+-------------------------------------------------+
Images:
+--------------------------------------------------+
| id | file_name | p_id | priority |
+-------------------------------------- -----------+
| 1 | image1 | 1 | 0 |
| 2 | image2 | 1 | 1 |
| 3 | image3 | 2 | 2 |
| 4 | image4 | 3 | 2 |
| 5 | image5 | 3 | 3 |
| 11 | image6 | 3 | 5 |
| 16 | image7 | 4 | 1 |
| 19 | image8 | 4 | 7 |
+--------------------------------------------------+
I need to get all of product information, as well as the file name of an image for the product. Notice that a product can have more than one image; I want the one with the lowest priority. Also, I only want results for products that are in a certain category.
So, say I need information for products in categories {1,2,3}, then after the query runs the result should return:
+-----------------------------------------------------------------+
| id | name | category | price | file_name |
+-------------------------------------- ----------+---------------+
| 1 | item1 | 1 | 0.99 | image1 |
| 2 | item2 | 2 | 1.99 | image3 |
| 3 | item3 | 3 | 2.95 | image4 |
+-------------------------------------------------+---------------+
I have tried writing a couple of different join statements, but none of them work; not surprising, since I'm a total novice when it comes to SQL.
Any help would be greatly appreciated!
I will add a step by step tutorial, first getting the join right,
then adding some conditions to filter the category and finally, grouping
and using the having clause with a sub-select. You will need to use the last select
in your code. I also tested this on a mysql instance and it works.
I'm using group by in case you need some other complex stuff. It's good to have an example.
The syntax is ansii sql, it should work on all databases not just mysql
-- get everything by joining
select p.*, i.file_name
from products p
join image i on (p.id = i.p_id)
/* get everything by joining
* + filter by category
*/
select p.*, i.file_name
from products p
join image i on (p.id = i.p_id)
where p.category in (1,2,3)
/* get everything by joining
* + filter by category
* + image is the one with the lowest priority
* note: selecting the priority is not necessary
* but it's good for demonstration purposes
*/
select p.*, i.file_name, i.priority
from products p
join image i on (p.id = i.p_id)
where p.category in (1,2,3)
group by p.id
having i.priority = (select min(priority) from image where p_id = p.id)
This is the answer:
select a.id, a.name, a.category, a.price, b.filename as file_name
from products a left join (
select i.p_id, i.filename from (select id, min(priority) as min_p
from images group by p_id) q
left join images i on q.id = i.id
) b on a.id = b.p_id
where a.category in (1, 2, 3);
EXPLANATION:
First, you need to get a set where for each products with lowest priority, which is from this query:
select id, min(priority) as min_p from images group by p_id;
The result will be:
+----+----------+
| id | lowest_p |
+----+----------+
| 1 | 0 |
| 2 | 2 |
| 3 | 2 |
| 4 | 1 |
+----+----------+
4 rows in set (0.00 sec)
The next step will be to get an outer join, in this case I'd choose (arbitrarily according to my preference), the left join:
select i.p_id, i.filename from (select id, min(priority) as min_p
from images group by p_id) q left join images i on q.id = i.id;
This query produce what you want in short:
+------+----------+
| p_id | filename |
+------+----------+
| 1 | image1 |
| 2 | image3 |
| 3 | image4 |
| 4 | image7 |
+------+----------+
4 rows in set (0.00 sec)
Now you just need to decorate this, again using left join:
select a.id, a.name, a.category, a.price, b.filename as file_name
from products a left join (
select i.p_id, i.filename from (select id, min(priority) as min_p
from images group by p_id) q
left join images i on q.id = i.id
) b on a.id = b.p_id
where a.category in (1, 2, 3);
And you'll get what you want:
+------+-------+----------+-------+-----------+
| id | name | category | price | file_name |
+------+-------+----------+-------+-----------+
| 1 | item1 | 1 | 0.99 | image1 |
| 2 | item2 | 2 | 1.99 | image3 |
| 3 | item3 | 3 | 2.95 | image4 |
+------+-------+----------+-------+-----------+
3 rows in set (0.00 sec)
You can also put the products in the right hand side of the left join, depending on what you expected when there is product without images available. The query above will display the view as above, with the file_name field as "null".
On the other hand, it will not display any if you put products on the right hand side of hte left join.
Building on sarwar026's answer...
SELECT p.id, name, priority, price, file_name
FROM Products p, Images i
WHERE p.id = i.p_id
AND i.priority = (SELECT MIN(priority) FROM Images ii WHERE ii.p_id = p.id)
AND p.category IN (1,2,3)
(tested on a mysql database with copies of your tables)

Concatenating rows in relation to a JOIN

Suppose I have a cooking show:
cookingepisodes
id | date
---------------
1 | A
2 | B
3 | C
4 | D
…
This show reviews products in these categories (left) and are linked by the table to the right:
tests testitems
id | name id | episodeid | testid | name
------------ ------------------------------------
1 | cutlery 1 | 1 | 1 | Forks
2 | spices 2 | 2 | 1 | Knives
3 | 4 | 1 | Spoons
4 | 4 | 2 | Oregano
My desired output is this:
showid | testid | testname
4 | 1,2 | cutlery, spices
3 | NULL | NULL
2 | 1 | cutlery
1 | 1 | cutlery
I've tried using this query, and it works as long as I don't need to concatenate the results (when there are two tests on the same episode). Then the join will create multiple rows based on the number of
SELECT DISTINCT e.*, i.testid, t.name AS testname
FROM cookingepisodes AS e
LEFT OUTER JOIN testitems AS i ON i.episodeid = e.id
LEFT OUTER JOIN tests AS t ON i.testid = t.id
ORDER BY e.date DESC
I've also tried something like this, but I can't get it to work because of the outer block reference (e.id):
JOIN (
SELECT GROUP_CONCAT(DISTINCT testid)
FROM testitems
WHERE testitems.episodeid = e.id
) AS i
Any tips on how I can solve this without restructuring the database?
Try this one -
SELECT
ce.id showid,
GROUP_CONCAT(te.testid) testid,
GROUP_CONCAT(t.name) testname
FROM cookingepisodes ce
LEFT JOIN testitems te
ON te.episodeid = ce.id
LEFT JOIN tests t
ON t.id = te.testid
GROUP BY
ce.id DESC;