Oracle left join with conditions from the target table - sql

I have two tables Customers, Sales like below:
Customers :
id
first_name
last_name
gender
age
customer_since
1
Daniel
Black
M
34
2014-10-13
2
Erik
Brown
M
25
2015-06-10
3
Diana
Trump
F
39
2015-10-25
4
Anna
Yao
F
19
2017-02-20
5
Christian
Sanders
M
42
2018-01-31
Sales:
id
date
book_id
customer_id
quantity
amount
1
2019-09-02
2
3
1
14.99
2
2019-10-01
1
2
1
12.99
3
2019-10-01
3
4
1
15.75
I need to get all customers and their data in sales table even if they don't have records in sales table so I did left join like below :
SELECT c.id as customer_id, c.first_name, c.last_name, c.gender, c.age, c.customer_since,
s.sdate AS sales_date, s.id as sale_id
FROM customers c
LEFT JOIN sales s
ON c.id = s.customer_id
and I got this result:
CUSTOMER_ID
FIRST_NAME
LAST_NAME
GENDER
AGE
CUSTOMER_SINCE
SALES_DATE
SALE_ID
3
Diana
Trump
F
39
25-OCT-15
02-SEP-19
1
2
Erik
Brown
M
25
10-JUN-15
01-OCT-19
2
1
Daniel
Black
M
34
13-OCT-14
-
-
4
Anna
Yao
F
19
20-FEB-17
-
-
5
Christian
Sanders
M
42
31-JAN-18
-
-
which is expected my question is what if I need to get all customers that only in the source table and customers from the target table which has specific condition I run the below query
SELECT c.id as customer_id, c.first_name, c.last_name, c.gender, c.age, c.customer_since,
s.sdate AS sales_date, s.id as sale_id
FROM customers c
LEFT JOIN sales s
ON c.id = s.customer_id
where s.id=1
and I got only one record like this
CUSTOMER_ID
FIRST_NAME
LAST_NAME
GENDER
AGE
CUSTOMER_SINCE
SALES_DATE
SALE_ID
3
Diana
Trump
F
39
25-OCT-15
02-SEP-19
1
but I need this result
CUSTOMER_ID
FIRST_NAME
LAST_NAME
GENDER
AGE
CUSTOMER_SINCE
SALES_DATE
SALE_ID
3
Diana
Trump
F
39
25-OCT-15
02-SEP-19
1
1
Daniel
Black
M
34
13-OCT-14
-
-
4
Anna
Yao
F
19
20-FEB-17
-
-
5
Christian
Sanders
M
42
31-JAN-18
-
-
What should I do?

You need to move the criteria from the WHERE clause of the second query to the ON clause of the join:
SELECT c.id as customer_id, c.first_name, c.last_name, c.gender, c.age,
c.customer_since, s.sdate AS sales_date, s.id AS sale_id
FROM customers c
LEFT JOIN sales s
ON c.id = s.customer_id AND s.id = 1;
By restricting the sales ID in the WHERE clause, you filter off any non matching records. By moving this restriction to the join, all records would be retained, but NULL values would appear for all non matching sales fields.

Related

Joining multiple tables and getting MAX value in subquery PostgreSQL

I have 4 Tables in PostgreSQL with the following structure as you can see below:
"Customers"
ID | NAME
101 Max
102 Peter
103 Alex
"orders"
ID | customer_id | CREATED_AT
1 101 2022-05-12
2 101 2022-06-14
3 101 2022-07-9
4 102 2022-02-14
5 102 2022-06-18
6 103 2022-05-22
"orderEntry"
ID | order_id | product_id |
1 3 10
2 3 20
3 3 30
4 5 20
5 5 40
6 6 20
"product"
ID | min_duration
10 P10D
20 P20D
30 P30D
40 P40D
50 P50D
Firstly I need to select "orders" with the max(created_at) date for each customer this is done with the query (it works!):
SELECT c.id as customerId,
o.id as orderId,
o.created_at
FROM Customer c
INNER JOIN Orders o
ON c.id = o.customer_id
INNER JOIN
(
SELECT customer_id, MAX(created_at) Max_Date
FROM Orders
GROUP BY customer_id
) res ON o.customer_id = res.customer_id AND
o.created_at = res.Max_date
the result will look like this:
customer_id | order_id | CREATED_AT
101 3 2022-07-9
102 5 2022-06-18
103 6 2022-05-22
Secondly I need to select for each order_id from "orderEntry" Table, "products" with the max(min_duration) the result should be:
order_id | max(min_duration)
3 P30D
5 P40D
6 P20D
and then join results from 1) and 2) queries by "order_id" and the total result which I'm trying to get should look like this:
customer_name | customer_id | Order_ID | Order_CREATED_AT | Max_Duration
Max 101 3 2022-07-9 P30D
Peter 102 5 2022-06-18 P40D
Alex 103 6 2022-05-22 P20D
I'm struggling to get query for 2) and then join everything with query from 1) to get the result. Any help I would appreciate!
You could make the first query to an CTE and use that to join the rest of the queries.
Like this.
WITH CTE AS ( SELECT c.id as customerId,
o.id as orderId,
o.created_at
FROM Customer c
INNER JOIN Orders o
ON c.id = o.customer_id
INNER JOIN
(
SELECT customer_id, MAX(created_at) Max_Date
FROM Orders
GROUP BY customer_id
) res ON o.customer_id = res.customer_id AND
o.created_at = res.Max_date)
SELECT customerId,orderId,created_at,p.min_duration
FROM CTE
JOIN (SELECT "orderId", MAX("product_id") as product_id FROM "orderEntry" GROUP BY orderId) oe ON CTE.orderId = oe.orderId
JOIN "product" pr ON oe.product_id = pr."ID"

