Fetch rows joined that don't match a query - sql

I have a an application with interns who can apply to internships.
We have a script that automatically qualifies the interns.
I would like to fetch all interns who aren't qualified to any internship.
The database is like this :
Intern (Id,...)
Application (Id, status, intern_id, internship_id,...)
Internship (Id,...)
For the status, we have 'applied', 'qualified', 'current' and 'done'
Basically I need to do a join request of Interns who don't have Application with 'qualified' status, but my sql skills are pretty basic.
EDIT
I forgot to mention that there can be several applications for an intern, an intern can only be qualified to one internship, but he can still be applying to another, so this won't work
SELECT *
FROM Intern i
INNER JOIN Application a ON i.Id = a.intern_id
WHERE a.status <> 'qualified'

Include the status in a LEFT JOIN, and filter for non-matches like so:
SELECT ....
FROM Intern i
LEFT JOIN
Application a
ON i.Id = a.intern_id
AND a.status = 'qualified'
WHERE a.Id IS NULL
Substitute your list of columns, obviously!

SELECT *
FROM Intern i
WHERE NOT EXISTS
(SELECT *
FROM Intern j
, Application a
WHERE j.id = a.intern_id
AND i.id = j.id
AND a.status = 'qualified')

SELECT * FROM Intern
WHERE Id IN ( SELECT Intern_id FROM Application WHERE status <> 'qualified' );

This query should return what you're looking for:
SELECT *
FROM Intern i
INNER JOIN Application a ON i.Id = a.intern_id
WHERE a.status <> 'qualified'
Obviously, you could replace the * with just the fields you were looking for. If you're looking for the interns that DIDN'T qualify, I wouldn't think you would need the Internship table. This does assume that there is only one application per intern... if that is not the case, you could use this query:
SELECT *
FROM Intern i
WHERE i.Id IN
(
SELECT intern_id
FROM Application a
WHERE a.status <> 'qualified'
)

Related

SQL Oracle Update containing left join

I want to perform an update for the results of a select query.
SELECT
a.reason,
n.note
FROM applications a
LEFT JOIN notes n on n.app_id = a.app_id
AND n.note LIKE '%old%'
WHERE a.code = 'run' AND a.reason IS NULL
I thought I could perform these updates separately wrapping the select in an update however I get the error ORA-01733: virtual column not allowed here. How can I go about performing these updates?
UPDATE (
SELECT
a.reason AS Reason
FROM applications a
LEFT JOIN notes n on n.app_id = a.app_id
AND n.note LIKE '%old%'
WHERE a.code = 'run' AND a.reason IS NULL
) SET Reason = null
UPDATE (
SELECT
n.note AS Note
FROM applications a
LEFT JOIN notes n on n.app_id = a.app_id
AND n.note LIKE '%old%'
WHERE a.code = 'run' AND a.reason IS NULL
) SET Note = null
You can't update the two tables at the same time. You need two different update statements as follows:
Updating the APPLICATIONS table is quite easy as all the records of the APPLICATIONS table having a.code = 'run' AND a.reason IS NULL will be there in your SELECT query.
UPDATE APPLICATIONS A
SET
REASON = NULL
WHERE A.CODE = 'run'
AND A.REASON IS NULL;
To update the NOTES table, you can use the EXISTS clause as follows:
UPDATE NOTES N
SET
NOTE = NULL
WHERE EXISTS (
SELECT 1
FROM APPLICATIONS A
WHERE N.APP_ID = A.APP_ID
AND A.CODE = 'run'
AND A.REASON IS NULL
)
AND N.NOTE LIKE '%old%'
You must update the NOTES table first and then APPLICATIONS table as while updating the NOTES table you are using the condition A. REASON IS NULL but while updating the APPLICATIONS table, you are updating the REASON column.

Oracle SQL - selective filtering causes cartesian

