Join tables by child table id without inner select - sql

I have two tables (owner and employee) and a mapping table (owner_employee)
I need a select statement which gets all owner employees by employee_id without using inner selects
Example:
SELECT * FROM employee e
[...Joins here...]
WHERE e.id = 1
should return
owner | employee
1 | 1
1 | 2
Owner table
id | title
----------
1 | a
2 | b
3 | c
Employee table
id | title
----------
1 | a
2 | a
3 | b
4 | c
Owner_Employee
id_owner | id_employee
----------------------
1 | 1
1 | 2
2 | 3
3 | 4
Working implementation of inner select here (probably working, because I rewrote it from original example)
SELECT *
FROM owner_employee oe
JOIN owner o ON o.id = oe.id_owner
JOIN employee e ON e.id = oe.id_employee
WHERE o.id in
(SELECT id_owner
FROM owner_employee oe
WHERE oe.id_employee in (1))

perhaps like this:
SELECT DISTINCT oe.*,o.*,e.*
FROM owner_employee AS z
JOIN owner_employee oe on oe.id_owner = z.id_owner
JOIN owner o ON o.id = oe.id_owner
JOIN employee e ON e.id = oe.id_employee
WHERE z.id_employee in (1);

The query code you give (which does not return what your text asks for) is the same as
SELECT *
FROM owner_employee oe
JOIN owner o ON o.id = oe.id_owner
JOIN employee e ON e.id = oe.id_employee
WHERE oe.id_employee = 1
But what your text asks for, "all owner employees by employee_id" apparently doesn't make sense. Becuase from your data one owner can have more than one id_employee.
If you want "all id_owners and their id_employees":
SELECT * FROM owner_employee
IF you want "id_employees of all owners":
SELECT id_employee FROM owner_employee
Or maybe you want "all owners, each by one of their employee ids"?

Related

select single row from foreign table in left join

I want to fetch the first row where foreign key match. I don't know how to select first row
where foreign key matches
events table
id | name
----------------
1 | john
----------------
2 | Cat
event_attendee table
id | event_id | type
--------------------------
1 | 1 | User
--------------------------
2 | 1 | Local
--------------------------
3 | 1 | User
--------------------------
4 | 2 | User
--------------------------
5 | 2 | User
I want this result
id | name | event_id | type
------------------------------------
1 | John | 1 | User
------------------------------------
2 | Cat | 2 | User
Tried
select
a.*,
b.*
from
events as a
left join (
select
distinct
event_attendee.events_id,
event_attendee.type
from
event_attendee
left join events on
event_attendees.events_id = events.id
where
events.id = event_attendees.events_id
limit 1
) as b on
a.id = b.events_id
Problem
It only works for the 1st row, for 2nd row its show empty
id | name | type
------------------------------------
1 | John | User
------------------------------------
2 | Cat |
You can do this using a lateral join. In Postgres, the syntax is:
select e.*, ea.*
from events e left join lateral
(select ea.event_Id, ea.Type
from event_attendee ea
where ea.event_id = e.id
order by ea.id
) ea
on 1=1;
However, distinct on is a way to do this with no subqueries:
select distinct on (e.event_id) e.*, ea.*
from events e join
event_attendee ea
on ea.event_id = e.id
order by e.event_id, ea.id;
I would expect the lateral join to work better on larger tables, particularly with the correct indexes.
This is easy with a cross apply:
select *
from events e
cross apply (
select top (1) event_Id, Type
from event_attendee ea
where ea.event_id=e.id
order by id
)x
Edit, alternative compatible method!
select e.*,ea.event_Id, (select type from event_attendee ea2 where ea2.id=ea.id ) Type
from (
select Min(id) Id, event_id
from event_attendee
group by event_id
)ea
join events e on e.id=ea.event_id
One way to get the rank and use it to filter 1st record:
select
t_.id, t_.name, t_.type
from
(
select a.*, b.type,
rank() OVER (PARTITION BY a.id ORDER BY b.id asc) rank_
from events a
left join event_attendees b
on
a.id = b.events_id
) t_
where
t_.rank_ = 1

How to get average with recursive query

