Bagaimana mengembalikan hasil SELECT di dalam fungsi di PostgreSQL?

106

Saya memiliki fungsi ini di PostgreSQL, tetapi saya tidak tahu cara mengembalikan hasil kueri:

CREATE OR REPLACE FUNCTION wordFrequency(maxTokens INTEGER)
  RETURNS SETOF RECORD AS
$$
BEGIN
    SELECT text, count(*), 100 / maxTokens * count(*)
    FROM (
        SELECT text
    FROM token
    WHERE chartype = 'ALPHABETIC'
    LIMIT maxTokens
    ) as tokens
    GROUP BY text
    ORDER BY count DESC
END
$$
LANGUAGE plpgsql;

Tapi saya tidak tahu bagaimana mengembalikan hasil kueri di dalam fungsi PostgreSQL.

Saya menemukan bahwa tipe pengembalian seharusnya SETOF RECORD, bukan? Tetapi perintah kembali tidak benar.

Bagaimana cara yang benar untuk melakukan ini?

Renato Dinhani
sumber
Mengapa Anda menghitungnya; apakah Anda memiliki token duplikat di TABEL token Anda? Juga: tambahkan definisi tabel ke pertanyaan Anda.
wildplasser
1
Apakah ini seluruh fungsi Anda? Jika Anda tidak memiliki pernyataan lain dalam fungsi tersebut, Anda harus membuatnya LANGUAGE SQL.
jpmc26

Jawaban:

135

Penggunaan RETURN QUERY:

CREATE OR REPLACE FUNCTION word_frequency(_max_tokens int)
  RETURNS TABLE (txt   text   -- also visible as OUT parameter inside function
               , cnt   bigint
               , ratio bigint) AS
$func$
BEGIN
   RETURN QUERY
   SELECT t.txt
        , count(*) AS cnt                 -- column alias only visible inside
        , (count(*) * 100) / _max_tokens  -- I added brackets
   FROM  (
      SELECT t.txt
      FROM   token t
      WHERE  t.chartype = 'ALPHABETIC'
      LIMIT  _max_tokens
      ) t
   GROUP  BY t.txt
   ORDER  BY cnt DESC;                    -- potential ambiguity 
END
$func$  LANGUAGE plpgsql;

Panggilan:

SELECT * FROM word_frequency(123);

Penjelasan:

  • Hal ini jauh lebih praktis secara eksplisit menentukan jenis kembali dari sekedar menyatakan sebagai rekor. Dengan cara ini Anda tidak perlu menyediakan daftar definisi kolom dengan setiap panggilan fungsi. RETURNS TABLEadalah salah satu cara untuk melakukannya. Ada yang lainnya. Tipe data OUTparameter harus sama persis dengan apa yang dikembalikan oleh kueri.

  • Pilih nama untuk OUTparameter dengan hati-hati. Mereka terlihat di tubuh fungsi hampir di mana saja. Kolom kualifikasi tabel dengan nama yang sama untuk menghindari konflik atau hasil yang tidak diharapkan. Saya melakukan itu untuk semua kolom dalam contoh saya.

    Tapi perhatikan konflik penamaan potensial antara OUTparameter cntdan kolom alias dari nama yang sama. Dalam kasus khusus ini ( RETURN QUERY SELECT ...) Postgres menggunakan alias kolom di atas OUTparameter dengan cara apa pun. Ini bisa jadi ambigu dalam konteks lain. Ada berbagai cara untuk menghindari kebingungan:

    1. Gunakan posisi ordinal dari item dalam daftar SELECT: ORDER BY 2 DESC. Contoh:
    2. Ulangi ekspresi tersebut ORDER BY count(*).
    3. (Tidak berlaku di sini.) Setel parameter konfigurasi plpgsql.variable_conflictatau gunakan perintah khusus #variable_conflict error | use_variable | use_columndalam fungsi. Lihat:
  • Jangan gunakan "teks" atau "hitung" sebagai nama kolom. Keduanya legal untuk digunakan di Postgres, tetapi "count" adalah kata yang dicadangkan dalam SQL standar dan nama fungsi dasar dan "teks" adalah tipe data dasar. Dapat menyebabkan kesalahan yang membingungkan. Saya menggunakan txtdan cntdalam contoh saya.

  • Menambahkan ;kesalahan sintaks yang hilang dan mengoreksi di header. (_max_tokens int), bukan (int maxTokens)- ketik setelah nama .

  • Saat bekerja dengan pembagian bilangan bulat, lebih baik mengalikannya terlebih dahulu dan membagi nanti, untuk meminimalkan kesalahan pembulatan. Lebih baik lagi: bekerja dengan numeric(atau tipe floating point). Lihat di bawah.

