T-SQL循环遍历两个表

时间:2018-04-20 11:38:50

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

我一直无法找到解决困境的工作方案。

我正在使用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表的T-SQL代码,提取UserId并从Id表中检索每个Users的相关名称。< / p>

所以循环结果如下所示:

John
Paul,Mike,Tom
Lisa

3 个答案:

答案 0 :(得分:4)

FOR 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;

FOR 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

答案 1 :(得分:3)

这里有几个问题。第一个问题是您以分隔格式存储值。接下来,因为您以分隔格式存储值,所以值将存储为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;

答案 2 :(得分:2)

试试这个解决方案:

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

我根据Id中的出现UserId加入了两个表格。为了使其成为可能并避免匹配: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