I'm trying to write recursive query with postgres and its working fine and returning me all users which comes under user 5 with this codes :
WITH RECURSIVE subordinates AS (
SELECT
id,
supervisor_id,
name
FROM
employees
WHERE
id = 5
UNION
SELECT
e.id,
e.supervisor_id,
e.name
FROM
employees e
INNER JOIN subordinates s ON s.id = e.supervisor_id
) SELECT
*
FROM
subordinates;
But I've 1 more table in it which is called biscuits which have 3 columns type, weight and cooked_by_employee_id
now I want average weight of biscuits with id, name and supervisor_id but just small twist is if user id 1 comes under 3 and 3 comes under 4 and 4 comes under 5 then it should return average weight of all 1,3,4 and 5 cooked_by_employee_id records and 1 user can have multiple records in biscuits
I tried this but not working
WITH RECURSIVE subordinates AS (
SELECT
e.id,
e.supervisor_id,
e.name,
AVG(b.weight)
FROM
employees e
LEFT JOIN burrito b ON
e.id=b.cooked_by_employee_id
WHERE
e.id = 5
UNION
SELECT
e.id,
e.supervisor_id,
e.name,
b.weight
FROM
employees e
INNER JOIN subordinates s ON s.id = e.supervisor_id
LEFT JOIN burrito b ON b.cooked_by_employee_id=e.supervisor_id
) SELECT
*
FROM
subordinates;
Sample data :
employees :
+----+------+---------------+
| id | name | supervisor_id |
+----+------+---------------+
| 1 | a | 3 |
+----+------+---------------+
| 2 | b | 4 |
+----+------+---------------+
| 3 | c | 5 |
+----+------+---------------+
| 4 | d | 0 |
+----+------+---------------+
| 5 | e | 0 |
+----+------+---------------+
burrito :
+----+-------+--------+-----------------------+
| id | type | weight | cooked_by_employee_id |
+----+-------+--------+-----------------------+
| 1 | sweet | 1 | 1 |
+----+-------+--------+-----------------------+
| 2 | salty | 2 | 1 |
+----+-------+--------+-----------------------+
| 3 | sweet | 3 | 3 |
+----+-------+--------+-----------------------+
So if it runs for employee_id 5 then it should return
+-------------+------+-----------------------------------------------------+
| employee_id | name | weight |
+-------------+------+-----------------------------------------------------+
| 5 | e | 2 (Average 2 because both 1 and 3 id comes under 5) |
+-------------+------+-----------------------------------------------------+
You have the recursive CTE figured out and that could be half (or more) of the battle. The following however builds a slightly different one. The recursive cte (path_to_chef) gets employee ids and builds a path from the top level to the employee. The second cte (cook_chef) then strips the path string back to the top level employee id. The result then contains 2 columns, the employee id and the employee id for the root level employee. This then sets the stage to join with the other tables for the final result.
The query can work as stand alone and for a single root level by supplying id where indicated. But I tend to create generic reusable routines so I've wrapped it into a SQL function.
create or replace function chef_avg_burrito_weight()
returns table ( employee_id integer
, name text
, weight numeric
)
language sql
as $$
with recursive path_to_chef as
( select id, trim(to_char(id, '9999'))||'>' path
from employees
where supervisor_id = 0
union all
select e.id, path || trim(to_char(e.id, '9999'))||'>'
from employees e
join path_to_chef c on e.supervisor_id = c.id
) -- select * from path_to_chef
, cook_chef as
(select id cook
, substring(path from '^([[:digit:]]+)>')::integer chef
from path_to_chef
) -- select * from cook_chef
select e.id, e.name, round(avg(b.weight),2) average_weight
from burrito b
join cook_chef c on (b.cooked_by_employee_id = c.cook)
join employees e on (e.id = c.chef)
-- where chef = 5
group by e.id, e.name;
$$;
-- test
select *
from chef_avg_burrito_weight()
where employee_id = 5;

Update only rows that does not match rows from another table

I have 3 tables:
1) tblPerson
id pin name dept_id
---|-----------|------------|------------|
1 | 123 | Lisa | 100 |
2 | 234 | Rob | 200 |
2) tblDepartment
id dept_name
-----|-----------|
100 | IT |
200 | HR |
3) tblMaster
id emplid name m_dept
---|-----------|------------|------------|
1 | 123 | Lisa | IT |
2 | 234 | Rob | HR |
• tblDepartment gets its rows from existing departments present in tblMaster
• in tblPerson, dept_id is a foreign key from tblDepartment
I am trying to create a query that will update the dept_id in tblPerson for all rows where the m_dept does not match the dept_name of the dept_id linked to a person.
So for example, if in tblMaster, I change 'IT' under m_dept from row 1 to 'HR', running the query will change the dept_id of row 1 in tblPerson to '200'
SQL Query:
UPDATE [dbo].[tblPerson]
SET dept_id = d.id
FROM [dbo].[tblMaster] m
INNER JOIN [dbo].[tblPerson] p
ON p.pin = m.emplid
INNER JOIN [dbo].[tblDepartment] d
ON d.dept = m.m_dept
the query above will update all rows..when i try to add:
WHERE d.dept != m.m_dept
and I change the m_dept 'IT' to 'HR' of row 1 in tblMaster ..it does not update anything
need help please ;m;
You are currently using both d.dept = m.m_dept (in the JOIN condition) and d.dept != m.m_dept in the WHERE, so of course you are updating no rows.
You either use a EXISTS or NOT EXISTS, or another JOIN:
UPDATE P
SET P.dept_id = D1.id
FROM dbo.tblPerson P
INNER JOIN dbo.tblMaster M
ON P.pin = M.emplid
INNER JOIN dbo.tblDepartment D1
ON D1.dept_name = M.m_dept
INNER JOIN dbo.tblDepartment D2
ON P.dept_id = D2.id
WHERE D1.dept_name <> D2.dept_name
;

