use*_*833 4 postgresql select greatest-n-per-group
查看下面的示例,从第一行 ( id=9
)开始,然后向下工作,选择我们尚未看到的具有's的行数限制。我们“选择”是因为我们还没有. 我们继续像这样往下工作,但是当我们到达时我们跳过它,因为我们已经有了(从行开始)。我们以同样的方式继续,我们最终停下来,因为我们已经积累了行(我们想要的限制)。4
sec
id=9
sec=1
id=7
sec=5
id=8
id=3
4
id | sec
----+-----
9 | 1 <- 1
8 | 5 <- 2
7 | 5 # skip, already have sec=5
6 | 4 <- 3
5 | 1 # skip, already have sec=1
4 | 1 # skip, already have sec=1
3 | 3 <- 4
2 | 2
1 | 1
Run Code Online (Sandbox Code Playgroud)
当然,SQL
算法可以(将!)与我描述的不同。
想要的结果:
id
----
9
8
6
3
(4 rows)
Run Code Online (Sandbox Code Playgroud)
如果我想增加5
行的限制,那么结果id=2
中将包含行。但是,如果我增加了限6
行,与行id=1
会不会被添加,因为sec=1
已经看到。
注意:虽然没关系,但我使用的是PostgreSQL 9.3.1。
如果您想快速构建表以进行测试:
CREATE TABLE my_table (id serial primary key, sec integer DEFAULT 0 NOT NULL);
INSERT INTO my_table (sec) VALUES
(1)
, (2)
, (3)
, (1)
, (1)
, (4)
, (5)
, (5)
, (1);
CREATE INDEX index_my_table_on_sec ON my_table (sec);
Run Code Online (Sandbox Code Playgroud)
SELECT id,
sec
FROM (
SELECT id,
sec,
row_number() OVER (PARTITION BY sec ORDER BY id DESC) AS rn
FROM my_table
) t
WHERE rn = 1
ORDER BY id DESC
LIMIT 4;
Run Code Online (Sandbox Code Playgroud)
SQLFiddle 示例:http ://sqlfiddle.com/#!15/1ca01/1
在 Postgres 中,这更简单DISTINCT ON
:
SELECT *
FROM (
SELECT DISTINCT ON (sec)
id, sec
FROM tbl
ORDER BY sec, id DESC
) sub
ORDER BY id DESC
LIMIT 4;
Run Code Online (Sandbox Code Playgroud)
关于SO的相关答案中的详细说明:
对于大表和小LIMIT
表,无论是 this 还是@a_horse 的解决方案都不是很有效。子查询将遍历整个表,浪费大量时间......
过去,我曾尝试使用递归 CTE 解决类似问题,但未能解决,于是求助于 PL/pgSQL 的过程式解决方案。例子:
最后,这是一个有效的 rCTE:
WITH RECURSIVE cte AS (
( -- parentheses required
SELECT id, '{}'::int[] AS last_arr, ARRAY[sec] AS arr
FROM tbl
ORDER BY id DESC
LIMIT 1
)
UNION ALL
(
SELECT b.id, c.arr
, CASE WHEN b.sec = ANY (c.arr) THEN c.arr ELSE b.sec || c.arr END
FROM cte c
JOIN tbl b ON b.id < c.id
WHERE array_length(c.arr, 1) < 4
ORDER BY id DESC
LIMIT 1
)
)
SELECT id, arr[1] AS sec
FROM cte
WHERE last_arr <> arr;
Run Code Online (Sandbox Code Playgroud)
它不像我希望的那样快速或优雅,也不像下面的函数那么快,但在我的测试中比上面的查询快。
迄今为止最快的:
CREATE OR REPLACE FUNCTION f_first_uniq(_rows int)
RETURNS TABLE (id int, sec int) AS
$func$
DECLARE
_arr int[];
BEGIN
FOR id, sec IN
SELECT t.id, t.sec FROM tbl t ORDER BY t.id DESC
LOOP
IF sec = ANY (_arr) THEN
-- do nothing
ELSE
RETURN NEXT;
_arr := _arr || sec;
EXIT WHEN array_length(_arr, 1) >= _rows;
END IF;
END LOOP;
END
$func$ LANGUAGE plpgsql;
Run Code Online (Sandbox Code Playgroud)
称呼:
SELECT * FROM f_first_uniq(4);
Run Code Online (Sandbox Code Playgroud)
SQL Fiddle演示了所有三个。
可以为任何以表名和列名作为参数和动态 SQL 的表工作EXECUTE
...
在只有30k
行的测试表中,该函数的运行速度比上述查询快2000 倍(已经比 a_horse 的版本快了约 30%)。这种差异随着表的大小而增长。函数的性能几乎是恒定的,而查询的性能会逐渐变差,因为它首先尝试在所有表中找到不同的值。在一个有一百万行的表中试试这个......