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 个解决方案
- 查询行id,然后查询PagedResult(-1行)并在后台连接
- 找到一种方法来查询 rowId selected 的页码,然后将用户直接带到该页面
- 想办法让我们的 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;
一些上下文:我有一个多步骤表单,在第 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 个解决方案
- 查询行id,然后查询PagedResult(-1行)并在后台连接
- 找到一种方法来查询 rowId selected 的页码,然后将用户直接带到该页面
- 想办法让我们的 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;