QUESTION ABOUT MULTIPLE EXCEPT STATEMENTS

  • i created a CTE which finds a subset of records from a table

    I then ran a SELECT statement against the same table as

    SELECT * FROM TABLE

    EXCEPT (SELECT * FROM CTE)

    Is it possible to add another EXCEPT statement after the CTE EXCEPT statement to cover

    a condition not incorporated in the CTE definition?

  • It's possible, but you need to be sure that you're applying the correct precedence.

    From BOL:

    If EXCEPT or INTERSECT is used together with other operators in an expression, it is evaluated in the context of the following precedence:

    Expressions in parentheses

    The INTERSECT operator

    EXCEPT and UNION evaluated from left to right based on their position in the expression

    Luis C.
    General Disclaimer:
    Are you seriously taking the advice and code from someone from the internet without testing it? Do you at least understand it? Or can it easily kill your server?

    How to post data/code on a forum to get the best help: Option 1 / Option 2
  • For fun and furthering on Luis's answer, this examples brings back all primes between 10 and 100 using except.

    😎

    USE Test;

    GO

    SET NOCOUNT ON;

    /*

    For fun only, selecting all primes between ten and one hundred

    using except.

    */

    DECLARE @SAMPLE_SIZE INT = 100;

    ;WITH T(N) AS (SELECT N FROM (VALUES (NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL)) AS X(N))

    , NUMS(N) AS (SELECT TOP (@SAMPLE_SIZE) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS N FROM T T1,T T2,T T3,T T4,T T5,T T6,T T7,T T8,T T9)

    ,EVERY_OTHER(N) AS

    (

    SELECT

    NM.N

    FROM NUMS NM

    WHERE NM.N % 2 = 0

    )

    ,EVERY_THIRD(N) AS

    (

    SELECT

    NM.N

    FROM NUMS NM

    WHERE NM.N % 3 = 0

    )

    ,EVERY_FOURTH(N) AS

    (

    SELECT

    NM.N

    FROM NUMS NM

    WHERE NM.N % 4 = 0

    )

    ,EVERY_FIFTH(N) AS

    (

    SELECT

    NM.N

    FROM NUMS NM

    WHERE NM.N % 5 = 0

    )

    ,EVERY_SIXTH(N) AS

    (

    SELECT

    NM.N

    FROM NUMS NM

    WHERE NM.N % 6 = 0

    )

    ,EVERY_SEVENTH(N) AS

    (

    SELECT

    NM.N

    FROM NUMS NM

    WHERE NM.N % 7 = 0

    )

    ,EVERY_EIGHTH(N) AS

    (

    SELECT

    NM.N

    FROM NUMS NM

    WHERE NM.N % 8 = 0

    )

    ,EVERY_NINETH(N) AS

    (

    SELECT

    NM.N

    FROM NUMS NM

    WHERE NM.N % 9 = 0

    )

    SELECT

    NM.N AS TRAN_ID

    FROM NUMS NM

    WHERE NM.N > 10

    EXCEPT

    (SELECT * FROM EVERY_OTHER)

    EXCEPT

    (SELECT * FROM EVERY_THIRD)

    EXCEPT

    (SELECT * FROM EVERY_FOURTH)

    EXCEPT

    (SELECT * FROM EVERY_FIFTH)

    EXCEPT

    (SELECT * FROM EVERY_SIXTH)

    EXCEPT

    (SELECT * FROM EVERY_SEVENTH)

    EXCEPT

    (SELECT * FROM EVERY_EIGHTH)

    EXCEPT

    (SELECT * FROM EVERY_NINETH)

    ;

    Output

    TRAN_ID

    11

    13

    17

    19

    23

    29

    31

    37

    41

    43

    47

    53

    59

    61

    67

    71

    73

    79

    83

    89

    97

  • I'm amazed at this question for 2 reasons...

    1) The coverage for such a question in Books Online is not obvious. You actually have to infer that such a capability exists by reading the remarks. There is no example of using more than two sets of data and even the syntax example doesn't show that such a thing is possible.

    2) That, not withstanding, I don't understand why people post such questions instead of just trying it. It takes longer to post a question on this subject than it does to write code to try it.

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

  • Jeff Moden (5/17/2015)


    I don't understand why people post such questions instead of just trying it.

    That's what I would do.

    Of course, then Eirikur would have no fun posting up an example using prime numbers (very entertaining).


    My mantra: No loops! No CURSORs! No RBAR! Hoo-uh![/I]

    My thought question: Have you ever been told that your query runs too fast?

    My advice:
    INDEXing a poor-performing query is like putting sugar on cat food. Yeah, it probably tastes better but are you sure you want to eat it?
    The path of least resistance can be a slippery slope. Take care that fixing your fixes of fixes doesn't snowball and end up costing you more than fixing the root cause would have in the first place.

    Need to UNPIVOT? Why not CROSS APPLY VALUES instead?[/url]
    Since random numbers are too important to be left to chance, let's generate some![/url]
    Learn to understand recursive CTEs by example.[/url]
    [url url=http://www.sqlservercentral.com/articles/St

  • Eirikur Eiriksson (5/17/2015)


    For fun and furthering on Luis's answer, this examples brings back all primes between 10 and 100 using except.

    😎

    USE Test;

    GO

    SET NOCOUNT ON;

    /*

    For fun only, selecting all primes between ten and one hundred

    using except.

    */

    DECLARE @SAMPLE_SIZE INT = 100;

    ;WITH T(N) AS (SELECT N FROM (VALUES (NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL)) AS X(N))

    , NUMS(N) AS (SELECT TOP (@SAMPLE_SIZE) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS N FROM T T1,T T2,T T3,T T4,T T5,T T6,T T7,T T8,T T9)

    ,EVERY_OTHER(N) AS

    (

    SELECT

    NM.N

    FROM NUMS NM

    WHERE NM.N % 2 = 0

    )

    ,EVERY_THIRD(N) AS

    (

    SELECT

    NM.N

    FROM NUMS NM

    WHERE NM.N % 3 = 0

    )

    ,EVERY_FOURTH(N) AS

    (

    SELECT

    NM.N

    FROM NUMS NM

    WHERE NM.N % 4 = 0

    )

    ,EVERY_FIFTH(N) AS

    (

    SELECT

    NM.N

    FROM NUMS NM

    WHERE NM.N % 5 = 0

    )

    ,EVERY_SIXTH(N) AS

    (

    SELECT

    NM.N

    FROM NUMS NM

    WHERE NM.N % 6 = 0

    )

    ,EVERY_SEVENTH(N) AS

    (

    SELECT

    NM.N

    FROM NUMS NM

    WHERE NM.N % 7 = 0

    )

    ,EVERY_EIGHTH(N) AS

    (

    SELECT

    NM.N

    FROM NUMS NM

    WHERE NM.N % 8 = 0

    )

    ,EVERY_NINETH(N) AS

    (

    SELECT

    NM.N

    FROM NUMS NM

    WHERE NM.N % 9 = 0

    )

    SELECT

    NM.N AS TRAN_ID

    FROM NUMS NM

    WHERE NM.N > 10

    EXCEPT

    (SELECT * FROM EVERY_OTHER)

    EXCEPT

    (SELECT * FROM EVERY_THIRD)

    EXCEPT

    (SELECT * FROM EVERY_FOURTH)

    EXCEPT

    (SELECT * FROM EVERY_FIFTH)

    EXCEPT

    (SELECT * FROM EVERY_SIXTH)

    EXCEPT

    (SELECT * FROM EVERY_SEVENTH)

    EXCEPT

    (SELECT * FROM EVERY_EIGHTH)

    EXCEPT

    (SELECT * FROM EVERY_NINETH)

    ;

    Output

    TRAN_ID

    11

    13

    17

    19

    23

    29

    31

    37

    41

    43

    47

    53

    59

    61

    67

    71

    73

    79

    83

    89

    97

    No need to account for multiples of values divisible by a lower group. For this example all you need to exclude are evens (/ 2), thirds (/ 3), fifths (/ 5), and sevenths (/ 7).

  • Jeff Moden (5/17/2015)


    I'm amazed at this question for 2 reasons...

    1) The coverage for such a question in Books Online is not obvious. You actually have to infer that such a capability exists by reading the remarks. There is no example of using more than two sets of data and even the syntax example doesn't show that such a thing is possible.

    2) That, not withstanding, I don't understand why people post such questions instead of just trying it. It takes longer to post a question on this subject than it does to write code to try it.

    I certainly agree with 2. But then I disagree with 1, I think Books Online is clear, so 2 for me amounts to checking that books online isn't telling me something wrong.

    The syntax definition

    { <query_specification> | ( <query_expression> ) }

    { EXCEPT | INTERSECT }

    { <query_specification> | ( <query_expression> ) }

    is pretty straightfiorward - it specifies how EXCEPT or INTERSECT is used in a query, so quite clearly what it defines is either a query_specification or a query_expression, so whichever it is the bit before the EXCEPT | INTERSECT shown can contain EXCEPT | INTERSECT, and so can the bit after. So chain together as many as you like, but do note that the nesting is ambiguous unless you want flat left to right so use brackets if you don't (that bit is only clear if you read the additional text, the syntax definition doesn't constrain it).

    Tom

  • Just to point out an alternative, instead of using multiple EXCEPTs, each with a SELECT pulling a set you'd like to exclude, you could also just UNION the SELECTs in one EXCEPT. Interestingly, at least in a couple quick tests I threw together, I get slightly different plans with slightly different performance characteristics for each.

    Cheers!

  • dwain.c (5/17/2015)


    Jeff Moden (5/17/2015)


    I don't understand why people post such questions instead of just trying it.

    That's what I would do.

    Of course, then Eirikur would have no fun posting up an example using prime numbers (very entertaining).

    Spot on there Dwain, I think I have somewhat of a set based humor:-D and as the "primer" shows, now with a mixture of the English sarcasm;-)

    😎

  • Jacob Wilkins (5/18/2015)


    Just to point out an alternative, instead of using multiple EXCEPTs, each with a SELECT pulling a set you'd like to exclude, you could also just UNION the SELECTs in one EXCEPT. Interestingly, at least in a couple quick tests I threw together, I get slightly different plans with slightly different performance characteristics for each.

    Cheers!

    Yes, but sometimes an EXCEPT is there to indicate things you don't want to exclude rather that to pull a set you want to exclude, and then teh multiple EXCEPTs cant'be reduced to just one by USING UNION.

    SELECT X from A EXCEPT (SELECT X from B EXCEPT SELECT X from C)

    is a nice simple example of that.

    Tom

  • @tom: Absolutely, but from the OP's post, it seemed he was wanting a way to exclude another set in addition to the set returned by the CTE, in which case my point holds.

    Of course if he's wanting to nest EXCEPTs, as in your example, instead of simply using them to indicate additional excluded sets, as in his post and Eirikur's prime numbers example, then everything changes 🙂

  • In Ben-Gan's T-SQL Fundamentals 2012 (Ch. 6) he dedicates a whole chapter to set operators. I would encourage anyone to trying to get a grasp on this topic to take a look at that as he does a much better job explaining them, precedence, how to manipulate set operator precedence, etc than BOL IMHO. He includes some great examples and explains things in a way that I have never seen set operators explained before (e.g. referring to UNION as UNION DISTINCT.) That's my 2 cents.

    "I cant stress enough the importance of switching from a sequential files mindset to set-based thinking. After you make the switch, you can spend your time tuning and optimizing your queries instead of maintaining lengthy, poor-performing code."

    -- Itzik Ben-Gan 2001

Viewing 12 posts - 1 through 11 (of 11 total)

You must be logged in to reply to this topic. Login to reply