En PostgreSQL, ¿existe una función agregada first () segura de tipo?


21

Pregunta completa reescritura

Estoy buscando una función agregada First ().

Aquí encontré algo que casi funciona:

CREATE OR REPLACE FUNCTION public.first_agg ( anyelement, anyelement )
RETURNS anyelement LANGUAGE sql IMMUTABLE STRICT AS $$
        SELECT $1;
$$;

-- And then wrap an aggregate around it
CREATE AGGREGATE public.first (
        sfunc    = public.first_agg,
        basetype = anyelement,
        stype    = anyelement
);

El problema es que cuando una columna varchar (n) pasa a través de la primera función (), se convierte en varchar simple (sin tamaño). Intentando devolver la consulta en una función como DEVOLUCIÓN DE CONFIGURACIÓN, obtengo el siguiente error:

ERROR: la estructura de la consulta no coincide con el tipo de resultado de la función Estado de SQL: 42804 Detalles: La variación del carácter de tipo devuelto no coincide con el tipo de variación esperado (40) en la columna 2. Contexto: función PL / pgSQL vsr_table_at_time (anyelement, marca de tiempo sin zona horaria ) línea 31 en CONSULTA DEVUELTA

En la misma página wiki hay un enlace a una versión C de la función que reemplazaría lo anterior. No sé cómo instalarlo, pero me pregunto si esta versión podría resolver mi problema.

Mientras tanto, ¿hay alguna manera de que pueda cambiar la función anterior para que devuelva exactamente el mismo tipo de la columna de entrada?

Respuestas:


18

DISTINCT ON()

Solo como una nota al margen, esto es precisamente lo que DISTINCT ON()hace (no debe confundirse con DISTINCT)

SELECT DISTINCT ON ( expression [, ...] ) mantiene solo la primera fila de cada conjunto de filas donde las expresiones dadas se evalúan como iguales . Las DISTINCT ONexpresiones se interpretan usando las mismas reglas que para ORDER BY(ver arriba). Tenga en cuenta que la "primera fila" de cada conjunto es impredecible a menos que ORDER BYse utilice para garantizar que la fila deseada aparezca primero. Por ejemplo

Entonces, si fueras a escribir,

SELECT myFirstAgg(z)
FROM foo
GROUP BY x,y;

Es efectivamente

SELECT DISTINCT ON(x,y) z
FROM foo;
-- ORDER BY z;

En eso se necesita el primero z. Hay dos diferencias importantes,

  1. Puede también seleccionar otras columnas sin costo adicional de la agregación ..

    SELECT DISTINCT ON(x,y) z, k, r, t, v
    FROM foo;
    -- ORDER BY z, k, r, t, v;
  2. Debido a que no existe GROUP BYpuede no utilizar agregados (real) con ella.

    CREATE TABLE foo AS
    SELECT * FROM ( VALUES
      (1,2,3),
      (1,2,4),
      (1,2,5)
    ) AS t(x,y,z);
    
    SELECT DISTINCT ON (x,y) z, sum(z)
    FROM foo;
    
    -- fails, as you should expect.
    SELECT DISTINCT ON (x,y) z, sum(z)
    FROM foo;
    
    -- would not otherwise fail.
    SELECT myFirstAgg(z), sum(z)
    FROM foo
    GROUP BY x,y;

No olvides ORDER BY

Además, aunque no lo en negrita, lo haré

Tenga en cuenta que la "primera fila" de cada conjunto es impredecible a menos que ORDER BY se use para garantizar que la fila deseada aparezca primero. Por ejemplo

Siempre use un ORDER BYconDISTINCT ON

Uso de una función agregada de conjunto ordenado

Me imagino un montón de gente está buscando first_value, Funciones ordenada Conjunto de agregado . Solo quería tirar eso por ahí. Se vería así, si existiera la función:

SELECT a, b, first_value() WITHIN GROUP (ORDER BY z)    
FROM foo
GROUP BY a,b;

Pero, por desgracia, puedes hacer esto.

SELECT a, b, percentile_disc(0) WITHIN GROUP (ORDER BY z)   
FROM foo
GROUP BY a,b;

1
El problema con esta respuesta es que solo funciona si desea UN agregado en su lista de selección, lo cual no está implícito en la pregunta. Si, por ejemplo, desea seleccionar de una tabla y encontrar varios primeros valores ordenados, DISTINCT ONno funcionará en este caso. No es una función agregada, en realidad está filtrando los datos, por lo que solo puede hacerlo una vez.
DB140141

6

Sí, descubrí una manera fácil con su caso mediante el uso de algunas características en PostgreSQL 9.4+

Veamos este ejemplo:

select  (array_agg(val ORDER BY i))[1] as first_value_orderby_i,
    (array_agg(val ORDER BY i DESC))[1] as last_value_orderby_i,
    (array_agg(val))[1] as last_value_all,
    (array_agg(val))[array_length(array_agg(val),1)] as last_value_all
   FROM (
        SELECT i, random() as val
        FROM generate_series(1,100) s(i)
        ORDER BY random()
    ) tmp_tbl

Espero que te ayude en tu caso.


El problema con esta solución es que no funciona con DOMAINtipos de datos u otras pequeñas excepciones. También es mucho más complejo y requiere mucho tiempo, ya que crea una matriz de todo el conjunto de datos. La solución simple sería crear un agregado personalizado, pero hasta ahora no he encontrado la solución ideal incluso con eso. Las funciones de ventana también son malas, ya que no se pueden usar de la misma manera que se podrían usar agregados (con declaraciones FILTER o en CROSS JOIN LATERAL)
AlexanderMP

5

No es una respuesta directa a su pregunta, pero debe probar la first_valuefunción de ventana. Funciona así:

CREATE TABLE test (
    id SERIAL NOT NULL PRIMARY KEY,
    cat TEXT,
    value VARCHAR(2)
    date TIMESTAMP WITH TIME ZONE

);

Luego, si desea el primer elemento en cada cat(categoría), consultará así:

SELECT
    cat,
    first_value(date) OVER (PARTITION BY cat ORDER BY date)
FROM
    test;

o:

SELECT
    cat,
    first_value(date) OVER w
FROM
    test
WINDOW w AS (PARTITION BY cat ORDER BY date);

Lo siento, no creo que esto se aplique a mi caso de uso. First_value no es una función de agregación, que muestra todos los registros del con un cierto valor común (su gato de ejemplo) que se evalúa como el primero de acuerdo con algún orden (su fecha de ejemplo). Mi necesidad es diferente Necesito, en la misma selección, agregar varias columnas eligiendo el primer valor no nulo. Es decir, debería generar un único registro para cada combinación de valores en GROUP BY.
Alexandre Neto

2
Lo anterior se puede hacer que el trabajo lanzando distinta a la mezcla: select distinct x, first_value(y) over (partition by x), first_value(z) over (partition by x) from .... Probablemente ineficiente pero suficiente para que yo pueda seguir con la creación de prototipos. ¡Definitivamente algo para volver a visitar!
Max Murphy
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.