Search code examples
sql-serverstored-procedurespivotsql-server-2014-express

SQL Server: Select Varying Number Of Values Based On Column Value


Version: Microsoft SQL Server 2014 - 12.0.2000.8 (X64) Feb 20 2014 20:04:26 Copyright (c) Microsoft Corporation Express Edition (64-bit) on Windows NT 6.1 (Build 7601: Service Pack 1)

I need to select a varying number of values from a table where a certain column is equal to a parameter and a certain column is LIKE 'String1' or 'String2'.

I have created a stored procedure that is returning the MAX and MIN strings, but naturally this method is not dynamic.

I have tried the following query which says it completes successfully, but does not return any results.

SELECT UPC, PartNum, PartDesc
FROM dbo.table
WHERE UPC = @upc
GROUP BY UPC, PartNum, PartDesc
HAVING PartDesc in ('%RED%','%BLUE%')
ORDER BY PartDesc; 

Example table:

ID          UPC       PartNum      PartDesc
-------------------------------------------
1           123        543         Red1
2           123        345         Blue1
3           123        654         Red2
4           123        765         Blue2

I need to pass a parameter to a stored procedure as @upc from an application.

Where it will find any PartDesc that are like '%RED%' or '%BLUE%' AND where the UPC = @upc. Then store the Part#(s) found in a new table to be queried later.

Created Table From Stored Procedure:

ID    UPC    Red1    Red2    Blue1    Blue2
----------------------------------------------------------
1     123    543     654     345      765

There can be any number or combination of "Red" or "Blue" per UPC number. i.e., Some UPC numbers may only have two "Red" parts and one "Blue" part and others may only have two "Red" parts and no "Blue" parts. Maybe five "Red" parts and ten "Blue" parts.

How do would I write the query that will store the varying number of found results to a new table in a stored procedure?

Edit It seems as though the PIVOT function should be used, but I am unsure of how to use the required aggregate in my scenario. I don't need to pivot on the "SUM" of PartDesc or any other column for that matter. Perhaps a dynamic Pivot?

EDIT Based on Corgi's recommendation. Also, showing my work.

DECLARE @upc As varchar(13)
DECLARE @Red1 As nvarchar(100) =    CASE
                                        WHEN
                                                (
                                                    SELECT MIN(PartNum) FROM dbo.table
                                                    WHERE PartDesc LIKE '%RED%' AND UPC = @upc
                                                ) IS NOT NULL THEN

                                                (                                               
                                                    SELECT MIN(PartNum) FROM dbo.table
                                                    WHERE PartDesc LIKE '%RED%' AND UPC = @upc
                                                ) 

                                        ELSE 'Not Found'
                                     END

DECLARE @Red2 As nvarchar(100) =    CASE
                                        WHEN
                                                (
                                                    SELECT MAX(PartNum) FROM dbo.table
                                                    WHERE PartDesc LIKE '%RED%' AND UPC = @upc
                                                ) IS NOT NULL THEN

                                                (                                               
                                                    SELECT MAX(PartNum) FROM dbo.table
                                                    WHERE PartDesc LIKE '%RED%' AND UPC = @upc
                                                ) 

                                        ELSE 'Not Found'
                                     END

DECLARE @Blue1 As nvarchar(100) =   CASE
                                        WHEN
                                                (
                                                    SELECT MAX(PartNum) FROM dbo.table
                                                    WHERE PartDesc LIKE '%BLUE%' AND UPC = @upc
                                                ) IS NOT NULL THEN

                                                (                                               
                                                    SELECT MAX(PartNum) FROM dbo.table
                                                    WHERE PartDesc LIKE '%BLUE%' AND UPC = @upc
                                                ) 

                                        ELSE 'Not Found'
                                     END

;WITH MostColumns AS
(
    SELECT UPC, @Red1 As Part1, @Red2 As Part2, @Blue1 As Part3
    FROM   (SELECT UPC, PartNum, PartDesc
            FROM   dbo.table) AS source
           PIVOT
           (MIN(PartNum) FOR PartDesc IN ([Part1], [Part2], [Part3])) AS pvt
)
SELECT MIN(p.ID) AS ID, p.UPC, mc.Part1, mc.Part2, mc.Part3
INTO   MyNewTable
FROM   dbo.table p
INNER JOIN MostColumns mc ON p.UPC = mc.UPC
GROUP BY p.UPC, mc.Part1, mc.Part2, mc.Part3

Result:

ID      UPC       Part1           Part2            Part3
2876    123      Not Found      Not Found         Not Found
2758    213      Not Found      Not Found         Not Found
2321    312      Not Found      Not Found         Not Found
802     321      Not Found      Not Found         Not Found
868     132      Not Found      Not Found         Not Found

This is the correct format, but no cigar. I know for a fact, that all of my UPCs contain atleast one Red1 part. For some reason, it did not find any of the parts.

EDIT--ANSWER @Corgi After more research on dynamic pivots I arrived at this solution. I will still need to build on it to make it operate the way I need it to. Although, those are not relevant to this question. Thank you @bluefeet for your answer in this post. SQL Dynamic Pivot

DECLARE @cols AS NVARCHAR(MAX),
    @query  AS NVARCHAR(MAX)

select @cols = STUFF((SELECT distinct ',' 
                        + QUOTENAME('Part_' + cast(rn as varchar(10))) 
                    from dbo.table
                    cross apply
                    (
                      select row_number() over(partition by UPC order by PartNum) rn
                      from dbo.table
                    ) x
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')

set @query = 'SELECT UPC, ' + @cols + ' from 
             (
                select UPC, PartNum,
                  ''Component_''
                    + cast(row_number() over(partition by UPC order by PartNum) as varchar(10)) val
                from dbo.table
            ) x
            pivot 
            (
                max(PartNum)
                for val in (' + @cols + ')
            ) p '

execute(@query)

Solution

  • Your observation about PIVOT is on track, but you can't actually use PIVOT without specifying the output column names (that is, the values from PartDesc). It sounds like, because there can be a varying number of these PartDesc values, the closest you can get would be to find all the values:

    SELECT DISTINCT t.PartDesc
    FROM   MyTable t
    WHERE  t.PartDesc LIKE '%Red%' OR t.PartDesc LIKE '%Blue%'
    

    Then you can use the values to build your query. If you really have to have the query be dynamic, you would need to construct a query string to use with something like sp_executesql. The way you would create the table from the output is by using SELECT... INTO within your dynamic query.

    The PIVOT syntax you need, combined with SELECT... INTO, might look something like:

    ;WITH MostColumns AS
    (
        SELECT UPC, Red1, Red2, Blue1, Blue2
        FROM   (SELECT UPC, PartNum, PartDesc
                FROM   dbo.table) AS source
               PIVOT
               (MIN(PartNum) FOR PartDesc IN ([Red1], [Red2], [Blue1], [Blue2])) AS pvt
    )
    SELECT MIN(p.ID) AS ID, p.UPC, mc.Red1, mc.Red2, mc.Blue1, mc.Blue2
    INTO   MyNewTable
    FROM   dbo.table p
    INNER JOIN MostColumns mc ON p.UPC = mc.UPC
    GROUP BY p.UPC, mc.Red1, mc.Red2, mc.Blue1, mc.Blue2
    

    The MostColumns common table expression is there because it doesn't work well to have ID in your original query -- it's an "extra" column that is not part of the pivot.