SQL: Attribute matches two different conditions at the same time - sql

I want to make a query where an attribute (same attribute) matches two different conditions at the same time. I have to check if a driver was found in both cities.
I tried to use intersect but I don't get any matches. But in my table I have one driver that matches this conditions.
SELECT s.NumeSofer
FROM Soferi s
INNER JOIN contraventii c ON s.idSofer=c.idSofer
INNER JOIN localitati l ON c.idLocContr=l.idLoc
WHERE l.DenLoc IN ('Iasi', 'Rosiori') AND l.Jud IN ('IS', 'NT');
INTERSECT
SELECT s.NumeSofer
FROM Soferi s
INNER JOIN contraventii c ON s.idSofer=c.idSofer
INNER JOIN localitati l ON c.idLocContr=l.idLoc
WHERE l.DenLoc='Rosiori' AND l.Jud='NT';

You can use aggregation and a HAVING clause, like:
SELECT s.NumeSofer
FROM Soferi s
INNER JOIN contraventii c ON s.idSofer=c.idSofer
INNER JOIN localitati l
ON c.idLocContr = l.idLoc
AND (l.DenLoc, l.Jud) IN ( ('Iasi', 'IS'), ('Rosiori', 'NT') )
GROUP BY s.NumeSofer
HAVING
MAX(CASE WHEN l.DenLoc = 'Iasi' AND l.Jud = 'IS' THEN 1 END) = 1
AND MAX(CASE WHEN l.DenLoc = 'Rosiori' AND l.Jud = 'NT' THEN 1 END) = 1
This will bring you all NumeSofer for which at least one record exists in localitati with DenLoc='Iasi' AND Jud='IS' and at least one record exists with DenLoc='Rosiori' AND Jud='NT'.
Note: the IN operator can be used with tuple values; this reduce the lenght of the query, while avoiding using OR, which is usually not good for general performance.

Do a GROUP BY instead. Use case expressions to do conditional aggregation:
SELECT s.NumeSofer, count(distinct l.DenLoc) as totcount,
count(case when l.DenLoc='Rosiori' then 1 end) as Rosioricount,
count(case when l.DenLoc='Iasi' then 1 end) as Iasicount
FROM Soferi s
INNER JOIN contraventii c ON s.idSofer=c.idSofer
INNER JOIN localitati l ON c.idLocContr=l.idLoc
WHERE (l.DenLoc='Rosiori' AND l.Jud='NT')
OR (l.DenLoc='Iasi' AND l.Jud='IS')
GROUP BY s.NumeSofer
ORDER BY totcount desc
Any rows with totcount = 2?
To get only drivers with both DenLoc's add a HAVING clause:
SELECT s.NumeSofer, count(distinct l.DenLoc) as totcount,
count(case when l.DenLoc='Rosiori' then 1 end) as Rosioricount,
count(case when l.DenLoc='Iasi' then 1 end) as Iasicount
FROM Soferi s
INNER JOIN contraventii c ON s.idSofer=c.idSofer
INNER JOIN localitati l ON c.idLocContr=l.idLoc
WHERE (l.DenLoc='Rosiori' AND l.Jud='NT')
OR (l.DenLoc='Iasi' AND l.Jud='IS')
GROUP BY s.NumeSofer
HAVING count(distinct l.DenLoc) > 1

Related

SQL / Oracle SQL Code for looking data from other tables

How to write query to get data from other table for each columns in existing table.
You can left join three times on system_code:
select
d.id,
s_cat.full_name cat_code_full_name,
s_group.full_name group_code_full_name,
s_other.full_name other_code_full_name
from data_table d
left join system_code s_cat
on s_cat.value = d.cat_code and s.code = 1
left join system_code s_group
on s_group.value = d.group_code and s_group.code = 2
left join system_code s_other
on s_other.value = d.other_code and s_other.code = 3
To avoid repeating the joins, an alternative solution is to do conditional aggregation:
select
d.id,
max(case when s.value = d.cat_code and s.code = 1 then s.full_name end) cat_code_full_name,
max(case when s.value = d.group_code and s.code = 2 then s.full_name end) group_code_full_name,
max(case when s.value = d.other_code and s.code = 3 then s.full_name end) other_code_full_name
from data_table d
left join system_code s on s.value in (d.cat_code, d.group_code, d.other_code)
gtoup by d.id
I guess the problem you are facing here is, how to get full name for all 3 columns. 1 of the method is to join SYSTEM_CODE table thrice -
SELECT DT.ID
,SC1.FULL_NAME CAT_CODE_FULL_NAME
,SC2.FULL_NAME GROUP_CODE_FULL_NAME
,SC3.FULL_NAME OTHER_CODE_FULL_NAME
,DT.PRODUCT
FROM DATA_TABLE DT
JOIN SYSTEM_CODE SC1 ON SC1.VALUE = DT.CAT_CODE
JOIN SYSTEM_CODE SC2 ON SC2.VALUE = DT.CAT_CODE
JOIN SYSTEM_CODE SC3 ON SC3.VALUE = DT.CAT_CODE

2 SQL query with Union Building

