具有重复数据的SQL Server存储过程中的分页

时间:2018-03-09 12:11:12

标签: c# sql-server tsql stored-procedures pagination

我在SQL Server中有一个存储过程,它基于来自多个表的多个过滤器(例如DateOfBirthDisplayName,...)来获取联系人。我需要更改存储过程以包括分页和总计数,因为分页是在后端完成的。 PartyId是唯一的密钥。需要注意的是,一个人可以拥有多个电子邮件和电话,并且我们说我们会搜索DisplayName = "Sarah",查询将返回以下内容:

TotalCount  PartyId     DisplayName EmailAddress      PhoneNumber   
-----------------------------------------------------------------
3           1           Sarah       sarah@gmail.com   1
3           1           Sarah       sarah2@gmail.com  1
3           1           Sarah       sarah@gmail.com   2

这大致是存储过程的作用,CurrentPagePageSize的指定值以及底部的ORDER BY OFFSET来测试分页:

DECLARE @CurrentPage int = 1
DECLARE @PageSize int = 1000

SELECT 
    COUNT(*) OVER () as TotalCount,
    p.Id AS PartyId,
    e.EmailAddress,
    pn.PhoneNumber
    etc.....                            
FROM 
    [dbo].[Party] AS p WITH(NOLOCK) 
INNER JOIN 
    [dbo].[Email] AS e WITH(NOLOCK) ON p.[Id] = e.[PartyID]
INNER JOIN 
    [dbo].[PhoneNumber] AS pn WITH(NOLOCK) ON p.[Id] = pn.[PartyID]    
    etc.....
WHERE 
    p.PartyType = 1 /*Individual*/ 
GROUP BY 
    p.Id, e.EmailAddress, pn.PhoneNumber etc...  
ORDER BY 
    p.Id 
    OFFSET (@CurrentPage - 1) * @PageSize ROWS 
    FETCH NEXT @PageSize ROWS ONLY

这是我们在后端按PartyId分组并分配相应的电子邮件和电话。

var responseModel = unitOfWork.PartyRepository.SearchContacts(model);

if (responseModel != null && responseModel.Count == 0)
{
    return null;
}

// get multiple phones/emails for a party
var emailAddresses = responseModel.GroupBy(p => new { p.PartyId, p.EmailAddress })
                            .Select(x => new {
                                    x.Key.PartyId,
                                    x.Key.EmailAddress
                            });

var phoneNumbers = responseModel.GroupBy(p => new { p.PartyId, p.PhoneNumber, p.PhoneNumberCreateDate })
                            .Select(x => new {
                                    x.Key.PartyId,
                                    x.Key.PhoneNumber,
                                    x.Key.PhoneNumberCreateDate
                            }).OrderByDescending(p => p.PhoneNumberCreateDate);

// group by in order to avoid multiple records with different email/phones
responseModel = responseModel.GroupBy(x => x.PartyId)
                   .Select(grp => grp.First())
                   .ToList();

var list = Mapper.Map<List<SearchContactResponseModelData>>(responseModel);

// add all phones/emails to respective party
list = list.Select(x =>
                    {
                        x.EmailAddresses = new List<string>();
                        x.EmailAddresses.AddRange(emailAddresses.Where(y => y.PartyId == x.PartyId).Select(y => y.EmailAddress));

                        x.PhoneNumbers = new List<string>();
                        x.PhoneNumbers.AddRange(phoneNumbers.Where(y => y.PartyId == x.PartyId).Select(y => y.PhoneNumber));
                        return x;
                    }).ToList();

var sorted = SortAndPagination(model, model.SortBy, list);

SearchContactResponseModel result = new SearchContactResponseModel()
            {
                Data = sorted,
                TotalCount = list.Count
            };

return result;

响应将是:

{
  "TotalCount": 1,
  "Data": [
    {
      "PartyId": 1,
      "DisplayName": "SARAH",
      "EmailAddresses": [
        "sarah@gmail.com",
        "sarah2@gmail.com"
      ],
      "PhoneNumbers": [
        "1",
        "2"
      ]
    }
  ]
}

从存储过程返回的TotalCount显然不是真实的,并且在后端代码(我们通过id分配电子邮件/电话和组)之后,我们获得真实的totalCount,它是1而不是3.

如果我们有3个名为Sarah的人,由于多个电话/电子邮件,存储过程中的totalCount将被设为9,真实计数将为3,如果我执行存储过程以使人从1到2,由于9条记录,分页不起作用。

如何在上述场景中实现分页?

1 个答案:

答案 0 :(得分:0)

您可以尝试使用CTE将查询与Party表隔离。这将允许您拉出正确的行数(以及正确的总行数),而无需担心电子邮件和电话号码的扩展。

它看起来像这样(重新排列上面的查询):

DECLARE @CurrentPage int = 1;
DECLARE @PageSize int = 1000;

WITH PartyList AS (
    SELECT 
        COUNT(*) OVER () as TotalCount,
        p.Id AS PartyId         
    FROM 
        [dbo].[Party] AS p WITH(NOLOCK) 
    WHERE 
        p.PartyType = 1 /*Individual*/ 
    GROUP BY -- You might not need this now depending on your data
        p.Id
    ORDER BY 
        p.Id 
        OFFSET (@CurrentPage - 1) * @PageSize ROWS 
        FETCH NEXT @PageSize ROWS ONLY
)
SELECT
    pl.TotalCount,
    pl.PartyId,
    e.EmailAddress,
    pn.PhoneNumber   
FROM PartyList AS pl
    INNER JOIN 
        [dbo].[Email] AS e WITH(NOLOCK) ON pl.[PartyId] = e.[PartyID]
    INNER JOIN 
        [dbo].[PhoneNumber] AS pn WITH(NOLOCK) ON pl.[PartyId] = pn.[PartyID];

请注意,CTE将要求先前的声明以分号结束。