Postgresql seleccione entre el rango de meses

Tengo una tabla con una de las columnas como fecha en formato 'YYYY-MM-DD'. ¿Puedo usar select para obtener todos los datos en un rango mensual? Digamos que quiero todos los datos desde 2012-01-xx hasta 2013-04-xx. Así que básicamente estoy buscando una consulta SQL como la que se muestra a continuación:

SELECT * FROM table WHERE date IN BETWEEN '2012-01' AND '2013-04' (INVALID QUERY)

Dado que todos los meses comienzan con '01', puedo modificar la consulta anterior para ajustar la condición de inicio.

SELECT * FROM table WHERE date IN BETWEEN '2012-01-01' AND '2013-04' (INVALID QUERY)

Ahora el problema viene con la fecha de finalización. Tengo que calcular manualmente la última fecha del mes dado, teniendo en cuenta todos los factores, como la duración del mes, el año bisiesto, etc., ya que la consulta falla si la fecha dada no es válida. Así que actualmente estoy haciendo algo como esto:

SELECT * FROM table WHERE date IN BETWEEN '2012-01-01' AND 'VALID_MONTH_END_DATE' (VALID Query)

Quiero saber si hay alguna forma de evitar este cálculo de fecha de finalización válido.

Aclaración

He pensado anteriormente en el primer día del próximo mes, pero incluso entonces tendré que aplicar algo de lógica, por ejemplo, si es diciembre, el próximo mes sería enero del próximo año. Quería saber si es posible una solución solo de SQL.

preguntado el 02 de julio de 12 a las 01:07

5 Respuestas

es bueno evitar BETWEEN para comparaciones de rango de fechas. mejor uso >= y < ya que funciona igualmente con columnas/valores de fecha y fecha y hora.

Una forma (si puede construir las fechas externamente):

WHERE date >= DATE '2012-01-01' 
  AND date < DATE '2013-05-01'      --- first date of the next month

También puede usar la aritmética de fechas:

WHERE date >= DATE '2012-01-01' 
  AND date < DATE ('2013-04-01' + INTERVAL '1 MONTH')

o OVERLAPS operador:

WHERE (date, date) OVERLAPS
      (DATE '2012-01-01', DATE '2013-05-01')

También debe leer la documentación de Postgres: Funciones y operadores de fecha / hora

El manual explica aquí el porqué OVERLAPS funciona de esta manera:

Se considera que cada período de tiempo representa el inicio del intervalo semiabierto <= tiempo < final, a menos que el inicio y el final sean iguales, en cuyo caso representa ese único instante de tiempo. Esto significa, por ejemplo, que dos períodos de tiempo con solo un punto final en común no se superponen.

Respondido 02 Jul 12, 02:07

Las superposiciones funcionan bien cuando sé la última fecha. Estoy atascado cuando el último mes es diciembre. - Parakkat Vipin

¿De dónde sacas los parámetros? ¿Una aplicación? ¿La web? ¿Otra consulta? ¿Los tienes como cadenas o enteros (año, mes)? - ypercubeᵀᴹ

Se debe tener cuidado al agregar un interval a una date. El resultado es un timestamp. Sin embargo, funciona en el ejemplo anterior. Echa el resultado a date, si importa. Agregar un intervalo 'n mes' siempre da como resultado el mismo día del mes, independientemente de la cantidad real de días en los meses involucrados, o el día máximo disponible, cuando el mes resultante tiene menos días. Para agregar un número exacto de días, agregue un integer a una date, esto da como resultado una date. - Erwin Brandstetter

¿No es cierto que la mayoría de los optimizadores modernos traducirán "entre" a ">= y <="? Hay muy poco impacto en el rendimiento, si es que lo hay, cuando usas el medio. A menos que estés en una versión anterior. - sam yi

+1 para superposiciones... pero estas opciones aún no abordan las necesidades de la operación. ¿Qué sucede si se proporcionan dos fechas (fecha de inicio y fecha de finalización)... y tiene que calcular el final del mes? - sam yi

Esta es una necesidad muy común en los entornos de informes. He creado varias funciones para acomodar estas manipulaciones de fechas.

CREATE OR REPLACE FUNCTION public.fn_getlastofmonth (
  date
)
RETURNS date AS
$body$
begin
    return (to_char(($1 + interval '1 month'),'YYYY-MM') || '-01')::date - 1;
end;
$body$
LANGUAGE 'plpgsql'
VOLATILE
CALLED ON NULL INPUT
SECURITY INVOKER
COST 100;

