MySQL Group Join Table - mysql

I have below three SQL statement and I want to select out like below, I tried but not success.
Need some
help.
Output:
member_id, balance, firstname, lastname, LastPurchase, LastOrde
SELECT c.member_id
, c.firstname
, c.lastname
, m.balance
FROM member m
, customer c
where m.member_id = c.member_id
order
by m.member_id
SELECT member_id, max(date) as LastPurchase
FROM purchase
GROUP
BY member_id
SELECT member_id, max(date) as LastOrder
FROM ordert
GROUP
BY member_id

You can join these statements -
SELECT c.member_id, c.firstname, c.lastname, m.balance, p.LastPurchase, o.LastOrder
FROM member m
join customer c on m.member_id = c.member_id
left join (SELECT member_id, max(date) as LastPurchase
FROM purchase
GROUP BY member_id) p on p.member_id = m.member_id
left join (SELECT member_id, max(date) as LastOrder
FROM ordert
GROUP BY member_id) o on o.member_id = m.member_id
order by m.member_id

You can join the aggregate queries. The JOIN ... USING syntax comes handy here, since all join column names are the same:
SELECT c.member_id, c.firstname, c.lastname, m.balance, p.last_purchase, o.last_purchase
FROM member m
INNER JOIN customer c USING(member_id)
INNER JOIN (
SELECT member_id, max(date) last_purchase FROM purchase GROUP BY member_id
) p USING(member_id)
INNER JOIN (
SELECT member_id, max(date) last_order FROM order GROUP BY member_id
) o USING(member_id)
ORDER BY c.member_id
Important: your original query uses implicit, old-shool joins (with a comma in the from clause) - this syntax fell out of favor more than 20 years ago and its use is discourage, since it is harder to write, read, and understand.
One of the many benefits of using explicit joins here is that you can easily change the INNER JOINs to LEFT JOINs if there is a possibility that a member has no purchase or no order at all.

Related

SQL reuse a subquery 'AS' as a parameter for another subquery

I am new to SQL and try to re-use an alias/subquery I made as a parameter for another subquery.
For a certain period of time I want all the customers who have made a purchase, I get the date of the last purchase but now I am trying to pass this date to the invoices in order to get the name of the salesperson associated to this invoice.
So far I have this:
SELECT c.id,
c.firstname,
c.lastname,
c.language,
c.sex,
c.company,
c.city,
c.postal_code,
c.email,
c.created_at,
(SELECT max(`created_at`) FROM invoices WHERE client_id=c.id) AS last_purchase_date,
[...]
FROM
clients c
JOIN
boutiques b ON b.id = c.boutique_id
JOIN
brands br ON br.id = b.brand_id
[...]
and would like something like:
SELECT c.id,
c.firstname,
c.lastname,
c.language,
c.sex,
c.company,
c.city,
c.postal_code,
c.email,
c.created_at,
u.name
(SELECT max(`created_at`) FROM invoices WHERE client_id=c.id) AS last_purchase_date,
(SELECT id FROM invoices WHERE created_at = last_purchase_date) AS last_invoice_id
(SELECT name FROM users u WHERE id=last_invoice.user_id) AS sales_advisor
[...]
FROM
clients c
JOIN
boutiques b ON b.id = c.boutique_id
JOIN
users u ON u.boutique_id = b.id
JOIN
brands br ON br.id = b.brand_id
[...]
Thanks in advance!
Consider migrating those subqueries into derived tables (i.e., queries in FROM or JOIN clauses instead of SELECT clause). In fact, two of those subqueries can become whole tables: invoices and second users.
SELECT c.id,
c.firstname,
c.lastname,
c.language,
c.sex,
c.company,
c.city,
c.postal_code,
c.email,
c.created_at,
u.name,
agg.last_purchase_date,
i.id AS last_invoice_id,
u2.name AS sales_advisor
[...]
FROM
clients c
JOIN
boutiques b ON b.id = c.boutique_id
JOIN
users u ON u.boutique_id = b.id
JOIN
brands br ON br.id = b.brand_id
JOIN
(
SELECT client_id, max(`created_at`) as last_purchase_date
FROM invoices
GROUP BY client_id
) agg
ON c.id = agg.client_id
JOIN
invoices i ON i.client_id = agg.client_id
AND i.created_at = agg.last_purchase_date
JOIN
users u2 ON u2.id = i.user_id
[...]

Find the youngest customer with AT LEAST 1 purchase order

