【问题标题】:HOW TO query multi-valued column by key in SQL SERVER如何在 SQL SERVER 中按键查询多值列
【发布时间】:2019-08-23 17:02:19
【问题描述】:

如何在multivalued 列中查询特定键?

样本数据

ID   DAY              PRICE
1    01;02;03;04;...  100;230;110;34.5;...
2    01;02;03;04;...  120;240;510;34.5;...

例如:

选择 ... 其中 DAY 键 = '02'

预期:

ID DAY PRICE
1  02  230
2  02  240

备注

实际的表格有 30 多个字段。

加入多个 CROSS APPLY SPLIT_STRING 看起来像是一个乏味的解决方案

【问题讨论】:

  • 如果是你的,2016+ 版本有一个名为 STRING_SPLIT 的函数。
  • @BarbarosÖzhan 我试过了。您需要逐个字段重新构建源表,然后加入。实际的表有 30 多个字段......我希望有一种更聪明的方法来做到这一点
  • @CharlesOkwuagwu,最聪明的方法是将详细信息存储在单独的表中。如果无法重构 db,请使用 [DelimitedSplit8K_LEAD] sp 与 STRING_SPLIT 不同,它返回编号的令牌,因此您可以轻松地通过令牌编号匹配两 (30) 列中的令牌。
  • 不过,理想情况下,您应该通过规范化而不是在单个值中存储多个值来修复您的设计。
  • 繁琐的解决方案通常是您为糟糕的数据库设计付出的代价。

标签: sql sql-server split multivalue


【解决方案1】:

此解决方案使用 cte,我希望它适用于您:

with cte1 as
( 
select id, value daykey,
 row_number() over(order by (select null)) as rowid
from mvct
cross apply string_split(day, ";")
),
cte2 as
(
select id, value pricekey,
 row_number() over(order by (select null)) as rowid
from mvct
cross apply string_split(price, ";")
)
select cte1.id, cte1.daykey, cte2.pricekey
from cte1
inner join cte2 on cte1.id = cte2.id
and cte1.rowid = cte2.rowid
and cte1.daykey = "02"

【讨论】:

  • 谢谢,我最终使用了这个的变体——这是最简单的解决方案
【解决方案2】:

这里有一个选项可以动态地对您的数据进行反透视(实际上不使用动态 SQL),然后对结果进行透视。

您只需在 for Item in (...) 部分列出 30 列

Cross Apply B会将ROW转换成XML

Cross Apply CUNPIVOT XML

Cross Apply D将从C中解析/拆分分隔字符串(带有序列)

那么对于PIVOT来说就是小事了

示例

Declare @YourTable Table ([ID] varchar(50),[DAY] varchar(50),[PRICE] varchar(50))
Insert Into @YourTable Values 
 (1,'01;02;03;04','100;230;110;34.5')
,(2,'01;02;03;04','120;240;510;34.5')

Select *
 From  (
        Select A.ID
              ,C.Item
              ,D.*
         From  @YOurTable A
         Cross Apply ( values (cast((Select A.* for XML RAW) as xml))) B(XMLData)
         Cross Apply (
                        Select Item  = xAttr.value('local-name(.)', 'varchar(100)')
                              ,Value = xAttr.value('.','varchar(max)')
                         From  XMLData.nodes('//@*') xNode(xAttr)
                         Where xAttr.value('local-name(.)','varchar(100)') not in ('Id','Other-Columns','To-Exclude')
                     ) C
         Cross Apply (
                        Select RetSeq = row_number() over (order by (Select null))
                              ,RetVal = ltrim(rtrim(B.i.value('(./text())[1]', 'varchar(100)')))
                        From  ( values (cast('<x>' + replace(C.Value,';','</x><x>')+'</x>' as xml))) as A(x)
                        Cross Apply x.nodes('x') AS B(i)
                     ) D
        ) src
 Pivot (max(RetVal) for Item in ([Day],[Price]) ) pvt
 Where Day='02'

退货

ID  RetSeq  Day Price
1   2       02  230
2   2       02  240