SQL statement select columns with specific value

I need some help making an sql statement; I don't really know how to aproach the situation. I have two tables, Departments and Employees
from which I want to select the Dpt_num and the Dpt_name of the departments that have at least one employee and that all their employees are from Barcelona
Case 1
== Departments =======
| Dpt_num | Dpt_name |
| 1 | A |
| 2 | B |
== Employees ===================
| E_num | Dpt_num | City |
| 1 | 1 | Barcelona |
| 2 | 1 | Barcelona |
The result in this case should be
Dpt_num Dpt_name
------------------
1 A
Case 2
== Departments =======
| Dpt_num | Dpt_name |
| 1 | A |
| 2 | B |
== Employees ==================
| E_num | Dpt_num | City |
| 1 | 1 | Barcelona |
| 2 | 1 | Madrid |
The result in this case should be empty.
I tried this for example but it seems very inefficient and it does not work in all the cases
select
num_dpt, nom_dpt
from
departements
where
1 = (select count(distinct e.ciutat_empl)
from empleats e
where e.num_dpt = num_dpt)
and not exists (select * from empleats e
where e.ciutat_empl != 'BARCELONA' and e.num_dpt = num_dpt);
I really appreciate any help. Thanks!
You want to go down the path of doing the filtering in the where clause. Then, use exists and not exists:
select d.num_dpt, d.nom_dpt
from departaments d
where exists (select 1
from empleats e
where e.num_dpt = d.num_dpt and e.ciutat_empl = 'BARCELONA'
) and
not exists (select 1
from empleats e
where e.num_dpt = d.num_dpt and e.ciutat_empl <> 'BARCELONA'
);
The first condition checks that at least one employee is from Barcelona. The second checks that no employees are from any other city.
One major problem in your version is your correlation clause:
e.num_dpt = num_dpt
You think this is doing:
e.num_dpt = departaments.num_dpt
But it is really doing:
e.num_dpt = e.num_dpt
Always qualify your column names. This is especially important when you have more than one table reference in the query.
Join the tables, group by the department and check if the count of employees in Barcelona is equal to the count of all employess of the department.
SELECT d.dpt_num,
d.dpt_name
FROM departments d
INNER JOIN employees e
ON e.dpt_num = d.dpt_num
GROUP BY d.dpt_num,
d.dpt_name
HAVING count(CASE
WHEN e.city = 'Barcelona' THEN
1
END) = count(*);
I believe this should work:
select d.dpt_num, d.dpt_name
from departments d
inner join employees e on
d.dpt_num = e.dpt_num
group by d.dpt_num, d.dpt_name
having count(*) = sum(case when e.city = 'Barcelona' then 1 else 0 end)
INNER JOIN makes sure there's at least 1 employee
HAVING count(*) = sum(case when e.city = 'Barcelona' then 1 else 0 end) makes sure that all employees are from Barcelona
demo: db<>fiddle
SELECT dpt_num, dpt_name
FROM (
SELECT d.dpt_num, d.dpt_name, array_agg(city) as cities
FROM dept d
JOIN empl e
ON d.dpt_num = e.dpt_num
GROUP BY d.dpt_num, d.dpt_name
) s
WHERE 'Barcelona' = ALL(cities)
Aggregate the cities and then you can filter with the ALL operator which checks if all array elements fit the condition.
Generally speaking, you compare COUNT(*) with COUNT(some condition) for such problems:
SELECT *
FROM Departments
WHERE EXISTS (
SELECT 1
FROM Employees
WHERE Employees.Dpt_num = Departments.Dpt_num
HAVING COUNT(*) > 0 -- it is possible to get a 0 if where did not match
AND COUNT(*) = COUNT(CASE WHEN Employees.City = 'Barcelona' THEN 1 END)
)
DB Fiddle
Pl try query below
select a.dpt_number,a.dpt_name from yy_department a
where exists (select 'x' from yy_employees y where y.dpt_number = a.dpt_number and y.city = 'Barcelona')
and not exists (select 'x' from yy_employees y where y.dpt_number = a.dpt_number and nvl(y.city,'x') <> nvl('Barcelona','y'))