Oracle 12.2
I have a SQL statement that is causing me issues. I am retrieving data from a table called BURNDOWN. If the user is an admin, they get to see all the data. If the user is NOT an admin, they are restricted to what they can see, based on some join conditions.
The issue I am running into is when the user is an ADMIN, I don’t need the other tables… subsequently, the JOIN condition is not relevant, so Oracle is deciding to do a cartesian join across everything…
How do I get around this so that is the user is an Admin, I only look at one table, else I look at all tables and include the join condition?
The example SQL is a contrived example, but it shows the issue.
Select
BURNDOWN.NAME,
BURNDOWN.ADDRESS,
BURNDOWN.STATE
from BURNDOWN, FILTER_A, FILTER_B, FILTER_C
Where
(
:ISAdmin = 1
Or
(
BURNDOWN.x=FILTER_A.x and
FILTER_A.y=FILTER_B.y and
FILTER_B.z=FILTER_C.z and
FILTER_C.user = :ThisUser
)
)
Use an EXISTS to see if the data exists in the FILTER tables without joining them in to the results.
select bd.*
from burndown bd
where ( :isadmin = 1 or
exists ( select 1
from filter_a a
inner join filter_b b on b.y = a.y
inner join filter_c c on c.z = b.z
where a.x = bd.x
and c.user = :ThisUser )
)
Presumably, you want:
select bd.*
from burndown bd
where :ISAdmin = 1 or
(exists (select 1 from FILTER_A a where bd.x = a.x) or
exists (select 1 from FILTER_B b where bd.y = b.y) or
exists (select 1 from FILTER_C c where bd.z = c.z)
);

Query to fetch all referenced entities recursively

I have a datamodels which consists of 'Claims' which (to make things simple for stackoverflow) only has an OpenAmount field. There are two other tables, 'ClaimCoupling' and 'ClaimEntryReference'.
The ClaimCoupling table directly references back to the Claim table and the ClaimEntryReference is effectively the booking of a received amount that can be booked over multiple claims (See ClaimEntry_ID). See this diagram;
For simplicity I've removed all amounts as that's not what I am currently struggling with.
What I want is a query that will start # the Claim table, and fetches all a claim with an OpenAmount which is <> 0. However I want to be able to print out an accurate report of how this OpenAmount came to be, which means I'll need to also print out any Claims coupled to this claim. To make it even more interesting the same thing applies to the bookings, if a booking was made on claim X and claim Y and only X has an open amount I want to fetch both X and Y so I can then show the payment which was booked as a whole.
I've attempted to do this with a recursive CTE but this (rightfully) blows up on the circulair references. I figured I'd fix that with a simple where statement where I would say only recursively add records which are not yet part of CTE but this is not allowed....
WITH coupledClaims AS (
--Get all unique combinations
SELECT cc.SubstractedFromClaim_ID AS Claim_ID,
cc.AddedToClaim_ID AS Linked_Claim_ID FROM dbo.ClaimCoupling cc
UNION
SELECT cc.AddedToClaim_ID AS Claim_ID,
cc.SubstractedFromClaim_ID AS Linked_Claim_ID FROM dbo.ClaimCoupling cc
),
MyClaims as
(
SELECT * FROM Claim WHERE OpenAmount <> 0
UNION ALL
SELECT c.* FROM coupledClaims JOIN MyClaims mc ON coupledClaims.claim_id = mc.ID JOIN claim c ON c.ID = coupledClaims.linked_Claim_ID
WHERE c.ID NOT IN (SELECT ID FROM MyClaims)
)
SELECT * FROM MyClaims
After fiddling around with that for way too long I decided I'd do it with an actual loop... ##Rowcount and simply manually add them to a table variable but as I was writing this solution (which I'm sure I can get to work) I figured I'd ask here first because I don't like writing loops in TSQL as I always feel it's ugly and inefficient.
See the following sql Fiddle for the data models and some test data (I commented out the recursive part as otherwise I was not allowed to create a link);
http://sqlfiddle.com/#!6/129ad5/7/0
I'm hoping someone here will have a great way of handling this problem (likely I'm doing something wrong with the recursive CTE). For completion this is done on MS SQL 2016.
So here is what I've learned and done so far. Thanks to the comment of habo which refers to the following question; Infinite loop in CTE when parsing self-referencing table
Firstly I decided to at least 'solve' my problem and wrote some manual recursion, this solves my problem but is not as 'pretty' as the CTE solution which I was hoping/thinking would be easier to read as well as out perform the manual recursion solution.
Manual Recursion
/****************************/
/* CLAIMS AND PAYMENT LOGIC */
/****************************/
DECLARE #rows as INT = 0
DECLARE #relevantClaimIds as Table(
Debtor_ID INT,
Claim_ID int
)
SET NOCOUNT ON
--Get anchor condition
INSERT INTO #relevantClaimIds (Debtor_ID, Claim_ID)
select Debtor_ID, ID
from Claim c
WHERE OpenAmount <> 0
--Do recursion
WHILE #rows <> (SELECT COUNT(*) FROM #relevantClaimIds)
BEGIN
set #rows = (SELECT COUNT(*) FROM #relevantClaimIds)
--Subtracted
INSERT #relevantClaimIds (Debtor_ID, Claim_ID)
SELECT DISTINCT c.Debtor_ID, c.id
FROM claim c
inner join claimcoupling cc on cc.SubstractedFromClaim_ID = c.ID
JOIN #relevantClaimIds rci on rci.Claim_ID = cc.AddedToClaim_ID
--might be multiple paths to this recursion so eliminate duplicates
left join #relevantClaimIds dup on dup.Claim_ID = c.id
WHERE dup.Claim_ID is null
--Added
INSERT #relevantClaimIds (Debtor_ID, Claim_ID)
SELECT DISTINCT c.Debtor_ID, c.id
FROM claim c
inner join claimcoupling cc on cc.AddedToClaim_ID = c.ID
JOIN #relevantClaimIds rci on rci.Claim_ID = cc.SubstractedFromClaim_ID
--might be multiple paths to this recursion so eliminate duplicates
left join #relevantClaimIds dup on dup.Claim_ID = c.id
WHERE dup.Claim_ID is null
--Payments
INSERT #relevantClaimIds (Debtor_ID, Claim_ID)
SELECT DISTINCT c.Debtor_ID, c.id
FROM #relevantClaimIds f
join ClaimEntryReference cer on f.Claim_ID = cer.Claim_ID
JOIN ClaimEntryReference cer_linked on cer.ClaimEntry_ID = cer_linked.ClaimEntry_ID AND cer.ID <> cer_linked.ID
JOIN Claim c on c.ID = cer_linked.Claim_ID
--might be multiple paths to this recursion so eliminate duplicates
left join #relevantClaimIds dup on dup.Claim_ID = c.id
WHERE dup.Claim_ID is null
END
Then after I received and read the comment I decided to try the CTE solution which looks like this;
CTE Recursion
with Tree as
(
select Debtor_ID, ID AS Claim_ID, CAST(ID AS VARCHAR(MAX)) AS levels
from Claim c
WHERE OpenAmount <> 0
UNION ALL
SELECT c.Debtor_ID, c.id, t.levels + ',' + CAST(c.ID AS VARCHAR(MAX)) AS levels
FROM claim c
inner join claimcoupling cc on cc.SubstractedFromClaim_ID = c.ID
JOIN Tree t on t.Claim_ID = cc.AddedToClaim_ID
WHERE (','+T.levels+',' not like '%,'+cast(c.ID as varchar(max))+',%')
UNION ALL
SELECT c.Debtor_ID, c.id, t.levels + ',' + CAST(c.ID AS VARCHAR(MAX)) AS levels
FROM claim c
inner join claimcoupling cc on cc.AddedToClaim_ID = c.ID
JOIN Tree t on t.Claim_ID = cc.SubstractedFromClaim_ID
WHERE (','+T.levels+',' not like '%,'+cast(c.ID as varchar(max))+',%')
UNION ALL
SELECT c.Debtor_ID, c.id, t.levels + ',' + CAST(c.ID AS VARCHAR(MAX)) AS levels
FROM Tree t
join ClaimEntryReference cer on t.Claim_ID = cer.Claim_ID
JOIN ClaimEntryReference cer_linked on cer.ClaimEntry_ID = cer_linked.ClaimEntry_ID AND cer.ID <> cer_linked.ID
JOIN Claim c on c.ID = cer_linked.Claim_ID
WHERE (','+T.levels+',' not like '%,'+cast(c.ID as varchar(max))+',%')
)
select DISTINCT Tree.Debtor_ID, Tree.Claim_ID
from Tree
This solution is indeed a lot 'shorter' and easier on the eyes but does it actually perform better?
Performance differences
Manual; CPU 16, Reads 1793, Duration 13
CTE; CPU 47, Reads 4001, Duration 48
Conclusion
Not sure if it's due to the varchar cast that is required in the CTE solution or that it has to do one extra iteration before completing it's recursion but it actually requires more resources on all fronts than the manual recursion.
In the end it is possible with CTE however looks aren't everything (thank god ;-)) performance wise sticking with the manual recursion seems like a better route.

PostgreSQL - how to query "result IN ALL OF"?

