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

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.

Related

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

SQL select logic two tables

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.

Group Join between three tables to get the percentage

I have three tables Attendance, Employee, Sector
Employee Table
EmiId -Name -SectorId
123 ABC 1
231 BCD 2
125 WER 1
Attendance
AttId -EmpId -Dt
1 123 12/12/2014 9:00
2 231 12/12/2014 10:00
Sector
SectorId -SectorName
1 North Sector
2 East Sector
my query is
SELECT COUNT(Attendance.Emp_Id) as AttCount,(select COUNT(*) from Employee) as EmpCount
FROM Employee INNER JOIN
Sector ON Employee.SectorId = Sector.SectorId INNER JOIN
Attendance ON Employee.EmpId = Attendance.EmpId
group by Sector.SectorId
and i keep getting same number of employees for both instead so the (select COUNT(*) from Employee)- EmpCount seems to be incorrect.I keep getting the same number for both the sectors. Although the Attcount seems to work fine.
Please help. Thanks in advance.
You just making select count from the same table - do not expect other results.
Perhaps someone could do it better
SELECT COUNT(Attendance.Emp_Id),COUNT(case when Employee.empid=attendance.attid then 1 else 0 end)
FROM Employee JOIN Sector ON Employee.SectorId = Sector.SectorId
LEFT JOIN Attendance ON Employee.EmpId = Attendance.Emp_Id
group by Sector.SectorId
Maybe you need LEFT JOIN with Attendance
SELECT COUNT(Attendance.Emp_Id),(select COUNT(*) from Employee)
FROM Employee JOIN Sector ON Employee.SectorId = Sector.SectorId
LEFT JOIN Attendance ON Employee.EmpId = Attendance.EmpId
group by Sector.SectorId

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.