0

I have a query which checks if someone already filled in something in another table. It checks first if it is IN the table, and secondly after the UNION it checks if it is NOT in the table..

I built this query together with someone who knows a lot more about SQL then me. Now i'm wondering if this can be shortened?

I was thinking maybe with an CASE WHEN .. THEN 0 ELSE 1 in stead of checking with 2 queries and doing the union

SELECT
        l.id, 
        l.naam, 
        r.id as revisie, 
        r.beschrijving as revisiebeschrijving,
        "DONE"
    FROM lijsten l
    JOIN revisies r ON r.lijst_id = l.id
    JOIN werknemerlijsten wl ON wl.lijst_id = l.id
    WHERE
        wl.werknemer_id = :id AND r.actief = 1
        AND r.id IN 
        (
            SELECT
                    r2.id
                FROM revisies r2
                JOIN antwoorden a ON a.revisie_id = r2.id
                WHERE a.werknemer_id = wl.werknemer_id
        )
UNION
SELECT
        l.id, 
        l.naam, 
        r.id as revisie, 
        r.beschrijving as revisiebeschrijving,
        "NOT DONE"
    FROM lijsten l
    JOIN revisies r ON r.lijst_id = l.id
    JOIN werknemerlijsten wl ON wl.lijst_id = l.id
    WHERE
        wl.werknemer_id = :id AND r.actief = 1
        AND r.id NOT IN 
        (
            SELECT
                    r2.id
                FROM revisies r2
                JOIN antwoorden a ON a.revisie_id = r2.id
                WHERE a.werknemer_id = wl.werknemer_id
        )

Final Solution

this is the final solution, And the ORDER BY works in this version, without subquerys

SELECT l.id
 , l.naam
 , r.id as revisie
 , r.beschrijving as revisiebeschrijving
 , CASE WHEN a.werknemer_id IS NULL
        THEN 0
        ELSE 1 END AS ingevuld
FROM werknemerlijsten AS wl 
INNER  
JOIN lijsten AS l
ON l.id =  wl.lijst_id 
INNER
JOIN revisies AS r 
ON r.lijst_id = l.id
AND r.actief = 1
LEFT OUTER
JOIN antwoorden AS a 
 ON a.revisie_id = r.id
AND a.werknemer_id = wl.werknemer_id
WHERE wl.werknemer_id = :id
ORDER BY ingevuld
4

2 回答 2

1

You can use a LEFT JOIN instead of IN/NOT IN, this should perform better than a correlated subquery anyway. Then you can use a CASE statement to check whether there is a match in the LEFT JOIN or not to determine wether it should be DONE or NOT DONE.

SELECT  l.id, 
        l.naam, 
        r.id as revisie, 
        r.beschrijving as revisiebeschrijving,
        CASE WHEN a.ID IS NOT NULL THEN "DONE" ELSE "NOT DONE" END
FROM    lijsten l
        JOIN revisies r 
            ON r.lijst_id = l.id
        JOIN werknemerlijsten wl 
            ON wl.lijst_id = l.id
        LEFT JOIN
        (   SELECT  r2.id, a.werknemer_id
            FROM    revisies r2
                    JOIN antwoorden a 
                        ON a.revisie_id = r2.id
        ) a
            ON a.ID = r.ID
            AND a.werknemer_id = wl.werknemer_id
WHERE   wl.werknemer_id = :id 
AND     r.actief = 1;

I am not 100% sure, but I think this can be simplified further to this:

SELECT  l.id, 
        l.naam, 
        r.id as revisie, 
        r.beschrijving as revisiebeschrijving,
        CASE WHEN a.revisie_id IS NOT NULL THEN "DONE" ELSE "NOT DONE" END
FROM    lijsten l
        JOIN revisies r 
            ON r.lijst_id = l.id
        JOIN werknemerlijsten wl 
            ON wl.lijst_id = l.id
        LEFT JOIN antwoorden a 
            ON a.revisie_id = r.iD
            AND a.werknemer_id = wl.werknemer_id
WHERE   wl.werknemer_id = :id 
AND     r.actief = 1

This avoids subqueries and should give the same result, and avoiding subqueries will improve performance.

EDIT

If there are duplicates in antwoorden then you will need to add DISTINCT to remove them:

SELECT  l.id, 
        l.naam, 
        r.id as revisie, 
        r.beschrijving as revisiebeschrijving,
        CASE WHEN a.revisie_id IS NOT NULL THEN "DONE" ELSE "NOT DONE" END
FROM    lijsten l
        JOIN revisies r 
            ON r.lijst_id = l.id
        JOIN werknemerlijsten wl 
            ON wl.lijst_id = l.id
        LEFT JOIN
        (   SELECT  DISTINCT a.revisie_id, a.werknemer_id
            FROM    antwoorden a 
        ) a
            ON a.revisie_id= r.ID
            AND a.werknemer_id = wl.werknemer_id
WHERE   wl.werknemer_id = :id 
AND     r.actief = 1;

EDIT 2

In fact, I think the duplicate issue can be resolved without a subquery:

SELECT  l.id, 
        l.naam, 
        r.id as revisie, 
        r.beschrijving as revisiebeschrijving,
        CASE WHEN MAX(a.revisie_id) IS NOT NULL THEN "DONE" ELSE "NOT DONE" END AS ingevuld
FROM    lijsten l
        JOIN revisies r 
            ON r.lijst_id = l.id
        JOIN werknemerlijsten wl 
            ON wl.lijst_id = l.id
        LEFT JOIN antwoorden a 
            ON a.revisie_id = r.ID
            AND a.werknemer_id = wl.werknemer_id
WHERE   wl.werknemer_id = :id 
AND     r.actief = 1
GROUP BY l.id, l.naam, r.id, r.beschrijving
ORDER BY ingevuld;
于 2013-09-25T09:13:17.870 回答
1

Your guess is right this can be shortened using CASE WHEN...END, this way you can shorten the WHERE and also get rid of the UNION SELECT which could also provide some speed benefit. One quick rewrite could result in the following:

SELECT
        l.id, 
        l.naam, 
        r.id as revisie, 
        r.beschrijving as revisiebeschrijving,
        CASE WHEN
            (
                SELECT
                    r2.id
                FROM revisies r2
                JOIN antwoorden a ON a.revisie_id = r2.id
                WHERE
                    a.werknemer_id = wl.werknemer_id
                    AND r2.id = r.id
            ) IS NOT NULL THEN
            "DONE"
        ELSE
            "NOT DONE"
        END
    FROM lijsten l
    JOIN revisies r ON r.lijst_id = l.id
    JOIN werknemerlijsten wl ON wl.lijst_id = l.id
    WHERE
        wl.werknemer_id = :id AND r.actief = 1

As mentioned in the comments a LEFT JOIN would also be just fine here, so the subselect here in the CASE would then move down to a LEFT JOIN...

于 2013-09-25T09:15:07.753 回答