howw to make rating for each actor , separately for male and female customers?

the exercise is:
Report the number of movie rentals and the average rating for each actor, separately for male and female customers.
Report only actors with more than 5 movie rentals.
code:
SELECT a.name, c.gender,
COUNT(*) AS number_views,
AVG(r.rating) AS avg_rating
FROM renting as r
LEFT JOIN customers AS c
ON r.customer_id = c.customer_id
LEFT JOIN actsin as ai
ON r.movie_id = ai.movie_id
LEFT JOIN actors as a
ON ai.actor_id = a.actor_id
GROUP BY a.name ___ -- For each actor, separately for male and female customers
HAVING AVG(r.rating) IS NOT NULL
where movie_id > 5 -- Report only actors with more than 5 movie rentals
ORDER BY avg_rating DESC, number_views DESC;
how to make rating for each actor, separately for male and female customers?
renting table
renting_id
customer_id
movie_id
rating
date_renting
1
41
8
null
2018-10-09
2
10
29
10
2017-03-01
customers table
customer_id
name
country
gender
date_of_birth
date_account_start
2
Wolfgang Ackermann
Austria
male
1971-11-17
2018-10-15
3
Daniela Herzog
Austria
female
1974-08-07
2019-02-14
actor table
actor_id
name
year_of_birth
nationality
gender
1
Abbie Cornish
1982
Australia
female
2
Adam Sandler
1966
USA
male
actsin table
actsin_id
movie_id
actor_id
1
37
1
2
56
2

Filter records in outer join based on criteria

I have 2 simple tables as follows:-
Student
---------------------------------------------
student_id student_name student_class
107 paul A Level-I
108 susan Diploma
109 jack O Level-II
---------------------------------------------
Student_Positions
--------------------------------------------------
position_id student_id position date
1 107 1 1-1-2020
2 107 1 1-1-2021
3 109 2 1-1-2021
4 109 1 1-6-2019
I want a left outer join on these tables for the latest position of every student as fol:-
student_id student_name position date
107 paul 1 1-1-2021
108 susan
109 jack 2 1-1-2021
I have made multiple tries with different positions of max(date) and group by but in vain.
Please help with correct query
The canonical SQL solution uses a window function such as row_number():
select s.*, sp.position, sp.date
from students s left join
(select sp.*,
row_number() over (partition by student_id order by date desc) as seqnum
from student_positions sp
) sp
on sp.student_id = s.student_id and sp.seqnum = 1;

How do I find each female names' highest occurence and corresponding state in 1910 from public dataset in BigQuery

I don't know how to write the query to find each female names' highest occurence and corresponding state in 1910 from public dataset
For example, I tried for only one name in 1910.
select name,state,number high_occurence from `bigquery-public-data.usa_names.usa_1910_2013` where lower(name)="annie" and year=1910 order by number desc limit 1
Below is for BigQuery Standard SQL
#standardSQL
SELECT
a.name,
a.state,
a.year,
a.number AS high_occurence,
b.number AS number_in_1910
FROM (
SELECT
name,
ARRAY_AGG(STRUCT(year, state, number) ORDER BY number DESC LIMIT 1)[OFFSET(0)].*
FROM `bigquery-public-data.usa_names.usa_1910_2013`
WHERE gender = 'F'
GROUP BY name
) a
JOIN `bigquery-public-data.usa_names.usa_1910_2013` b
ON a.name = b.name
AND a.state = b.state
AND b.year = 1910
AND b.gender = 'F'
ORDER BY high_occurence DESC
with result as
Row name state year high_occurence number_in_1910
1 Mary PA 1918 8184 2913
2 Linda NY 1947 7542 9
3 Patricia NY 1951 5678 33
4 Helen PA 1918 5411 1604
.....
739 Cele NY 1914 5 5
740 Hilja MI 1910 5 5

