SQL Query to get missing staff member - sql

I have following problem to find out if a User with a specific job role is missing in one project:
Table 1:
ID | Project_ID
---+------------
1 | 11A
1 | 11B
1 | 11C
2 | 12B
2 | 12C
3 | 13A
3 | 13C
Table 2:
Project_ID | JobRole_ID
-------------+------------
11A | A
11B | B
11C | C
12B | B
12C | C
13A | A
13C | C
Table 3:
JobRole_ID | JobRole
-----------+---------
A | Manager
B | Project Leader
C | Project Assistent
For each project jobrole A,B and C are required (Table 3). Table 2 only contains added JobRoles, not missing ones.
What i expect is:
ID | JobRole
---+---------
1 | Manager
2 | NULL
3 | Manager
Please help me! Thx

You want to find Projects where exists a JobRole which is not tied to this particular project.
From top of my head:
--try to find projects
SELECT
T1.Id
FROM
Table1 AS T1
WHERE
--with at least one role
EXISTS(
SELECT
*
FROM
Table3 AS T3
WHERE
--where mapping does not exist
NOT EXISTS(
SELECT
*
FROM
Table2 AS T2
WHERE
T2.Project_ID = T1.Project_ID AND
T2.JobRole_ID = T3.JobRole_ID
)
)
EDIT:
Could this be what you want? If not, could you please give us more details?
SELECT
T1.Id, T3.JobRole_ID
FROM
Table1 AS T1 LEFT JOIN
Table2 AS T2 ON T2.Project_ID = T1.Project_ID LEFT JOIN
Table3 AS T3 ON T3.JobRole_ID = T2.JobRole_ID

Based on your updated requirements this should work, although I'm sure it can be simplified.
select a.ID, b.JobRole
from (
select * from Table1, table3 where JobRole_ID = 'A'
) a left join (
select t1.id, t3.jobrole
from table1 t1
left join table2 t2 on t2.project_id = t1.project_id
left join table3 t3 on t3.jobrole_id = t2.jobrole_id
) b on a.id = b.id and a.jobrole = b.jobrole
group by a.ID, b.JobRole

Here is a method:
select p.*, j.*
from projects p cross join
jobroles j left join
projectjobs pj
on p.project_id = pj.project_id and
j.jobrole_id = pj.jobrole_id
where pj.project_id is null;
It generates a list of all projects and all jobs (using the cross join). Then, using the left join and where clause, it filters out the ones that exist.
EDIT:
You can try with comma:
select p.*, j.*
from projects p,
jobroles j left join
projectjobs pj
on p.project_id = pj.project_id and
j.jobrole_id = pj.jobrole_id
where pj.project_id is null;
But this may not work because of the semantics of how the comma is parsed in the from clause. You might need a subquery:
select xpj.*
from (select p.*, j.*
from projects p, jobroles j
) xpj left join
projectjobs pj
on xpj.project_id = pj.project_id and
xpj.jobrole_id = pj.jobrole_id
where pj.project_id is null;

Related

How to create a table with multiple calculations?

If I do one calculation with one join:
SELECT
SUM(friends_made) as calc1, table2.group_id
FROM
friends_made_table as table1
INNER JOIN
grouped_users as table2 ON table1.user_id = table2.user_id
GROUP BY
table2.group_id
The result I get is:
calc1 | group_id
-----------------
400 | 1
320 | 2
330 | 3
But I also need another calculation (calc2) with the same inner join on table1 but with a different table (table3)
SELECT
SUM(request_accept) AS calc2, table1.group_id
FROM
friends_accept_table AS table3
INNER JOIN
grouped_users as table1 ON table1.user_id = table3.user_id
GROUP BY
table1.group_id
Result is:
calc2 | group_id
-----------------
100 | 1
150 | 2
120 | 3
How can I join these two queries and create a new table showing both of the calculations (calc1, calc2)?
calc1 |calc2 | group_id
-----------------------
400 | 100 | 1
320 | 150. | 2
330 | 120. | 3
EDITED to show tables/results and take out rounding
A join will suffice as long as there is a common set of group_ids across the two results. You may otherwise need a left/right join or full join.
with data1 as (
SELECT SUM(friends_made) as calc1, table2.group_id
FROM friends_made_table as table1 INNER JOIN grouped_users as table2
ON table1.user_id = table2.user_id
GROUP BY table2.group_id
), data2 as (
SELECT SUM(request_accept) as calc2, table1.group_id
FROM friends_accept_table as table3 INNER JOIN grouped_users as table1
ON table1.user_id = table3.user_id
GROUP BY table1.group_id
)
select calc1, calc2, d1.group_id
from data1 d1 inner join data2 d2 on d2.group_id = d1.group_id;
This does assume that your platform supports CTE syntax. If it doesn't there are probably similar rewrites.

