【问题标题】:T-SQL Merge Two Comma-Separated ColumnsT-SQL 合并两个逗号分隔的列
【发布时间】:2016-06-08 18:27:13
【问题描述】:

我正在尝试将一个表合并到另一个控制用户及其权限的表(我们将它们称为 Stage 和 Prod)。我的最终结果应该是一个单一的 Prod 表,它将每个用户 ID 的权限从 Stage 组合到 Prod 中。我遇到的问题是这些表格是由外部供应商设计的,并且在一个逗号分隔的列中包含多条信息。

阶段可能如下所示:

Userid     | Permissions
----------------------------------------------------------------
1          | schedule,upload,test,download,admin
2          | test,upload
3          | download

产品:

Userid     | Permissions
----------------------------------------------------------------
1          | test,admin,schedule,download,upload
2          | admin
3          | download,upload

合并后,用户 ID 应具有来自 Stage 的权限,以及与 Prod 中的权限。但是,当权限是逗号分隔的字符串时解决这个问题让我束手无策。

在下面的最终结果中,userid 1 的权限保持不变,因为它们在 Stage 和 Prod 中的权限相同,只是顺序不同。 Userid 2 将他的 Stage 权限添加到他的 Prod 中,因为他还没有这些权限。 Userid 3 的 Prod 权限未更改,因为他的 Stage 权限已包含在内。

结果:

Userid     | Permissions
----------------------------------------------------------------
1          | test,admin,schedule,download,upload
2          | admin,test,upload
3          | download,upload

有没有办法做到这一点?希望这是有道理的,但如果有更多信息可能会有所帮助,我很乐意提供它。非常感谢您的帮助。

【问题讨论】:

  • blog.sqlauthority.com/2015/04/21/… 将它们拆分成一个普通列表,联合(联合执行不同)来自两个环境的结果,然后将它们组合回来。虽然你为什么要以这种方式存储数据超出了我的理解。
  • 你关心他们最终的顺序吗?我同意 xQbert 的观点,如果有另一种存储方式会更好。此外,如果有预先确定的可能权限列表,还有一种比拆分字符串更简单的方法。
  • @xQbert 我绝对同意有更好的方法来存储这些数据,不幸的是这是一个供应商构建的表,我们无法真正更改其架构。
  • @DaveX 他们进入的顺序无关紧要,那些权限测试、管理、计划、下载、上传是唯一的权限。谢谢你们和大家的意见,我会试一试并标记答案。

标签: sql-server tsql merge


【解决方案1】:

有趣的是,这是Aaron BertrandMSSQLTips blog 上讨论的话题。借用他的代码,您可以创建完成以下工作所需的Numbers 表和字符串拆分/重组函数。如果您打算经常这样做并且被您所展示的架构所困扰,那么这就是您要走的路。

/*Create Test Data

create table StagePermissions (UserID int, [Permissions] nvarchar(max));
create table ProdPermissions (UserID int, [Permissions] nvarchar(max));

insert StagePermissions values
(1,'schedule,upload,test,download,admin'),
(2,'test,upload'),
(3,'download')

insert ProdPermissions values
(1,'test,admin,schedule,download,upload'),
(2,'admin'),
(3,'download,upload')
*/

select sp.UserID, dbo.ReassembleString(sp.Permissions+','+pp.Permissions,',',N'OriginalOrder') MergedPermissions
from StagePermissions sp
    join ProdPermissions pp on pp.UserID=sp.UserID

