1

我正在尝试解决一个有趣的问题。我有一个表格,其中包含这些列(此示例中的日期以欧洲格式显示 - dd/mm/yyyy):

n_place_id   dt_visit_date
(integer)    (date)
==========   =============
   1           10/02/2012
   3           11/03/2012
   4           11/05/2012
  13           14/06/2012
   3           04/10/2012
   3           03/11/2012
   5           05/09/2012
  13           18/08/2012

基本上,每个地方都可能被访问多次 - 日期可能是过去(完成访问)或将来(计划访问)。为简单起见,今天的访问是计划的未来访问的一部分。

现在,我需要在这个表上运行一个选择,它会从这个表中提取唯一的地点 ID(没有日期),按以下顺序排序:

  1. 未来的访问先于过去的访问
  2. 未来访问优先于对同一地点的过去访问进行排序
  3. 对于以后的访问,最早的日期必须优先于排序相同的地方
  4. 对于过去的访问,最晚日期必须优先于对同一地点进行排序。

例如,对于上面显示的示例数据,我需要的结果是:

 5     (earliest future visit)
 3     (next future visit into the future)
13     (latest past visit)
 4     (previous past visit)
 1     (earlier visit in the past)

case when现在,我可以像这样在order by子句中使用所需的排序:

select
    n_place_id
from
    place_visit
order by
    (case when dt_visit_date >= now()::date then 1 else 2 end),
    (case when dt_visit_date >= now():: date then 1 else -1 end) * extract(epoch from dt_visit_date)

这种我需要的,但它确实包含重复的 ID,而我需要唯一的地点 ID。如果我尝试添加distinct到 select 语句中,postgres 会抱怨我必须order by在 select 子句中添加 - 但是唯一的将不再有意义,因为我在那里有日期。

不知何故,我觉得应该有一种方法可以在一个 select 语句中获得我需要的结果,但我不知道该怎么做。

如果这不能完成,那么,当然,我将不得不在代码中完成整个事情,但我更愿意在一个 SQL 语句中完成它。

PS我不担心性能,因为我要排序的数据集并不大。应用该where子句后,它很少会包含超过 10 条记录。

4

2 回答 2

2

您可以轻松地显示该DISTINCT ON行的其他列,结果n_place_id如下:

SELECT n_place_id, dt_visit_date
FROM  (
   SELECT DISTINCT ON (n_place_id) *
         ,dt_visit_date < now()::date    AS prio  -- future first
         ,@(now()::date - dt_visit_date) AS diff  -- closest first
   FROM   place_visit
   ORDER  BY n_place_id, prio, diff
   ) x
ORDER  BY prio, diff;

实际上,我选择了具有最早未来日期(包括“今天”)的行,n_place_id或者过去最晚的日期,失败了。
然后生成的唯一行按相同的标准排序。

  • FALSE排序之前TRUE
  • 绝对值”@有助于“最接近的优先”排序
  • DISTINCT ON有关此相关答案中特定 Postgres 的更多信息。

结果:

 n_place_id | dt_visit_date
------------+--------------
 5          | 2012-09-05
 3          | 2012-10-04
 13         | 2012-08-18
 4          | 2012-05-11
 1          | 2012-02-10
于 2012-09-04T13:16:27.947 回答
1

试试这个

 select n_place_id
 from 
 (
    select *, 
    extract(epoch from (dt_visit_date - now())) as seconds,  
    1 - SIGN(extract(epoch from (dt_visit_date - now())) ) as futurepast
    from #t
 ) v
 group by n_place_id
 order by max(futurepast) desc, min(abs(seconds))
于 2012-09-03T09:45:40.690 回答