对范围内的每个日期运行复杂的查询


9

我有一张订单表

   Column   |            Type             |                      Modifiers                      
------------+-----------------------------+-----------------------------------------------------
 id         | integer                     | not null default nextval('orders_id_seq'::regclass)
 client_id  | integer                     | not null
 start_date | date                        | not null
 end_date   | date                        | 
 order_type | character varying           | not null

数据的client_id具有不重叠的常规,并且在临时数据具有匹配的client_id时,有时会覆盖其start_date上的常规。存在应用程序级别约束,以防止相同类型的订单重叠。

 id | client_id | start_date |  end_date  | order_type 
----+-----------+------------+------------+------------
 17 |        11 | 2014-02-05 |            | standing
 18 |        15 | 2014-07-16 | 2015-07-19 | standing
 19 |        16 | 2015-04-01 |            | standing
 20 |        16 | 2015-07-18 | 2015-07-18 | temporary

例如,在2015-07-18客户端16上,订单20为有效订单,因为它优先于常规订单19。我大惊小怪地发现了一种查询日期上有效订单ID的有效方法。

    SELECT id from (
      SELECT
        id,
        first_value(id) OVER (PARTITION BY client_id ORDER BY order_type DESC) active_order_id
      FROM orders
      WHERE start_date <= ? and (end_date is null OR end_date >= ?)
    ) active_orders
    WHERE id = active_order_id

如果使用2015-07-18占位符查询,您将获得

 id 
----
 17
 18
 20

与我的其他一些想法(例如,子查询计算某个日期的客户临时订单的数量)相比,此查询的查询计划非常小,对此我感到非常满意。(桌子的设计,我并不感到兴奋)

现在,我需要查找一个日期范围内所有有效订单及其有效日期。例如,如果日期范围为2015-07-18到,2015-07-19我希望得到以下结果。

active_date | id 
------------+----
 2015-07-18 | 17
 2015-07-18 | 18
 2015-07-18 | 20
 2015-07-19 | 17
 2015-07-19 | 18
 2015-07-19 | 19

订单20会覆盖2015-07-18而不是上的订单19 2015-07-19

我发现generate_series()可以生成一个日期范围,但是我不知道如何将其与日期和订单ID结合使用。我的直觉是交叉连接,但在这种情况下我不知道如何进行这项工作。

谢谢

UPDATE 添加了一个sql小提琴


2
您可以显示一些示例数据吗?第一次读取后,此活动/非活动和临时的事情不是很清楚。
dezso 2015年

是的,还不清楚。您的查询将为每个客户找到一个订单,这似乎不是确定性的。如果同一类型的客户端有2个或更多订单,则返回的两个订单中的哪一个将是任意的,并随每次执行而变化。因此,您在表上有一些未曾告诉我们的约束,或者您的查询不正确。
ypercubeᵀᴹ

我用更多详细信息更新了我的问题,是的,数据存在约束。
reconbot

Answers:


5

我会使用select distinct on而不是窗口功能,然后加入天。

select 
    distinct on (date, client_id) date, 
    id 
from orders
inner join generate_series('2015-07-18'::date, '2015-07-19'::date, '1 day') date
  on start_date <= date and (end_date is null or date <= end_date)
order by date, client_id, order_type desc

http://sqlfiddle.com/#!15/5a420/16/0

如果不清楚,我可以详细说明。


这不包括临时订单/常规订单,但可以在join =)之后完成
reconbot

这指定与窗口查询中相同的顺序。因此,对于任何(日期,client_id),它将以相反的字母顺序选择第一个order_type。
Simon Perepelitsa,2015年

内连接是完美的,并且与窗口相比,选择的区别更容易理解(并且效果也差不多)。还有其他我不应该使用窗口功能的原因吗?
reconbot

1
就是这样 我认为distinct on比窗口查询更优化。顺便说一句,我应该指出,这是一种常见的SQL“顶入组”的问题:stackoverflow.com/questions/3800551/...
西蒙Perepelitsa

这是一本好书,我有一些研究要做。如果您有空,我可以使用在这里学到的知识来扩展此问题。dba.stackexchange.com/questions/108767/…我敢肯定,我会回来用我从该链接中学到的知识来更新它。谢谢
侦察机器人2015年

0

编写一个函数,该函数将单个日期作为参数,并返回具有顺序的日期+ ID的列表。

然后,按照您的建议使用generate_series并在日期范围内调用该函数。

在SQL中处理复杂条件时,这是一种常见的策略。

我在下面包含了一些代码,但是上面的SQL答案要简单得多。

功能如下:

create or replace function o( date) returns setof INT AS '
SELECT id from (
 SELECT
  id,
  first_value(id) OVER (PARTITION BY client_id ORDER BY order_type DESC) active_order_id
 FROM orders
 WHERE start_date <= $1 and (end_date is null OR end_date >= $1)
) active_orders
WHERE id = active_order_id;
' LANGUAGE sql ;

以及如何称呼它:

select distinct d, o(d::date) 
from generate_series('2015-07-18'::date, '2015-07-19'::date, '1 day') as d;

SQLFiddle


2
您可能希望使用一些详细信息,示例代码等来冲洗该答案。就这样,由于答案很模糊,因此该答案可能会被删除。
Max Vernon

您可以举个例子来更新我的小提琴吗?sqlfiddle.com/#!15/5a420/3/0
reconbot

我已经更新了答案以包含一些代码,但是上面的答案更简单。
Don Drake
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.