【讨论】:

    【解决方案2】:

    获取史蒂夫的测试数据,但添加:

    create table BothPermissions (UserID int, [Permissions] nvarchar(max));
    

    此代码将使用固定数量的可能权限。

    DECLARE @XPermissions TABLE (
         UserID int
        ,XSchedule  BIT
        ,XUpload    BIT
        ,XTest      BIT
        ,XDownload  BIT
        ,XAdmin     BIT
        )
    
    INSERT INTO @XPermissions
        SELECT
            ISNULL(sp.UserID,pp.UserID),
            CHARINDEX('schedule',sp.[Permissions]) + CHARINDEX('schedule',pp.[Permissions]),
            CHARINDEX('upload',sp.[Permissions]) + CHARINDEX('upload',pp.[Permissions]),
            CHARINDEX('test',sp.[Permissions]) + CHARINDEX('test',pp.[Permissions]),
            CHARINDEX('download',sp.[Permissions]) + CHARINDEX('download',pp.[Permissions]),
            CHARINDEX('admin',sp.[Permissions]) + CHARINDEX('admin',pp.[Permissions])
    
        FROM StagePermissions sp
            FULL JOIN ProdPermissions pp
                ON sp.UserID = pp.UserID
    
    INSERT INTO BothPermissions
        SELECT
            UserID,
            CASE XSchedule WHEN 0 THEN '' ELSE 'schedule  ' END +
            CASE XUpload WHEN 0 THEN '' ELSE 'upload  ' END +
            CASE XTest WHEN 0 THEN '' ELSE 'test  ' END +
            CASE XDownload WHEN 0 THEN '' ELSE 'download  ' END +
            CASE XAdmin WHEN 0 THEN '' ELSE 'admin' END
        FROM @XPermissions
    
    UPDATE BothPermissions
        SET [Permissions] = REPLACE(RTRIM([Permissions]),'  ',', ')
    

    现在,我对史蒂夫的回答更加好奇了。我认为这是这里最强大的解决方案。但是,我想知道它将如何处理大型数据集。我仍然不知道答案,因为我还没有设置使用它所需的工具。但这里有一个查询,其中包括一些随机数生成来填充每个记录的 10,000 条记录:

    SELECT GETDATE()
    DECLARE @StagePerms TABLE (
         UserID INT IDENTITY
        ,Perms  NVARCHAR(MAX)
        )
    
    DECLARE @ProdPerms TABLE (
         UserID INT IDENTITY
        ,Perms  NVARCHAR(MAX)
        )
    
    DECLARE @Counter INT = 0
    DECLARE @XString NVARCHAR(MAX)
    
    WHILE @Counter < 10000
        BEGIN
            SET @Counter += 1
                SET @XString = REPLACE(RTRIM(
                    CASE ROUND(RAND()-.2,0) WHEN 0 THEN '' ELSE 'test  ' END +
                    CASE ROUND(RAND()-.2,0) WHEN 0 THEN '' ELSE 'admin  ' END +
                    CASE ROUND(RAND()-.2,0) WHEN 0 THEN '' ELSE 'schedule  ' END +
                    CASE ROUND(RAND()-.2,0) WHEN 0 THEN '' ELSE 'download  ' END +
                    CASE ROUND(RAND()-.2,0) WHEN 0 THEN '' ELSE 'upload  ' END)
                    ,'  ',', ') 
                INSERT INTO @StagePerms SELECT @XString
                SET @XString = REPLACE(RTRIM(
                    CASE ROUND(RAND()-.2,0) WHEN 0 THEN '' ELSE 'test  ' END +
                    CASE ROUND(RAND()-.2,0) WHEN 0 THEN '' ELSE 'admin  ' END +
                    CASE ROUND(RAND()-.2,0) WHEN 0 THEN '' ELSE 'schedule  ' END +
                    CASE ROUND(RAND()-.2,0) WHEN 0 THEN '' ELSE 'download  ' END +
                    CASE ROUND(RAND()-.2,0) WHEN 0 THEN '' ELSE 'upload  ' END)
                    ,'  ',', ')
                INSERT INTO @ProdPerms SELECT @XString
        END
    
    SELECT GETDATE()
    
    DECLARE @BothPerms TABLE (
         UserID INT
        ,Perms  NVARCHAR(MAX)
        )
    
    DECLARE @XPerms TABLE (
         UserID int
        ,XSchedule  BIT
        ,XUpload    BIT
        ,XTest      BIT
        ,XDownload  BIT
        ,XAdmin     BIT
        )
    
    INSERT INTO @XPerms
        SELECT
            ISNULL(sp.UserID,pp.UserID),
            CHARINDEX('schedule',sp.Perms) + CHARINDEX('schedule',pp.Perms),
            CHARINDEX('upload',sp.Perms) + CHARINDEX('upload',pp.Perms),
            CHARINDEX('test',sp.Perms) + CHARINDEX('test',pp.Perms),
            CHARINDEX('download',sp.Perms) + CHARINDEX('download',pp.Perms),
            CHARINDEX('admin',sp.Perms) + CHARINDEX('admin',pp.Perms)
    
        FROM @StagePerms sp
            FULL JOIN @ProdPerms pp
                ON sp.UserID = pp.UserID
    
    INSERT INTO @BothPerms
        SELECT
            UserID,
            CASE XTest WHEN 0 THEN '' ELSE 'test  ' END +
            CASE XAdmin WHEN 0 THEN '' ELSE 'admin  ' END +
            CASE XSchedule WHEN 0 THEN '' ELSE 'schedule  ' END +
            CASE XDownload WHEN 0 THEN '' ELSE 'download  ' END +
            CASE XUpload WHEN 0 THEN '' ELSE 'upload  ' END
    
        FROM @XPerms
    
    UPDATE @BothPerms
        SET Perms = REPLACE(RTRIM(Perms),'  ',', ')
    
    SELECT * FROM @BothPerms
    
    SELECT GETDATE()
    

    随机数生成时间不到一秒;其余的大约需要 31 秒。史蒂夫,我很想看看比较。显然,如果数据不允许我的解决方案,那也没关系。而且我确信某处有一个甜蜜点。

    【讨论】:

      【解决方案3】:

      请使用以下查询。它在 SQL Server 2012 中运行良好。

      DECLARE @Stage TABLE (Userid int, Permission Varchar (8000))
      DECLARE @Prod TABLE (Userid int, Permission Varchar (8000))
      DECLARE @temp TABLE (Userid int, Permission Varchar (8000))
      
      INSERT @Stage
      (Userid,Permission)
      VALUES
      (1,'schedule,upload,test,download,admin'),
      (2,'test,upload'),
      (3,'download')
      
      INSERT @Prod
      (Userid,Permission)
      VALUES
      (1,'test,admin,schedule,download,upload'),
      (2,'admin'),
      (3,'download,upload')
      -- Execution Part
      INSERT INTO @temp
      (Userid,Permission)
      (
          SELECT A.Userid AS Userid,Split.a.value('.', 'VARCHAR(100)') AS Permission FROM  
          (SELECT Userid,CAST ('<M>' + REPLACE(Permission, ',', '</M><M>') + '</M>' AS XML) AS Permission FROM @Stage A) AS A 
          CROSS APPLY Permission.nodes ('/M') AS Split(a)
          UNION
          SELECT A.Userid AS Userid,Split.a.value('.', 'VARCHAR(100)') AS Permission FROM  
          (SELECT Userid,CAST ('<M>' + REPLACE(Permission, ',', '</M><M>') + '</M>' AS XML) AS Permission FROM @Prod A) AS A 
          CROSS APPLY Permission.nodes ('/M') AS Split(a)
      )
      
      SELECT Userid, Permission = 
              STUFF((SELECT ', ' + Permission
                     FROM @temp b 
                     WHERE b.Userid = a.Userid 
                    FOR XML PATH('')), 1, 2, '')
      FROM @temp a
      GROUP BY Userid
      

      输出

      Userid  Permission
        1     admin, download, schedule, test, upload
        2     admin, test, upload
        3     download, upload
      

      【讨论】:

        【解决方案4】:

        您还可以使用 SQL Serv 2016 中引入的string splitting 的直接支持(当然,如果您已经开始使用此引擎版本:)) STRING_SPLIT 返回单列表...

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多