How exactly do aliases work in Oracle databases?

I've been dabbling around in sql code and recently was reading up on aliases. I am kind of confused why the following statement does not work:
select id, data from table1 a
inner join
(
select id, data from table2 b,
(
select id, data from table3 e
where b.id = e.id
) c
where b.id = a.id
)d on a.id = d.id
What I want is something like this to work:
select id, data from table1 a
inner join
(
select id, data from
(
select id, data from table3 e
where a.id = e.id
) c
)d on a.id = d.id
Currently my solution doesn't have the WHERE class at the end, meaning all of the table gets fetched.
...
where a.id = e.id
...
My point here, would be to use an ID present in table A in the table E. I'm open to suggestions as to changing the structure, but unfortunately I think the structure will have to stay the same since the actual query is much more complex. This is just an exert from the full query.
EDIT:
I'll try to elaborate as to why I have the current structure.
I have table 1 which contains ID's and text and other columns.
| id | data |
| -------- | ------ |
| table1_1 | text |
| table1_2 | text |
...
The second table contains multiple entries for an ID of table1.
| id | refid | data |
| -------- | -------- | ------ |
| table2_1 | table1_1 | proj1 |
| table2_1 | table1_1 | proj2 |
| table2_2 | table1_1 | proj1 |
| table2_3 | table1_2 | proj5 |
| table2_3 | table1_2 | proj1 |
What I now do is join the entries from table2 to a list of entries with:
LISTAGG(table2.refid, ',') WITHIN GROUP( ORDER BY table2.refid) list_of_projects,
To use this, I need to use group by
My problem was, that I couldn't use the table1.ID in table2.refid.
For better understanding of how sub-queries work, just imagine that database processes them separate from each other.
It means the sub-query
select id, data
from table3 e
where b.id = e.id
will be executed first. There is no alias b in this context -> an error
The next sub-query has the same problem
select id, data from table2 b,
(
select id, data from table3 e
where b.id = e.id
) c
where b.id = a.id
There is no data source called "a" -> another error
And to be honest, using sub-queries in this case is a bad idea. Join is what you need here
I believe something like this will help you out.
select a.id, a.data
from table1 a
inner join table3 e
on a.id = e.id;
From Oracle 12c, you can use CROSS APPLY or a LATERAL join to pass the outer scope into the inner sub-query:
SELECT id, data
FROM table1 a
CROSS APPLY
(
SELECT data
FROM table2 b
CROSS APPLY (
SELECT data
FROM table3 e
WHERE a.id = e.id
) c
WHERE b.id = a.id
) d
or:
SELECT a.id, data
FROM table1 a
INNER JOIN LATERAL
(
SELECT b.id,
c.data
FROM table2 b
INNER JOIN LATERAL (
SELECT e.id,
e.data
FROM table3 e
) c
ON ( a.id = c.id )
) d
ON ( d.id = a.id )
Which, for the sample data:
CREATE TABLE table1 ( id ) AS
SELECT 1 FROM DUAL;
CREATE TABLE table2 ( id ) AS
SELECT 1 FROM DUAL;
CREATE TABLE table3 ( id, data ) AS
SELECT 1, 'A' FROM DUAL;
Both output:
ID | DATA
-: | :---
1 | A
db<>fiddle here

Select Minus with fixed column in Oracle sql

