Ejecutar una consulta compleja para cada fecha en un rango

9

Tengo una tabla de pedidos

   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

Los datos tienen órdenes permanentes no superpuestas para un client_id y, ocasionalmente, una orden temporal que anula la orden permanente en su start_date cuando tienen un client_id coincidente. Existen restricciones a nivel de aplicación que evitan que se superpongan órdenes del mismo tipo.

 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

Por ejemplo, en el 2015-07-18cliente 16 tiene la orden # 20, ya que es una orden activa porque anula la orden permanente # 19. Con un poco de alboroto, encontré una forma eficiente de consultar los ID de pedidos activos en una fecha.

    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

Si consulta esto 2015-07-18como marcadores de posición, obtendrá

 id 
----
 17
 18
 20

El plan de consulta en esta consulta en comparación con algunas de mis otras ideas (como las subconsultas que cuentan el número de pedidos temporales para un cliente en una fecha) es bastante pequeño y estoy bastante contento con él. (el diseño de la mesa, no me entusiasma)

Ahora, necesito un para encontrar todas las órdenes activas para un rango de fechas unidas con las fechas en que están activas. Por ejemplo, con el rango de fechas de 2015-07-18a 2015-07-19me gustaría el siguiente resultado.

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

La orden 20 anula la orden 19 en 2015-07-18pero no en 2015-07-19.

Descubrí generate_series()que puedo generar un rango de fechas, pero no tengo ni idea de cómo unir eso con esto para obtener una tabla de fechas y órdenes de identificación. Mi presentimiento es una unión cruzada, pero no puedo entender cómo hacer que eso funcione en esta circunstancia.

Gracias

ACTUALIZACIÓN Se agregó un violín sql .

reconbot
fuente
2
¿Podría mostrar algunos datos de ejemplo? Estas cosas activas / no activas y temporales no son muy claras después de la primera lectura.
dezso
Sí, no está claro Su consulta encontrará un pedido por cliente y no parece ser determinista. Si hay 2 o más pedidos para un cliente, con el mismo tipo, cuál de los dos será devuelto será arbitrario y variará según la ejecución. Por lo tanto, tiene algunas restricciones en la tabla que no nos ha dicho o su consulta no es correcta.
ypercubeᵀᴹ
Actualicé mi pregunta con muchos más detalles, y sí, hay restricciones en los datos.
Reconbot

Respuestas:

5

Usaría en select distinct onlugar de la función de ventana, luego solo uniría los días.

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

Puedo elaborar más si algo no está claro.

Simon Perepelitsa
fuente
Esto no cubre la orden temporal / orden permanente, pero eso podría hacerse después de la unión =)
reconbot
Esto especifica el mismo orden que en su consulta de ventana. Entonces, para cualquier (fecha, id_cliente) seleccionaría el primer tipo_orden en orden alfabético inverso.
Simon Perepelitsa
La unión interna es perfecta, y la selección distinta es mucho más fácil de entender (y funciona igual de bien) que la ventana. ¿Alguna otra razón por la que no debería usar las funciones de ventanas?
Reconbot
1
Eso es todo. Creo que distinct onestá aún más optimizado que la consulta de ventana. Por cierto, debo mencionar que este es un problema común "top-in-group" en SQL: stackoverflow.com/questions/3800551/…
Simon Perepelitsa
Esa es una gran lectura, tengo que estudiar un poco. Si tiene algo de tiempo, tengo una versión ampliada de esta pregunta que utiliza lo que aprendí aquí. dba.stackexchange.com/questions/108767/… Estoy seguro de que volveré para actualizarlo con lo que aprendí de ese enlace. Y gracias
reconbot
0

Escriba una función que tome una sola fecha como parámetro y devuelva una lista de fechas + id que tienen un orden.

Luego, use la serie generate_series como sugirió y llame a la función durante el intervalo de fechas.

Esta es una estrategia común cuando se trata de condiciones complejas en SQL.

He incluido un código a continuación, pero la respuesta de SQL anterior es mucho más simple.

Aquí está la función:

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 ;

Y como llamarlo:

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

SQLFiddle

Don drake
fuente
2
Es posible que desee eliminar esa respuesta con algunos detalles, código de muestra, etc. Tal como está, esta respuesta puede eliminarse ya que es bastante vaga.
Max Vernon
¿Podrías actualizar mi violín con un ejemplo? sqlfiddle.com/#!15/5a420/3/0
reconbot
He actualizado mi respuesta para incluir algo de código, pero la respuesta anterior es más simple.
Don Drake