SQL Query: Using AND/OR in the WHERE clause - sql

I'm currently working on a project where I have a list of dental patients, and I'm supposed to be displaying all the patients who have two specific procedure codes attached to their profiles. These patients must have BOTH procedure codes, not one or the other. At first I thought I could accomplish this by using a basic AND statement in my WHERE clause, like so.
SELECT [stuff]
FROM [this table]
WHERE ProcedureCode = 'ABC123' AND ProcedureCode = 'DEF456';
The query of course returns nothing because the codes are entered individually and you can't have both Procedure 1 and Procedure 2 simultaneously.
I tried switching the "AND" to "OR" just out of curiosity. Of course I'm now getting results for patients who only have one code or the other, but the patients who have both are showing up too, and the codes are displayed as separate results, like so:
Patient ID Last Name First Name Code Visit Date
1111111 Doe Jane ABC123 11-21-2015
5555555 Smith John ABC123 12-08-2015
5555555 Smith John DEF456 12-08-2015
My SQL is pretty rusty these days. I'm trying to think of a way to filter out patients like Jane Doe and only include patients like John Smith who have both procedure codes. Ideas?
ADDING INFO BASED ON CHRISTIAN'S ANSWER:
This is what the updated query looks like:
SELECT PatientID, LastName, FirstName, Code, VisitDate
FROM VisitInfo
WHERE PatientID IN
(
SELECT PatientID
FROM VisitInfo
WHERE Code = 'ABC123' OR Code = 'DEF456'
GROUP BY PatientID
HAVING COUNT(*) > 1
)
AND (Code = 'ABC123' OR Code = 'DEF456');
So I'm still getting results like the following where a patient is only showing one procedure code but possibly multiple instances of it:
Patient ID Last Name First Name Code Visit Date
1111111 Doe Jane ABC123 11-02-2015
1111111 Doe Jane ABC123 11-21-2015
5555555 Smith John ABC123 12-08-2015
5555555 Smith John DEF456 12-08-2015
5555555 Smith John ABC123 12-14-2015
9999999 Jones Mike DEF456 11-22-2015
9999999 Jones Mike DEF456 12-06-2015
Even though Jane Doe and Mike Jones have 2 results, they're both the same code, so we don't want to include them. And even though John Smith still has 2 of the same code, his results also include both codes, so we want to keep him and other patients like him.
ANOTHER UPDATE:
I just learned that I now need to include a few basic demographic details for the patients in question, so I've joined my VisitInfo table with a PatientInfo table. The updated query looks like this:
SELECT v.PatientID, v.LastName, v.FirstName, v.Code, v.VisitDate, p.DateOfBirth, p.Gender, p.PrimaryPhone
FROM VisitInfo v JOIN PatientInfo p ON v.PatientID = p.PatientID
WHERE v.PatientID IN
(
SELECT PatientID
FROM VisitInfo
WHERE Code = 'ABC123' OR Code = 'DEF456'
GROUP BY PatientID
HAVING COUNT(*) > 1
)
AND (Code = 'ABC123' OR Code = 'DEF456');
I wasn't sure if the new JOIN would affect anyone's answers...

SELECT *
FROM TABLE
WHERE Patient_ID IN
(
SELECT Patient_ID
FROM TABLE
WHERE Code = 'ABC123' OR Code = 'DEF456'
GROUP BY Patient_ID
HAVING COUNT(*) = 2
)
AND (Code = 'ABC123' OR Code = 'DEF456')
UPDATE 1:
As a patient can have multiple ´procedure codes´, this way will work better:
SELECT *
FROM TABLE T1
WHERE EXISTS (SELECT 1
FROM TABLE T2
WHERE T1.Patient_ID = T2.Patient_ID
AND T2.Code = 'ABC123')
AND EXISTS (SELECT 1
FROM TABLE T2
WHERE T1.Patient_ID = T2.Patient_ID
AND T2.Code = 'DEF456')
AND T1.Code IN ('ABC123','DEF456')

