Peter Larsson Blog

Patron Saint of Lost Yaks

EXISTS is my new favorite tool

Since this blog post http://weblogs.sqlteam.com/peterl/archive/2007/09/24/Finding-group-of-records-with-a-certain-status.aspx I have kept the technique in similar situations and perhaps never bothered to rethink my prerogative.

Well, today I have. Recently I blogged here http://weblogs.sqlteam.com/peterl/archive/2009/06/12/Timings-of-different-techniques-for-finding-missing-records.aspx
 about finding records in one table which are missing in another table. In that blog post, I noticed that NOT EXISTS were the better choice for all scenarios, and have learnt to adopt the NOT EXISTS approach.

Why haven't I extended that knowledge to the "Certain status" scenario? I can't tell. And that is a good thing.
Because being active on a good forum now and then makes you rethink your positions. As I did today.

Here is the test case from which I now have decided to use NOT EXISTS in the "Certain status" cases from now on.


CREATE TABLE    #Patients
                (
                    PatientID SMALLINT
                )

INSERT #Patients
        (
            PatientID
        )

SELECT  Number
FROM    master..spt_values
WHERE   Type = 'P'

CREATE UNIQUE CLUSTERED INDEX IX_Patients ON #Patients(PatientID)

CREATE TABLE    #Medications
                (
                    PatientID SMALLINT,
                    MedicinID SMALLINT
                ) 

INSERT          #Medications
                (
                    PatientID,
                    MedicinID
                )
SELECT DISTINCT ABS(CHECKSUM(NEWID())) % 2048,
                ABS(CHECKSUM(NEWID())) % 32768
FROM            master..spt_values AS v1
INNER JOIN      master..spt_values AS v2 ON v2.Type = 'P'
WHERE           v1.Type = 'P'

CREATE UNIQUE CLUSTERED INDEX IX_Medications ON #Medications(PatientID, MedicinID)

CREATE NONCLUSTERED INDEX UX_Medications ON #Medications(MedicinID)
INCLUDE(PatientID)

CREATE NONCLUSTERED INDEX YX_Medications ON #Medications(PatientID)
INCLUDE(MedicinID)

-- NOT EXISTS, my new favorite
SELECT  p.PatientID
FROM    #Patients AS p
WHERE   NOT EXISTS (
                        SELECT  * 
                        FROM    #Medications AS m
                        WHERE   m.PatientID = p.PatientID
                                AND m.MedicinID = 0
                    )

-- HAVING, my old favorite (Peso 1)
SELECT      p.PatientID
FROM        #Patients AS p
INNER JOIN  (
                SELECT      PatientID
                FROM        #Medications
                GROUP BY    PatientID
                HAVING      MAX(CASE WHEN MedicinID = 0 THEN 1 ELSE 0 END) = 0
            ) AS x ON x.PatientID = p.PatientID

-- Peso 2
SELECT p.PatientID
FROM    #Patients AS p
WHERE   EXISTS (
                   SELECT 0

                   EXCEPT

                   SELECT m.MedicinID
                   FROM    #Medications AS m
                   WHERE   m.PatientID = p.PatientID
               )

-- Peso 3
SELECT      p.PatientID
FROM        #Patients AS p
LEFT JOIN   #Medications AS m ON m.PatientID = p.PatientID
                AND m.MedicinID = 0
WHERE       m.PatientID IS NULL

-- Peso 4
SELECT  PatientID,
        0
FROM    #Patients

EXCEPT

SELECT  PatientID,
        MedicinID
FROM    #Medications

-- Peso 5
SELECT  p.PatientID
FROM    #Patients AS p
WHERE   p.PatientID NOT IN (
                               SELECT  m.PatientID
                               FROM    #Medications AS m
                               WHERE   m.MedicinID = 0
                                       AND m.PatientID = p.PatientID
                           )

DROP TABLE  #Patients,
            #Medications