In a MySQL database I have the following tables:
customers
CUSTOMER_ID | NAME
---------------------------
1 | John Doe
2 | Peter Jones
3 | David Smith
products
PRODUCT_ID | DESCRIPTION
---------------------------
1 | Toothbrush
2 | Shaving cream
3 | Deodorant
customer_product
CUSTOMER_ID | PRODUCT_ID
---------------------------
1 | 2
1 | 3
2 | 1
3 | 1
3 | 2
The table customer_product is a pivot table. When a customer orders a product, it will be logged there.
My question is: how can I select all customers that didn't ordered a certain product?
So for example, I want to retrieve all customers that never ordered a Toothbrush.
You can use NOT EXISTS:
SELECT CUSTOMER_ID,NAME
FROM customers AS c
WHERE NOT EXISTS (
SELECT 1
FROM customer_product AS cp
INNER JOIN products AS p
ON cp.PRODUCT_ID= p.PRODUCT_ID
WHERE p.DESCRIPTION = 'Toothbrush' AND cp.CUSTOMER_ID = c.CUSTOMER_ID)
Here is another way that will work for you
select c.customer_id,c.Name from customers c
where c.customer_id not in
(select c.customer_id from customers c
left join customer_product cp on c.customer_id = cp.customer_id
inner join products p on cp.product_id = p.product_id
where p.description ='toothbrush'
) ;
Related
I have 3 tables: foods, order_detail, and orders
Here are the records for table foods:
id | name | type
------------------------------
F01 | Omelette | Breakfast
F02 | Burger | Breakfast
F03 | Satay | Lunch
F04 | Fried Rice | Dinner
Here are the records for table order_detail:
food_id | order_id
-----------------------------
F01 | T01
F04 | T01
F02 | T02
F03 | T03
F03 | T04
And here are the records for orders table:
order_id | date | qty
---------------------------------
T01 | 2017-05-01 | 2
T02 | 2017-05-02 | 1
T03 | 2017-05-05 | 1
T04 | 2017-05-07 | 1
I want to show count order detail grouped by food type. I expected this result:
type | total_order
-------------------------
Breakfast | 2
Lunch | 2
Dinner | 1
Here is my approach, but it still doesn't show the expected result.
SELECT
f.type,
(SELECT COUNT(*) FROM order_detail od WHERE f.id = od.food_id) AS total_order
FROM foods f
LEFT JOIN order_detail od ON f.id = od.food_id
GROUP BY f.type
ORDER BY f.id
The result is:
type | total_order
-------------------------
Breakfast | 1
Lunch | 2
Dinner | 1
How can I get the result I want? Thanks in advance!
Aggregation can work here, but you need to join across all three tables:
SELECT f.type, COUNT(o.order_id) AS total_order
FROM foods f
LEFT JOIN order_detail od ON od.food_id = f.id
LEFT JOIN orders o ON o.order_id = od.order_id
GROUP BY f.type
ORDER BY f.id;
Note that we do a left join across all three tables in order to not drop any food type which might happen to have zero orders.
you can add the orderdtails to the subselect to get the correct number
SELECT
f.type,
(SELECT COUNT(*) FROM order_detail od2
WHERE f.id = od2.food_id AND od2.order_id = od.order_id
) AS total_order
FROM foods f
LEFT JOIN order_detail od ON f.id = od.food_id
GROUP BY f.type
ORDER BY f.id
WITH cte AS (
SELECT type
FROM foods
INNER JOIN order_detail ON (order_detail.food_id=foods.id)
INNER JOIN orders ON (order_detail.ord_id=orders.ord_id)
)
SELECT DISTINCT type, COUNT(type) OVER (PARTITION BY type) AS qty
FROM cte
I have a regular table with employee info:
ID int
NAME varchar
Besides that I have a table with purchases. The employee id is listed here
again either in the column seller or contractor. It is possible that a employee
has not done any sales. It is also possible that no contractor or no seller is involved.
EMPLOYEES
ID NAME
1 Bill
2 Cliff
3 Mary
4 Jon
PURCHASES
ID SELLER CONTRACTOR
1 1 2
2 1
3 2 1
4 2 3
I want to get the list with the employee id and name and information if
this employee is listed in the seller and/or contractor columns. So basically
if this employee has done any sales.
ID NAME SALES
1 Bill 1
2 Cliff 1
3 Mary 1
4 Jon 0
What I get is double lines when employees are listed in multiple sales. I have tries numerous LEFT JOIN statements.
You can do it with EXISTS:
select e.*,
exists (select 1 from purchases where e.id in (seller, contractor)) sales
from employees e
See the demo.
Or with a LEFT JOIN and aggregation:
select e.id, e.name, max(p.id) is not null sales
from employees e left join purchases p
on e.id in (p.seller, p.contractor)
group by e.id, e.name
See the demo.
Results:
| ID | NAME | sales |
| --- | ----- | ----- |
| 1 | Bill | 1 |
| 2 | Cliff | 1 |
| 3 | Mary | 1 |
| 4 | Jon | 0 |
You could use LEFT JOIN and COUNT based on column from "outer table":
SELECT e.ID, e.name, COUNT(p1.seller) AS cnt_seller, COUNT(p2.contractor) AS cnt_contractor
FROM Employees e
LEFT JOIN Purchases p1
ON e.ID = p1.seller
LEFT JOIN Purchases p2
ON e.ID = p2.contractor
GROUP BY e.ID, e.name
My case looks simple but i'm messing around with this..
I have 4 tables: User, Macros, Categories, and another one that relate users with categories. One Macro have many Categories.
What i need, is a query that based on the Macro, get the users and the Categories where user is NOT IN.
Example: I have a macro named VEICULES, with categories CAR,TRUCK and Motorcycle. User José is on category CAR and User Julio on category CAR and TRUCK, so my query should return:
José | TRUCK,Motorcycle
Julio | Motorcycle
Tables:
prd_users
id | name | Email
---------------------------
1 | José | jose#email.com
2 | Júlio | julio#email.com
3 | André | andre#email.com
cat_macros
macro_id | macro_name
-----------------------
1 | Veicules |
cat_categories
category_id | category_name | macro_id
---------------------------------------
1 | Cars | 1
2 | Trucks | 1
3 | Motorcycles | 1
prd_tr_rabbit_catg
id | category_id | tasker_user_id
---------------------------------------
1 | 1 | 1
2 | 1 | 2
3 | 2 | 2
I'm stucked on just getting the categories where the user already is ..
SELECT prd_users.id, prd_users.name,
prd_users.email,cat_macros.macro_name as macro,
GROUP_CONCAT(cat_categories.category_name SEPARATOR ', ') as in_categories
FROM prd_users
INNER JOIN prd_tr_rabbit_catg ON prd_tr_rabbit_catg.tasker_user_id = prd_users.id
INNER JOIN cat_categories ON cat_categories.category_id = prd_tr_rabbit_catg.category_id
INNER JOIN cat_macros ON cat_macros.macro_id = cat_categories.macro_id
WHERE cat_macros.macro_id = '45'
GROUP BY prd_users.id;
To solve this problem it's necessary to create a list of all users joined with all categories for the given macro category. This can be done with a CROSS JOIN:
SELECT *
FROM prd_users u
CROSS JOIN (SELECT m.macro_id, m.macro_name, c.category_name, c.category_id
FROM cat_macros m
JOIN cat_categories c ON c.macro_id = m.macro_id) c
This can then be LEFT JOINed to the prd_tr_rabbit_catg table and by selecting those rows where there is no matching entry in the prd_tr_rabbit_catg table, we can find the users who don't have an entry for the given category:
SELECT c.macro_name, u.id AS user_id, u.name, u.Email, GROUP_CONCAT(c.category_name) AS missing_cats
FROM prd_users u
CROSS JOIN (SELECT m.macro_id, m.macro_name, c.category_name, c.category_id
FROM cat_macros m
JOIN cat_categories c ON c.macro_id = m.macro_id) c
LEFT JOIN prd_tr_rabbit_catg x ON x.tasker_user_id = u.id AND x.category_id = c.category_id
WHERE x.id IS NULL
AND c.macro_id = 1
GROUP BY c.macro_name, u.id
For your sample data, this gives:
macro_name user_id name Email missing_cats
Veicules 1 José jose#email.com Motorcycles,Trucks
Veicules 2 Júlio julio#email.com Motorcycles
Veicules 3 André andre#email.com Cars,Motorcycles,Trucks
Update
To exclude users who don't have any of the categories, add a HAVING clause:
HAVING COUNT(*) < (SELECT COUNT(*) FROM cat_categories WHERE macro_id = 1)
Demo on SQLFiddle
I research almost 200 example pages about mysql complex queries but stuck in it.
This is my stucture
Table name: zones
zoneId | zoneName
------------------
Table name: customers
customesId | zoneId | customerName
----------------------------------
Table name: products
productId | productName
-----------------------
Table name: sales
sid | zoneId | customerId | productId | amount
----------------------------------------------
Is it possible to get the following output only with the query?
zoneName | customerName | productName | amount(SUM)
---------------------------------------------------
ZoneX | customerA | productName_1 | 10
| | productName_2 | 0
| | productName_3 | 4
| | productName_4 | 0
ZoneX | customerB | productName_1 | 7
| | productName_2 | 0
| | productName_3 | 4
| | productName_4 | 3
.......
I want to get as "0" even customer or product has no sale
I tried:
SELECT zones.zoneName
, customers.customerName
, products.productName
, SUM(amount) AS amount
FROM customers
INNER JOIN zones
ON customers.zoneId = zones.zoneId
LEFT JOIN sales
ON customers.customerId = sales.customerId
LEFT JOIN products
ON sales.productId = products.productId
You need to cross join all the customers to the products so that each customer has every product listed regardless of sale.
SELECT z.zoneName
, c.customerName
, p.productName
, SUM(coalesce(s.amount,0)) AS amount
FROM customers c
INNER JOIN zones z
ON c.zoneId = z.zoneId
CROSS JOIN PRODUCTS P
LEFT JOIN sales S
ON c.customerId = s.customerId
and s.productID = p.productID
GROUP BY z.zoneName
, c.customerName
, p.productName
You can try this query
SELECT c.zoneId ,c.customesId ,c.customerName,IF(s.amount IS NULL, 0 , s.amount)
FROM customers AS c, products AS p
LEFT JOIN sales AS s ON s.productId = p.productId and s.customersid = c.customersid
Hope this helps.
This question already has answers here:
Sum total of table with two related tables
(2 answers)
Closed 9 years ago.
I have 4 tables, with the relevant columns summarized here:
customers:
id
name
credits:
id
customer_id # ie customers.id
amount
sales:
id
customer_id # ie customers.id
sales_items:
id
sale_id # ie sales.id
price
discount
The idea is that customers lists all of our customers, credits lists each time they have paid us, sales lists each time they have bought things from us (but not what things they bought) and sales_items lists all of the items they bought at each of those sales. So you can see that credits and sales both relate back to customers, but sales_items only relates back to sales.
As an example dataset, consider:
customers:
id | name
5 | Carter
credits:
id | customer_id | amount
1 | 5 | 100
sales:
id | customer_id
3 | 5
sales_items:
id | sale_id | price | discount
7 | 3 | 5 | 0
8 | 3 | 0 | 0
9 | 3 | 10 | 0
I have tried this in MySQL:
SELECT c.*,
SUM( cr.amount ) AS paid,
SUM( i.price + i.discount ) AS bought
FROM customers AS c
LEFT JOIN sales AS s ON s.customer_id = c.id
LEFT JOIN sales_items AS i ON i.sale_id = s.id
LEFT JOIN credits AS cr ON cr.customer_id = c.id
WHERE c.id = 5
But it returns:
id | name | paid | bought
5 | Carter | 300 | 15
If I omit the SUM() functions, it returns:
id | name | paid | bought
5 | Carter | 100 | 5
5 | Carter | 100 | 0
5 | Carter | 100 | 15
So it looks like it's returning one row for every record matched in sales_items, but it's filling in the amount column with same value from credits each time. I see that this is happening, but I'm not understanding why it's happening.
So, two questions:
1. What is happening that it's smearing that one value through all of the rows?
2. What SQL can I throw at MySQL so that I can get this back:
id | name | paid | bought
5 | Carter | 100 | 15
I know that I could break it all up in subqueries, but is there a away to do it just with joins? I was hoping to learn a thing or two about joins as I tackled this problem. Thank you.
Edit: I created an SQL Fiddle for this: http://sqlfiddle.com/#!2/0051b/1/0
select distinct (c.id, c.name), sum(i.price+i.discount) AS bought, cr.amount AS paid
from customer c, credits cr, sales s, sales_items i
where s.customer_id = c.id
and i.sale_id = s.id
and cr.customer_id = c.id and c.id = 5
group by c.id, c.name;
I'm not very sure, but try this. Use group by; that is surely the solution.
Please try this
SELECT c.*,( SELECT SUM( cr.amount ) FROM customer c INNER JOIN credits cr ON
cr.customer_id = c.id WHERE c.id = 5 GROUP BY cr.id ) AS paid
,SUM( i.price + i.discount ) AS bought
FROM customers AS c INNER JOIN sales s ON s.customer_id = c.id
INNER JOIN sales_items i ON i.sale_id = s.id
INNER JOIN credits cr ON cr.customer_id = c.id
WHERE c.id = 5 GROUP BY s.id,cr.id