I need to write a query to find the youngest customer who bought atleast 1 product
Here is the data:
CUSTOMER:
ORDER_DETAIL:
This is my query so far:
SELECT c.CUSTOMERID, c.age, c.name
from (
SELECT CUSTOMERID, COUNT(ORDERID) as "totalOrder"
FROM FACEBOOK_ORDER_DETAIL
GROUP BY CUSTOMERID
HAVING COUNT(ORDERID) >=1) AS tbl
LEFT JOIN FACEBOOK_CUSTOMER c on c.CUSTOMERID = tbl.CUSTOMERID
order by c.age ;
However, above query gives me
But I need the list of customers with the minimum age.
If you really only want a single youngest customer, even should there be a tie, then use LIMIT:
SELECT c.CUSTOMERID, c.age, c.name
FROM CUSTOMER c
INNER JOIN FACEBOOK_ORDER_DETAIL o
ON c.CUSTOMERID = c.CUSTOMERID
ORDER BY
c.age
LIMIT 1;
This should work because if a customer joins to the order details table, it implies that he had at least one order.
If instead you want to find all youngest customers, including all ties, then a nice way to handle this uses the RANK analytic function:
SELECT DISTINCT CUSTOMERID, age, name
FROM
(
SELECT c.CUSTOMERID, c.age, c.name, RANK() OVER (ORDER BY c.age) rnk
FROM CUSTOMER c
INNER JOIN FACEBOOK_ORDER_DETAIL o
ON c.CUSTOMERID = o.CUSTOMERID
) t
WHERE rnk = 1;
Demo
For earlier versions of MySQL, we can use a subquery as a workaround for not having RANK:
SELECT DISTINCT c.CUSTOMERID, c.age, c.name
FROM CUSTOMER c
INNER JOIN FACEBOOK_ORDER_DETAIL o
ON c.CUSTOMERID = c.CUSTOMERID
WHERE c.age = (SELECT MIN(t1.age)
FROM CUSTOMER t1
INNER JOIN FACEBOOK_ORDER_DETAIL t2
ON t1.CUSTOMERID = t2.CUSTOMERID);
Demo
You only want columns from customers, so I would phrase this as:
select c.*
from (select c.*,
rank() over (order by age) as seqnum
from customers c
where exists (select 1
from facebook_order_detail fod
where fod.customerid = c.customerid
)
) c
where seqnum = 1;
In particular, this requires no duplicate elimination or aggregation, so it should be faster. And it can use an index on face_book_details(customerid) and also perhaps on customers(age, customerid).

inner join three tables results in multiplied values

I'm trying to (let's say) gather a report on customers.
In that report I want to include sum of orders and ticket number for each client.
Tables:
Customer(id, name)
Order(id, customer_id, amount)
support_ticket(id, customer_id)
query:
select
c.id as 'Customer',
count(distinct t.id) as "Ticket count",
count(distinct o.id) as "Order count",
sum(o.amount) as 'Order Amount'
from customer as c
inner join `order` as o on c.id = o.customer_id
inner join support_ticket as t on c.id = t.customer_id
group by c.id
Since I join with customer.id on the two tables, I get all the rows "duplicated", since I get all possible combinations, so if the client as multiple tickets, the sum(o.amount) will we multiplied because of "duplicated rows"
sqlFiddle (mysql): http://sqlfiddle.com/#!9/ba39ba/13
sqlFiddle (pg): http://sqlfiddle.com/#!17/bc32e/7
It seems like a simple case but I've been looking at it too much I think, I can't find the proper way to do that report.
What am I doing wrong?
your best bet is to re-write the Aggregation off the Order table as as Derived Table;
EG
select
c.id as 'Customer',
count(distinct t.id) as "Ticket count",
o.amount as 'Order Amount' ,
o.[Order count]
from customer as c
inner join
(SELECT
o.customer_id,
sum(amount) as amount ,
count(distinct o.id) as "Order count"
from [order]
group by o.customer_id)
as o on c.id = o.customer_id
inner join support_ticket as t on c.id = t.customer_id
group by
c.id ,
o.amount ,
o.[Order count]
Note that the Derived Table Columns then are added to the group by clause at the bottom.
Cheers!
Just calculate order values in a sub-query and join it.
SELECT
c.id as 'Customer'
,count(DISTINCT st.id) as 'Ticket Count'
,o.`Order Count`
,o.amount as `Order Amount`
FROM customer c
INNER JOIN support_ticket st
on c.id = st.customer_id
INNER JOIN (
SELECT
customer_id
,SUM(amount) as 'amount'
,count(distinct id) as 'Order Count'
FROM `order`
group by customer_id
) o
on c.id = o.customer_id
GROUP BY c.id;
select c.id as 'Customer'
,t2.count_ticket as "Ticket count"
,t1.count_order as "Order count"
,t1.amount as 'Order Amount'
from customer as c
inner join (select customer_id
,count(id) as count_order
,sum(amount) as amount
from Order group by customer_id) t1
on c.id = t1.customer_id
inner join (select customer_id
,count(id) as count_ticket
from support_ticket group by customer_id) t2
on c.id = t2.customer_id
In cases like yours, when I think the solution of my problem should be fairly simple but I cant wrap my head around it, I tend to use a WITH clause.
Not because its better, but because it helps me to understand my code better by splitting up complexity. First I create a relatively simple temp. Solving the first part of my problem.
WITH temp AS (
SELECT
c.id AS "customer",
COUNT(DISTINCT o.id) AS "order_count",
SUM(o.amount) AS "order_amount"
FROM customer AS c
INNER JOIN "order" AS o on c.id = o.customer_id
GROUP BY c.id
)
Then I simply select the first half of my solution from temp, adding this way all intermediate results, and solve the second part of my initial sql.
SELECT
temp.customer,
COUNT(DISTINCT t.id) as "ticket_count",
temp.order_count,
temp.order_amount
FROM temp
INNER JOIN support_ticket as t on temp.customer = t.customer_id
GROUP BY temp.customer, temp.order_count, temp.order_amount
The principle is the same like in all previous answers, but SELECTS are separated and I can check them fast, and continue on if I'm happy with parts of the solution.