SQL Database Table Joining to create an IdealTable

I need some helps to join the tables I have currently.
Leave, Overtime And Roster's Date, EmployeeID need to match
Note: ShiftDuration is set to default value = 8.25
Note: Leave and Overtime table will only have entries when an employee applies for leave and overtime.
Employee
EmplyeeeID | Username | Password | GivenName | FamilyName | TeamID | ContactNo | StaffType
------------------------------------------------------------------------------------------
123 123 abc John Snow 1 999 1
1234 1234 abcd Jack Waller 2 223 1
12345 12345 abcde Ali Saw 1 123 1
123456 123456 abcdef Peter Peter 2 223 1
1234567 1234567 abcdeg Bryan Peter 1 333 1
Roster
Duty_ID | EmployeeID | Date | ShiftType | ShiftDuration
--------------------------------------------------------------------
2 123 2018-05-05 1 8.25
4 1234 2018-05-04 1 8.25
5 12345 2018-05-05 1 8.25
7 123456 2018-05-04 1 8.25
8 1234567 2018-05-05 1 8.25
Overtime
OTID | EmployeeID | Date | OT_Duration | OT_Reason
------------------------------------------------------------
2 1234 2018-05-04 2 Cover Duty
Leave
LeaveID | EmployeeID | Date | Duration_Off | Reason
----------------------------------------------------------
3 123 2018-05-05 2 NIL
IdealTable (Via Query)
Date | EmployeeID | GivenName | FamilyName | TeamID | ShiftType | ShiftDuration | Duration_Off | OT_Duration | Total_Hours
---------------------------------------------------------------------------------------------------------------------------------
2018-05-05 123 John Snow 1 1 8.25 2 0 6.25
2018-05-04 1234 Jack Waller 1 1 8.25 0 2 10.25
2018-05-05 12345 Ali Saw 1 1 8.25 0 0 8.25
2018-05-04 123456 Peter Peter 1 1 8.25 0 0 8.25
2018-05-05 1234567 Bryan Peter 1 1 8.25 2 0 8.25
I have 4 tables, they are Employee, Leave, Overtime, Roster
Employee
-EmployeeID (PK)
-Username
-Password
-GivenName
-FamilyName
-TeamID
-ContactNo
-StaffType
Leave
-LeaveID (PK)
-EmployeeID (FK)
-Date
-Duration_Off
-Reason
Overtime
-OTID (PK)
-EmployeeID (FK)
-Date
-OT_Duation
-OT_Reason
Roster
-DutyID (PK)
-EmployeeID (FK)
-Date
-ShiftType
-Shift Duration (Default Value = 8.25)
What I am trying to do is join the data from this 4 tables using Query
Ideal Table
-Date (From Leave, Overtime and Roster Table)
-EmployeeID (Employee Table)
-GivenName (Employee Table)
-FamilyName (Employee Table)
-TeamID (Employee Table)
-ShiftType (Roster Table)
-ShiftDuration (Roster Table)
-Duration_Off (Leave Table)
-OT_Duration (Overtime Table)
-Total_Hours (Calculation from joint table [(ShiftDuration + OT_Duration) - Duration_Off]
My database diagram design Do ignore the TimeData table as I initially wanted to use the TimeData table to achieve the IdealTable
My current query
USE [SMRT Dashboard]
GO
;With Dates
AS
(
SELECT [Date] FROM dbo.Roster
UNION
SELECT [Date] FROM dbo.Leave
UNION
SELECT [Date] FROM dbo.Overtime
),
Work_Matrix
AS
(
SELECT EmployeeID,[Date],ShiftType,ShiftDuration,CAST(NULL AS Decimal(30,2)) AS Duration_Off,CAST(NULL AS Decimal(30,2)) AS OT_Duration
FROM dbo.Roster
UNION ALL
SELECT EmployeeID,[Date], NULL, NULL,Duration_Off
FROM dbo.Leave
UNION ALL
SELECT EmployeeID,[Date],NULL,NULL,NULL,OT_Duration
FROM dbo.Overtime
)
SELECT d.[Date],
e.EmployeeID,
e.GivenName,
e.FamilyName,
e.TeamID,
w.ShiftType,
w.ShiftDuration,
w.Duration_Off,
w.OT_Duration,
w.Total_Hours
FROM Dates d
INNER JOIN
(
SELECT EmployeeID,
[Date],
MAX(ShiftType) AS ShiftType,
SUM(ShiftDuration) AS ShiftDuration,
SUM(Duration_Off) AS Duration_Off,
SUM(OT_Duration) AS OT_Duration,
SUM(ShiftDuration) + SUM(OT_Duration) - SUM(Duration_Off) AS Total_Hours
FROM Work_Matrix
GROUP BY EmployeeID,
[Date]
)w
ON d.[Date] = w.[Date]
JOIN dbo.Employee e
ON e.EmployeeID = w.EmployeeID
Current Errors:
Msg 205, Level 16, State 1, Line 4
All queries combined using a UNION, INTERSECT or EXCEPT operator must have an equal number of expressions in their target lists.
I have not tested this but you may now check this. Hope your problem will be solved.
SELECT
a.EmployeeID,
a.GivenName,
a.FamilyName,
a.TeamID,
d.ShiftType,
d.ShiftDuration,
b.Duration_Off,
c.OT_Duration,
b.Date,
(d.ShiftDuration + c.OT_Duration) - b.Duration_Off as Total_Hours
FROM Employee a
INNER JOIN Roster d ON a.EmployeeID = d.EmployeeID
LEFT JOIN Leave b ON a.EmployeeID = b.EmployeeID
LEFT JOIN Overtime c ON a.EmployeeID = c.EmployeeID
I assume Date is same from all three tables (Leave, Overtime and Roster Table)
Try this query :
select
l.Date,
o.Date,
r.Date,
e.EmployeeID,
e.GivenName,
e.FamilyName,
e.TeamID,
r.ShiftType,
r.ShiftDuration,
l.Duration_Off,
o.OT_Duration,
((r.ShiftDuration+o.OT_Duration)-l.Duration_Off) as Total_Hours
FROM Employee e
INNER JOIN Leave l ON e.EmployeeID = l.EmployeeID
INNER JOIN Overtime o ON e.EmployeeID = o.EmployeeID
INNER JOIN Roster r ON r.EmployeeID = r.EmployeeID;
Try this Query ! You will get the correct output .
SELECT
e.EmployeeID AS 'Emp ID',
e.GivenName AS 'Emp Name',
l.Date AS Date,
e.FamilyName AS 'Family Name',
e.TeamID AS 'Team ID',
r.ShiftType AS 'Shift Type',
r.ShiftDuration AS 'Shift Duration',
l.Duration_Off AS 'Duration Off',
o.OT_Duration AS 'OT Duration',
(r.ShiftDuration + o.OT_Duration) - l.Duration_Off as 'Total Hours'
FROM
Employee e,
Leave l,
Overtime o,
Roster r
WHERE
e.EmployeeID = l.EmployeeID
AND
e.EmployeeID = o.EmployeeID
AND
e.EmployeeID = r.EmployeeID
I think the issue here is that an employee may have leave, may have overtime, or neither. By using INNER JOIN, you're filtering down the set of employees to ones that have Leave, and on a Roster, and have Overtime.
I created a SqlFiddle with the data you present (see here) that should get you closer.
SELECT
COALESCE(r.Date, o.Date, l.Date) as Date,
e.EmployeeID,
e.GivenName,
e.FamilyName,
e.TeamID,
r.ShiftType,
r.ShiftDuration,
IFNULL(l.Duration_Off, 0) as Duration_Off,
IFNULL(o.OT_Duration, 0) as OT_Duration,
r.ShiftDuration + IFNULL(o.OT_Duration, 0) - IFNULL(l.Duration_Off, 0) as Total_Hours
FROM Employee e
INNER JOIN Roster r on
e.EmployeeID = r.EmployeeID
LEFT JOIN Overtime o on
e.EmployeeID = o.EmployeeID
LEFT JOIN `Leave` l on
e.EmployeeID = l.EmployeeID
There's a few cavaets that this query won't solve:
If an employee has multiple leave entries or multiple overtime entries, the person will be listed N times. You can potentially use a GROUP BY statement to deal with this
The sample data in your question doesn't reconcile correctly. For example, Employe 1234 has a shift on 5/4/2018 and OT on 5/5/2018 but gets 10.25 on 5/4/2018.
If you use TimeData, this becomes a lot easier (assuming one record per date per employee):
SELECT
COALESCE(r.Date, o.Date, l.Date) as Date,
e.EmployeeID,
e.GivenName,
e.FamilyName,
e.TeamID,
r.ShiftType,
IFNULL(r.ShiftDuration, 0) as ShiftDuration,
IFNULL(l.Duration_Off, 0) as Duration_Off,
IFNULL(o.OT_Duration, 0) as OT_Duration,
IFNULL(r.ShiftDuration, 0) + IFNULL(o.OT_Duration, 0) - IFNULL(l.Duration_Off, 0) as Total_Hours
from TimeData t
INNER JOIN Employee e on
t.EmployeeID = e.EmployeeID
LEFT JOIN Roster r on
t.Duty_ID = r.Duty_ID
LEFT JOIN Overtime o on
t.OTID = o.OTID
LEFT JOIN `Leave` l on
t.LeaveID = l.LeaveID
Example SqlFiddle can be found here