There's a bunch of ways to skin this particular cat - here's another one:
WITH ABC123 AS (SELECT DISTINCT PATIENTID
FROM VISITINFO
WHERE PROCEDURECODE = 'ABC123'),
DEF456 AS (SELECT DISTINCT PATIENTID
FROM VISITINFO
WHERE PROCEDURECODE = 'DEF456')
SELECT v.PATIENTID, v.LASTNAME, v.FIRSTNAME, v.PROCEDURECODE, v.VISITDATE,
p.DateOfBirth, p.Gender, p.PrimaryPhone
FROM VISITINFO v
INNER JOIN ABC123 a
ON a.PATIENTID = v.PATIENTID
INNER JOIN DEF456 d
ON d.PATIENTID = v.PATIENTID
INNER JOIN PatientInfo p
ON v.PatientID = p.PatientID
WHERE v.PROCEDURE_CODE IN ('ABC123', 'DEF456')
ORDER BY v.PATIENTID, v.VISITDATE, v.PROCEDURECODE;
What we're doing here is using a couple of CTE's to get the PATIENTID's for each patient who has the the procedures in question performed. We then start with all records in VISITINFO and inner join those with the two CTE's. Because an INNER JOIN requires that matching information exist in the tables on both sides of the join this has the effect of retaining only the visits which match the information in both of the CTE's, based on the join criteria which in this case is the PATIENTID.
Best of luck.

Select the records with the two codes, but use COUNT OVER to count the distinct codes per patient. Only keep those records with a count of 2 codes for the patient, i.e. patients with both codes.
select patient_id, last_name, first_name, code, visit_date
from
(
select mytable.*, count(distinct code) over (partition by patient_id) as cnt
from mytable
where code in ('ABC123','DEF456')
) data
where cnt = 2;

Related

Find records for students missing a class

I have two tables, one that flags a user as having passed a course, and a list of courses per job code. I'm trying to query to return a record for all users that are missing classes.
Here are the tables:
Attended
--------
empid jobcode classcode grade
555 1 100 A
555 1 101 A
444 2 200 A
JobClassCode
--------
jobcode classcode
1 100
1 101
1 102
2 100
2 200
3 300
3 301
I started with this query to find classes with a missing user:
select * from attended at
right outer join jobcodeclass jc on at.jobcode = jc.jobcode and at.classcode = jc.classcode
I then tried to take that to build a correlated subquery, but I don't see a way to return both the user ID and missing course ID:
select * from jobcodeclass oq where classcode in (select jc.classcode from attended at
right outer join jobcodeclass jc on at.jobcode = jc.jobcode
and at.classcode = jc.classcode and jc.jobcode = oq.jobcode
and oq.classcode = jc.classcode and empid is null)
Generate all the possible classes that each employee needs by joining on the jobcode. The see which ones the student attended:
select ej.empid, ej.jobcode, jss.classcode
from (select distinct empid, jobcode from attended) ej join
JobClassCode jcc
on jcc.jobcode = ej.jobcode left join
attended a
on a.empid= e.empid and a.jobcode = ej.jobcode and
a.classcode = jcc.classcode
where a.empid is null;
If you just need the employees, use select distinct ej.empid.

SQL Statement to Join Users and Last Login Date,Location

I have two tables, a list of Users, and a UserLoginHistory table that has their history of login dates, IP Addresses, and Geolocations.
I want to create one SQL statement that will return one each User + their last LoginDate and Geolocation.
Users.UserId, Users.Name
100 Bill
101 Steve
UserLoginHx.UserId, UserLoginHx.LoginDate, UserLoginLocation
100 1/1/2018 New York
101 1/1/2018 Los Angeles
100 1/4/2018 Chicago
101 1/5/2018 Denver
....
Result desired in this example should return two rows as:
100 Bill 1/4/2018 Chicago
101 Steve 1/5/2018 Denver
Thanks. (so far nobody got close)
Try below query:
select UserLoginHx.UserId,users.name,a.logindate,a.location from UserLoginHx
inner join
(select UserLoginHx.UserId,max(UserLoginHx.LoginDate) as logindate
from UserLoginHx
group by UserLoginHx.UserId)a on a.UserId=UserLoginHx.UserId and a.logindate=UserLoginHx.LoginDate
inner join Users on Users.UserId=UserLoginHx.UserId
SELECT u.UserId, u. Name, ulh.LoginDate, ulh.UserLoginLocation
FROM Users u
JOIN UserLoginHistory ulh ON u.UserId = ulh.UserId
A JOIN clause is used to combine rows from two or more tables, based on a related column between them.
so for you need join two tables
SELECT u.*, uh.LoginDate, uh.UserLoginLocation
FROM Users u
JOIN UserLoginHistory uh ON u.UserId = uh.UserId
Try the following query-:
with cte as
(
select a.*,LoginDate,UserLoginLocation,
ROW_NUMBER() over (partition by a.UserId order by Login desc) rn
from Users a
join UserLoginHistory b
on a.UserId=b.UserId
)select * from cte where rn=1
SQL Server

