MYSQL Query: Customer Sales Gap Analysis - mysql

I'm looking to query 3 tables (Customers, Sales, & Top-Items) and create a list of the top-items that each customer has not purchased.
Relevant fields are
Customers: Customer#
Sales: Invoice#, Customer#, Item#
Top-Items: Item#

SELECT c.*, GROUP_CONCAT(t.`Item#`) AS list_of_top_items_not_purchased
FROM Customers c
CROSS JOIN `Top-Items` t
LEFT OUTER JOIN Sales s ON c.`Customer#` = s.`Customer#` AND s.`Item#` = t.`Item#`
WHERE s.`Item#` IS NULL
GROUP BY c.`Customer#`;

Related

Joining the tables while manipulating SELECT

I am new to SQL and am wondering how to join two tables based on the user ID while, at the same time, manipulating one of the columns. I have the table of orders per ID, and another table with ID demographics. I want to sum the orders per ID, and then join the demographics information.
Separately, the codes work:
This one sums the orders per id up:
SELECT SUM(order) AS expenses, id
FROM orders
GROUP BY id;
And this one joins another table:
SELECT orders.id, demographics.*
FROM orders
JOIN demographics
ON orders.id = demographics.user;
But how can you do the two simultaneously? So the table becomes like:
id | expenses | demographics.1 | demographics.2 | demographics.3 | etc
SELECT orders.id, sum(order) as expenses, max(demographics.1), max(demographics.2),...
FROM orders
JOIN demographics
ON orders.id = demographics.user
Group by orders.id
select
o.id,
d.demographics.1,
d.demographics.2,
d.demographics.3,
sum (o.order) Expenses
from order o,
demographics d
where 1=1
and.o.id = d.user
group by
o.id

SQL Join multiple tables query

Hi I have these 2 problems below and my attempt. Please give me some advice.
SQL database:
CUSTOMER table: CUST_ID, CUST_NAME, ...
PRODUCT table: PROD_ID, PROD_DESCRIPTION, ...
CUSTOMER_PRODUCTS table: CUST_ID and PROD_ID.
This database is used to track the products a customer owns, so CUSTOMER_PRODUCTS has an entry for each product a customer owns.
I want to:
A:
Write a SQL query that will return a list of all customers who do not own any products.
Here is my attempt:
SELECT CUSTOMER _PRODUCTS.CUST_ID, CUSTOMER.CUST_ID, CUSTOMER.CUST_NAME
FROM CUSTOMER
INNER JOIN CUSTOMER_PRODUCTS
ON CUSTOMER_PRODUCTS.CUST_ID != CUSTOMER.CUST_ID;
B:
Write a SQL query that will return a list of all customers who own a product with ‘SAW’ in the name.
Here is my attempt:
SELECT CUSTOMER _PRODUCTS.CUST_ID, CUSTOMER.CUST_ID, CUSTOMER.CUST_NAME
FROM CUSTOMER
INNER JOIN CUSTOMER_PRODUCTS
ON CUSTOMER_PRODUCTS.CUST_ID = CUSTOMER.CUST_ID and WHERE PROD_DESCRIPTION LIKE 'SAW';
What do you guys think? am I headed in the right direction?
For B
SELECT CUSTOMER _PRODUCTS.CUST_ID, CUSTOMER.CUST_ID, CUSTOMER.CUST_NAME
FROM CUSTOMER
INNER JOIN CUSTOMER_PRODUCTS ON CUSTOMER_PRODUCTS.CUST_ID = CUSTOMER.CUST_ID
and WHERE PROD_DESCRIPTION LIKE '%SAW%';
Other person beat me to part A
For the A, you may want to use LEFT JOIN and IS NULL:
SELECT CUSTOMER.CUST_NAME
FROM CUSTOMER
LEFT JOIN CUSTOMER_PRODUCTS ON CUSTOMER.CUST_ID = CUSTOMER_PRODUCTS.CUST_ID
WHERE CUSTOMER_PRODUCTS.CUST_ID IS NULL;
for the B, pease ask separate question, as it is separate.

How can I SUM values in a joined table without screwing up totals of values in the first table?

