SQL select logic two tables - sql

I have two tables
Customer(ID, First_Name, Last_Name, Address);
Orders (ID, Product_Name, PRICE, Order_Date DATE, Customer_ID, Amount);
I must select last names of the customers along with the count of their orders.
output of select request must be
SMITH | 0
GREG | 2
WATSON | 0
HOLMSE | 2
RUST | 4
FRINGE | 1
TKACH | 3

You can use the following, using a LEFT JOIN and GROUP BY:
SELECT c.Last_Name, COUNT(o.ID)
FROM Customer c LEFT JOIN Orders o ON c.ID = o.Customer_ID
GROUP BY c.ID

SELECT
c.Last_Name, COUNT(o.ID)
FROM
Customer c
LEFT JOIN
Orders o ON c.ID = o.Customer_ID
GROUP BY c.Last_Name
ORDER BY c.Last_Name;
You can do this with left join and group by on Last_Name columns.

Related

Join on two columns, if null then only join on one

I have the following two tables:
customers:
customer_id
department_id
aaaa
1234
bbbb
3456
status:
department_id
customer_id
status
1234
NULL
silver
3456
bbbb
gold
1234
bbbb
gold
I want to join status on customers, but if if it returns NULL I want to give the customer the department default. My ideal Output for this would be the following:
customer_id
department_id
status
aaaa
1234
silver
bbbb
3456
gold
I have tried to do two left joins, but am getting a memory usage error. Is there another way to do this that is efficient?
You can do:
select c.*, coalesce(s.status, d.status) as status
from customers c
left join status d on d.department_id = c.department_id
and d.customer_id is null
left join status s on s.department_id = c.department_id
and s.customer_id = c.customer_id
This might work:
SELECT *,
(
SELECT TOP 1 status
FROM status s
WHERE s.customer_id = c.customer_id
OR (c.customer_id IS NULL AND s.department_id = c.department_id)
ORDER BY CASE WHEN s.customer_id is NOT NULL THEN 0 ELSE 1 END
) as status
FROM customers c
The kicker is what kind of database you're using. If it's MySql you might want LIMIT 1 instead of TOP 1. For Oracle you'd look at the ROWNUM field.
Assuming that there is always a match at least on the department_id, you need an INNER join and FIRST_VALUE() window function will pick the proper status:
SELECT DISTINCT
c.customer_id,
c.department_id,
FIRST_VALUE(s.status) OVER (
PARTITION BY c.customer_id, c.department_id
ORDER BY CASE
WHEN s.customer_id = c.customer_id THEN 1
WHEN s.customer_id IS NULL THEN 2
ELSE 3
END
) status
FROM customers c INNER JOIN status s
ON s.department_id = c.department_id;
Depending on the database that you use the code may be simplified.
See the demo.

SQL Query to get value of recent order alongwith data from other tables

