【问题标题】:SQL Server 2017 - Dynamically generate a string based on the number of columns in another stringSQL Server 2017 - 根据另一个字符串中的列数动态生成一个字符串
【发布时间】:2019-03-06 16:17:52
【问题描述】:

我有以下表格和数据:

CREATE TABLE dbo.TableMapping 
(
[GenericMappingKey] [nvarchar](256) NULL,
[GenericMappingValue] [nvarchar](256) NULL,
[TargetMappingKey] [nvarchar](256) NULL,
[TargetMappingValue] [nvarchar](256) NULL
)

INSERT INTO dbo.TableMapping 
(
     [GenericMappingKey]
    ,[GenericMappingValue]
    ,[TargetMappingKey]
    ,[TargetMappingValue]
)
VALUES
( 
     'Generic' 
    ,'Col1Source|Col1Target;Col2Source|Col2Target;Col3Source|Col3Target;Col4Source|Col4Target;Col5Source|Col5Target;Col6Source|Col6Target'
    ,'Target'
    ,'Fruit|Apple;Car|Red;House|Bungalo;Gender|Female;Material|Brick;Solution|IT'
)

我需要能够根据 TargetMappingValue 列中的列对数自动生成我的 GenericMappingValue 字符串。

目前,有 6 个列映射对。但是,如果我的 TargetMapping 中只有两个映射列对,例如以下...

'Fruit|Apple;Car|Red'

然后我希望自动生成(更新)GenericMappingValue,如下所示,因此,我的字符串中只有 2 列对...

'Col1Source|Col1Target;Col2Source|Col2Target'

我已经开始构建以下查询逻辑:

DECLARE @Mapping nvarchar(256)
SELECT @Mapping = [TargetMappingValue] from TableMapping
print @Mapping
SELECT count(*) ColumnPairCount
FROM String_split(@Mapping, ';')

上述查询为我的列对提供了正确的 6 计数。

我怎样才能继续我的逻辑来实现我自动生成的映射字符串?

