[英]SQL query to return n top records which have a null field or records grouped by that field
我有一個具有過濾器列的表A
| id | name | filter |
| 1 | joe | a |
| 2 | anna | a |
| 3 | mike | null |
| 4 | frank | null |
| 5 | sarah | b |
| 6 | jamie | b |
假設記錄按ID排序。 具有相同過濾值的記錄應僅計為一個。
TOP(1)應該返回
| id | name | filter |
| 1 | joe | a |
| 2 | anna | a |
TOP(2)應該返回
| id | name | filter |
| 1 | joe | a |
| 2 | anna | a |
| 3 | mike | null |
TOP(3)應該返回
| id | name | filter |
| 1 | joe | a |
| 2 | anna | a |
| 3 | mike | null |
| 4 | frank | null |
TOP(4)應該返回
| id | name | filter |
| 1 | joe | a |
| 2 | anna | a |
| 3 | mike | null |
| 4 | frank | null |
| 5 | sarah | b |
| 6 | jamie | b |
再次考慮,您嘗試選擇前n個不同的過濾器。 只需為每個過濾器找到最小的ID並編號即可:
DECLARE @A TABLE(id INT, name VARCHAR(100), filter VARCHAR(100));
INSERT INTO @A VALUES
(1, 'joe', 'y' ), -- 1st
(2, 'anna', 'x' ), -- 2nd
(3, 'mike', NULL), -- 3rd
(4, 'frank', NULL), -- 4th
(5, 'sarah', 'x' ),
(6, 'jamie', 'y' ),
(9, 'forrest', 'z' ); -- 5th
WITH filter_minid AS (
SELECT filter, MIN(id) AS minid
FROM @A
GROUP BY filter, CASE WHEN filter IS NULL THEN id END
), filter_minid_number AS (
SELECT filter, minid, ROW_NUMBER() OVER (ORDER BY minid) AS rn
FROM filter_minid
)
SELECT *
FROM @A a
INNER JOIN filter_minid_number ON a.filter = filter_minid_number.filter OR a.id = filter_minid_number.minid
WHERE rn <= 5 -- this is where you filter for n distinct ids
結果:
| id | name | filter | filter | minid | rn |
|----|---------|--------|--------|-------|----|
| 1 | joe | y | y | 1 | 1 |
| 2 | anna | x | x | 2 | 2 |
| 3 | mike | NULL | NULL | 3 | 3 |
| 4 | frank | NULL | NULL | 4 | 4 |
| 5 | sarah | x | x | 2 | 2 |
| 6 | jamie | y | y | 1 | 1 |
| 9 | forrest | z | z | 9 | 5 |
您可以使用帶窗口的MIN()
對具有相同過濾器的值進行分組(以及將NULL值分配給不同的組),然后使用DENSE_RANK()
展平這些值,以便以后進行過濾。
IF OBJECT_ID('tempdb..#Values') IS NOT NULL
DROP TABLE #Values
CREATE TABLE #Values (
ID INT IDENTITY,
Name VARCHAR(10),
Filter VARCHAR(10))
INSERT INTO #Values (
Name,
Filter)
VALUES
('joe', 'a'),
('anna', 'a'),
('mike', NULL),
('frank', NULL),
('sarah', 'b'),
('jamie', 'b'),
('john', 'a')
DECLARE @v_TopFilter INT = 4 -- Your top filter here
;WITH MinimumByFilter AS
(
SELECT
V.*,
MinimumIDByFilter = MIN(V.ID) OVER (
PARTITION BY
V.Filter,
CASE WHEN V.Filter IS NULL THEN V.ID END)
FROM
#Values AS V
),
DenseRank AS
(
SELECT
M.*,
DenseRank = DENSE_RANK() OVER(ORDER BY M.MinimumIDByFilter ASC)
FROM
MinimumByFilter AS M
)
SELECT
D.ID,
D.Name,
D.Filter
FROM
DenseRank AS D
WHERE
D.DenseRank <= @v_TopFilter
ORDER BY
D.ID ASC
您可以在此處檢查函數返回的內容:
ID Name Filter MinimumIDByFilter DenseRank
1 joe a 1 1
2 anna a 1 1
7 john a 1 1
3 mike NULL 3 2
4 frank NULL 4 3
5 sarah b 5 4
6 jamie b 5 4
你可以試試看
DECLARE @Tbl TABLE ( id INT, name varchar(10), filter varchar(10))
INSERT INTO @Tbl VALUES
(1 ,'joe', 'a'),
(2 ,'anna', 'a'),
(3 ,'mike', null),
(4 ,'frank', null),
(5 ,'sarah', 'b'),
(6 ,'jamie', 'b')
DECLARE @TOP INT = 3
SELECT id, name, filter FROM
( SELECT *, DENSE_RANK() OVER(ORDER BY SUB_RNK) RNK
FROM ( SELECT *,
MIN(id) OVER(PARTITION BY ISNULL(filter,id) ) SUB_RNK
FROM @Tbl ) T1
) T2
WHERE
T2.RNK <= @TOP
結果:(對於前3名)
id name filter
----------- ---------- ----------
1 joe a
2 anna a
3 mike NULL
4 frank NULL
使用子查詢
CREATE PROCEDURE `top` (IN x INT UNSIGNED)
BEGIN
select * from tableA where `filter` in (select distinct `filter` from tableA LIMIT x )
END
使用聯接
CREATE PROCEDURE `top` (IN x INT UNSIGNED)
BEGIN
select * from tableA A
join (select distinct `filter` from tableA LIMIT x ) AA
on A.`filter` = AA.`filter`
END
您可以使用dense_rank()
:
select t.*
from (select t.*,
dense_rank() over (order by filter,
(case when filter is null then id end)
end) as seqnum
from t
) t
where seqnum < ? -- whatever your limit is;
如果您想為此使用top
,則可以將top with ties
使用:
select top (?) with ties t.*
from (select t.*,
dense_rank() over (order by filter,
(case when filter is null then id end)
end) as seqnum
from t
) t
order by seqnum;
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.