I'd like to SELECT a count of the number of customers, the sum of customer order totals, and a count of customers in specific states.
I know how to do this in two queries easily, however the same WHERE constraints will be used, so it seems like it would be better to do it in one query and avoid repetition. I'm eager to improve my SQL but I can't work out how to combine them. Having them as two separate queries feels very clumsy.
Is there a way to combine them? What factors should I consider to determine if combining them is a good idea?
Customers Table
*-------------*-------------*--------------*------------*
| ID_Customer | ID_State | Name | ...etc... |
*-------------*-------------*--------------*------------*
States Table
*-------------*-------------*
| ID_State | Name |
*-------------*-------------*
Orders Table
*----------*-------------*--------------*------------*
| ID_Order | ID_Customer | ...etc... | Total |
*----------*-------------*--------------*------------*
Query 1.1 - Select Count of Customers and Count of Customers in specific states
SELECT
COUNT(*) AS Customers,
SUM(States.Name = 'California') AS California_Customers,
SUM(States.Name = 'New York') AS NewYork_Customers
FROM Customers
INNER JOIN States ON Customers.ID_State = States.ID_State
Query 1.2 - Select Sum of Customer Order Totals
SELECT
SUM(Total) AS SumOfOrderTotals
FROM Orders
INNER JOIN Customers ON Customers.ID_Customer = Orders.ID_Customer
Query 2 - An attempt at combining the queries into one (does not work)
SELECT
COUNT (DISTINCT(Customers.ID_Customer)) AS Customers,
SUM (Orders.Total) AS SumOfOrderTotals,
SUM (States.Name = 'California') AS California_Customers,
SUM (States.Name = 'New York') AS NewYork_Customers
FROM
Customers
INNER JOIN Orders ON Customers.ID_Customer = Orders.ID_Customer
INNER JOIN States ON Customers.ID_State = States.ID_State
Obviously this does not work as it is because the INNER JOIN between Customers and Orders means that States.Names are counted xN (where N is the number of orders a customer has) for each customer, making those totals wrong.
I considered a Subquery, however I'm not sure how to apply one in this case (if that is what I should be doing).
You need to do the aggregation before the join or use subqueries:
SELECT COUNT(DISTINCT(c.ID_Customer)) AS Customers,
o.SumOfOrderTotals,
SUM(s.Name = 'California') AS California_Customers,
SUM(s.Name = 'New York') AS NewYork_Customers
FROM Customers c JOIN
States s
ON c.ID_State = s.ID_State CROSS JOIN
(SELECT SUM(Total) as SumOfOrderTotals
FROM Orders o
) o;
You could also write this as:
SELECT COUNT(DISTINCT(c.ID_Customer)) AS Customers,
(SELECT SUM(Total)
FROM Orders o
) as SumOfOrderTotals,
SUM(s.Name = 'California') AS California_Customers,
SUM(s.Name = 'New York') AS NewYork_Customers
FROM Customers c JOIN
States s
ON c.ID_State = s.ID_State;
You place the subquery where you would have placed any additional field, as another thing in the SELECT clause.
SELECT
COUNT(*) AS Customers,
SUM(States.Name = 'California') AS California_Customers,
SUM(States.Name = 'New York') AS NewYork_Customers,
(SELECT SUM(Total) FROM Orders) AS SumOfOrderTotals
FROM Customers
INNER JOIN States ON Customers.ID_State = States.ID_State;

mySQL using same table/fields multiple times in a single query

I have 2 tables in mySQL database :
customers
============
customer_id (1, 2 )
customer_name (john, mark)
orders
============
order_id = 123
customer_id = 1
customer_from_id = 2
Idea is to do single query on orders table joining customers table whereby
orders.customer_id = customers.customer_id
orders.customer_from_id = customers.customer_id
to get the "customer_name" by JOIN(ing) two tables.
So how do i do single query on "orders" and expand all (2) "customer_name" fields so result looks like this :
+--------+------------+---------------------+------------------+---------------------+
order_id customer_id customer_order_name customer_from_id customer_from_name
+--------+------------+---------------------+------------------+---------------------+
123 1 john 2 mark
+--------+------------+---------------------+------------------+---------------------+
It means using same table 2x in a query and
aliasing output field "customer_name" 2x with
"customer_order_name" and "customer_from_name".
It shall be simple but i am stuck.
Any help would be much appreciated.
Thank You.
Join twice and use prefix and give aliases:
select order_id, buyer.customer_id, buyer.customer_name, seller.customer_id as customer_from_id, seller.customer_name as customer_from_name from orders o
join customers seller on o.customer_from_id = seller.customer_id
join customers buyer on o.customer_id = buyer.customer_id;
select order_id, c1.customer_id as customer_id,
c1.customer_name as customer_order_name ,
c2.customer_id as customer_from_id,
c2.customer_name as customer_from_name
from orders o
left join customers c1 using (customer_id)
left join customers c2 on o.customer_from_id = c2.customer_id;
fiddle

WHERE value IS NOT IN (subquery)

I've been struggling with this query.
I have two tables. One with coupons and Invoicenumbers. One with Invoicenumbers and customer names.
I need to get the customers who have not used a given coupon.
Here are the tables:
Promotion table:
Promotions
Invoice | Coupon
----------------
1 | couponA
2 | couponB
3 | couponB
Orders Table:
Orders
Invoice | Customer
------------------
1 | Jack
2 | Jack
3 | Jill
So Jack has used coupons A and B. And Jill has only used coupon B.
If my query were select customers who have not used coupon A, I should get Jill.
This works, but it seems clumsy and slow. Is there a better way?
SELECT Customer
FROM Promotions INNER JOIN Orders
ON Promotions.Invoice = Orders.Invoice
WHERE Customer NOT IN(
SELECT Customer
FROM Promotions INNER JOIN Orders
ON Promotions.Invoice = Orders.Invoice
WHERE Coupon = couponA)
GROUP BY Customer
Thanks for looking!
edit:
Here's an SQLFiddle schema
http://sqlfiddle.com/#!2/21d31/6
Updated: We should use prefer to use joins for better performance when its easy to do for us. Join vs. sub-query
Sql Fiddle
Select distinct Customer from orders o
join
(
SELECT distinct Customer as changedname FROM Orders o2
join
(
Select distinct invoice from Promotions where Coupon='couponA'
) t3
on o2.invoice = t3.invoice
) t2
on o.customer != t2.changedname;
Note: I changed column name customer for t3 because two joined tables must have different column names
Explanation:
Using inner or sub query is expensive when you have big data. use joins instead, lets learn converting subquery to join
With Subquery We had:
Select distinct Customer from orders where customer not in
(SELECT distinct Customer FROM Orders where invoice in
(Select distinct invoice from Promotions where Coupon='couponA'));
Converting sub-query to join
First step:
Select distinct Customer from orders o
join
(
SELECT distinct Customer as changedname FROM Orders where invoice in
(Select distinct invoice from Promotions where Coupon='couponA')
) t2
on o.customer != t2.changedname;
2nd step:
Select distinct Customer from orders o
join
(
SELECT distinct Customer as changedname FROM Orders o2 where invoice
join
(
Select distinct invoice from Promotions where Coupon='couponA'
) t3
on o2.invoice = t3.invoice
) t2
on o.customer != t2.changedname;
And that's it, much faster for tables having numerous rows
Original answer:
Use not in. Have a look.
Select distinct Customer from orders where customer not in
(SELECT distinct Customer FROM Orders where invoice in
(Select distinct invoice from Promotions where Coupon='couponA'));
Edit I have added distinct to make query faster
SQL Fiddle
SELECT DISTINCT o2.customer FROM ORDER o2
LEFT JOIN (promotions p1
JOIN Orders o1 ON p1.cuopon = 'CuoponA' AND p1.invoice = o1.invoice ) p3
ON o2.customer = p3.customer
WHERE p3.customer IS NULL
Try this query instead:
SELECT DISTINCT Customer
FROM Orders o1
WHERE NOT EXISTS (
SELECT 1
FROM Orders o2
INNER JOIN Promotions ON Promotions.Invoice = o2.Invoice
WHERE o1.Customer = o2.Customer AND Coupon = 'couponB')
The idea is to get rid of the GROUP BY by removing a join in the top part of the query, and also eliminate the NOT IN by making a coordinated subquery.
Here is a link to sqlfiddle.
Try this with a right join
SELECT Customer, Coupon
FROM Promotions
RIGHT JOIN Orders ON Promotions.Invoice = Orders.Invoice
AND Coupon = 'couponA'
GROUP BY Customer
HAVING Coupon IS NULL