Kinyanjui Kamau
Kinyanjui Kamau

Reputation: 1986

How to modify an SQL query with sub query to get an extra column

I am trying to modify a user query in order to return an extra column, INV1.WhsCode from the following SQL query:

SELECT  T0.CardCode,
        T2.CardName,
        T0.CodeBars,
        T0.ItemCode,
        T0.ItemName,
        T3.Price AS [POS Price],
        T1.AvgPrice,
        T1.OnHand,
        T1.MinStock,
        T1.MaxStock,
        T0.NumInBuy AS Packsize,
        T0.LstSalDate,
        (
            SELECT SUM(Quantity) AS Expr1
            FROM dbo.INV1
            INNER JOIN OINV
                    ON INV1.DocEntry = OINV.DocEntry
            WHERE INV1.ItemCode = T0.ItemCode
                    AND INV1.WhsCode = [%2]
                    AND Month(OINV.DocDate) = month(GetDate())
        ) AS [Current Period],
        (
            SELECT SUM(Quantity) AS Expr1
            FROM dbo.INV1
            INNER JOIN OINV
                    ON INV1.DocEntry = OINV.DocEntry
            WHERE INV1.ItemCode = T0.ItemCode
                    AND INV1.WhsCode = [%2]
                    AND Month(OINV.DocDate) = month(GetDate()) - 1
        ) AS [Previous Period],
        (
            SELECT SUM(Quantity) AS Expr1
            FROM dbo.INV1
            INNER JOIN OINV
                    ON INV1.DocEntry = OINV.DocEntry
            WHERE INV1.ItemCode = T0.ItemCode
                    AND INV1.WhsCode = [%2]
                    AND Month(OINV.DocDate) = month(GetDate()) - 2
        ) AS [60-90],
        (
            SELECT TOP 1 OPDN.DocDate AS Expr1
            FROM dbo.PDN1
            INNER JOIN OPDN
                    ON PDN1.DocEntry = OPDN.DocEntry
            WHERE PDN1.ItemCode = T0.ItemCode
            ORDER BY OPDN.DocDate DESC
        ) AS LastGRNDate
FROM    OITM T0
        INNER JOIN OITW T1
            ON T0.ItemCode = T1.ItemCode
        INNER JOIN OCRD T2
            ON T0.CardCode = T2.CardCode
        INNER JOIN ITM1 T3
            ON T0.ItemCode = T3.ItemCode
        INNER JOIN OWHS T4
            ON T1.WhsCode = T4.WhsCode
        INNER JOIN OITB T5
            ON T0.ItmsGrpCod = T5.ItmsGrpCod
WHERE   T3.PriceList = '3'
        AND T4.WhsName = [%0]
        AND T5.ItmsGrpNam = [%1]

How do I achieve this? (MS SQL Server 2008)

Upvotes: 0

Views: 556

Answers (2)

GarethD
GarethD

Reputation: 69799

There are a number of things wrong/inefficient about your query which I know you didn't ask about, but I am going to answer anyway because it will help answer the question you did ask.

You need to avoid correlated subqueries where possible, there are times that they are unavoidable and the best solution, however I so often see them in a place where a JOIN would do the same job and the optimiser will deal with join so much better. For instance you have:

SELECT  (
            SELECT TOP 1 OPDN.DocDate AS Expr1
            FROM dbo.PDN1
            INNER JOIN OPDN
                    ON PDN1.DocEntry = OPDN.DocEntry
            WHERE PDN1.ItemCode = T0.ItemCode
            ORDER BY OPDN.DocDate DESC
        ) AS LastGRNDate
FROM    OITM T0

This evaluates the subquery for each row, whereas if you re-wrote as so:

SELECT  LastGRN.LastGRNDate
FROM    OITM TO
        LEFT JOIN
        (   SELECT  PDN1.ItemCode, [LastGRNDate] = MAX(OPDN.DocDate)
            FROM    dbo.PDN1
                    INNER JOIN OPDN
                        ON PDN1.DocEntry = OPDN.DocEntry
            GROUP BY PDN1.ItemCode
        ) LastGRN
            ON LastGRN.ItemCode = T0.ItemCode

you would get the same result, but evaluated in a much more efficent manner.

The next fault is your method of using MONTH(GETDATE()) - 1 to get 2 months ago. In January this will evauluate to 0 and get no matches. The best way to do this is to convert each date to the first of each month using something akin to his:

SELECT  [FirstOfThisMonth] = DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()), 0),
        [FirstOfLastMonth] = DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE() - 1), 0)

The same principle of joins rather than correlated subqueries can also be applied to your quantity columns, and this gives access to the WhsCode columns, it is not necessary, but I have used a common table expression to clean up the query (using the date logic from above)