i have 2 SQL query, i want to make both in one SQL query and get all the elements from each query only in one row.
I want to have 6 columns for the result("kunde", "GesamtdateiGrosse", "anzahlObjGesamt", "GesamtdateiGrossefertig", "anzahlObjFertig" "RestGross") and 2 lines. RestGross would then be the difference between GesamtdateiGrosse and GesamtdateiGrossefertig
SELECT
kunde.nummer AS kunde,
SUM(datei.groesse) as "GesamtdateiGrosse",
COUNT (DISTINCT objekt.id) as anzahlObjGesamt
FROM datei
INNER JOIN objekt ON datei.id_objekt = objekt.id
INNER JOIN auftrag ON objekt.id_auftrag = auftrag.id
INNER JOIN kunde ON auftrag.id_kunde = kunde.id
WHERE kunde.nummer = 777
GROUP BY kunde.nummer
UNION
SELECT
kunde.nummer AS kunde,
SUM(datei.groesse) as "GesamtdateiGrossefertig",
COUNT (DISTINCT objekt.id) as anzahlObjFertig
FROM datei
INNER JOIN objekt ON datei.id_objekt = objekt.id
INNER JOIN auftrag ON objekt.id_auftrag = auftrag.id
INNER JOIN status ON objekt.id_restorestatus = status.id
INNER JOIN kunde ON auftrag.id_kunde = kunde.id
WHERE status.name = 'fertig' AND kunde.nummer = 777
GROUP BY kunde.nummer
I expect the output 3 rown 3 column (Kunde, "GesamtdateiGrosse",anzahlObjGesamt)
This should get you close to what you want. The idea is to add another join in the first to bring in the status table, and the do conditional aggregation.
This requires that there is a 1:1 relationship between objekt and status (meaning that, for a given record in objekt, there is always one and only one record in status that satisfies condition objekt.id_restorestatus = status.id).
Consider:
SELECT
kunde.nummer AS kunde,
SUM(datei.groesse) as "GesamtdateiGrosse",
COUNT(DISTINCT objekt.id) as anzahlObjGesamt,
SUM(CASE WHEN status.name = 'fertig' THEN datei.groesse ELSE 0 END) as "GesamtdateiGrossefertig",
COUNT (DISTINCT (CASE WHEN status.name = 'fertig' THEN objekt.id END)) as anzahlObjFertig
FROM datei
INNER JOIN objekt ON datei.id_objekt = objekt.id
INNER JOIN auftrag ON objekt.id_auftrag = auftrag.id
INNER JOIN kunde ON auftrag.id_kunde = kunde.id
INNER JOIN status ON objekt.id_restorestatus = status.id
WHERE kunde.nummer = 777
GROUP BY kunde.nummer

SQL SELECT clause with WHERE statement for specific order

I have a through table, doctor_specialties that has a column ordinal that I would like to use in order to create a column named primary_specialty and also secondary_specialty. The logic for primary_specialty is WHERE ordinal == 1.
How can I add the primary_specialty and secondary_specialty columns? One approach would be to use a WHERE statement with the INNER JOIN but I think that would be less efficient?
SELECT pd.name AS "doctor_name",
s.name AS "primary_specialty" WHERE ds.ordinal == 1
FROM doctor_profiles AS dp
INNER JOIN doctor_specialties AS ds on dp.id = ds.doctor_profile_id
INNER JOIN specialties AS s on ds.specialty_id = s.id
Desired output is
name primary_specialty secondary_specialty
Josh Dermatology, Cosmetic Dermatology
Linda Primary Care null
You need to achive this by using case statement. example is shared below
SELECT pd.name AS "doctor_name",
case when ds.ordinal = 1 then s.name end as "primary_specialty",
case when ds.ordinal <> 1 then s.name end as "secondary_specialty"
FROM doctor_profiles AS dp
INNER JOIN doctor_specialties AS ds on dp.id = ds.doctor_profile_id
INNER JOIN specialties AS s on ds.specialty_id = s.id
You can use conditional aggregation:
SELECT dp.name AS "doctor_name",
MAX(CASE WHEN ds.ordinal = 1 THEN s.name END) AS "primary_specialty",
MAX(CASE WHEN ds.ordinal != 2 THEN s.name END) AS "secondary_specialty"
FROM doctor_profiles AS dp
INNER JOIN doctor_specialties AS ds on dp.id = ds.doctor_profile_id
INNER JOIN specialties AS s on ds.specialty_id = s.id
GROUP BY pd.name
You can alter the existing, or use additional MAX aggregates containing CASE expressions, in order to implement the logic for secondary specialties.

SQL correct query or not