Join ruins the select

I have two tables that contains People who are working at the company and their Employment information (so People is one table, Employment is another).
The People table contains information on where the person lives, emergency contact, phone number bla bla bla. The Employment table contains information on where he works, closest boss and more.
These tables have been corrupted and now contains a few duplicates by misstake. Now in both tables there is a Person id, but the employment id is only located in Employment. I want both numbers on all people that have been duplicated.
This works perfectly:
SELECT DISTINCT
pp.Personid,
pp.Firstname,
pp.Lastname,
pp.Address,
FROM People pp
JOIN People pp2
ON pp.Firstname = pp2.Firstname
AND pp.Lastname = pp2.Lastname
AND pp.Address = pp2.Address
AND pp.Personid <> pp2.Personid
ORDER BY pp.Firstname, pp.Lastname, pp.Personid
returning the following values (but does not include Employment number as you can see):
1001 Carl Johnsson Bigstreet 1
1002 Carl Johnsson Bigstreet 1
1003 Carl Johnsson Bigstreet 1
1010 Andrew Wilkinsson Smallstreet 2
1011 Andrew Wilkinsson Smallstreet 2
Now, to add the employment id I join in that table like this:
SELECT DISTINCT
pp.Personid,
e.Employmentid,
pp.Firstname,
pp.Lastname,
pp.Address,
FROM People pp
JOIN People pp2
ON pp.Firstname = pp2.Firstname
AND pp.Lastname = pp2.Lastname
AND pp.Address = pp2.Address
AND pp.Personid <> pp2.Personid
JOIN Employment e on pp.Personid = e.Personid
ORDER BY pp.Firstname, pp.Lastname, pp.Personid
And everything goes to h**l in a handbasket with the following result:
1001 1111 Carl Johnsson Bigstreet 1
1001 1111 Carl Johnsson Bigstreet 1
1001 1111 Carl Johnsson Bigstreet 1
1010 1234 Andrew Wilkinsson Smallstreet 2
1010 1234 Andrew Wilkinsson Smallstreet 2
As you can see I get both Personid and Employmentid but now I only get one of each (repeated the correct number of times) so I don't have all the different Personid and Employmentid in my list.
Why?
What happened with my join that crashed the party?
Ok, let's make some sample data;
CREATE TABLE #People (PersonID int, FirstName varchar(50), LastName varchar(50), Address1 varchar(50))
INSERT INTO #People (PersonID, FirstName, LastName, Address1)
VALUES
('1','Mike','Hunt','Cockburn Crescent')
,('2','Mike','Hunt','Cockburn Crescent')
,('3','Mike','Hunt','Cockburn Crescent')
,('4','Connie','Lingus','Dyke Close')
,('5','Connie','Lingus','Dyke Close')
,('6','Eric','Shun','Tickle Avenue')
,('7','Ivana','Humpalot','Bottom Street')
CREATE TABLE #Employment (PersonID int, EmploymentID int)
INSERT INTO #Employment (PersonID, EmploymentID)
VALUES
('1','10')
,('2','11')
,('3','12')
,('4','13')
,('5','14')
,('6','15')
,('7','16')
I'd do the first query differently, if you work out the duplicates in a sub-select it would be easier, you'll then be able to join to the employment table with no problems;
SELECT pp.PersonID
,em.EmploymentID
,pp.FirstName
,pp.LastName
,pp.Address1
FROM #People pp
JOIN (
SELECT FirstName
,LastName
,Address1
,COUNT(1) records
FROM #People
GROUP BY FirstName
,LastName
,Address1
HAVING COUNT(1) > 1
) pp2 ON pp.FirstName = pp2.FirstName
AND pp.LastName = pp2.LastName
AND pp.Address1 = pp2.Address1
LEFT JOIN #Employment em ON pp.PersonID = em.PersonID
Remember to clean up the temp tables;
DROP TABLE #People
DROP TABLE #Employment
I think you should try this
SELECT DISTINCT
ep.Personid,
ep.Employementid,
ep.FirstName,
ep.LastName,
ep.Address
FROM Person P join
(SELECT
pp.Personid,
e.Employmentid,
pp.Firstname,
pp.Lastname,
pp.Address,
from PP
JOIN Employment e on pp.Personid = e.Personid ) ep
on
P.Firstname = ep.Firstname
AND P.Lastname = ep.Lastname
AND P.Address = ep.Address
AND P.Personid <> ep.Personid
ORDER BY P.Firstname, P.Lastname, P.Personid
Sir please Check and reply to me
Your code should work and I am unable to reproduce your issue using data I have made up. The outcome you are seeing suggests to me that there are multiple person id's for carl johnsson in the employment table and that the employmentid is different - even though it looks the same in the output.
Can you supply your table definitions and sample data?

