PostgreSQL:根据排序顺序选择最近的行
我有一张这样的桌子:PostgreSQL:根据排序顺序选择最近的行,postgresql,sql-order-by,selection,rows,window-functions,Postgresql,Sql Order By,Selection,Rows,Window Functions,我有一张这样的桌子: a | user_id ----------+------------- 0.1133 | 2312882332 4.3293 | 7876123213 3.1133 | 2312332332 1.3293 | 7876543213 0.0033 | 2312222332 5.3293 | 5344343213 3.2133 | 4122331112 2.3293 | 9999942333
a | user_id
----------+-------------
0.1133 | 2312882332
4.3293 | 7876123213
3.1133 | 2312332332
1.3293 | 7876543213
0.0033 | 2312222332
5.3293 | 5344343213
3.2133 | 4122331112
2.3293 | 9999942333
我想找到一个特定的行,例如1.3293 | 7876543213,然后选择最近的4行。可能的话,上面2个,下面2个。
排序顺序是ASC的顺序
在这种情况下,我将得到:
0.0033 | 2312222332
0.1133 | 2312882332
2.3293 | 9999942333
3.1133 | 2312332332
如何使用PostgreSQL实现这一点?顺便说一句,我正在使用PHP
注意:对于最后一行或第一行,最近的行在上面4行或下面4行。测试用例:
查询:
与@Tim版本的主要差异:
根据问题a,用户id构成搜索条件,而不仅仅是a。这会以微妙不同的方式更改窗口框架、ORDERBY和WHERE子句
立即进行联合,不需要额外的查询级别。您需要在两个UNION查询周围加上括号,以允许单独的ORDERBY
按要求对结果进行排序。几乎不需要任何代价就需要另一个查询级别
由于参数在多个地方使用,我将输入集中在领先的CTE中。
为了重复使用,您可以将此查询几乎“按原样”包装到SQL或plpgsql函数中
结果:
DROP TABLE
CREATE TABLE
INSERT 0 8
val | num | relrnk
--------+------------+--------
0.0033 | 2312222332 | -2
0.1133 | 2312882332 | -1
1.3293 | 7876543213 | 0
2.3293 | 9999942333 | 1
3.1133 | 2312332332 | 2
(5 rows)
正如Erwin指出的,输出中不需要中间行。此外,应使用行号而不是秩
WITH ranked_lutsers AS (
SELECT val, num
-- ,rank() OVER (ORDER BY val) AS rnk
, row_number() OVER (ORDER BY val, num) AS rnk
FROM lutser
) SELECT that.val, that.num
, (that.rnk-this.rnk) AS relrnk
FROM ranked_lutsers that
JOIN ranked_lutsers this ON (that.rnk BETWEEN this.rnk-2 AND this.rnk+2 )
WHERE this.val = 1.3293
AND that.rnk <> this.rnk
;
更新2:始终选择四个,即使我们在列表的顶部或底部。这使得这个查询有点难看。但没有欧文的丑-
WITH ranked_lutsers AS (
SELECT val, num
-- ,rank() OVER (ORDER BY val) AS rnk
, row_number() OVER (ORDER BY val, num) AS rnk
FROM lutser
) SELECT that.val, that.num
, ABS(that.rnk-this.rnk) AS srtrnk
, (that.rnk-this.rnk) AS relrnk
FROM ranked_lutsers that
JOIN ranked_lutsers this ON (that.rnk BETWEEN this.rnk-4 AND this.rnk+4 )
-- WHERE this.val = 1.3293
WHERE this.val = 0.1133
AND that.rnk <> this.rnk
ORDER BY srtrnk ASC
LIMIT 4
;
更新:带有嵌套CTE的版本,具有外部连接!!!。为了方便起见,我在表中添加了一个主键,这听起来是个好主意
WITH distance AS (
WITH ranked_lutsers AS (
SELECT id
, row_number() OVER (ORDER BY val, num) AS rnk
FROM lutser
) SELECT l0.id AS one
,l1.id AS two
, ABS(l1.rnk-l0.rnk) AS dist
-- Warning: Cartesian product below
FROM ranked_lutsers l0
, ranked_lutsers l1 WHERE l0.id <> l1.id
)
SELECT lu.*
FROM lutser lu
JOIN distance di
ON lu.id = di.two
WHERE di.one= 1
ORDER by di.dist
LIMIT 4
;
还有一个:
WITH prec_rows AS
(SELECT a,
user_id,
ROW_NUMBER() OVER (ORDER BY a DESC) AS rn
FROM tbl
WHERE a < 1.3293
ORDER BY a DESC LIMIT 4),
succ_rows AS
(SELECT a,
user_id,
ROW_NUMBER() OVER (ORDER BY a ASC) AS rn
FROM tbl
WHERE a > 1.3293
ORDER BY a ASC LIMIT 4)
SELECT a, user_id
FROM
(SELECT a,
user_id,
rn
FROM prec_rows
UNION ALL SELECT a,
user_id,
rn
FROM succ_rows) AS s
ORDER BY rn, a LIMIT 4;
AFAIR WITH将实例化一个内存表,因此此解决方案的重点是尽可能限制其大小(在本例中为八行)。您是否尝试过在窗口->秩上进行自联接?@wildplasser:情人眼里出西施。:不过,您的美貌有一些瑕疵:1返回4行,而不是5行,排除标准行。2使用行号,而不是排名。我对我的回答作了解释。3您的查询因第一行/最后一行的角案例而失败。如果this.val=5.3293 4您的WHERE子句仅在上筛选,则问题将用户id定义为筛选。现在还不清楚,这是否会产生影响。我没有看到OP不想要中间一排。关于排数和排名的事情你是对的,当然,如果有联系,他们的行为会有所不同。在排序中需要一个额外的列来确定地解决这个问题,我会修复它。WITH位不会查询整个表吗?@TimLandscheidt:它会,而且它需要。最终选择将只返回4行。如果我们有启发性的信息,比如在+/-0.1的范围内总是有4行,那么我们可以在WITH子句中有一个索引和预选-可能更快…@ErwinBrandstetter,但是为什么不在一行上使用索引呢?请参阅我的答案以获取示例。所需的行必须是所选行前面的四行和后面的四行的并集的一部分,不必查询整个表。您仍然缺少角案例。试试this.val=5.3293的位置,看问题的最后一行..Tnx。我从来都不喜欢花车上的选择。。。但是额外的条件会让我的问题和你的一样难看!尽可能的美丽,但尽可能的丑陋。就像生活一样+1非常好。使用大型表和合适的索引,您的解决方案将表现得更好。我对你的提问有几个问题,这些问题不适合发表评论。我给我的答案添加了一个版本。@TimLandscheidt我使用了选择a,用户id从预制行限制4-选择计数1从成功行/2联合所有[相同,但对于成功行]。但是你的可能更快。
WITH ranked_lutsers AS (
SELECT val, num
-- ,rank() OVER (ORDER BY val) AS rnk
, row_number() OVER (ORDER BY val, num) AS rnk
FROM lutser
) SELECT that.val, that.num
, (that.rnk-this.rnk) AS relrnk
FROM ranked_lutsers that
JOIN ranked_lutsers this ON (that.rnk BETWEEN this.rnk-2 AND this.rnk+2 )
WHERE this.val = 1.3293
AND that.rnk <> this.rnk
;
val | num | relrnk
--------+------------+--------
0.0033 | 2312222332 | -2
0.1133 | 2312882332 | -1
2.3293 | 9999942333 | 1
3.1133 | 2312332332 | 2
(4 rows)
WITH ranked_lutsers AS (
SELECT val, num
-- ,rank() OVER (ORDER BY val) AS rnk
, row_number() OVER (ORDER BY val, num) AS rnk
FROM lutser
) SELECT that.val, that.num
, ABS(that.rnk-this.rnk) AS srtrnk
, (that.rnk-this.rnk) AS relrnk
FROM ranked_lutsers that
JOIN ranked_lutsers this ON (that.rnk BETWEEN this.rnk-4 AND this.rnk+4 )
-- WHERE this.val = 1.3293
WHERE this.val = 0.1133
AND that.rnk <> this.rnk
ORDER BY srtrnk ASC
LIMIT 4
;
val | num | srtrnk | relrnk
--------+------------+--------+--------
0.0033 | 2312222332 | 1 | -1
1.3293 | 7876543213 | 1 | 1
2.3293 | 9999942333 | 2 | 2
3.1133 | 2312332332 | 3 | 3
(4 rows)
WITH distance AS (
WITH ranked_lutsers AS (
SELECT id
, row_number() OVER (ORDER BY val, num) AS rnk
FROM lutser
) SELECT l0.id AS one
,l1.id AS two
, ABS(l1.rnk-l0.rnk) AS dist
-- Warning: Cartesian product below
FROM ranked_lutsers l0
, ranked_lutsers l1 WHERE l0.id <> l1.id
)
SELECT lu.*
FROM lutser lu
JOIN distance di
ON lu.id = di.two
WHERE di.one= 1
ORDER by di.dist
LIMIT 4
;
WITH prec_rows AS
(SELECT a,
user_id,
ROW_NUMBER() OVER (ORDER BY a DESC) AS rn
FROM tbl
WHERE a < 1.3293
ORDER BY a DESC LIMIT 4),
succ_rows AS
(SELECT a,
user_id,
ROW_NUMBER() OVER (ORDER BY a ASC) AS rn
FROM tbl
WHERE a > 1.3293
ORDER BY a ASC LIMIT 4)
SELECT a, user_id
FROM
(SELECT a,
user_id,
rn
FROM prec_rows
UNION ALL SELECT a,
user_id,
rn
FROM succ_rows) AS s
ORDER BY rn, a LIMIT 4;