我正试图远离 OFFSET/FETCH
分页到 Keyset
分页(也称为搜索方法)。因为我刚刚开始,所以我有很多问题,但这是我尝试与过滤器一起正确分页的众多问题之一。
所以我有2张 table
aspnet_users
有列
PK
UserId uniquidentifier
字段 UserName NVARCHAR(256) NOT NULL,
AffiliateTag varchar(50) NULL
.....other fields
aspnet_membership
有列
PK+FK
UserId uniquidentifier
字段 Email NVARCHAR(256) NOT NULL
.....other fields
索引 Non Clustered
表索引 aspnet_users
(用户名) Non Clustered
表索引 aspnet_users
(AffiliateTag) Non Clustered
表索引 aspnet_membership
(邮箱) 我有一个页面将列出用户(基于
search term
),页面大小设置为 20。我想跨多个列进行搜索,而不是执行 OR
我发现每个都有一个单独的查询,然后 Union
他们将使索引正确使用。所以有将采取
search term
的存储过程和可选 UserName
和 UserId
下一页的最后一条记录。Create proc [dbo].[sp_searchuser]
@take int,
@searchTerm nvarchar(max) NULL,
@lastUserName nvarchar(256)=NULL,
@lastUserId nvarchar(256)=NULL
AS
IF(@lastUserName IS NOT NULL AND @lastUserId IS NOT NULL)
Begin
select top (@take) *
from
(
select u.UserId, u.UserName, u.AffiliateTag, m.Email
from aspnet_Users as u
inner join aspnet_Membership as m
on u.UserId=m.UserId
where u.UserName like @searchTerm
UNION
select u.UserId, u.UserName, u.AffiliateTag, m.Email
from aspnet_Users as u
inner join aspnet_Membership as m
on u.UserId=m.UserId
where u.AffiliateTag like convert(varchar(50), @searchTerm)
) as u1
where u1.UserName > @lastUserName
OR (u1.UserName=@lastUserName And u1.UserId > convert(uniqueidentifier, @lastUserId))
order by u1.UserName
End
Else
Begin
select top (@take) *
from
(
select u.UserId, u.UserName, u.AffiliateTag, m.Email
from aspnet_Users as u
inner join aspnet_Membership as m
on u.UserId=m.UserId
where u.UserName like @searchTerm
UNION
select u.UserId, u.UserName, u.AffiliateTag, m.Email
from aspnet_Users as u
inner join aspnet_Membership as m
on u.UserId=m.UserId
where u.AffiliateTag like convert(varchar(50), @searchTerm)
) as u1
order by u1.UserName
End
现在使用搜索词 mua
获取第一页的结果exec [sp_searchuser] 20, 'mua%'
它使用为 UserName 列创建的索引和为 AffiliateTag 列创建的另一个索引,这很好但问题是我发现内部联合查询返回所有匹配的行
就像在这种情况下,执行计划显示
UserName Like SubQuery
Number of Rows Read= 5
Actual Number of Rows= 4
AffiliateTag Like SubQuery Number of Rows Read= 465
Actual Number of Rows= 465
所以总共内部查询返回 469
匹配行然后外部查询取出 20 用于最终结果重置。所以真的要读取比需要更多的数据。
当进入下一页
exec [sp_searchuser] 20, 'mua%', 'lastUserName', 'lastUserId'
执行计划显示UserName Like SubQuery
Number of Rows Read= 5
Actual Number of Rows= 4
AffiliateTag Like SubQuery Number of Rows Read= 465
Actual Number of Rows= 445
总共内部查询返回 449
匹配行所以无论有没有分页,它都会读取比需要更多的数据。
我的期望是以某种方式限制内部查询,因此它不会返回所有匹配的行。
最佳答案
您可能对 Logical Processing Order 感兴趣,这决定了在一个步骤中定义的对象何时可用于后续步骤中的子句。 Logical Processing Order
步骤是:
当然,正如文档所述:
The actual physical execution of the statement is determined by the query processor and the order may vary from this list.
这意味着有时某些语句可以在上一个完成之前开始。
在您的情况下,您的查询如下所示:
user_name
排序没有办法减少数据提取部分中的行以获得确定性结果(我们实际上可能需要按
user_name, user_id
排序以获得这样的结果)我们需要获取所有匹配的行,对它们进行排序,然后获取所需的行.例如,想象第一个查询返回 20 个以“Z”开头的名字。第二个查询只返回一个以“A”开头的名字。如果您以某种方式停止执行并跳过第二个查询,您将得到错误的结果 - 20 个名称以“Z”开头,而不是一个以“A”开头和 19 个以“Z”开头。
在这种情况下,我更喜欢使用动态 T-SQL 语句以获得更好的执行时间并减少代码长度。你是说:
And I want to search across multiple columns so instead of doing OR I find out having a separate query for each and then Union them will make the index use correctly.
当您使用时
UNION
您正在对表执行双重读取。在您的情况下,您正在阅读 aspnet_Membership
表两次和 aspnet_Users
两次(是的,这里您使用了两个不同的索引,但我相信它们不是 covering 并且您最终执行查找以提取用户 name
和 email
。我猜你已经开始覆盖索引,如下例所示:
DROP TABLE IF EXISTS [dbo].[StackOverflow];
CREATE TABLE [dbo].[StackOverflow]
(
[UserID] INT PRIMARY KEY
,[UserName] NVARCHAR(128)
,[AffiliateTag] NVARCHAR(128)
,[UserEmail] NVARCHAR(128)
,[a] INT
,[b] INT
,[c] INT
,[z] INT
);
CREATE INDEX IX_StackOverflow_UserID_UserName_AffiliateTag_I_UserEmail ON [dbo].[StackOverflow]
(
[UserID]
,[UserName]
,[AffiliateTag]
)
INCLUDE ([UserEmail]);
GO
INSERT INTO [dbo].[StackOverflow] ([UserID], [UserName], [AffiliateTag], [UserEmail])
SELECT TOP (1000000) ROW_NUMBER() OVER(ORDER BY t1.number)
,CONCAT('UserName',ROW_NUMBER() OVER(ORDER BY t1.number))
,CONCAT('AffiliateTag', ROW_NUMBER() OVER(ORDER BY t1.number))
,CONCAT('UserEmail', ROW_NUMBER() OVER(ORDER BY t1.number))
FROM master..spt_values t1
CROSS JOIN master..spt_values t2;
GO
因此,对于以下查询:SELECT TOP 20 [UserID]
,[UserName]
,[AffiliateTag]
,[UserEmail]
FROM [dbo].[StackOverflow]
WHERE [UserName] LIKE 'UserName200%'
OR [AffiliateTag] LIKE 'UserName200%'
ORDER BY [UserName];
GO
这里的问题是我们正在读取所有行,即使我们正在使用索引。好处是索引正在覆盖并且我们没有执行查找。根据搜索条件,它的性能可能比您的方法更好。
如果性能不好,我们可以使用触发器到
UNPIVOT
原始数据和记录在单独的表中。它可能看起来像这样(最好使用 attribute_id 而不是像我这样的文本):DROP TABLE IF EXISTS [dbo].[StackOverflowAttributes];
CREATE TABLE [dbo].[StackOverflowAttributes]
(
[UserID] INT
,[AttributeName] NVARCHAR(128)
,[AttributeValue] NVARCHAR(128)
,PRIMARY KEY([UserID], [AttributeName], [AttributeValue])
);
GO
CREATE INDEX IX_StackOverflowAttributes_AttributeValue ON [dbo].[StackOverflowAttributes]
(
[AttributeValue]
)
INSERT INTO [dbo].[StackOverflowAttributes] ([UserID], [AttributeName], [AttributeValue])
SELECT [UserID]
,'Name'
,[UserName]
FROM [dbo].[StackOverflow]
UNION
SELECT [UserID]
,'AffiliateTag'
,[AffiliateTag]
FROM [dbo].[StackOverflow];
之前的查询将如下所示:SELECT TOP 20 U.[UserID]
,U.[UserName]
,U.[AffiliateTag]
,U.[UserEmail]
FROM [dbo].[StackOverflowAttributes] A
INNER JOIN [dbo].[StackOverflow] U
ON A.[UserID] = U.[UserID]
WHERE A.[AttributeValue] LIKE 'UserName200%'
ORDER BY U.[UserName];
现在,我们只读取索引行的一部分,然后执行查找。
为了比较性能,最好使用:
SET STATISTICS IO, TIME ON;
因为它将为您提供如何从索引中读取页面。结果可以可视化here .
关于sql-server - 键集分页 - 按跨多列的搜索词过滤,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63316273/