PostgreSQL'de tür güvenli bir ilk () toplama işlevi var mı?


21

Tam soru yeniden yazma

Bir First () toplama işlevi arıyorum.

Burada neredeyse işe yarayan bir şey buldum:

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
);

Sorun, bir varchar (n) sütunun ilk () işlevinden geçtiğinde, basit varchar'a (boyutsuz) dönüştürülmesidir. Bir işlevde RETURNS SETOF anyelement olarak sorguyu döndürmeye çalışırken, aşağıdaki hatayı alıyorum:

HATA: Sorgunun yapısı işlev sonuç türüyle eşleşmiyor SQL: 42804 Detalhe: Döndürülen tür karakteri, sütun 2'de beklenen tür karakter değişikliğiyle (40) eşleşmiyor. Contexto: PL / pgSQL işlevi vsr_table_at_time (herhangi bir zaman dilimi, zaman dilimi olmadan zaman damgası) ) 31 numaralı satırda RETURN QUERY

Aynı wiki sayfasında , yukarıdaki fonksiyonun yerine geçecek olan fonksiyonun C Sürümü bağlantısı vardır . Nasıl yükleyeceğimi bilmiyorum, ama bu sürümün sorunumu çözüp çözemeyeceğini merak ediyorum.

Bu arada, yukarıdaki işlevi aynı giriş sütunu türünü döndürecek şekilde değiştirebileceğim bir yol var mı?

Yanıtlar:


18

DISTINCT ON()

Bir yan not olarak, tam olarak bu nedir DISTINCT ON()(karıştırılmamalıdır DISTINCT)

SELECT DISTINCT ON ( expression [, ...] ) verilen ifadelerin eşit olduğu değerlendirilen her satır kümesinin yalnızca ilk satırını tutar . DISTINCT ONİfadeleri ile aynı kurallar kullanılarak yorumlanır ORDER BY(yukarıya bakınız). ORDER BYİstenen satırın önce görünmesini sağlamak için kullanılmadıkça , her kümenin "ilk satırı" nın öngörülemez olduğunu unutmayın. Örneğin

Eğer yazacak olsaydın,

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

Etkili

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

Bu ilk alır z. İki önemli fark vardır,

  1. Sen edebilirsiniz da ileri kümlenme ücret ödemeden diğer sütunları seçmek ..

    SELECT DISTINCT ON(x,y) z, k, r, t, v
    FROM foo;
    -- ORDER BY z, k, r, t, v;
  2. Olmadığından hiçbir GROUP BYyapabilirsiniz değil onunla (gerçek) agrega kullanın.

    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;

Unutma ORDER BY

Ayrıca, cesur olmasam da şimdi

İstenen satırın önce görünmesini sağlamak için ORDER BY kullanılmadığı sürece, her kümenin "ilk satırı" nın önceden kestirilemeyeceğini unutmayın. Örneğin

Her zaman bir ORDER BY ileDISTINCT ON

Sıralı Küme Toplama İşlevini Kullanma

Ben bir sürü insan arıyoruz hayal first_value, Sıralı-Set toplamak işlevleri . Sadece onu oraya atmak istedim. İşlev mevcut olsaydı şöyle görünürdi:

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

Ama ne yazık ki bunu yapabilirsiniz.

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

1
Bu yanıtla ilgili sorun, yalnızca seçim listenizde soru tarafından ima edilmeyen BİR toplu istiyorsanız istediğinizde çalışmasıdır. Örneğin bir tablodan seçim yapmak ve birkaç sıralı ilk değer bulmak istiyorsanız DISTINCT ON, bu durumda çalışmaz. Bu bir toplama işlevi değil, aslında verileri filtreliyorsunuz ve böylece yalnızca bir kez yapabilirsiniz.
DB140141

6

Yay, PostgreSQL 9.4+ içindeki bazı özellikleri kullanarak davanızla kolay bir yol buldum

Bu örneği görelim:

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

Umarım davada sana yardımcı olur.


Bu çözümün sorunu, DOMAINveri türleriyle veya diğer küçük istisnalarla çalışmadığıdır . Aynı zamanda çok daha karmaşık ve zaman alıcıdır, tüm veri kümesinin bir dizisini oluşturur. Basit çözüm, özel bir toplama oluşturmak olacaktır, ancak şimdiye kadar bununla bile ideal çözümü bulamadım. Pencere işlevleri de kötüdür, çünkü bunlar kümeleri (FILTER deyimleriyle veya CROSS JOIN LATERAL ile) kullanabileceğiniz gibi kullanılamazlar
AlexanderMP

5

Sorunuza doğrudan bir cevap değil, ancak first_valuepencere işlevini denemelisiniz . Şöyle çalışır:

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

);

Daha sonra, her bir catkategorideki (kategori) ilk öğeyi istiyorsanız şu şekilde sorgulayacaksınız:

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

veya:

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

Üzgünüm, bunun kullanım durumum için geçerli olduğunu düşünmüyorum. İlk_değer, bir sıralamaya göre ilk örnek olarak (örnek tarihiniz) değerlendirilen belirli bir ortak değere (örnek kediniz) ait tüm kayıtları gösteren bir toplama işlevi değildir. Benim ihtiyacım farklı. Aynı seçimde, ilk boş değeri seçerek birkaç sütun üzerinde anlaşmaya ihtiyacım var. Yani, GROUP BY içindeki değer kombinasyonlarının her biri için tek bir kayıt çıkarmalıdır.
Alexandre Neto

2
Yukarıdaki karışımına ayrı atarak çalışmalarına yapılabilir: select distinct x, first_value(y) over (partition by x), first_value(z) over (partition by x) from .... Muhtemelen verimsiz ama prototipleme yapmam için yeterli. Kesinlikle tekrar olsa bir şey!
Max Murphy
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.