How Do You Keep The "Pieces" Of A Complex Statement "Organized"

  • I just took a moment to get an appreciation of CTEs, found and article by Robert Sheldon, with examples that make it clear.

    Jeff - What do you mean by the "+1"?

    Paul - Can you give me an example of what you mean by 'break such queries up'? Maybe my example, or something simpler.

    I think I mentioned before that we created a 2nd DB, call it ERPPlus, to for our 'add-ons' to the ERP system. I do NOT want to change, in any way, what is in the DB that the software vendor has - nothing added, deleted, or modified (period!). Otherwise, they can claim we 'damaged' something and back out on resolving an issue. Since I can write a SQL statement in our ERPPlus which reference the ERP DB, I imagine I can create SP's and other objects that reference the ERP DB.

    Note - The SQL statement I included is probably a one-time request.

    I'm thinking forward now ... That said, it sounds like (correct me if I'm wrong) instead of creating a SQL statement like I did I can create an SP - including CTEs and other things I can't do within an Access Pass Through. The Access Pass Through would reference the SP?

    Now that I think about it ... As my Homework assignment ... Paul, If you meant CTEs by your 'break such queries up' then I could, modify my SQL statement and use CTEs. If you meant doing something else, then,

    (a) I can still create CTEs,

    (b) If you give me a little guidance I could modify my SQL statement to how you would construct it.

    I like this continued dialog - you all are "leading me to water". I appreciate your giving me the time and knowledge.

    [font="Verdana"]"The Road To Success Is Always Under Construction."[/font]

  • EdA ROC (9/24/2012)


    Jeff - What do you mean by the "+1"?

    Some forums allow you to rate an answer by clicking the "+1" button to add a vote of agreement. This one doesn't but a lot of us use "+1" as shorthand for "I agree, thanks for posting the answer, an/or I like 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)

  • EdA ROC (9/24/2012)


    Note - The SQL statement I included is probably a one-time request.

    I'm wondering how much code I've written that was a result of a one-time request that ended up running in Production for years. A lot I think.


    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

  • LOL - I know what you mean.

    If this customer follows their pattern, next year they will have a request with something else that is different. It's always been YTD Sales ... then the columns are different.

    However, the technique will be needed again, sooner or later.

    [font="Verdana"]"The Road To Success Is Always Under Construction."[/font]

  • EdA ROC (9/24/2012)


    Paul - Can you give me an example of what you mean by 'break such queries up'? Maybe my example, or something simpler.

    No, not CTEs; I'm referring to the idea of computing parts of the final result, storing them in temporary tables and performing a final query that operates on the partial results held in the temporary tables. If you can provide an execution plan from a real execution of your query* I might be able to put together an example for you. You won't be able to use it in a pass-through query though, as we discussed earlier.

    * Run the query with 'actual execution plan on', right-click on the graphical plan shown after the query completes, save it to a *.sqlplan file and attach it to your reply.

  • EdA ROC (9/24/2012)


    ...Paul - Can you give me an example of what you mean by 'break such queries up'? Maybe my example, or something simpler.

    I think I mentioned before that we created a 2nd DB, call it ERPPlus, to for our 'add-ons' to the ERP system. I do NOT want to change, in any way, what is in the DB that the software vendor has - nothing added, deleted, or modified (period!). Otherwise, they can claim we 'damaged' something and back out on resolving an issue. Since I can write a SQL statement in our ERPPlus which reference the ERP DB, I imagine I can create SP's and other objects that reference the ERP DB.

    Excellent - that means you can create an sp...

    I'm thinking forward now ... That said, it sounds like (correct me if I'm wrong) instead of creating a SQL statement like I did I can create an SP - including CTEs and other things I can't do within an Access Pass Through. The Access Pass Through would reference the SP?

    ...and yes, use SPT to call it.

    As an example of what Jeff and Paul meant by breaking up queries, your original query ain't too bad a place to start: lift out the whole left-joined derived table piece and resolve it out into a local temp table. The result set can be indexed just like a permanent table for performance;

    SELECT JobOpsList.JobNum

    ,CASE jobopslist.prestamp WHEN 0 THEN '' ELSE 'Y' END AS PreStamp

    ,CASE jobopslist.Decorative WHEN 0 THEN '' ELSE 'Y' END AS Decorative

    ,CASE jobopslist.Emboss WHEN 0 THEN '' ELSE 'Y' END AS Emboss

    INTO #OpsUsed

    FROM ( -- JobOpsList

    SELECT

    JobList.JobNum

    ,SUM(CASE ops.Mach_No WHEN 288 THEN 1 ELSE 0 END) AS PreStamp

    ,SUM(CASE ops.Mach_No WHEN 290 THEN 1 ELSE 0 END) AS Decorative

    ,SUM(CASE ops.Mach_No WHEN 292 THEN 1 ELSE 0 END) AS Emboss

    FROM ( -- JobList

    SELECT

    JOB_NUMBER AS JobNum,

    CASE ISNULL(o.COMBO_NO, '') WHEN '' THEN o.SPEC_NO ELSE o.COMBO_NO END AS MfrSpec

    FROM ORDERS o

    WHERE o.CSCODE = '2507'

    AND ORDER_DATE >= '1/1/2012'

    AND COMPLETION_FLG <> 'X'

    GROUP BY JOB_NUMBER,

    CASE ISNULL(o.combo_no, '') WHEN '' THEN o.SPEC_NO ELSE o.COMBO_NO END

    ) JobList

    LEFT JOIN OPERATIONS ops ON ops.SPEC_NO = JobList.MfrSpec

    WHERE ops.MACH_NO IN (288,290,292) -- turns LJ into INNER JOIN

    GROUP BY JobNum

    ) JobOpsList

    CREATE UNIQUE CLUSTERED INDEX UCX_JobNum ON #OpsUsed (JobNum)

    SELECT o.JOB_NUMBER AS Job#

    ,ORDER_NO AS Order#

    ,FORM_NO AS Form

    ,CUST_IDENT AS SpecID

    ,QTY_ORDERED AS QtyOrdered

    ,ORD_PRICE AS UnitPrice

    ,PRICING_METH AS UOM

    ,s.COLOR_DESC AS Colors

    ,opsused.PreStamp

    ,opsused.Decorative

    ,opsused.Emboss

    ,DESIGN_NO AS Drawing

    ,CAST(CAST(s.dim_A AS DECIMAL(9, 3)) AS VARCHAR(7)) + ' x ' + CAST(CAST(s.DIM_B AS DECIMAL(9, 3)) AS VARCHAR(7)) + ' x ' + CAST(CAST(s.DIM_C AS DECIMAL(9, 3)) AS VARCHAR(7)) AS Dimensions

    ,gf.DESCR AS Board

    ,DUE_DATE AS DueDate

    ,COMPLETION_FLG AS OrderStatus

    FROM ORDERS o

    LEFT JOIN SPECS s ON s.SPEC_NO = o.SPEC_NO

    LEFT JOIN GRADE_FILE gf ON gf.GRADE_CD = s.GRADE_CD

    LEFT JOIN #OpsUsed OpsUsed ON OpsUsed.JobNum = o.JOB_NUMBER

    WHERE o.CSCODE = '2507'

    AND ORDER_DATE >= '1/1/2012'

    AND COMPLETION_FLG <> 'X'

    Plonk this into a stored procedure and you’ve killed two birds with one stone – a performance lift, and a big improvement in readability.

    But that’s not all. Hoiking out that chunk of code makes it easier to see that the constraints upon the rows returned from table OPERATIONS (apart from the filter on ops.MACH_NO) already exist in the main body of the query. That means you can grab the data from OPERATIONS by correlating to ORDERS.COMBONO (or SPECNO) in the main query, something like this:

    SELECT o.JOB_NUMBER AS Job#

    ,ORDER_NO AS Order#

    ,FORM_NO AS Form

    ,CUST_IDENT AS SpecID

    ,QTY_ORDERED AS QtyOrdered

    ,ORD_PRICE AS UnitPrice

    ,PRICING_METH AS UOM

    ,s.COLOR_DESC AS Colors

    ,CASE jobopslist.prestamp WHEN 0 THEN '' ELSE 'Y' END AS PreStamp

    ,CASE jobopslist.Decorative WHEN 0 THEN '' ELSE 'Y' END AS Decorative

    ,CASE jobopslist.Emboss WHEN 0 THEN '' ELSE 'Y' END AS Emboss

    ,DESIGN_NO AS Drawing

    ,CAST(CAST(s.dim_A AS DECIMAL(9, 3)) AS VARCHAR(7)) + ' x ' + CAST(CAST(s.DIM_B AS DECIMAL(9, 3)) AS VARCHAR(7)) + ' x ' + CAST(CAST(s.DIM_C AS DECIMAL(9, 3)) AS VARCHAR(7)) AS Dimensions

    ,gf.DESCR AS Board

    ,DUE_DATE AS DueDate

    ,COMPLETION_FLG AS OrderStatus

    FROM ORDERS o

    LEFT JOIN SPECS s ON s.SPEC_NO = o.SPEC_NO

    LEFT JOIN GRADE_FILE gf ON gf.GRADE_CD = s.GRADE_CD

    CROSS APPLY (

    SELECT

    SUM(CASE ops.Mach_No WHEN 288 THEN 1 ELSE 0 END) AS PreStamp,

    SUM(CASE ops.Mach_No WHEN 290 THEN 1 ELSE 0 END) AS Decorative,

    SUM(CASE ops.Mach_No WHEN 292 THEN 1 ELSE 0 END) AS Emboss

    FROM OPERATIONS ops

    WHERE ops.SPEC_NO = CASE ISNULL(o.COMBO_NO, '') WHEN '' THEN o.SPEC_NO ELSE o.COMBO_NO

    AND ops.MACH_NO IN (288,290,292)

    ) jobopslist

    WHERE o.CSCODE = '2507'

    AND ORDER_DATE >= '1/1/2012'

    AND COMPLETION_FLG <> 'X'

    Right there, you’ve got a third reason for chunking a big query – it’s much easier to spot opportunities for simplification.

    “Write the query the simplest way. If through testing it becomes clear that the performance is inadequate, consider alternative query forms.” - Gail Shaw

    For fast, accurate and documented assistance in answering your questions, please read this article.
    Understanding and using APPLY, (I) and (II) Paul White
    Hidden RBAR: Triangular Joins / The "Numbers" or "Tally" Table: What it is and how it replaces a loop Jeff Moden

  • Wow! 🙂

    There's a lot I have to learn.

    Some of what you mentioned was over my head. That's OK, just letting you know.

    Did a cursory read of the Forum Etiquette. Will go over it again to comprehend the steps.

    Interesting, my initial question was to elicit how to organize statement construction to keep the pieces straight and it's morphed into all this - which is an answer to my question, just not what I expected. (ha, ha) Don't get me wrong, this is good, very good! I realize I'm about to take my skills up a (big) notch. I'm going to have to view this as a course/lesson and organize my time accordingly. This is definitely homework and not some quick and dirty thing I can squeeze in at work alone. So, please be patient as my responses may not come immediately.

    Have a fun day!

    [font="Verdana"]"The Road To Success Is Always Under Construction."[/font]

  • CELKO (9/27/2012)


    The only REFERENCES keyword I know is in creating foreign keys and since foreign keys can be NULL, I'm not sure how that would eliminate the LEFT OUTER JOIN. Unless of course your suggestion is to create the foreign key and make the column NOT NULL.

    Imagine you have a ZIP code column in a table, say, Customers. Instead of doing a LEFT OUTER JOIN to the table that matches a ZIP to a city_name and code, do a "zip_code REFERENCES Postal_Map (zip_code) ON UPDATE CASCADE" in the table. This will prevent bad or missing address data from getting into Customers. And it will save execution time and not block the optimizer.

    And you can do that in a SELECT? I've never come across that before.



    The opinions expressed herein are strictly personal and do not necessarily reflect the views or policies of my employer.

  • LightVader (9/27/2012)


    CELKO (9/27/2012)


    The only REFERENCES keyword I know is in creating foreign keys and since foreign keys can be NULL, I'm not sure how that would eliminate the LEFT OUTER JOIN. Unless of course your suggestion is to create the foreign key and make the column NOT NULL.

    Imagine you have a ZIP code column in a table, say, Customers. Instead of doing a LEFT OUTER JOIN to the table that matches a ZIP to a city_name and code, do a "zip_code REFERENCES Postal_Map (zip_code) ON UPDATE CASCADE" in the table. This will prevent bad or missing address data from getting into Customers. And it will save execution time and not block the optimizer.

    And you can do that in a SELECT? I've never come across that before.

    No... it's two parts. The LEFT OUTER JOIN is done in the SELECT. The part about "REFERENCES" is DRI in the form of an active foreign key that will automatically update the customer table if the ZipCode in the "Zip_Code" column of the "Postal_Map" table that Joe speaks of is ever updated.

    --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 (9/27/2012)


    LightVader (9/27/2012)


    CELKO (9/27/2012)


    The only REFERENCES keyword I know is in creating foreign keys and since foreign keys can be NULL, I'm not sure how that would eliminate the LEFT OUTER JOIN. Unless of course your suggestion is to create the foreign key and make the column NOT NULL.

    Imagine you have a ZIP code column in a table, say, Customers. Instead of doing a LEFT OUTER JOIN to the table that matches a ZIP to a city_name and code, do a "zip_code REFERENCES Postal_Map (zip_code) ON UPDATE CASCADE" in the table. This will prevent bad or missing address data from getting into Customers. And it will save execution time and not block the optimizer.

    And you can do that in a SELECT? I've never come across that before.

    No... it's two parts. The LEFT OUTER JOIN is done in the SELECT. The part about "REFERENCES" is DRI in the form of an active foreign key that will automatically update the customer table if the ZipCode in the "Zip_Code" column of the "Postal_Map" table that Joe speaks of is ever updated.

    That's what I originally thought - setting up a foreign key. And the question I was trying to ask in the first place (guess I didn't express that too well). It doesn't however eliminate a LEFT OUTER JOIN in all cases though.

    For example - in a parent/child relationship where the parent can have 0 or more children. If you're querying the parent but need a piece of information from the child, where available, an INNER JOIN can't do that for you. You would need either a LEFT OUTER JOIN or an INNER JOIN and a UNION.



    The opinions expressed herein are strictly personal and do not necessarily reflect the views or policies of my employer.

  • Stop The Presses... My Thread has been hijacked. 🙂

    Would you please take this REFERENCES conversation to another thread?

    [font="Verdana"]"The Road To Success Is Always Under Construction."[/font]

  • EdA ROC (9/27/2012)


    Stop The Presses... My Thread has been hijacked. 🙂

    Would you please take this REFERENCES conversation to another thread?

    Sorry about that. :blush: We don't need a new thread. I'm satisfied with the answer.



    The opinions expressed herein are strictly personal and do not necessarily reflect the views or policies of my employer.

Viewing 12 posts - 16 through 26 (of 26 total)

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