【问题标题】:SQL Server: Select Varying Number Of Values Based On Column ValueSQL Server:根据列值选择不同数量的值
【发布时间】:2017-05-04 15:06:04
【问题描述】:

版本:Microsoft SQL Server 2014 - 12.0.2000.8 (X64) Feb 20 2014 20:04:26 版权所有 (c) Microsoft Corporation Express Edition (64-bit) o​​n Windows NT 6.1 (Build 7601: Service Pack 1)

我需要从表中选择不同数量的值,其中某一列等于一个参数,并且某一列是 LIKE 'String1' 或 'String2'。

我创建了一个返回 MAX 和 MIN 字符串的存储过程,但这个方法自然不是动态的。

我尝试了以下查询,它说它成功完成,但没有返回任何结果。

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

示例表:

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

我需要从应用程序将参数作为@upc 传递给存储过程。

它将在哪里找到类似于“%RED%”或“%BLUE%”的任何 PartDesc,并且 UPC = @upc。 然后将找到的 Part#(s) 存储在新表中以供稍后查询。

从存储过程创建表:

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

每个 UPC 编号可以有任意数量或“红色”或“蓝色”的组合。 IE。, 一些 UPC 编号可能只有两个“红色”部分和一个“蓝色”部分,而其他 UPC 编号可能只有两个“红色”部分而没有“蓝色”部分。也许五个“红色”部分和十个“蓝色”部分。

如何编写将不同数量的找到结果存储到存储过程中的新表的查询?

编辑 似乎应该使用 PIVOT 函数,但我不确定如何在我的场景中使用所需的聚合。为此,我不需要关注 PartDesc 的“SUM”或任何其他列。 也许是动态枢轴?

编辑基于 Corgi 的建议。另外,展示我的作品。

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

结果:

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

这是正确的格式,但不是雪茄。我知道一个事实,我所有的 UPC 至少包含一个 Red1 部分。由于某种原因,它没有找到任何部件。

编辑--回答 @Corgi 在对动态枢轴进行更多研究后,我得出了这个解决方案。我仍然需要在它的基础上进行构建,以使其按照我需要的方式运行。虽然,这些与这个问题无关。 感谢@bluefeet 在这篇文章中的回答。 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)

【问题讨论】:

    标签: sql-server stored-procedures pivot sql-server-2014-express


    【解决方案1】:

    您对PIVOT 的观察正常,但如果不指定输出列名(即来自PartDesc 的值),您实际上无法使用PIVOT。听起来,因为这些 PartDesc 值的数量可能不同,所以您可以获得的最接近的值是找到所有值:

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

    然后您可以使用这些值来构建您的查询。如果您确实必须让查询是动态的,则需要构造一个查询字符串以与sp_executesql 之类的内容一起使用。从输出创建表的方式是在动态查询中使用SELECT... INTO

    您需要的PIVOT 语法与SELECT... INTO 结合起来可能类似于:

    ;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
    

    MostColumns 公用表表达式之所以存在,是因为在您的原始查询中包含 ID 效果不佳 - 它是一个“额外”列,不是数据透视表的一部分。

    【讨论】:

    • 谢谢柯基犬。如果我的值是静态的,您的最后一个示例似乎会起作用,不是吗?我玩过它,输出就是我需要的,但是,我只需要一个“UPC”号码,然后枚举任何可用的“红色”和“蓝色”部分。我将通过另一个应用程序请求的“UPC”传递一个“@upc”参数。然后它应该使用“@upc”参数来查找任何/所有“红色”和“蓝色”部分,然后使用结果以及“@upc”参数更新另一个表。我经常遇到“子查询返回太多结果”错误,但我实际上需要这些结果。
    • @SethJohnson 你说得对,如果值是静态的,最后一个例子应该可以工作。真正处理动态值的唯一方法是将查询连接在一起作为NVARCHAR(MAX) 变量,然后将查询作为参数运行EXEC sp_executesql。连接字符串以进行查询具有安全隐患(SQL 注入),因此请确保使用 QUOTENAME 正确引用所有列名。在不相关的说明中,COALESCE 可以消除其中一些 CASE 语句。