简体   繁体   English

如何避免 xml 路径的多次使用?

[英]How can i avoid a multiple using stuff for xml path?

I'm having a problem with sql query.我在使用 sql 查询时遇到问题。 What i'm trying to do is to get list of stores with tasks assigned on users in it.我要做的是获取商店列表,其中包含分配给用户的任务。 Each role in separated column with user names.用用户名分隔列中的每个角色。 I'm using STUFF from getting concatenated row but it looks like bad idea for 100K+ rows in tables.我正在使用 STUFF 来获取连接行,但对于表中的 100K+ 行来说,这似乎是个坏主意。

This is simplified structure:这是简化的结构:

Users table用户表

CREATE TABLE #temp_Users(
    [id] [int] IDENTITY(1,1) NOT NULL,  
    [user_name] [nvarchar](250) NULL,
 CONSTRAINT [PK_Users] PRIMARY KEY CLUSTERED ([id] ASC)
 )

 insert into #temp_Users (user_name) values ('Joe'),('Jeff'),('Jimm')

Tasks table任务表

create table #temp_Tasks (
    [id] [int] IDENTITY(1,1) NOT NULL,
    [id_user] [int] NULL,
    [id_store] [int] NULL,
    [id_role] [int] NULL,
    CONSTRAINT [PK_Tasks] PRIMARY KEY CLUSTERED  ([id] ASC)
) 

insert into #temp_Tasks ([id_user],[id_store],[id_role])
       values (1,1,0),(1,2,0),(2,1,0),(2,2,0),(1,1,1),(2,2,1),(3,1,0),(3,2,0),(3,2,1)

Select Select

SELECT distinct t.id_store,
  stuff( (SELECT ', ' + u.[user_name] FROM #temp_Tasks t2 left outer join #temp_Users u on u.id = t2.id_user WHERE t2.id_store = t.id_store and t2.id_role = 0 FOR XML PATH('')),1,2,'' ) as 'role_0',
  stuff( (SELECT ', ' + u.[user_name] FROM #temp_Tasks t2 left outer join #temp_Users u on u.id = t2.id_user WHERE t2.id_store = t.id_store and t2.id_role = 1 FOR XML PATH('')),1,2,'' )  as 'role_1' 
FROM #temp_Tasks t

Result结果

在此处输入图像描述

The problem is that select is getting slower and slower when tables filling more and more because every STUFF is another nested loop.问题是 select 越来越慢,因为每个 STUFF 都是另一个嵌套循环,所以当表格填充得越来越多时。 And if i need to add some "roles" in this select i have to add another STUFF like following:如果我需要在这个 select 中添加一些“角色”,我必须添加另一个东西,如下所示:

SELECT distinct t.id_store,
  stuff( (SELECT ', ' + u.[user_name] FROM #temp_Tasks t2 left outer join #temp_Users u on u.id = t2.id_user WHERE t2.id_store = t.id_store and t2.id_role = 0 FOR XML PATH('')),1,2,'' ) as 'role_0',
  stuff( (SELECT ', ' + u.[user_name] FROM #temp_Tasks t2 left outer join #temp_Users u on u.id = t2.id_user WHERE t2.id_store = t.id_store and t2.id_role = 1 FOR XML PATH('')),1,2,'' )  as 'role_1' 
  stuff( (SELECT ', ' + u.[user_name] FROM #temp_Tasks t2 left outer join #temp_Users u on u.id = t2.id_user WHERE t2.id_store = t.id_store and t2.id_role = 2 FOR XML PATH('')),1,2,'' )  as 'role_2' 
  stuff( (SELECT ', ' + u.[user_name] FROM #temp_Tasks t2 left outer join #temp_Users u on u.id = t2.id_user WHERE t2.id_store = t.id_store and t2.id_role = 3 FOR XML PATH('')),1,2,'' )  as 'role_3' 
  stuff( (SELECT ', ' + u.[user_name] FROM #temp_Tasks t2 left outer join #temp_Users u on u.id = t2.id_user WHERE t2.id_store = t.id_store and t2.id_role = 4 FOR XML PATH('')),1,2,'' )  as 'role_4' 
FROM #temp_Tasks t

The question is can i avoid this multiple STUFF functions using?问题是我可以避免使用这个多个 STUFF 函数吗? Or maybe i need to create nonclustered indexes on tables Tasks?或者我可能需要在表任务上创建非聚集索引? Maybe filtered indexes each one for every role?也许每个角色都有过滤索引? Thanks in advance!提前致谢!

EDIT: i'm using MSSQL 2016.编辑:我正在使用 MSSQL 2016。

As Gordon Linoff suggested i've changed Select from Distinct to Group By like this:正如 Gordon Linoff 建议的那样,我已经将 Select 从 Distinct 更改为 Group By ,如下所示:

SELECT t.id_store,
      stuff( (SELECT ', ' + u.[user_name] FROM #temp_Tasks t2 left outer join #temp_Users u on u.id = t2.id_user WHERE t2.id_store = t.id_store and t2.id_role = 0 FOR XML PATH('')),1,2,'' ) as 'role_0',
      stuff( (SELECT ', ' + u.[user_name] FROM #temp_Tasks t2 left outer join #temp_Users u on u.id = t2.id_user WHERE t2.id_store = t.id_store and t2.id_role = 1 FOR XML PATH('')),1,2,'' )  as 'role_1' 
    FROM #temp_Tasks t
group by t.id_store

and execution time decreases from 20 sec to 2 sec.执行时间从 20 秒减少到 2 秒。 So the problem was not about Stuff but Distinct.所以问题不在于 Stuff,而在于 Distinct。

Since SQL Server 2017, you use string_agg() ?自 SQL Server 2017 以来,您使用string_agg()吗?

SELECT t.id_store,
       STRING_AGG(CASE WHEN t.id_role = 0 THEN u.user_name END, ',') WITHIN GROUP (ORDER BY u.user_name) as role_0,
       STRING_AGG(CASE WHEN t.id_role = 1 THEN u.user_name END, ',') WITHIN GROUP (ORDER BY u.user_name) as role_1
FROM #temp_Tasks t JOIN
     #temp_Users u
     ON u.id = t2.id_users
GROUP BY t.id_store;

In older versions, you might benefit from using GROUP BY instead of SELECT DISTINCT as Aaron Bertrand explains .正如Aaron Bertrand 解释的那样,在旧版本中,您可能会受益于使用GROUP BY而不是SELECT DISTINCT I would also replace the LEFT JOIN s in the subqueries with INNER JOIN :我还将用INNER JOIN替换子查询中的LEFT JOIN JOIN :

SELECT t.id_store,
       stuff( (SELECT ', ' + u.[user_name] FROM #temp_Tasks t2 join #temp_Users u on u.id = t2.id_user WHERE t2.id_store = t.id_store and t2.id_role = 0 FOR XML PATH('')),1,2,'' ) as 'role_0',
       stuff( (SELECT ', ' + u.[user_name] FROM #temp_Tasks t2 join #temp_Users u on u.id = t2.id_user WHERE t2.id_store = t.id_store and t2.id_role = 1 FOR XML PATH('')),1,2,'' )  as 'role_1' 
FROM #temp_Tasks t
GROUP BY t.id_store;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM