Sql 选中的一行在最上面的分页结果

Sql Paging result with one selected row on top

一些上下文:我有一个多步骤表单,在第 1 步中,用户必须 select table 中的一行带有分页(过滤器 + 排序),然后他转到第 2 步但是在任何时候他都可以回到步骤 1 并需要查看之前 selected 的行。 注意:我不想将页码保留在前端存储中,因为如果 table 已更改,selected id 可能不再位于同一页面上

测试数据库:

CREATE TABLE [dbo].[Table_CTE](
    [Id] [int] NOT NULL,
    [FirstName] [nvarchar](50) NULL,
    [LastName] [nvarchar](50) NULL)
    
DECLARE @i int = 0
WHILE @i < 20 
BEGIN
    SET @i = @i + 1
    insert into TABLE_CTE values (@i, CONCAT('FirstName', @i), CONCAT('LastName',  @i))
END

当前查询示例

DECLARE @PagingOffset int = 0;
DECLARE @PagingSize int = 5;
DECLARE @SelectedId int = 20;

WITH CTE_FILTERED AS (
    SELECT Id, FirstName, LastName
        --+ lot of complex subselect
    FROM TABLE_CTE
    WHERE  1 = 1
        AND FirstName LIKE '%1%'
) 
SELECT 
    --Cant explain why but looks faster than COUNT(*) OVER()
    (SELECT COUNT(*) FROM CTE_FILTERED) Total
    , CTE_FILTERED.*
FROM CTE_FILTERED
-- Could be ordered by anything
ORDER Id
OFFSET @PagingOffset ROWS FETCH NEXT @PagingSize ROWS ONLY;

结果

Total   Id  FirstName   LastName
11  1   FirstName1  LastName1
11  10  FirstName10 LastName10
11  11  FirstName11 LastName11
11  12  FirstName12 LastName12
11  13  FirstName13 LastName13

从这里我看到了 3 个解决方案

  1. 查询行id,然后查询PagedResult(-1行)并在后台连接
  2. 找到一种方法来查询 rowId selected 的页码,然后将用户直接带到该页面
  3. 想办法让我们的 selected id 保持在所有结果的顶部,无论分页(过滤器和排序)=> 我认为最好的情况

我刚刚实现了解决方案 3,但它看起来很复杂,也许有更好的方法来实现它?

DECLARE @PagingOffset int = 0;
DECLARE @PagingSize int = 5;
DECLARE @SelectedId int = 20;

WITH CTE_FILTERED2 AS (
    SELECT Id, FirstName, LastName
        --+ lot of complex subselect
    FROM TABLE_CTE
    WHERE  1 = 1
        AND (Id = @SelectedId 
            OR FirstName LIKE '%1%')
),
CTE_FILTERED_SELECTED2 AS (
    SELECT Id, FirstName, LastName
    FROM CTE_FILTERED2
    WHERE CTE_FILTERED2.Id = @SelectedId
    
    UNION ALL
    
    SELECT Id, FirstName, LastName
    FROM CTE_FILTERED2
    WHERE CTE_FILTERED2.Id != @SelectedId
    -- Could be ordered by anything
    ORDER BY Id
    -- forced to add an offset to be able to sort
    OFFSET @PagingOffset ROWS FETCH NEXT @PagingSize ROWS ONLY
)
SELECT (SELECT COUNT(*) FROM CTE_FILTERED_SELECTED2) Total
    , CTE_FILTERED_SELECTED2.*
FROM CTE_FILTERED_SELECTED2
-- forced to order by something to be able to use offset
ORDER BY (SELECT NULL)
OFFSET @PagingOffset ROWS FETCH NEXT @PagingSize ROWS ONLY;

结果

Total   Id  FirstName   LastName
12  20  FirstName20 LastName20
12  1   FirstName1  LastName1
12  10  FirstName10 LastName10
12  11  FirstName11 LastName11
12  12  FirstName12 LastName12

编辑最终解决方案

SELECT TOP (@PagingSize) Priority, Id, FirstName, LastName, Total
FROM 
( 
  SELECT Priority = 1, Id, FirstName, LastName, -1 Total
   FROM dbo.TABLE_CTE WHERE Id = @SelectedId
  UNION ALL
  SELECT Priority = 2, Id, FirstName, LastName, Total
   FROM 
   (
    SELECT Id, FirstName, LastName, COUNT(*) OVER () + IIF(@SelectedId IS NULL, 0, 1) Total
      FROM dbo.TABLE_CTE
      WHERE FirstName LIKE '%1%'
      ORDER BY Id
    OFFSET @PagingOffset ROWS FETCH NEXT @PagingSize ROWS ONLY
   ) AS y
   WHERE Id != @SelectedId
) AS z
ORDER BY Priority;

Priority    Id  FirstName   LastName    Total
1   20  FirstName20 LastName20  -1
2   1   FirstName1  LastName1   12
2   10  FirstName10 LastName10  12
2   11  FirstName11 LastName11  12
2   12  FirstName12 LastName12  12

如果您有任意过滤器和任意排序,并希望保证您的用户始终看到包含任意行的页面...

DECLARE @PagingSize int = 5;
DECLARE @SelectedId int = 20;

WITH
  filtered_sorted_paged AS
(
  SELECT
    <anything>,
    (ROW_NUMBER() OVER (ORDER BY <anything>) - 1) / @PagingSize + 1  AS page_id
  FROM
    <anything>
  WHERE
    <anything>
),
  page_search AS
(
  SELECT
    *,
    MAX(CASE WHEN id = @SelectedId THEN page_id END) OVER ()  AS selected_page_id
  FROM
    filtered_sorted_paged
)
SELECT
  <stuff>
FROM
  page_search
WHERE
  page_id = selected_page_id

对于大型数据集,为每个用户操作重复此操作似乎很昂贵,但它似乎可以满足您的要求...

https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=f3e90f195bc4e3dbcd03b4e194d94d4e

这似乎有点简单(但我不想因为检索计数而使它复杂化)。基本上,您为 selected 行分配优先级,并为其他所有内容分配较低的优先级,然后您可以对该人工列进行排序。

DECLARE @PagingOffset  int = 0,
        @PagingSize    int = 5,
        @SortId        bit = 1,
        @SortFirstName bit = 0,
        @SortLastName  bit = 0,
        @SelectedId    int = 20;

SELECT TOP (@PagingSize) Id, FirstName, LastName
FROM 
( 
  SELECT Priority = 1, Id, FirstName, LastName
   FROM dbo.TABLE_CTE WHERE Id = @SelectedId
  UNION ALL
  SELECT Priority = 2, Id, FirstName, LastName
   FROM 
   (
    SELECT Id, FirstName, LastName
      FROM dbo.TABLE_CTE
      WHERE FirstName LIKE '%1%'
      -- AND Id <> @SelectedId
      ORDER BY 
        CASE WHEN @SortId = 1        THEN Id END,
        CASE WHEN @SortFirstName = 1 THEN FirstName END,
        CASE WHEN @SortLastName = 1  THEN LastName END
        OFFSET @PagingOffset ROWS FETCH NEXT @PagingSize ROWS ONLY
   ) AS y
) AS z
ORDER BY Priority;

外部顺序与您所写的一致 (SELECT NULL = don't care) 但您可以轻松地在外部重复 case 表达式:

ORDER BY Priority,
        CASE WHEN @SortId = 1        THEN Id END,
        CASE WHEN @SortFirstName = 1 THEN FirstName END,
        CASE WHEN @SortLastName = 1  THEN LastName END;