Alternatif

Menurut saya, seperti inilah tampilan kueri Anda (menghitung bagian relatif per token ):

CREATE OR REPLACE FUNCTION word_frequency(_max_tokens int)
  RETURNS TABLE (txt            text
               , abs_cnt        bigint
               , relative_share numeric) AS
$func$
BEGIN
   RETURN QUERY
   SELECT t.txt, t.cnt
        , round((t.cnt * 100) / (sum(t.cnt) OVER ()), 2)  -- AS relative_share
   FROM  (
      SELECT t.txt, count(*) AS cnt
      FROM   token t
      WHERE  t.chartype = 'ALPHABETIC'
      GROUP  BY t.txt
      ORDER  BY cnt DESC
      LIMIT  _max_tokens
      ) t
   ORDER  BY t.cnt DESC;
END
$func$  LANGUAGE plpgsql;

Ekspresi tersebut sum(t.cnt) OVER ()adalah fungsi jendela . Anda dapat menggunakan CTE daripada subkueri - cantik, tetapi subkueri biasanya lebih murah dalam kasus sederhana seperti ini.

Pernyataan eksplisitRETURN akhir tidak diperlukan (tetapi diizinkan) saat bekerja dengan OUTparameter atau RETURNS TABLE(yang membuat penggunaan OUTparameter secara implisit ).

round()dengan dua parameter hanya berfungsi untuk numerictipe. count()di subquery menghasilkan biginthasil dan sum()over ini bigintmenghasilkan numerichasil, sehingga kita menangani numericnomor secara otomatis dan semuanya jatuh ke tempatnya.

Erwin Brandstetter
sumber
Terima kasih banyak atas jawaban dan koreksi Anda. Sekarang berfungsi dengan baik (saya hanya mengubah jenis rasio ke numerik).
Renato Dinhani
@ RenatoDinhaniConceição Keren! Saya menambahkan versi yang mungkin atau mungkin tidak menjawab pertanyaan tambahan yang sebenarnya belum Anda tanyakan. ;)
Erwin Brandstetter
Bagus, satu-satunya hal adalah saya pikir Anda memerlukan RETURN;sebelum itu END;, setidaknya saya melakukannya - tapi saya melakukan UNION jadi saya tidak yakin apakah itu membuatnya berbeda.
yekta
@yekta: Saya menambahkan beberapa informasi tentang peran RETURN. Memperbaiki kesalahan yang tidak terkait dan menambahkan beberapa perbaikan saat melakukannya.
Erwin Brandstetter
1
Apa cara untuk melakukan ini bila Anda tidak ingin membatasi apa yang ada di Return TABLE (). IE RETURN TABLE (*)?
Nick
1

Hai, silakan periksa tautan di bawah ini

https://www.postgresql.org/docs/current/xfunc-sql.html

EX:

CREATE FUNCTION sum_n_product_with_tab (x int)
RETURNS TABLE(sum int, product int) AS $$
    SELECT $1 + tab.y, $1 * tab.y FROM tab;
$$ LANGUAGE SQL;
Moumita Das
sumber