【问题标题】:How to reference columns by name based on comma separated value in another column?如何根据另一列中的逗号分隔值按名称引用列?
【发布时间】:2021-07-28 04:23:21
【问题描述】:

我有一个如下所示的表格:

PartNumber    Part    ValuesList    x1    x2    x3    x4
123456        Fender  x1            11    10    9     12
123456        Fender  x1,x2         11    10    9     12
123456        Fender  x2,x4         11    10    9     12
123456        Fender  x1,x2,x3,x4   11    10    11    12
123456        Fender  x2,x3,x4      11    10    9     12

对于每一行,如何根据 ValuesList 中给出的列名从 x1、x2、x3、x4 列中找到最小值?

例如,第 1 行很简单。 ValuesList 的值为 x1,所以我只需要查看 x1 列,最小值为 11。

第 2 行 .. ValuesList 的值为 x1,x2。我需要 x1 和 x2 列(11 和 10)之间的较小数字。最小值为 10。

第 3 行 .. ValuesList 的值为 x2,x4。我需要列 x2 和 x4(10 和 12)之间的较小数字。最小值为 10。

第 4 行 .. ValuesList 的值为 x1,x2,x3,x4。我需要 x1、x2、x3 和 x4 列(11、10、11、12)之间的较小数字。最低值为 10。

第 5 行 .. ValuesList 的值为 x2、x3、x4。我需要 x2、x3 和 x4 列(10、9、12)之间的较小数字。最低值为 9。

实际的表有数十万行。

在伪代码中,我想要类似的东西

SELECT PartNumber, Part, MIN(ColumnValue) WHERE ColumnName IN (SELECT ColumnNames FROM ValuesList)

在最低数字出现在多列的情况下,最低数字取自哪一列并不重要。

谢谢!

【问题讨论】:

  • 理想情况下你应该修复你的设计;在 RDBMS 中存储分隔数据始终是一个缺陷。存储多个具有相同名称的列也是如此,只是递增(在本例中为 x1x2 等)。如果您修复您的设计,这非常很容易。只需几个 JOINs 和一个聚合函数。

标签: sql sql-server tsql


【解决方案1】:

我将假设您在这里使用的是最新版本的 SQL Server,因此可以访问STRING_SPLIT。如果没有,那么您需要使用用户定义的字符串拆分器;如果您进行搜索,那里有很多(尽管我建议使用基于集合的搜索)。然后,在STRING_SPLIT 旁边,您可以取消透视数据,为此我使用了一个表VALUES 构造,在WHERE 中隐含JOIN,最后聚合:

CREATE TABLE dbo.YourTable (PartNumber int,
                            Part varchar(10),
                            ValuesList varchar(8000),
                            x1 int,
                            x2 int,
                            x3 int,
                            x4 int);
GO
INSERT INTO dbo.YourTable
VALUES(123456,'Fender','x1         ',11,10,9 ,12),
      (123456,'Fender','x1,x2      ',11,10,9 ,12),
      (123456,'Fender','x2,x4      ',11,10,9 ,12),
      (123456,'Fender','x1,x2,x3,x4',11,10,11,12),
      (123456,'Fender','x2,x3,x4   ',11,10,9 ,12);
GO

SELECT YT.PartNumber,
       YT.Part,
       YT.ValuesList,
       MIN(V.XVal) AS MinX
FROM dbo.YourTable YT
     CROSS APPLY STRING_SPLIT(YT.ValuesList,',') SS
     CROSS APPLY (VALUES(N'x1',x1),
                        (N'x2',x2),
                        (N'x3',x3),
                        (N'x4',x4))V(XCol,XVal)
WHERE SS.[value] = V.XCol
GROUP BY YT.PartNumber,
         YT.Part,
         YT.ValuesList;
GO

DROP TABLE dbo.YourTable;

当然,我真的建议你在这里规范你的设计。不要存储分隔数据,也不要重复列值。如果您标准化您的设计,您可能只需要几个 JOINs 和一个聚合。

【讨论】:

    【解决方案2】:

    我强烈建议不要使用外部 group by

    SELECT t.*, s.min_val
    FROM t OUTER APPLY
         (SELECT MIN(val) as min_val
          FROM STRING_SPLIT(YT.ValuesList,',') s JOIN
               (VALUES('x1', x1), ('x2', x2), ('x3', x3),('x4', x4)
               )v(col, val)
               ON s.value = v.name
         ) s;
    

    对一行的值使用APPLY 通常比聚合整个数据集具有更好的性能特征。

    【讨论】:

      【解决方案3】:

      只是另一种选择

      示例

      Select A.PartNumber
            ,A.Part
            ,A.ValuesList
            ,B.MinValue 
       From  YourTable A
       Cross Apply ( Select MinValue=min(Value)
                      From  (values (case when charindex('x1',ValuesList)>0 then x1 end)
                                   ,(case when charindex('x2',ValuesList)>0 then x2 end)
                                   ,(case when charindex('x3',ValuesList)>0 then x3 end)
                                   ,(case when charindex('x4',ValuesList)>0 then x4 end)
                            ) B1(Value)
                   )B
      

      结果

      PartNumber  Part    ValuesList  MinValue
      123456      Fender  x1          11
      123456      Fender  x1,x2       10
      123456      Fender  x1,x2,x3,x4 10
      123456      Fender  x2,x3,x4    9
      123456      Fender  x2,x4       10
      

      【讨论】:

        猜你喜欢
        • 2022-06-23
        • 1970-01-01
        • 1970-01-01
        • 2020-01-21
        • 1970-01-01
        • 2021-12-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多