[英]SQL Server rownumbering and filtered results
I have an application in which I need to display "pages" of data in a list. 我有一个应用程序,我需要在列表中显示数据的“页面”。 The basic idea is fairly common.
基本思想相当普遍。 The display shows a list of items and at the bottom of the display are some kind of controls that allow you to go to the next "page" of data.
显示屏显示项目列表,显示屏底部是某种控件,可让您转到下一个“页面”数据。
All well and good. 一切都很好。 I have this working.
我有这个工作。
Following is the SQL in a view I am using to support the "next" behavior. 以下是我用于支持“下一步”行为的视图中的SQL。
CREATE VIEW CampaignParticipants AS
SELECT row_number() OVER (ORDER BY TPT.LastName, TPT.FirstName, TPT.DoB) AS RowNumber
,CGP.*
,TPT.*
FROM tblCampaignGEDPush CGP
JOIN tblParticipants TPT
ON CGP.PartID = TPT.PartID
Here is how I use the VIEW 这是我如何使用VIEW
SELECT *
FROM CampaignParticipants
WHERE RowNumber >= 0
AND RowNumber <= 100
This simulates grabbing the "first page" of 100 results from the VIEW. 这模拟了从VIEW中抓取100个结果的“第一页”。 Pages thru each set of results just peachy.
页面通过每组结果只是桃子。
Great.. BUT: 太棒了..但是:
As some of you that have dealt with this probably are aware, this is flawed. 正如你们中的一些人所知道的那样,这可能是有缺陷的。 If I want to search on
TPT.LastName like 'R%'
and get the first set of results, I'm doomed. 如果我想在
TPT.LastName like 'R%'
上搜索TPT.LastName like 'R%'
并获得第一组结果,我注定要失败。
I'm gonna want to start looking at RowNumber = 0
, stop at RowNumber = 100
, but the "R" results will probably be well outside that range. 我想要开始查看
RowNumber = 0
,停在RowNumber = 100
,但“R”结果可能远远超出该范围。 Upshot: list comes back empty. Upshot:列表返回空白。
And it gets stickier: the user wants to be able to "filter" on LastName, FirstName, DoB, Location, Phone, Zip, anything. 它变得更加棘手:用户希望能够“过滤”LastName,FirstName,DoB,Location,Phone,Zip等等。
**edit: i can't really put the filter on the "inner" query, as it's in a view, and the filter can change arbitrarily **编辑:我无法将过滤器放在“内部”查询上,因为它在视图中,并且过滤器可以任意更改
Anybody have any ideas how to get this result set have a row_number()
on the filtered set of results? 任何人都有任何想法如何让这个结果集在过滤的结果集上有一个
row_number()
?
Something like this should do... 这样的事应该做......
SELECT * FROM (
SELECT *, ROW_NUMBER() OVER (ORDER BY LastName, FirstName, DoB) AS __RN
FROM CampaignParticipants
WHERE LastName LIKE 'R%') innerData WHERE __RN BETWEEN 1 and 100
However, you should be using the column names, not '*'. 但是,您应该使用列名,而不是'*'。 I don't know what your tables look like so I can't fill that in for you.
我不知道你的桌子是什么样的,所以我不能为你填写。
首先尝试使用proc而不是视图,因为用户要求你有这么多过滤,然后proc只是你得到的解决方案.Inside proc使用所有这些过滤器过滤数据,然后生成row_number然后显示说首先100记录或类似的。
I wish there were something a bit more elegant than this. 我希望有一些比这更优雅的东西。 This is my stored proc solution.
这是我的存储过程解决方案。 The best my lame SQL skills can conjure up.
我最蹩脚的SQL技能可以让人联想到。
The OUT parameter in the parameter list is for the total rows in this set so the front end knows how many pages all together with this particular filter set. 参数列表中的OUT参数用于此集合中的总行数,因此前端知道所有这些特定过滤器集合的页面数量。
CREATE PROC [dbo].[procCampaignGEDPushSelect]
@LastName VARCHAR(50) = null
,@FirstName VARCHAR(50) = null
,@Location VARCHAR(255) = null
,@DoB DateTime = null
,@Zip VARCHAR(50) = null
,@Phone VARCHAR(50) = null
,@Email VARCHAR(255) = null
,@Gender VARCHAR(20) = null
,@IsGED Bit = 0
,@IsBTT Bit = 0
,@IsOACE Bit = 0
,@Completed Bit = 0
,@TotalCount INT OUT
AS
BEGIN
SELECT @LastName = @LastName + '%'
SELECT @FirstName = @FirstName + '%'
SELECT @Location = @Location + '%'
SELECT @Zip = @Zip + '%'
SELECT @Phone = @Phone + '%'
SELECT @Email = @Email + '%'
SELECT @Gender = @Gender + '%'
SELECT row_number() OVER (ORDER BY LastName, FirstName, DoB) AS RowNumber
, TPT.LastName
, TPT.FirstName
, TPT.WF1Site
, TPT.DOB
, TPT.Zip
, TPT.Telephone
, TPT.CellPhone
, TPT.Email
, TPT.Gender
, TPT.IsBTT
, TPT.IsGED
, TPT.IsOACE
, TPT.IsSRS
,CGP.*
FROM tblCampaignGEDPush CGP
JOIN tblParticipants TPT
ON CGP.PartID = TPT.PartID
WHERE 1=1
AND 1 = (CASE WHEN @LastName IS NOT NULL THEN (CASE WHEN TPT.LastName LIKE @LastName THEN 1 ELSE 0 END) ELSE 1 END)
AND 1 = (CASE WHEN @FirstName IS NOT NULL THEN (CASE WHEN TPT.FirstName LIKE @Firstname THEN 1 ELSE 0 END) ELSE 1 END)
AND 1 = (CASE WHEN @Location IS NOT NULL THEN (CASE WHEN TPT.WF1Site LIKE @Location THEN 1 ELSE 0 END) ELSE 1 END)
AND 1 = (CASE WHEN @Zip IS NOT NULL THEN (CASE WHEN TPT.Zip LIKE @Zip THEN 1 ELSE 0 END) ELSE 1 END)
AND
( 1 = (CASE WHEN @Phone IS NOT NULL THEN (CASE WHEN TPT.Telephone LIKE @Phone THEN 1 ELSE 0 END) ELSE 1 END)
OR 1 = (CASE WHEN @Phone IS NOT NULL THEN (CASE WHEN TPT.CellPhone LIKE @Phone THEN 1 ELSE 0 END) ELSE 1 END)
)
AND 1 = (CASE WHEN @Email IS NOT NULL THEN (CASE WHEN TPT.Email LIKE @Email THEN 1 ELSE 0 END) ELSE 1 END)
AND 1 = (CASE WHEN @Gender IS NOT NULL THEN (CASE WHEN TPT.Gender LIKE @Gender THEN 1 ELSE 0 END) ELSE 1 END)
AND 1 = (CASE WHEN @DoB IS NOT NULL THEN (CASE WHEN TPT.DoB = @DoB THEN 1 ELSE 0 END) ELSE 1 END)
AND 1 = (CASE WHEN @IsGED != 0 THEN (CASE WHEN TPT.IsGED = 1 THEN 1 ELSE 0 END) ELSE 1 END)
AND 1 = (CASE WHEN @IsBTT != 0 THEN (CASE WHEN TPT.IsBTT = 1 THEN 1 ELSE 0 END) ELSE 1 END)
AND 1 = (CASE WHEN @IsOACE != 0 THEN (CASE WHEN TPT.IsOACE = 1 THEN 1 ELSE 0 END) ELSE 1 END)
AND 1 = (CASE WHEN @Completed != 0 THEN (CASE WHEN CGP.Completed = 1 THEN 1 ELSE 0 END) ELSE 1 END)
ORDER BY TPT.LastName
, TPT.FirstName
, TPT.DoB
SELECT @TotalCount = @@ROWCOUNT
END
So then I got started thinking. 所以我开始思考了。 Rather than use this tricky, bug-prone proc (which by the way works fairly well), since I am in .NET, I wonder if there is a nice tight solution there.
而不是使用这个棘手的,容易出错的过程(顺便说一下工作得很好),因为我在.NET中,我想知道那里是否有一个很好的紧密解决方案。
Now I know the original question has nothing to do with .NET, so I'm leaving the above proc available for those interested in the strict SQL solution, which, I think, works fairly well. 现在我知道原始问题与.NET无关,所以我将上述proc留给那些对严格SQL解决方案感兴趣的人,我认为这些解决方案运行得相当好。
So I started digging into the IQueryable interface and struck gold: 所以我开始深入研究IQueryable界面并打出金牌:
IQueryable<queryParticipant> qparticipant = db.queryParticipants.AsQueryable();
...
qparticipant = qparticipant.Where( ... any filter you choose );
...
return qparticipant
.OrderBy( p => p.LastName )
.OrderBy( p => p.FirstName )
.OrderBy( p => p.DOB )
.Select( ... whatever you like ... )
.Skip( StartRecordNumber ) // This is the trick! Start the query here..
.Take( PageSize ) // Take only as many as you need
;
And that's it. 就是这样。 The .NET approach is nice if available.
如果可用,.NET方法很好。 The Stored Proc is great when such an API is not available.
当这样的API不可用时,Stored Proc很棒。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.