SQL join tables, total spent by all customers

I'm a newbie at SQL and this is the question I've been struggling with:
I have these tables:
customers(custID, firstname, familyname)
items(itemID, unitcost)
lineitems(quantity, orderID, itemID)
orders(orderID, custID, date)
I need to find the names and total spend of all customers that made more than one order.
SELECT SUM(items.unitcost*lineitems.quantity) AS "total_spent"
FROM orders
INNER JOIN customers
ON orders.custID=customers.custID
GROUP BY firstname
HAVING COUNT(DISTINCT orderID)>1
LIMIT 0,30
I think you just need to continue your joins:
SELECT c.custId, c.firstname, SUM(i.unitcost*li.quantity) total_spent
FROM customers c
JOIN orders o ON c.custId = o.custId
JOIN lineitems li ON o.orderId = li.orderId
JOIN items i ON li.itemId = i.itemId
GROUP BY c.custId, c.firstname
HAVING COUNT(DISTINCT o.orderID)>1
LIMIT 0,30

Transform sub-queries to joins

I have three tables: userProfile, loginTimes, orders.
I am trying get each user's profile row, his last login time, and his last order row.
Here's my query:
Select u.*, t.loginTime, orders.* From userProfiles u
Inner Join
(Select userId, MAX(time) loginTime From loginTimes Group By userID) t
On u.userId = t.userID
Inner Join
(Select userId, MAX(enterDate) orderDate From orders Group By userId) o
On u.userID = o.userID
Inner Join
orders On orders.userId = u.userId And orders.enterDate = o.orderDate
Is there any way to rewrite without so many sub queries?
OP I think this is the query you are going for, this still requires 2 subqueries, but I don't believe your original query functioned as intended.
You could remove the loginTimes subquery, and use MAX(loginTime) in the outer SELECT list, but then you'd need to GROUP BY every field in the order table, which is arguably just as unclean.
The following query retrieves the UserId, latest LoginTime and the entire order record for the user's most recent order:
SELECT u.userId,
u.userName,
l.loginTime,
o.*
FROM userProfiles u
INNER JOIN ( SELECT userId,
loginTime = MAX(time)
FROM loginTimes
GROUP BY userID) l ON u.userId = l.userId
INNER JOIN ( SELECT *,
rowNum = ROW_NUMBER() OVER (PARTITION BY userId
ORDER BY enterDate DESC)
FROM orders) o ON u.userId = o.userId AND o.rowNum = 1
Working on SQLFiddle
You can easily re-write the aggregation with the following.
-- Aggregation
(
SELECT
T.userId,
MAX(time) as loginTime,
MAX(enterDate) as orderDate
FROM
loginTimes as T INNER JOIN orders as O
ON
T.userId = O.userId
GROUP BY
T.userId
)
However, I do not understand why you are calculating MAX(enterDate) and not using it.
The two tables that are joined without aggregation is easy also. You should stay away from using *. It is just wasted overhead if all the fields are not being used.
SELECT
U.*, O.*
FROM
userProfiles as U
INNER JOIN
orders as O
ON O.userId = U.userId
Please explain what you are trying to return as values from the Query. What is the business logic?
I believe this will do:
SELECT u.userID
,u.otherColumn
,MAX(t.time) AS loginTime
,MAX(o.enterDate) AS orderDate
FROM userProfiles u
JOIN loginTimes t ON t.userID = u.userID
JOIN orders o ON o.userID = u.userID
GROUP BY u.userID, u.otherColumn
For every other column in userProfiles you add to the SELECT clause, you need to add it to the GROUP BY clause as well..
Update:
Just because it can be done.. I tried it without any subquery :)
SELECT u.userID
,MAX(t.time) AS loginTime
,o.*
FROM userProfiles u
JOIN loginTimes t ON t.userID = u.userID
JOIN orders o ON o.userID = u.userID
LEFT JOIN orders o1 ON o.userID = o1.userID AND o.enterDate < o1.enterDate
WHERE o1.orderID IS NULL
GROUP BY u.userID
,o.* --write out the fields here
You'll have to write down the fields of the orders table you want in the select clause in your GROUP BY clause also.