Allow nulls / de-duplicate within multi-table join? T-SQL

I was wondering if there is a way in either SSIS or T-SQL (SQL Server 2012) to easily return non-duplicate data when doing a multi-table join (per-column, not per row)
I am trying to denormalize / flatten a bunch of data for conversion into a warehouse and I am winding up duplicating a ton of data. I'm hoping there is a sort of rollup/summary function or a design concept I am missing that can help me when merging multiple tables to a single destination.
Example
Let's say for example I have three tables: CUSTOMERS, CUSTOMER_ADDRESSES and CUSTOMER_ACCOUNTS. They and their data look like this:
CUSTOMERS
CUST_ID NAME
1 Burton Guster
CUSTOMER_ADDRESSES
CUST_ID ADDR_SEQ ADDRESS
1 1 123 Awesome St
1 2 456 Fake St
CUSTOMER_ACCOUNTS
CUST_ID ACCT_SEQ ACCT_TYPE ACCOUNT_OPEN_DT
1 1 TAP 1/1/1989
1 2 PHARMA 1/1/2010
I join them using a query like this:
SELECT a.CUST_ID, a.NAME, b.ADDRESS, c.ACCT_TYPE, c.ACCOUNT_OPEN_DT
FROM CUSTOMERS a
JOIN CUSTOMER_ADDRESSES b on a.CUST_ID = b.CUST_ID
JOIN CUSTOMER_ACCOUNTS c on a.CUST_ID = c.CUST_ID
Obviously each row joins to each row and as expected my output looks like this:
ID NAME ADDRESS ACCT_TYPE ACCT_OPEN_DT
1 Burton Guster 123 Awesome St TAP 1/1/1989
1 Burton Guster 123 Awesome St PHARMA 1/1/2010
1 Burton Guster 456 Fake St TAP 1/1/1989
1 Burton Guster 456 Fake St PHARMA 1/1/2010
Is there any way for me to get something like this instead?:
ID NAME ADDRESS ACCT_TYPE ACCT_OPEN_DT
1 Burton Guster 123 Awesome St TAP 1/1/1989
1 NULL 456 Fake St PHARMA 1/1/2010
The goal being to group each column, returning the distinct value per column only once. The larger set would be grouped by the customer ID.
Thank you
Sure, it can be done, although it's kinda awkward to do... :-)
You can use ROW_NUMBER() to get a running row number per costumer from each table independently. Then you can use these row numbers to bring the data together:
;WITH custCTE AS (
SELECT CUST_ID, NAME, 1 AS CUST_ROW_N
FROM CUSTOMERS
),
addrCTE AS (
SELECT CUST_ID, ADDRESS, ROW_NUMBER() OVER(PARTITION BY CUST_ID ORDER BY ADDR_SEQ) CUST_ROW_N
FROM CUSTOMER_ADDRESSES
),
acctCTE AS (
SELECT CUST_ID, ACCT_TYPE, ACCOUNT_OPEN_DT, ROW_NUMBER() OVER(PARTITION BY CUST_ID ORDER BY ACCT_SEQ) CUST_ROW_N
FROM CUSTOMER_ACCOUNTS
)
SELECT COALESCE(a.CUST_ID, b.CUST_ID, c.CUST_ID), a.NAME, b.ADDRESS, c.ACCT_TYPE, c.ACCOUNT_OPEN_DT
FROM custCTE a FULL JOIN addrCTE b ON
a.CUST_ID = b.CUST_ID AND a.CUST_ROW_N = b.CUST_ROW_N FULL JOIN acctCTE c ON
(b.CUST_ID = c.CUST_ID AND b.CUST_ROW_N = c.CUST_ROW_N) OR (a.CUST_ID = c.CUST_ID AND a.CUST_ROW_N = c.CUST_ROW_N)
Here's an SQLFiddle

In SQL how do I write a query to return 1 record from a 1 to many relationship?