【问题讨论】:

    标签: string tsql dynamic count sql-server-2017


    【解决方案1】:

    我想我明白你在追求什么。这应该会让你朝着正确的方向前进。

    由于您已标记 2017,您可以使用 STRING_AGG()

    • 您需要在子查询中使用 STRING_SPLIT() 和 ROW_NUMER() 来拆分 TargetMappingValue。 (注意:我们不能保证此处使用带有 ROW_NUMBER 的 string_split() 的顺序,但适用于这种情况。如果我们需要确保准确的顺序,下面的示例使用 OPENJSON。)
    • 然后您可以使用该 ROW_NUMBER() 作为 CONCAT() 中的列指示符/编号。
    • 然后使用STRING_AGG() 将它们重新组合在一起

    看看这个工作示例:

    DECLARE @TableMapping TABLE
        (
            [GenericMappingKey] [NVARCHAR](256) NULL
          , [GenericMappingValue] [NVARCHAR](256) NULL
          , [TargetMappingKey] [NVARCHAR](256) NULL
          , [TargetMappingValue] [NVARCHAR](256) NULL
        );
    
    INSERT INTO @TableMapping (
                                  [GenericMappingKey]
                                , [GenericMappingValue]
                                , [TargetMappingKey]
                                , [TargetMappingValue]
                              )
    VALUES ( 'Generic'
           , 'Col1Source|Col1Target;Col2Source|Col2Target;Col3Source|Col3Target;Col4Source|Col4Target;Col5Source|Col5Target;Col6Source|Col6Target'
           , 'Target'
           , 'Fruit|Apple;Car|Red;House|Bungalo;Gender|Female;Material|Brick;Solution|IT' );
    
    
    
    SELECT   [col].[GenericMappingKey]
           , STRING_AGG(CONCAT('Col', [col].[ColNumber], 'Source|Col', [col].[ColNumber], 'Target'), ';') AS [GeneratedGenericMappingValue]
           , [col].[TargetMappingKey]
           , [col].[TargetMappingValue]
    FROM     (
                 SELECT      *
                           , ROW_NUMBER() OVER ( ORDER BY (
                                                              SELECT 1
                                                          )
                                               ) AS [ColNumber]
                 FROM        @TableMapping
                 CROSS APPLY STRING_SPLIT([TargetMappingValue], ';')
             ) AS [col]
    GROUP BY [col].[GenericMappingKey]
           , [col].[TargetMappingKey]
           , [col].[TargetMappingValue];
    

    以下是假设您的主键是 GenericMappingKey 列的更新示例:

    --This what an update would look like
    --Assuming your primary key is the [GenericMappingKey] column
    UPDATE     [upd]
    SET        [upd].[GenericMappingValue] = [g].[GeneratedGenericMappingValue]
    FROM       (
                   SELECT   [col].[GenericMappingKey]
                          , STRING_AGG(CONCAT('Col', [col].[ColNumber], 'Source|Col', [col].[ColNumber], 'Target'), ';') AS [GeneratedGenericMappingValue]
                          , [col].[TargetMappingKey]
                          , [col].[TargetMappingValue]
                   FROM     (
                                SELECT      *
                                          , ROW_NUMBER() OVER ( ORDER BY (
                                                                             SELECT 1
                                                                         )
                                                              ) AS [ColNumber]
                                FROM        @TableMapping
                                CROSS APPLY [STRING_SPLIT]([TargetMappingValue], ';')
                            ) AS [col]
                   GROUP BY [col].[GenericMappingKey]
                          , [col].[TargetMappingKey]
                          , [col].[TargetMappingValue]
               ) AS [g]
    INNER JOIN @TableMapping [upd]
        ON [upd].[GenericMappingKey] = [g].[GenericMappingKey];
    

    Shnugo 在 cmets 中提出了一个很好的观点,即我们不保证使用 string_split() 和使用行号的排序顺序。在这种特殊情况下,通用的输出映射无关紧要。但是,如果您需要使用最终“GenericMappingValue”中“TargetMappingValue”列中的元素,那么您需要确保排序顺序准确无误。

    这是一个示例,展示了如何使用 OPENJSON(),它是使用 Shnugo 示例来保证订单的“关键”:

    SELECT   [col].[GenericMappingKey]
           , STRING_AGG(CONCAT('Col', [col].[colNumber], 'Source|Col', [col].[colNumber], 'Target'), ';') AS [GeneratedGenericMappingValue]
           , [col].[TargetMappingKey]
           , [col].[TargetMappingValue]
    FROM     (
                 SELECT      [tm].*
                           , [oj].[Key] + 1 AS [colNumber] --Use the key as our order/column number, adding 1 as it is zero based.
                           , [oj].[Value] -- and if needed we can bring the split value out.
                 FROM        @TableMapping [tm]
                 CROSS APPLY OPENJSON('["' + REPLACE([tm].[TargetMappingValue], ';', '","') + '"]') [oj] --Basically turn the column value into JSON string.
             ) AS [col]
    GROUP BY [col].[GenericMappingKey]
           , [col].[TargetMappingKey]
           , [col].[TargetMappingValue];
    

    【讨论】:

    • 顺便说一句:STRING_SPLIT() 不保证返回预期的排序顺序。因此,您的 ORDER BY SELECT 1 可能在所有测试中都有效,但在生产中失败 - 但是...您可能会 read this answer 关于使用 OPENJSON 的字符串拆分解决方案解决此问题...
    • @Shnugo 感谢您的反馈。注意到,因为我没有考虑到这一点。考虑到这一点,在这种特殊情况下,只要我们获得相同数量的映射对,顺序并不重要,因为它们想要生成的映射是通用的。
    • @Shnugo 再次感谢,我更新了我的答案以指出这一点,并包含一个使用 OPENJSON 的示例。这是一个不错的技巧。
    • 太好了。您已经完全彻底地解决了我的问题!
    【解决方案2】:

    如果数据已经在表中,并且您想将其分成列,这应该可以工作

    select 
         v.value
        ,left(v.value, charindex('|',v.value) -1) col1
        ,reverse(left(reverse(v.value), charindex('|',reverse(v.value)) -1)) col2 
    from String_split(@mapping,';') v
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-08-16
      • 1970-01-01
      • 2021-06-15
      • 1970-01-01
      • 2015-06-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多