WITH Quantities AS
(   SELECT  [DocMonth] = DATEADD(MONTH, DATEDIFF(MONTH, 0, IONV.DocDate),
            Inv1.WhsCode,
            ItemCode,
            [Quantity] = SUM(Quantity)
    FROM    dbo.Inv1
            INNER JOIN OINV
                ON Inv1.DocEntry = OINV.DocEntry
    GROUP BY DATEADD(MONTH, DATEDIFF(MONTH, 0, IONV.DocDate), WhsCode, itemCode
)
SELECT  T0.ItemCode,
        [Current Period] = COALESCE(Cur.Quantity, 0),
        [Previous Period] = COALESCE(prev.Quantity, 0),
        [60-90] = COALESCE(prev2.Quantity, 0)
FROM    OITM T0
        LEFT JOIN Quantities cur
            ON cur.ItemCode = T0.ItemCode
            AND cur.DocDate = DATEADD(MONTH, DATEDIFF(MONTH, 0, CURRENT_TIMESTAMP), 0)
            AND Cur.WhsCode = [%2]
        LEFT JOIN Quantities prev
            ON prev.ItemCode = T0.ItemCode
            AND prev.DocDate = DATEADD(MONTH, DATEDIFF(MONTH, 0, CURRENT_TIMESTAMP) - 1, 0)
            AND prev.WhsCode = [%2]
        LEFT JOIN Quantities prev2
            ON prev2.ItemCode = T0.ItemCode
            AND prev2.DocDate = DATEADD(MONTH, DATEDIFF(MONTH, 0, CURRENT_TIMESTAMP) - 2, 0)
            AND prev2.WhsCode = [%2]

Combining all this into your final query gives:

SELECT  T0.CardCode,
        T2.CardName,
        T0.CodeBars,
        T0.ItemCode,
        T0.ItemName,
        T3.Price,
        T1.AvgPrice,
        T1.OnHand,
        T1.MinStock,
        T1.MaxStock,
        T0.NumInBuy AS Packsize,
        T0.LstSalDate
        [Current Period] = COALESCE(Cur.Quantity, 0),
        [Previous Period] = COALESCE(prev.Quantity, 0),
        [60-90] = COALESCE(prev2.Quantity, 0)
        LastGRN.LastGRNDate
FROM    OITM T0
        INNER JOIN OITW T1
            ON T0.ItemCode = T1.ItemCode
        INNER JOIN OCRD T2
            ON T0.CardCode = T2.CardCode
        INNER JOIN ITM1 T3
            ON T0.ItemCode = T3.ItemCode
        INNER JOIN OWHS T4
            ON T1.WhsCode = T4.WhsCode
        INNER JOIN OITB T5
            ON T0.ItmsGrpCod = T5.ItmsGrpCod
        LEFT JOIN Quantities cur
            ON cur.ItemCode = T0.ItemCode
            AND cur.DocDate = DATEADD(MONTH, DATEDIFF(MONTH, 0, CURRENT_TIMESTAMP), 0)
            AND Cur.WhsCode = [%2]
        LEFT JOIN Quantities prev
            ON prev.ItemCode = T0.ItemCode
            AND prev.DocDate = DATEADD(MONTH, DATEDIFF(MONTH, 0, CURRENT_TIMESTAMP) - 1, 0)
            AND prev.WhsCode = [%2]
        LEFT JOIN Quantities prev2
            ON prev2.ItemCode = T0.ItemCode
            AND prev2.DocDate = DATEADD(MONTH, DATEDIFF(MONTH, 0, CURRENT_TIMESTAMP) - 2, 0)
            AND prev2.WhsCode = [%2]
        LEFT JOIN
        (   SELECT  PDN1.ItemCode, [LastGRNDate] = MAX(OPDN.DocDate)
            FROM    dbo.PDN1
                    INNER JOIN OPDN
                        ON PDN1.DocEntry = OPDN.DocEntry
            GROUP BY PDN1.ItemCode
        ) LastGRN
            ON LastGRN.ItemCode = T0.ItemCode
WHERE   T3.PriceList = '3'
AND     T4.WhsName = [%0]
AND     T5.ItmsGrpNam = [%1]

This is all untested so there may be some typos/slight syntax errors, but the same principals should still apply.

If you are still set on the idea of correlated subqueries, you could use APPLY to allow you to access multiple columns from it. e.g.

SELECT  T0.Code, cur.WhsCode, cur.Expr1 AS [Current Period]
FROM    OITM T0
        OUTER APPLY
        (   SELECT  INV1.WhsCode, SUM(Quantity) AS Expr1
            FROM    dbo.INV1
                    INNER JOIN OINV
                        ON INV1.DocEntry = OINV.DocEntry
            WHERE   INV1.ItemCode = T0.ItemCode
            AND     INV1.WhsCode = [%2]
            AND     MONTH(OINV.DocDate) = MONTH(GETDATE())
        ) cur

Upvotes: 1

Sebastian Meine
Sebastian Meine

Reputation: 11813

All your subqueries contain in the where clause the following: INV1.WhsCode = [%2]

That means instead of INV1.WhsCode you can just return [%2] like this:

SELECT  [%2] as WhsCode,
        T0.CardCode,
        T2.CardName,
        T0.CodeBars,
......

This assumes that [%2] and the other similar tokens get replaced by the app with something SQL Server understands, before this gets send to get executed.

Upvotes: 1

Related Questions