Let's say I have a Person table and a Purchases table with a 1 to many relationship. I want to run a single query that returns this person and just their latest purchase. This seems easy but I just can't seem to get it.
select p.*, pp.*
from Person p
left outer join (
select PersonID, max(PurchaseDate) as MaxPurchaseDate
from Purchase
group by PersonID
) ppm
left outer join Purchase pp on ppm.PersonID = pp.PersonID
and ppm.MaxPurchaseDate = pp.PurchaseDate
where p.PersonID = 42
This query will also show the latest purchase for all users if you remove the WHERE clause.
Assuming you have something like a PurchaseDate column and want a particular person (SQL Server):
SELECT TOP 1 P.Name, P.PersonID, C.PurchaseDescription FROM Persons AS P
INNER JOIN Purchases AS C ON C.PersonID = P.PersonID
WHERE P.PersonID = #PersonID
ORDER BY C.PurchaseDate DESC
Many Databases preform the "Limit or Top" command in different ways. Here is a reference http://troels.arvin.dk/db/rdbms/#select-limit and below are a few samples
If using SQL Server
SELECT TOP 1
*
FROM Person p
INNER JOIN Purchases pc on pc.PersonID = P.PersonID
Order BY pc.PurchaseDate DESC
Should work on MySQL
SELECT
*
FROM Person p
INNER JOIN Purchases pc on pc.PersonID = P.PersonID
Order BY pc.PurchaseDate DESC
LIMIT 1
Strictly off the top of my head!...If it's only one record then...
SELECT TOP 1 *
FROM Person p
INNER JOIN Purchases pu
ON p.ID = p.PersonId
ORDER BY pu.OrderDate
WHERE p.ID = *thePersonYouWant*
otherwise...
SELECT TOP 1 *
FROM Person p
INNER JOIN
(
SELECT TOP 1 pu.ID
FROM Purchases pu
ON pu.PersonID = p.Id
ORDER BY pu.OrderDate
) sq
I think! I haven't got access to a SQL box right now to test it on.
Without knowing your structure at all, or your dbms, you would order the results descending by the purchase date/time, and return only the first joined record.
Try TOP 1 With an order by desc on date. Ex:
CREATE TABLE #One
(
id int
)
CREATE TABLE #Many
(
id int,
[date] date,
value int
)
INSERT INTO #One (id)
SELECT 1 UNION ALL
SELECT 2 UNION ALL
SELECT 3
INSERT INTO #Many (id, [date], value)
SELECT 1, GETDATE(), 1 UNION ALL
SELECT 1, DATEADD(DD, 1 ,GETDATE()), 3 UNION ALL
SELECT 1, DATEADD(DD, -1 ,GETDATE()), 0
SELECT TOP 1 *
FROM #One O
JOIN #Many M ON O.id = M.id
ORDER BY [date] DESC
If you want to select the latest purchase for each person, that would be:
SELECT PE.ID, PE.Name, MAx(PU.pucrhaseDate) FROM Persons AS PE JOIN PURCHASE as PU ON PE.ID = PU.Person_ID
If you want to have all persons also those who have no purchases, you need to use LEFT JOIN.
I think you need one more table called Items for example.
The PERSONS table would uniquely define each person and all their attributes, while the ITEMS table would uniquely define each items and their attributes.
Assume the following:
Persons |Purchases |Items
PerID PerName |PurID PurDt PerID ItemID |ItemID ItemDesc ICost
101 Joe Smith |201 101107 101 301 |301 Laptop 500
|202 101107 101 302 |302 Desktop 699
102 Jane Doe |203 101108 102 303 |303 iPod 199
103 Jason Tut |204 101109 101 304 |304 iPad 499
|205 101109 101 305 |305 Printer 99
One Person Parent may tie to none, one or many Purchase Child.
One Item Parent may tie to none, one or many Purchase Child.
One or more Purchases Children will tie to one Person Parent, and one Item Parent.
select per.PerName as Name
, pur.PurDt as Date
, itm.ItemDesc as Item
, itm.ICost as Cost
from Persons per
, Purchases pur
, Items itm
where pur.PerID = per.PerID -- For that Person
and pur.ItemID = itm.ItemID -- and that Item
and pur.PurDt = -- and the purchase date is
( Select max(lst.PurDt) -- the last date
from Purchases lst -- purchases
where lst.PerID = per.PerID ) -- for that person
This should return:
Name Date Item Cost
Joe Smith 101109 Ipad 499
Joe Smith 101109 Printer 99
Jane Doe 101108 iPod 199