given these relationships, how could you query the following:
The tourists (name and email) that booked at least a pension whose rating is greater than 9, but didn't book any 3 star hotel with a rating less than 9.
Is the following correct?
SELECT Tourists.name, Tourists.email
FROM Tourists
WHERE EXISTS (
SELECT id FROM Bookings
INNER JOIN Tourists ON Bookings.touristId=Tourists.id
INNER JOIN AccomodationEstablishments ON Bookings.accEstId=AccomodationEstablishments.id
INNER JOIN AccomodationTypes ON AccomodationEstablishments.accType=AccomodationTypes.id
WHERE AccomodationTypes.name = 'Pension' AND
AccomodationEstablishments.rating > 9
) AND NOT EXISTS (
SELECT id FROM Bookings
INNER JOIN Tourists ON Bookings.touristId=Tourists.id
INNER JOIN AccomodationEstablishments ON Bookings.accEstId=AccomodationEstablishments.id
INNER JOIN AccomodationTypes ON AccomodationEstablishments.accType=AccomodationTypes.id
WHERE AccomodationTypes.name = 'Hotel' AND
AccomodationEstablishments.noOfStars = 3 AND
AccomodationEstablishments.rating < 9
)
I would do this using aggregation and having:
SELECT t.name, t.email
FROM Bookings b INNER JOIN
Tourists t
ON b.touristId = t.id INNER JOIN
AccomodationEstablishments ae
ON b.accEstId = ae.id INNER JOIN
AccomodationTypes a
ON ae.accType = a.id
GROUP BY t.name, t.email
HAVING SUM(CASE WHEN a.name = 'Pension' AND ae.rating > 9 THEN 1 ELSE 0 END) > 0 AND
SUM(a.name = 'Hotel' AND ae.noOfStars = 3 AND ae.rating < 9 THEN 1 ELSE 0 END)= 0;
Your method also works, but you probably need t.id in the subqueries.

Query for logistic regression, multiple where exists

A logistic regression is a composed of a uniquely identifying number, followed by multiple binary variables (always 1 or 0) based on whether or not a person meets certain criteria. Below I have a query that lists several of these binary conditions. With only four such criteria the query takes a little longer to run than what I would think. Is there a more efficient approach than below? Note. tblicd is a large table lookup table with text representations of 15k+ rows. The query makes no real sense, just a proof of concept. I have the proper indexes on my composite keys.
select patient.patientid
,case when exists
(
select c.patientid from tblclaims as c
inner join patient as p on p.patientid=c.patientid
and c.admissiondate = p.admissiondate
and c.dischargedate = p.dischargedate
where patient.patientid = p.patientid
group by c.patientid
having count(*) > 1000
)
then '1' else '0'
end as moreThan1000
,case when exists
(
select c.patientid from tblclaims as c
inner join patient as p on p.patientid=c.patientid
and c.admissiondate = p.admissiondate
and c.dischargedate = p.dischargedate
where patient.patientid = p.patientid
group by c.patientid
having count(*) > 1500
)
then '1' else '0'
end as moreThan1500
,case when exists
(
select distinct picd.patientid from patienticd as picd
inner join patient as p on p.patientid= picd.patientid
and picd.admissiondate = p.admissiondate
and picd.dischargedate = p.dischargedate
inner join tblicd as t on t.icd_id = picd.icd_id
where t.descrip like '%diabetes%' and patient.patientid = picd.patientid
)
then '1' else '0'
end as diabetes
,case when exists
(
select r.patientid, count(*) from patient as r
where r.patientid = patient.patientid
group by r.patientid
having count(*) >1
)
then '1' else '0'
end
from patient
order by moreThan1000 desc
I would start by using subqueries in the from clause:
select q.patientid, moreThan1000, moreThan1500,
(case when d.patientid is not null then 1 else 0 end),
(case when pc.patientid is not null then 1 else 0 end)
from patient p left outer join
(select c.patientid,
(case when count(*) > 1000 then 1 else 0 end) as moreThan1000,
(case when count(*) > 1500 then 1 else 0 end) as moreThan1500
from tblclaims as c inner join
patient as p
on p.patientid=c.patientid and
c.admissiondate = p.admissiondate and
c.dischargedate = p.dischargedate
group by c.patientid
) q
on p.patientid = q.patientid left outer join
(select distinct picd.patientid
from patienticd as picd inner join
patient as p
on p.patientid= picd.patientid and
picd.admissiondate = p.admissiondate and
picd.dischargedate = p.dischargedate inner join
tblicd as t
on t.icd_id = picd.icd_id
where t.descrip like '%diabetes%'
) d
on p.patientid = d.patientid left outer join
(select r.patientid, count(*) as cnt
from patient as r
group by r.patientid
having count(*) >1
) pc
on p.patientid = pc.patientid
order by 2 desc
You can then probably simplify these subqueries more by combining them (for instance "p" and "pc" on the outer query can be combined into one). However, without the correlated subqueries, SQL Server should find it easier to optimize the queries.
Example of left joins as requested...
SELECT
patientid,
ISNULL(CondA.ConditionA,0) as IsConditionA,
ISNULL(CondB.ConditionB,0) as IsConditionB,
....
FROM
patient
LEFT JOIN
(SELECT DISTINCT patientid, 1 as ConditionA from ... where ... ) CondA
ON patient.patientid = CondA.patientID
LEFT JOIN
(SELECT DISTINCT patientid, 1 as ConditionB from ... where ... ) CondB
ON patient.patientid = CondB.patientID
If your Condition queries only return a maximum one row, you can simplify them down to
(SELECT patientid, 1 as ConditionA from ... where ... ) CondA