I'd like to do a MINUS operation between two tables like these:
table1:
employee_id | job | sector
----------- | ------ | ------
10 | a | 1
10 | a | 2
10 | b | 4
table2:
job | sector
---- | ------
a | 1
a | 2
a | 3
b | 1
b | 4
c | 1
c | 2
and as result I want, for every employee_id, the {job,sector} not connected in table1.
RESULT:
employee_id | job | sector
----------- | --- | ------
10 | a | 3
10 | b | 1
10 | c | 1
10 | c | 2
Is it possible?
I hope I have written in a clear way! Thanks!
First select the complete data set, i.e. employee_id X job/sector. From these remove the existing table1 entries to get the lacking ones. (I've renamed your table table2 to job_sector for readability. I also suppose you have an employee table.)
select e.employee_id, js.job, js.sector
from employee e
cross join job_sector js
minus
select employee_id, job, sector
from table1;
Left join, where t2 is null
select t1.*
from table1 t1
let join table2 t2
on t1.job = t2.job
and t1.sector = t2.sector
where t2.job is null
This sounds like just a left join (or not in or not exists):
select 10 as employee_id, t2.*
from table2 t2 left join
table1 t1
on t2.job = t1.job and t2.sector = t1.sector
where t1.job is null;
I am a bit confused on how you get the employee id if the tables are not linked.
If you have multiple employees in t1, then you can do:
select e.employee_id, t2.*
from (select distinct employee_id from t1) e cross join
table2 t2 left join
table1 t1
on t2.job = t1.job and t2.sector = t1.sector and
e.employee_id = t1.employee_id
where t1.job is null;
You can simply achieve it by doing a left join. You query will look something like this:
SELECT T2.*
FROM TABLE2 T2 LEFT JOIN
TABLE1 T1
ON T2.JOB = T1.JOB AND T2.SECTOR = T2.SECTOR
WHERE T1.JOB IS NULL;
Though table must be linked in order to get the employee id.

Get count of related records in two joined tables

Firstly, I apologize for my English. I want get auctions with count of bids and buys. It should look like this:
id | name | bids | buys
-----------------------
1 | Foo | 4 | 1
2 | Bar | 0 | 0
I have tables like following:
auction:
id | name
---------
1 | Foo
2 | Bar
auction_bid:
id | auction_id
---------------
1 | 1
2 | 1
3 | 1
4 | 1
auction_buy:
id | auction_id
---------------
1 | 1
I can get numbers in two queries:
SELECT *, COUNT(abid.id) AS `bids` FROM `auction` `t` LEFT JOIN auction_bid abid ON (t.id = abid.auction) GROUP BY t.id
SELECT *, COUNT(abuy.id) AS `buys` FROM `auction` `t` LEFT JOIN auction_buy abuy ON (t.id = abuy.auction) GROUP BY t.id
But when i combined it into one:
SELECT *, COUNT(abid.id) AS `bids`, COUNT(abuy.id) AS `buys` FROM `auction` `t` LEFT JOIN auction_bid abid ON (t.id = abid.auction) LEFT JOIN auction_buy abuy ON (t.id = abuy.auction) GROUP BY t.id
It was returning wrong amount (bids as much as buys).
How to fix this and get counts in one query?
You'll need to count DISTINCT abuy and abid IDs to eliminate the duplicates;
SELECT t.id, t.name,
COUNT(DISTINCT abid.id) `bids`,
COUNT(DISTINCT abuy.id) `buys`
FROM `auction` `t`
LEFT JOIN auction_bid abid ON t.id = abid.auction_id
LEFT JOIN auction_buy abuy ON t.id = abuy.auction_id
GROUP BY t.id, t.name;
An SQLfiddle to test with.
Try this:
SELECT t.*,COUNT(abid.id) as bids,buys
FROM auction t LEFT JOIN
auction_bid abid ON t.id = abid.auction_id LEFT JOIN
(SELECT t.id, Count(abuy.id) as buys
FROM auction t LEFT JOIN
auction_buy abuy ON t.id = abuy.auction_id
GROUP BY t.id) Temp ON t.id=Temp.id
GROUP BY t.id
Result:
ID NAME BIDS BUYS
1 Foo 2 0
2 Bar 1 1
Result in SQL Fiddle.

Select first record in a One-to-Many relation using left join

I'm trying to join two tables using a left-join. And the result set has to include only the first record from the "right" joined table.
Lets say I have two tables A and B as below;
Table "A"
code | emp_no
101 | 12222
102 | 23333
103 | 34444
104 | 45555
105 | 56666
Table "B"
code | city | county
101 | Glen Oaks | Queens
101 | Astoria | Queens
101 | Flushing | Queens
102 | Ridgewood | Brooklyn
103 | Bayside | New York
Expected Output:
code | emp_no | city | county
101 | 12222 | Glen Oaks | Queens
102 | 23333 | Ridgewood | Brooklyn
103 | 34444 | Bayside | New York
104 | 45555 | NULL | NULL
105 | 56666 | NULL | NULL
If you notice my result has only the one matched record from table "B"(doesn't matter what record is matched) after left join (and it is a one to many mapping)
I need to pick the first matched record from table B and ignore all other rows.
Please help!
Thanks
After playing around a bit, this turns out to be trickier than I'd expected! Assuming that table_b has some single column that is unique (say, a single-field primary key), it looks like you can do this:
SELECT table_a.code,
table_a.emp_no,
table_b.city,
table_b.county
FROM table_a
LEFT
JOIN table_b
ON table_b.code = table_a.code
AND table_b.field_that_is_unique =
( SELECT TOP 1
field_that_is_unique
FROM table_b
WHERE table_b.code = table_a.code
)
;
Another option: OUTER APPLY
If supported by the database, OUTER APPLY is an efficient and terse option.
SELECT *
FROM
Table_A a
OUTER APPLY
(SELECT TOP 1 *
FROM Table_B b_1
WHERE b_1.code = a.code
) b
;
This results in a left join to the indeterminate first matched record. My tests show it to be quicker than any other posted solution (on MS SQL Server 2012).
The highest voted answer does not seem correct to me, and seems overcomplicated.
Just group by the code field on table B in your subquery and select the maximum Id per grouping.
SELECT
table_a.code,
table_a.emp_no,
table_b.city,
table_b.county
FROM
table_a
LEFT JOIN
table_b
ON table_b.code = table_a.code
AND table_b.field_that_is_unique IN
(SELECT MAX(field_that_is_unique)
FROM table_b
GROUP BY table_b.code)
If you are on SQL Server 2005 or later version, you could use ranking to achieve what you want. In particular, ROW_NUMBER() seems to suit your needs nicely:
WITH B_ranked AS (
SELECT
*,
rnk = ROW_NUMBER() OVER (PARTITION BY code ORDER BY city)
FROM B
)
SELECT
A.code,
A.emp_no,
B.city,
B.county
FROM A
LEFT JOIN B_ranked AS B ON A.code = B.code AND b.rnk = 1
OR
WITH B_unique_code AS (
select * from(
SELECT
*,
rnk = ROW_NUMBER() OVER (PARTITION BY code ORDER BY city)
FROM B
) AS s
where rnk = 1
)
SELECT
A.code,
A.emp_no,
B.city,
B.county
FROM A
LEFT JOIN B_unique_code AS B ON A.code = B.code
I modified the answer from ruakh and this seem to work perfectly with mysql.
SELECT
table_a.code,
table_a.emp_no,
table_b.city,
table_b.county
FROM table_a a
LEFT JOIN table_b b
ON b.code = a.code
AND b.id = ( SELECT id FROM table_b
WHERE table_b.code = table_a.code
LIMIT 1
)
;
this is how:
Select * From TableA a
Left Join TableB b
On b.Code = a.Code
And [Here put criteria predicate that 'defines' what the first record is]
Hey, if the city and county are unique, then use them
Select * From TableA a
Left Join TableB b
On b.Code = a.Code
And b.City + b.county =
(Select Min(city + county)
From TableB
Where Code = b.Code)
But the point is you have to put some expression in there to tell the query processor what it means to be first.
In Oracle you can do:
WITH first_b AS (SELECT code, min(rowid) AS rid FROM b GROUP BY code))
SELECT a.code, a.emp_no, b.city, b.county
FROM a
INNER JOIN first_b
ON first_b.code = a.code
INNER JOIN b
ON b.rowid = first_b.rid