【讨论】:

    【解决方案3】:

    您可以在https://www.sqlservercentral.com/articles/reaping-the-benefits-of-the-window-functions-in-t-sql-2找到原始的DelimitedSplit8k_LEAD功能代码

    2列演示

    select id, details.[day], details.price
    from (
         values
         (1,'01;02;03;04','100;230;110;34.5')
        ,(2,'01;02;03;04','120;240;510;34.5')
    ) t (ID,[Day],Price)
    cross apply (
        select d.item [day], p.item price
        from DelimitedSplit8k_LEAD([Day],';') d
        join DelimitedSplit8k_LEAD(Price,';') p on d.ItemNumber = p.ItemNumber
    ) details
    

    【讨论】:

      【解决方案4】:

      如果您可以在数据库中创建额外的 FUNCTION,则可以使用以下脚本来获得所需的输出。

      创建函数:

      CREATE FUNCTION FIND_CHARINDEX
      (@TargetStr   VARCHAR(8000), 
       @SearchedStr VARCHAR(8000), 
       @Occurrence  INT
      )
      RETURNS INT
      AS
           BEGIN
               DECLARE @pos INT, @counter INT, @ret INT;
               SET @pos = CHARINDEX(@TargetStr, @SearchedStr);
               SET @counter = 1;
               IF @Occurrence = 1
                   SET @ret = @pos;
                   ELSE
                   BEGIN
                       WHILE(@counter < @Occurrence)
                           BEGIN
                               SELECT @ret = CHARINDEX(@TargetStr, @SearchedStr, @pos + 1);
                               SET @counter = @counter + 1;
                               SET @pos = @ret;
               END;
               END;
               RETURN(@ret);
           END;
      

      SELECT 语句:

      DECLARE @S_String VARCHAR(20) = '05'
      DECLARE @S_String_New VARCHAR(20) = ';'+@S_String+';'
      
      SELECT 
      REVERSE(
          SUBSTRING(
              REVERSE(
                  SUBSTRING(
                      ';'+Day+';',
                      0, 
                      (CHARINDEX(@S_String_New,';'+Day+';',0)+LEN(@S_String)) +1
                  )
              ),
              0,
              CHARINDEX(
                  ';',
                  REVERSE(SUBSTRING(';'+Day+';',0, (CHARINDEX(@S_String_New,';'+Day+';',0)+LEN(@S_String)) +1)),
                  0
              )
          )
      ),
      
      REVERSE(
          SUBSTRING(
              REVERSE(
                  SUBSTRING(
                      ';'+PRICE+';',
                      0,    
                      (
                          dbo.FIND_CHARINDEX(
                              ';',
                              ';'+PRICE+';',
                              (
                                  LEN(SUBSTRING(';'+Day+';',0, (CHARINDEX(@S_String_New,';'+Day+';',0)+LEN(@S_String)) +1)) 
                                      - LEN(REPLACE(SUBSTRING(';'+Day+';',0,  (CHARINDEX(@S_String_New,';'+Day+';',0)+LEN(@S_String))  +1),';',''))+1
                              )
                          )
                      )
                  )
              ),
              0,
              CHARINDEX(
                  ';',
                  REVERSE(
                      SUBSTRING(
                          ';'+PRICE+';',
                          0,    
                          (
                              dbo.FIND_CHARINDEX(
                                  ';',
                                  ';'+PRICE+';',   
                                  (
                                      LEN(SUBSTRING(';'+Day+';',0, (CHARINDEX(@S_String_New,';'+Day+';',0)+LEN(@S_String)) +1)) 
                                          - LEN(REPLACE(SUBSTRING(';'+Day+';',0,  (CHARINDEX(@S_String_New,';'+Day+';',0)+LEN(@S_String))  +1),';',''))+1
                                  )
                              )
                          )
                      )
                  ),
                  1
              )
          )
      )
      FROM your_table
      WHERE ';'+Day+';' LIKE '%'+@S_String_New+'%'
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-05-07
        • 1970-01-01
        • 1970-01-01
        • 2021-05-16
        • 2023-03-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多