Stored procedure containing inner join with count not working

Let's say i've got a databasetable looking a bit like this, containing information about some assignments.
Id | ProfessionId | Title | Deadline | DateCreated | ClosingDate
1 | 5 | Something | 01-12-2012 | 05-11-2012 | 12-11-2012
2 | 6 | Something | 01-12-2012 | 05-11-2012 | 12-11-2012
3 | 7 | Something | 01-12-2012 | 05-11-2012 | 12-11-2012
4 | 7 | Something | 01-12-2012 | 05-11-2012 | 12-11-2012
I want to generate an overview foreach profession (assignments belong to a certain profession) and count the number of assignment in each profession. The overview coming from the database should look like this;
Id | Name | FriendlyUrl | Ordinal | NumberOfAssignments
5 | Profession 1 | profession-1 | 1 | 1
6 | Profession 2 | profession-2 | 1 | 1
7 | Profession 3 | profession-3 | 1 | 2
8 | Profession 4 | profession-4 | 1 | 0
I've currently got a stored procedure returning the overview above, except that the amount of assignments isn't correct. Assignments with a closingdate in the past (we then assume the assignment is closed) shouldn't be taken into the the total number of assignment.
The current stored procedure is like this:
BEGIN
SELECT p.Id,
p.Naam,
p.FriendlyUrl,
p.Ordinal,
COUNT(a.ProfessionId) AS NumberOfAssignments
FROM ME_Profession AS p
LEFT OUTER JOIN ME_Assignment AS a ON a.ProfessionId = p.Id
INNER JOIN ME_Client AS c ON a.ClientId = c.Id
INNER JOIN aspnet_Membership AS m ON m.UserId = c.UserId
WHERE m.IsApproved = 1
GROUP BY p.Id, p.Naam, p.FriendlyUrl, p.Ordinal
END
I've already came up with and modified procedure like the one below, but it doesn't work. It feels like i'm either thinking too difficult or missing something obvious. What could go wrong?
SELECT p.Id, p.Naam, p.FriendlyUrl, p.Ordinal, pc.NumberOfAssignments
FROM ME_Profession AS p
INNER JOIN ME_Assignment AS a ON a.ProfessionId = p.Id
INNER JOIN ME_Client AS c ON a.ClientId = c.Id
INNER JOIN aspnet_Membership AS m ON m.UserId = c.UserId
INNER JOIN (SELECT a2.ProfessionId, COUNT(*) AS NumberOfAssignments FROM ME_Assignment AS a2 GROUP BY a2.ProfessionId WHERE a2.Closingdate > GETDATE()) pc ON p.ProfessionId = pc.ProfessionId
WHERE m.IsApproved = 1 AND a.Closingdate > GETDATE()
GROUP BY p.Id, p.Naam, p.FriendlyUrl, p.Ordinal
UPDATE 1: Added where condition for date
I don't think that you need to join against the table ME_profession again, try this:
SELECT p.Id, p.Naam, p.FriendlyUrl, p.Ordinal, pc.NumberOfAssignments,
COUNT(CASE WHEN ClosingDate > GETDATE() OR ClosingDate IS NULL THEN 1 END) AS NumberOfAssignments
FROM ME_Profession AS p
INNER JOIN ME_Assignment AS a
ON a.ProfessionId = p.Id
INNER JOIN ME_Client AS c
ON a.ClientId = c.Id
INNER JOIN aspnet_Membership AS m
ON m.UserId = c.UserId
WHERE (m.IsApproved = 1)
GROUP BY p.Id, p.Naam, p.FriendlyUrl, p.Ordinal
How about ... LEFT OUTER JOIN (SELECT * FROM ME_Assignment WHERE ClosingDate > GETDATE()) as a ...
I don't see criteria in the current stored procedure that would satisfy this statement:
Assignments with a closingdate in the past (we then assume the
assignment is closed) shouldn't be taken into the the total number of
assignment.
It may be you simply need to add the criteria you have in your work in progress to the existing proc:
WHERE ClosingDate > GETDATE()