I am writing an SQL query to get data from more than 3 tables, but for simplifying the question here I am using a similar scenario with 3 tables.
Table1 Customer (PK-CustomerID, Name)
CustomerID
Name
1
John
2
Tina
3
Sam
Table2 Sales (FK-Id, SalePrice)
ID
SalePrice
1
200.00
2
300.00
3
400.00
Table3 Order (PK-Id, FK-CustomerID, Date, Amount)
Id
CustomerID
Date
Amount
101
1
25-09-2021
30.0
102
1
27-09-2021
40.0
103
2
19-09-2021
60.0
In the output, Date and Amount should be the from most recent Order (latest Date), for a customer
My approach was
Select c.CustomerID, c.Name, s.SalePrice, RecentOrder.Date, RecentOrder.Amount from
Customer as c
LEFT JOIN Sales s ON c.CustomerID = s.ID
LEFT JOIN (SELECT top 1 o.Date, o.Amount, o.CustomerID
FROM Order o, Customer c1 WHERE c1.CustomerID = o.CustomerID ORDER BY o.Date DESC)
RecentOrder ON c.CustomerID = RecentOrder.CustomerID
Output I get
CustomerID, Name, SalePrice, Date, Amount
CustomerID
Name
SalePrice
Date
Amount
1
John
200.00
27-09-2021
40.0
2
Tina
300.00
null
null
3
Sam
400.00
null
null
The output I get includes the most recent order out of all the orders. But I want to get the recent order out of the orders made by that customer
Output Required
CustomerID, Name, SalePrice, Date, Amount
CustomerID
Name
SalePrice
Date
Amount
1
John
200.00
27-09-2021
40.0
2
Tina
300.00
19-09-2021
60.0
3
Sam
400.00
null
null
Instead of subquery in left join, you can check with outer apply.
Check following way
Select c.CustomerID, c.Name, s.SalePrice, RecentOrder.Date, RecentOrder.Amount
from Customer c
LEFT JOIN Sales s ON c.CustomerID = s.ID
OUTER APPLY (
SELECT top 1 o.Date, o.Amount, o.CustomerID
FROM [Order] o
WHERE o.CustomerID = c.CustomerID ORDER BY o.Date DESC) RecentOrder`
You need to pre-aggregate or identify the most recent order for each customer order, your query is selecting 1 row for all orders.
Try the following (untested!)
select c.CustomerID, c.Name, s.SalePrice, o.Date, o.Amount
from Customer c
left join Sales s on c.CustomerID = s.ID
outer apply (
select top (1) date, amount
from [order] o
where o.CustomerId=c.CustomerId
order by Id desc
)o

SQL MAX aggregate function not bringing the latest date

Purpose: I am trying to find the max date of when the teachers made a purchase and type.
Orders table
ID
Ordertype
Status
TeacherID
PurchaseDate
SchoolID
TeacherassistantID
1
Pencils
Completed
1
1/1/2021
1
1
2
Paper
Completed
1
3/5/2021
1
1
3
Notebooks
Completed
1
4/1/2021
1
1
4
Erasers
Completed
2
2/1/2021
2
2
Teachers table
TeacherID
Teachername
1
Mary Smith
2
Jason Crane
School table
ID
schoolname
1
ABC school
2
PS1
3
PS2
Here is my attempted code:
SELECT o.ordertype, o.status, t.Teachername, s.schoolname
,MAX(o.Purchasedate) OVER (PARTITION by t.ID) last_purchase
FROM orders o
INNER JOIN teachers t ON t.ID=o.TeacherID
INNER JOIN schools s ON s.ID=o.schoolID
WHERE o.status in ('Completed','In-progress')
AND o.ordertype not like 'notebook'
It should look like this:
Ordertype
Status
teachername
last_purchase
schoolname
Paper
Completed
Mary Smith
3/5/2021
ABC School
Erasers
Completed
PS1
2/1/2021
ABC school
It is bringing multiple rows instead of just the latest purchase date and its associated rows. I think i need a subquery.
Aggregation functions are not appropriate for what you are trying to do. Their purpose is to summarize values in multiple rows, not to choose a particular row.
Just a window function does not filter any rows.
You want to use window functions with filtering:
SELECT ordertype, status, Teachername, schoolname, Purchasedate
FROM (SELECT o.ordertype, o.status, t.Teachername, s.schoolname,
o.Purchasedate,
ROW_NUMBER() OVER (PARTITION by t.ID ORDER BY o.PurchaseDate DESC) as seqnum
FROM orders o JOIN
teachers t
ON t.ID = o.TeacherID
schools s
ON s.ID = o.schoolID
WHERE o.status in ('Completed', 'In-progress') AND
o.ordertype not like 'notebook'
) o
WHERE seqnum = 1;
You can use it in different way. it's better to use Group By for grouping the other columns and after that use Order by for reorder all records just like bellow.
SELECT top 1 o.ordertype, o.status, t.Teachername, s.schoolname
,o.Purchasedate
FROM orders o
INNER JOIN teachers t ON t.ID=o.TeacherID
INNER JOIN schools s ON s.ID=o.schoolID
having o.status in ('Completed','In-progress')
AND o.ordertype not like 'notebook'
group by o.ordertype, o.status, t.Teachername, s.schoolname
order by o.Purchasedate Desc

Build customers report of last years with T-SQL

I've 3 tables (simplified):
-----------Orders--------------------
Id | Total_Price | Customer_Id | Date
--------Order Details---------------------
Id | Order_Id | Product Name | Qty | Value
----Customers------
Id | Name | Address
I take a total order value of single customer with this query:
SELECT C.ID, C.NAME , SUM(O.TOTAL_PRICE)
FROM CUSTOMERS C
JOIN ORDERS O ON O.CUSTOMER_ID = C.ID
GROUP BY C.ID, C.NAME
Now, I want to build a report with total order value filtered by a range of dates:
SELECT C.ID, C.NAME , SUM(O.TOTAL_PRICE)
FROM CUSTOMERS C
JOIN ORDERS O ON O.CUSTOMER_ID = C.ID
WHERE O.DATE BETWEEN #value1 AND #value2
GROUP BY C.ID, C.NAME
this works OK, but I want to select last 3 year sums of total orders value grouped by customer, this is the results that I want:
1Year | 2Year | 3Year | Customer_Name
-------------------------------------------------
XXX | YYY | ZZZZ | Customer1
XYX | YYZ | ZZTZ | Customer2
....
I've this cardinality:
Customer table with 22.000 rows
Orders table with 87.000 rows
Orders details with 600.000
It is possible without temptable,vartable or stored procedure with long execution time?
In my report I want also to calculate total Qty of last 3 years grouped by customer of a product, but this is the next step.
Any ideas?
Thanks
You can use a case statement to get the result you want. Since there is some ambiguity in your post about how the year ranges are defined, I've left out any calculations to get those year end/starts and just put variables in. You can revise to suit your need.
SELECT C.ID
,C.NAME
,SUM(CASE
WHEN o.DATE BETWEEN #year1start
AND #year1end
THEN O.TOTAL_PRICE
ELSE 0
END) Year1
,SUM(CASE
WHEN o.DATE BETWEEN #year2start
AND #year2end
THEN O.TOTAL_PRICE
ELSE 0
END) Year2
,SUM(CASE
WHEN o.DATE BETWEEN #year3start
AND #year3end
THEN O.TOTAL_PRICE
ELSE 0
END) Year3
FROM CUSTOMERS C
INNER JOIN ORDERS O ON O.CUSTOMER_ID = C.ID
GROUP BY C.ID
,C.NAME
Another option is to use pivot statement. I assume every your date range equals to one year (e.g. 2013, 2014 and so on).
If these years are strongly determined pivot isn't very beautiful option (look at full sqlfiddle example, it has possible solution for your additional question):
select
c.Id, c.Name, c.Address, CostByYear.[2013], CostByYear.[2014], CostByYear.[2015]
from Customers c
left join (
select
pt.Customer_Id, isnull(pt.[2013], 0) as [2013],
isnull(pt.[2014], 0) as [2014], isnull(pt.[2015], 0) as [2015]
from (
select
o.Customer_Id, year(o.Date) [Year], sum(o.Total_Price) [TotalCost]
from Orders o
group by
o.Customer_Id, year(o.Date)
) src
pivot (
sum(TotalCost) for [Year] in ([2013], [2014], [2015])
) pt
) CostByYear on
c.Id = CostByYear.Customer_Id
order by
c.Name
Also you can do both approaches (mine and prev answer) with dynamically created queries if year ranges aren't known and strongly defined.

Having trouble finding the right join statement for my query

I am trying to export data from a database and am joining the "customers" table with the "orders" table. It's a one to many relationship where customers can have multiple orders. I'm trying to write a query that returns basic customer info from the customers table - email_address, firstname, lastname, but to also include the date of the last order they placed.
customers as c
- customer_id
- firstname
- lastname
- email_address
orders as o
- orders_id
- customers_id
- purchase_date
I want the result to return a single result for each customer where the purchase date is the last purchase that customer made.
c.firstname, c.lastname, c.email_address, o.purchase_date
What is the correct SQL syntax to make this happen?
select c.*, o.LastOrderDate
from customers c
LEFT JOIN
(select customers_id, max(purchase_date) as LastOrderDate
from orders
group by customers_id) o on o.customers_id=c.customers_id
Will get all customers and the date of the last order, if one exists.
What about:
SELECT c.firstname, c.lastname, c.email_address, MAX(o.purchase_date)
FROM customers AS c
JOIN orders AS o ON o.customers_id = c.customer_id
GROUP BY c.firstname, c.lastname, c.email_address
This only lists customers who have placed at least one order. If you want all customers, then you should be able to use a LEFT JOIN instead of a simple (INNER) JOIN as shown.
This returns all Customers, regardless of whether they have any Orders:
SQL> select c.name
2 , c.email_address
3 , ( select max (o.order_date) from orders o
4 where o.customer_no = c.customer_no )as last_order
5 from customers c
6 /
NAME EMAIL_ADDRESS LAST_ORDE
-------------------- ------------------------- ---------
ACME Industries info#acme.com 07-APR-10
Tyrell Corporation accounts#tyrellcorp.com 26-MAR-10
Lorax Textiles Co the.lorax#hotmail.com
SQL>
It is equivalent to the LEFT OUTER JOIN:
SQL> select c.name
2 , c.email_address
3 , o.last_order_date
4 from customers c
5 left join ( select o.customer_no
6 , max (o.order_date) as last_order_date
7 from orders o
8 group by o.customer_no ) o
9 on o.customer_no = c.customer_no
10 /
NAME EMAIL_ADDRESS LAST_ORDE
-------------------- ------------------------- ---------
ACME Industries info#acme.com 07-APR-10
Tyrell Corporation accounts#tyrellcorp.com 26-MAR-10
Lorax Textiles Co the.lorax#hotmail.com
SQL>
A RIGHT OUTER JOIN would only return rows for Customers with Orders. Assuming an Order must have a Customer (i.e. enforced foreign key) then that would be the same as an INNER JOIN.
If your flavour of database supports analytic functions then RANK() offers an alternative way of solving it...
SQL> select name
2 , email_address
3 , order_date
4 from (
5 select c.name
6 , c.email_address
7 , o.order_date
8 , rank () over (partition by c.customer_no
9 order by o.order_date desc ) as rnk
10 from customers c
11 join orders o
12 on ( o.customer_no = c.customer_no)
13 )
14 where rnk = 1
15 /
NAME EMAIL_ADDRESS ORDER_DAT
-------------------- ------------------------- ---------
ACME Industries info#acme.com 07-APR-10
Tyrell Corporation accounts#tyrellcorp.com 26-MAR-10
SQL>
This also only returns rows for Customers with Orders.