I am new to PostgreSQL and I have a problem with the following query:
WITH relevant_einsatz AS (
SELECT einsatz.fahrzeug,einsatz.mannschaft
FROM einsatz
INNER JOIN bergefahrzeug ON einsatz.fahrzeug = bergefahrzeug.id
),
relevant_mannschaften AS (
SELECT DISTINCT relevant_einsatz.mannschaft
FROM relevant_einsatz
WHERE relevant_einsatz.fahrzeug IN (SELECT id FROM bergefahrzeug)
)
SELECT mannschaft.id,mannschaft.rufname,person.id,person.nachname
FROM mannschaft,person,relevant_mannschaften WHERE mannschaft.leiter = person.id AND relevant_mannschaften.mannschaft=mannschaft.id;
This query is working basically - but in "relevant_mannschaften" I am currently selecting each mannschaft, which has been to an relevant_einsatz with at least 1 bergefahrzeug.
Instead of this, I want to select into "relevant_mannschaften" each mannschaft, which has been to an relevant_einsatz WITH EACH from bergefahrzeug.
Does anybody know how to formulate this change?
The information you provide is rather rudimentary. But tuning into my mentalist skills, going out on a limb, I would guess this untangled version of the query does the job much faster:
SELECT m.id, m.rufname, p.id, p.nachname
FROM person p
JOIN mannschaft m ON m.leiter = p.id
JOIN (
SELECT e.mannschaft
FROM einsatz e
JOIN bergefahrzeug b ON b.id = e.fahrzeug -- may be redundant
GROUP BY e.mannschaft
HAVING count(DISTINCT e.fahrzeug)
= (SELECT count(*) FROM bergefahrzeug)
) e ON e.mannschaft = m.id
Explain:
In the subquery e I count how many DISTINCT mountain-vehicles (bergfahrzeug) have been used by a team (mannschaft) in all their deployments (einsatz): count(DISTINCT e.fahrzeug)
If that number matches the count in table bergfahrzeug: (SELECT count(*) FROM bergefahrzeug) - the team qualifies according to your description.
The rest of the query just fetches details from matching rows in mannschaft and person.
You don't need this line at all, if there are no other vehicles in play than bergfahrzeuge:
JOIN bergefahrzeug b ON b.id = e.fahrzeug
Basically, this is a special application of relational division. A lot more on the topic under this related question:
How to filter SQL results in a has-many-through relation
Do not know how to explain it, but here is an example how I solved this problem, just in case somebody has the some question one day.
WITH dfz AS (
SELECT DISTINCT fahrzeug,mannschaft FROM einsatz WHERE einsatz.fahrzeug IN (SELECT id FROM bergefahrzeug)
), abc AS (
SELECT DISTINCT mannschaft FROM dfz
), einsatzmannschaften AS (
SELECT abc.mannschaft FROM abc WHERE (SELECT sum(dfz.fahrzeug) FROM dfz WHERE dfz.mannschaft = abc.mannschaft) = (SELECT sum(bergefahrzeug.id) FROM bergefahrzeug)
)
SELECT mannschaft.id,mannschaft.rufname,person.id,person.nachname
FROM mannschaft,person,einsatzmannschaften WHERE mannschaft.leiter = person.id AND einsatzmannschaften.mannschaft=mannschaft.id;

SQL - GROUPING, ID, NAME

I was wondering what the answer should be for 1c. on this website:
http://sqlzoo.net/6.htm
SELECT company
FROM route WHERE stop=4
GROUP BY name, ID
this obviously isn't working, the ID and name isn't showing up no matter what. What is missing here? Thanks.
SELECT stops.id, stops.name
FROM route
INNER JOIN stops on route.stop = stops.id
WHERE route.num = 4 AND route.company = 'LRT'
You need to join the tables as the data you want to return is in a different table to the one which filters the data.
This works and does not include any unnecessary table joins. A good rule of thumb is to use EXISTS to verify values in a table that you do not need the output for. Otherwise, you would use a JOIN
SELECT stops.id, stops.name
FROM stops
WHERE EXISTS
(
SELECT 1 FROM route
WHERE route.stop = stops.id AND num = '4' AND company = 'LRT'
)
select s.id, s.name
from stops s
inner join route r
on s.id = r.stop
where r.num= 4
AND r.company= 'LRT'
It gives you this error:
sql: Unknown column 'name' in 'group statement'
There is no name in route tabel.
The tables structure are:
stops(id, name)
route(num,company,pos, stop)
So the answer for this quiz is:
SELECT s.id, s.name
FROM route r, stops s
WHERE r.stop= s.id
and r.num = 4 AND r.company = 'LRT'