Entonces puedes usar ...

WHERE date >= '2012-01-01' 
  AND date < fn_getlastofmonth('2013-04-01') 

Respondido 02 Jul 12, 01:07

No he probado esto, pero vale la pena intentarlo.

SELECT * 
FROM some_table 
WHERE some_date 
BETWEEN '2012-01-01' AND date('2013-04-01') - integer '1'

http://www.postgresql.org/docs/9.1/static/functions-datetime.html

Respondido 02 Jul 12, 01:07

ypercube tiene la mejor respuesta, no es necesario usar entre - house9

Todas las respuestas anteriores proporcionan una solución funcional, pero están incompletas de una forma u otra. Como estaba buscando una solución solo de SQL (sin función), estoy combinando los mejores consejos de las soluciones anteriores.

La solución ideal para mi pregunta sería:

SELECT * FROM table
WHERE date >= '2012-01-01' AND date < date('2013-04-01') + interval '1 month'

EDITAR

No estoy usando la función de superposición aquí porque estoy pasando los valores de fecha predeterminados para el inicio y el final como 'época' y 'ahora'. Si el usuario no ha especificado ningún intervalo de tiempo, la consulta se convierte en:

SELECT * FROM table
WHERE date >= 'epoch' AND date < 'now'

La función de superposición no puede manejar 'época' y 'ahora' y da un error de SQL mientras que el código anterior funciona perfectamente para ambos casos.

PD: He votado a favor todas las respuestas que eran correctas de alguna manera y me llevaron a esta solución.

Respondido 02 Jul 12, 05:07

Creo que necesitas quitar el - interval '1 day' parte. Pruébalo tal cual, con un '2013-04-30' fecha en la mesa. - ypercubeᵀᴹ

Estoy buscando datos mensuales. Digamos que necesito obtener todos los datos de algún año, mes a aaaa-mm. Mi fecha de finalización siempre es aaaa-mm-01 a la que le sumo un mes y resto un día. - Parakkat Vipin

Solo prueba la consulta: SELECT * FROM table WHERE (date, date) OVERLAPS('2012-01-01' , date('2013-04-01') + interval '1 month' - interval '1 day') cuando hay un date en la tabla con valor 2013-04-30. ¿Se devuelve? - ypercubeᵀᴹ

Hola, lo acabo de revisar. no funciona Gracias por mencionarlo. Se debió a la naturaleza de la función de superposición. - Parakkat Vipin

SET search_path=tmp;

DROP TABLE zdates;
CREATE TABLE zdates
        ( zdate timestamp NOT NULL PRIMARY KEY
        , val INTEGER NOT NULL
        );
-- some data
INSERT INTO zdates(zdate,val)
SELECT s, 0
FROM generate_series('2012-01-01', '2012-12-31', '1 day'::interval ) s
        ;

UPDATE zdates
SET val = 1000 * random();

DELETE FROM zdates
WHERE random() < 0.1;

-- CTE to round the intervals down/up to the begin/end of the month
WITH zope AS (
        SELECT date_trunc('month', zdate)::date AS zbegin
        ,  date_trunc('month', zdate+interval '1 month')::date AS zend
        , val AS val
        FROM zdates
        )
SELECT z.zbegin
        , z.zend
        , COUNT(*) AS zcount
        , SUM(val) AS zval
FROM zope z
GROUP BY z.zbegin, z.zend
ORDER BY z.zbegin, z.zend
        ;

RESULTADO:

CREATE TABLE
INSERT 0 366
UPDATE 366
DELETE 52
   zbegin   |    zend    | zcount | zval  
------------+------------+--------+-------
 2012-01-01 | 2012-02-01 |     28 | 13740
 2012-02-01 | 2012-03-01 |     28 | 14923
 2012-03-01 | 2012-04-01 |     26 | 13775
 2012-04-01 | 2012-05-01 |     25 | 11880
 2012-05-01 | 2012-06-01 |     25 | 12693
 2012-06-01 | 2012-07-01 |     25 | 11082
 2012-07-01 | 2012-08-01 |     26 | 13254
 2012-08-01 | 2012-09-01 |     28 | 13632
 2012-09-01 | 2012-10-01 |     28 | 16461
 2012-10-01 | 2012-11-01 |     23 | 12622
 2012-11-01 | 2012-12-01 |     24 | 12554
 2012-12-01 | 2013-01-01 |     28 | 14563
(12 rows)

Respondido 02 Jul 12, 11:07

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.