【问题标题】:T-SQL loop through two tablesT-SQL 循环遍历两个表
【发布时间】:2018-04-20 11:38:50
【问题描述】:

我一直无法为以下困境找到可行的解决方案。

我使用的是 SQL Server 2016,并且在数据库中有如下所示的 2 个表。

Users表:

Id   Name
----------
1    Lisa
2    Paul
3    John
4    Mike
5    Tom

Role表:

Id   UserId   Role
------------------------
1    3        Manager
2    2,4,5    Developer
3    1        Designer

我正在寻找遍历Role 表、提取UserIds 并从Users 表中检索每个Id 的关联名称的T-SQL 代码。

所以循环的结果应该是这样的:

John
Paul,Mike,Tom
Lisa

【问题讨论】:

  • 修复您的数据模型。不要将数字 ID 存储为分隔字符串。在关系数据库中存储数据是一种错误的方式。
  • 而且只会给你带来很多麻烦。 (就像这里...)
  • 我同意,但不幸的是我没有创建数据库,我只是被问题困住了
  • 您没有更改数据库的选项吗?仅仅因为数据库最初设计不佳并不意味着您应该坚持使用它;修复设计,修复问题。另外,你试过什么?一个简单的搜索可能会在 SO 上为您提供 100 个锁定的重复项。
  • “我一直无法找到解决以下困境的有效解决方案。” 这意味着您尝试了任何方法。发布那是什么。 SO 期待一些先前努力的证据。发布您已经尝试过但没有奏效的内容也有助于避免读者通过提出不合适的建议来浪费时间。

标签: sql sql-server tsql stored-procedures sql-server-2016


【解决方案1】:

SQL SERVER 2017

SELECT R1.Id,STRING_AGG(U.Name , ','),R1.Role
FROM Users U
INNER JOIN
(
SELECT R.Id,S.value AS UserId,R.Role
FROM Role R
     CROSS APPLY STRING_SPLIT (UserID, ',') S
) R1
ON U.Id=R1.UserId
GROUP BY R1.ID,R1.Role
ORDER BY R1.ID;

SQL SERVER 2016

WITH CTE AS
(
SELECT R2.ID,U.Name,R2.UserId,R2.Role
FROM Users U
INNER JOIN
(
SELECT R.Id,S.value AS UserId,R.Role
FROM Role R
     CROSS APPLY STRING_SPLIT (UserID, ',') S
)R2
ON U.id=R2.UserId
)
SELECT DISTINCT R1.Id,
       STUFF((
          SELECT ',' + name
          FROM CTE R3
          WHERE R1.Role = R3.Role
          FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '') AS NAME
          ,R1.Role
FROM CTE AS R1;

对于旧版本

With CTE AS
(
SELECT r.id,
       u.name, 
       r.Role
FROM Users u 
INNER JOIN Role r
ON ',' + CAST(r.Userid AS NVARCHAR(20)) + ',' like '%,' + CAST(u.id AS NVARCHAR(20)) + ',%'
)
SELECT DISTINCT id,
          STUFF((
          SELECT ',' + name
          FROM CTE md
          WHERE T.Role = md.Role
          FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '') AS NAME,
          Role
FROM
CTE AS T
ORDER BY id

输出

id  NAME            Role
1   John            Manager
2   Paul,Mike,Tom   Developer
3   Lisa            Designer

演示

http://sqlfiddle.com/#!18/04a2d/69

【讨论】:

  • OP 使用的是 SQL Server 2016。为什么不STRING_SPLIT
  • 欢迎 :-) @Cindro
【解决方案2】:

这里有几个问题。第一个问题是您以分隔格式存储值。接下来,因为您以分隔格式存储值,所以这些值将存储为varchar。这也有问题,因为我猜测你的列Id 在表Users 中的值是int;意味着需要隐式转换并破坏任何 SARGability。

所以,在我看来,解决方案是解决问题。因为你有一个多对多的关系,你需要一个额外的表。无论如何,让我们按照您现在的方式设计表格:

CREATE TABLE Users (Id int, Name varchar(100));
CREATE TABLE Role (Id int, UserId varchar(100), [Role] varchar(100));

INSERT INTO Users
VALUES (1,'Lisa'),
       (2,'Paul'),
       (3,'John'),
       (4,'Mike'),
       (5,'Tom');

INSERT INTO Roles
VALUES(1,'3','Manager'),
      (2,'2,4,5','Developer'),
      (3,'1','Designer');

现在,我们需要一个新表:

CREATE TABLE UserRoles (Id int, UserID int, RoleID int);

现在,我们可以将正确的行插入数据库。当您使用 SQL Server 2016 时,我们可以使用STRING_SPLIT

INSERT INTO UserRoles (UserID, RoleID)
SELECT SS.value, R.Id
FROM Roles R
     CROSS APPLY STRING_SPLIT (UserID, ',') SS;

在此之后,如果您愿意,您可以使用以下内容删除现有列,但是,我认为现在保留它并没有什么坏处:

ALTER TABLE Roles DROP COLUMN UserID;

现在,我们可以正确查询数据了:

SELECT *
FROM Users U
     JOIN UserRoles UR ON U.ID = UR.UserID
     JOIN Roles R ON UR.RoleID = R.Id;

如果你想对这些数据进行分隔,你可以使用STUFF,但是不要把它存回去;我已经解释了如何更正您的数据是有原因的! :)

SELECT [Role],
       STUFF((SELECT ',' + [Name]
              FROM Users U
                   JOIN UserRoles UR ON U.Id = UR.UserID
              WHERE UR.RoleID = R.Id
              FOR XML PATH ('')),1,1,'') AS Users
FROM Roles R;

如果您使用的是 SQL Server 2017,则可以使用 STRING_AGG

清理脚本:

DROP TABLE UserRoles;
DROP TABLE Users;
DROP TABLE Roles;

【讨论】:

    【解决方案3】:

    试试这个解决方案:

    declare @users table (Id int, Name varchar(100))
    declare @role table (Id int, UserId varchar(100), [Role] varchar(100))
    
    insert into @users values
    (1, 'Lisa'),
    (2, 'Paul'),
    (3, 'John'),
    (4, 'Mike'),
    (5, 'Tom')
    insert into @role values
    (1, '3', 'Manager'),
    (2, '2,4,5', 'Developer'),
    (3, '1', 'Designer')
    
    select * from @role [r]
    join @users [u] on
    CHARINDEX(',' + cast([u].Id as varchar(3)) + ',', ',' + [r].UserId + ',', 1) > 0
    

    我根据IdUserId 中的出现加入了这两个表。为了使它成为可能并避免匹配:212 匹配,我决定只匹配用逗号包围的 ID。这就是为什么我在查询中用逗号 Id 包裹,并且还在逗号中包裹 UserId,以匹配 userId 末尾和开头的 ID。

    这个查询应该给你满意的结果,但是要精确匹配你想要的输出,你必须把这个查询包装在一个 CTE 中,并用字符串连接执行group by

    ;with cte as (
        select [r].Id, [r].Role, [u].Name from @role [r]
        join @users [u] on
        CHARINDEX(',' + cast([u].Id as varchar(3)) + ',', ',' + [r].UserId + ',', 1) > 0
    )
    
    select Id,
           (select Name + ',' from cte where Id = [c].Id for xml path('')) [Name],
           --I believe this should work in your case, if so, just pick one column from these two
           string_agg(Name + ',') [Name2],
           Role
    from cte [c]
    group by Id, Role
    

    【讨论】:

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