PostgreSQL
 sql >> Teknologi Basis Data >  >> RDS >> PostgreSQL

Gabungkan tabel dan ubah log menjadi tampilan di PostgreSQL

Dengan asumsi Postgres 9.1 atau lebih baru.
Saya menyederhanakan / mengoptimalkan kueri dasar Anda untuk mengambil nilai terbaru:

SELECT DISTINCT ON (1,2)
       c.unique_id, a.attname AS col, c.value
FROM   pg_attribute a
LEFT   JOIN changes c ON c.column_name = a.attname
                     AND c.table_name  = 'instances'
                 --  AND c.unique_id   = 3  -- uncomment to fetch single row
WHERE  a.attrelid = 'instances'::regclass   -- schema-qualify to be clear?
AND    a.attnum > 0                         -- no system columns
AND    NOT a.attisdropped                   -- no deleted columns
ORDER  BY 1, 2, c.updated_at DESC;

Saya menanyakan katalog PostgreSQL alih-alih skema informasi standar karena itu lebih cepat. Perhatikan pemeran khusus untuk ::regclass .

Sekarang, itu memberi Anda tabel . Anda ingin semua nilai untuk satu unique_id dalam baris .
Untuk mencapainya, pada dasarnya Anda memiliki tiga opsi:

  1. Satu subpilih (atau gabung) per kolom. Mahal dan berat. Tetapi opsi yang valid hanya untuk beberapa kolom.

  2. CASE besar pernyataan.

  3. Fungsi poros . PostgreSQL menyediakan crosstab() fungsi dalam modul tambahan tablefunc untuk itu.
    Petunjuk dasar:

    • Kueri Tab Silang PostgreSQL

Tabel pivot dasar dengan crosstab()

Saya benar-benar menulis ulang fungsinya:

SELECT *
FROM   crosstab(
    $x$
    SELECT DISTINCT ON (1, 2)
           unique_id, column_name, value
    FROM   changes
    WHERE  table_name = 'instances'
 -- AND    unique_id = 3  -- un-comment to fetch single row
    ORDER  BY 1, 2, updated_at DESC;
    $x$,

    $y$
    SELECT attname
    FROM   pg_catalog.pg_attribute
    WHERE  attrelid = 'instances'::regclass  -- possibly schema-qualify table name
    AND    attnum > 0
    AND    NOT attisdropped
    AND    attname <> 'unique_id'
    ORDER  BY attnum
    $y$
    )
AS tbl (
 unique_id integer
-- !!! You have to list all columns in order here !!! --
);

Saya memisahkan pencarian katalog dari kueri nilai, sebagai crosstab() fungsi dengan dua parameter menyediakan nama kolom secara terpisah. Nilai yang hilang (tidak ada entri dalam perubahan) diganti dengan NULL secara otomatis. Sangat cocok untuk kasus penggunaan ini!

Dengan asumsi bahwa attname cocok dengan column_name . Tidak termasuk unique_id , yang memainkan peran khusus.

Otomasi penuh

Mengatasi komentar Anda:Ada cara untuk menyediakan daftar definisi kolom secara otomatis. Ini bukan untuk menjadi lemah hati, meskipun.

Saya menggunakan sejumlah fitur Postgres lanjutan di sini:crosstab() , fungsi plpgsql dengan SQL dinamis, penanganan tipe komposit, kutipan dolar tingkat lanjut, pencarian katalog, fungsi agregat, fungsi jendela, tipe pengenal objek, ...

Lingkungan pengujian:

CREATE TABLE instances (
  unique_id int
, col1      text
, col2      text -- two columns are enough for the demo
);

INSERT INTO instances VALUES
  (1, 'foo1', 'bar1')
, (2, 'foo2', 'bar2')
, (3, 'foo3', 'bar3')
, (4, 'foo4', 'bar4');

CREATE TABLE changes (
  unique_id   int
, table_name  text
, column_name text
, value       text
, updated_at  timestamp
);

INSERT INTO changes VALUES
  (1, 'instances', 'col1', 'foo11', '2012-04-12 00:01')
, (1, 'instances', 'col1', 'foo12', '2012-04-12 00:02')
, (1, 'instances', 'col1', 'foo1x', '2012-04-12 00:03')
, (1, 'instances', 'col2', 'bar11', '2012-04-12 00:11')
, (1, 'instances', 'col2', 'bar17', '2012-04-12 00:12')
, (1, 'instances', 'col2', 'bar1x', '2012-04-12 00:13')

, (2, 'instances', 'col1', 'foo2x', '2012-04-12 00:01')
, (2, 'instances', 'col2', 'bar2x', '2012-04-12 00:13')

 -- NO change for col1 of row 3 - to test NULLs
, (3, 'instances', 'col2', 'bar3x', '2012-04-12 00:13');

 -- NO changes at all for row 4 - to test NULLs

Fungsi otomatis untuk satu tabel

CREATE OR REPLACE FUNCTION f_curr_instance(int, OUT t public.instances) AS
$func$
BEGIN
   EXECUTE $f$
   SELECT *
   FROM   crosstab($x$
      SELECT DISTINCT ON (1,2)
             unique_id, column_name, value
      FROM   changes
      WHERE  table_name = 'instances'
      AND    unique_id =  $f$ || $1 || $f$
      ORDER  BY 1, 2, updated_at DESC;
      $x$
    , $y$
      SELECT attname
      FROM   pg_catalog.pg_attribute
      WHERE  attrelid = 'public.instances'::regclass
      AND    attnum > 0
      AND    NOT attisdropped
      AND    attname <> 'unique_id'
      ORDER  BY attnum
      $y$) AS tbl ($f$
   || (SELECT string_agg(attname || ' ' || atttypid::regtype::text
                       , ', ' ORDER BY attnum) -- must be in order
       FROM   pg_catalog.pg_attribute
       WHERE  attrelid = 'public.instances'::regclass
       AND    attnum > 0
       AND    NOT attisdropped)
   || ')'
   INTO t;
END
$func$  LANGUAGE plpgsql;

Tabel instances dikodekan dengan keras, skema memenuhi syarat untuk tidak ambigu. Perhatikan penggunaan tipe tabel sebagai tipe pengembalian. Ada tipe baris yang terdaftar secara otomatis untuk setiap tabel di PostgreSQL. Ini pasti cocok dengan jenis kembalian crosstab() fungsi.

Ini mengikat fungsi ke jenis tabel:

  • Anda akan mendapatkan pesan kesalahan jika mencoba DROP meja
  • Fungsi Anda akan gagal setelah ALTER TABLE . Anda harus membuatnya kembali (tanpa perubahan). Saya menganggap ini bug di 9.1. ALTER TABLE seharusnya tidak merusak fungsi secara diam-diam, tetapi menimbulkan kesalahan.

Ini berkinerja sangat baik.

Telepon:

SELECT * FROM f_curr_instance(3);

unique_id | col1  | col2
----------+-------+-----
 3        |<NULL> | bar3x

Perhatikan bagaimana col1 adalah NULL di sini.
Gunakan dalam kueri untuk menampilkan instance dengan nilai terbarunya:

SELECT i.unique_id
     , COALESCE(c.col1, i.col1)
     , COALESCE(c.col2, i.col2)
FROM   instances i
LEFT   JOIN f_curr_instance(3) c USING (unique_id)
WHERE  i.unique_id = 3;

Otomasi penuh untuk meja apa pun

(Ditambahkan 2016. Ini adalah dinamit.)
Memerlukan Postgres 9.1 atau nanti. (Bisa dibuat untuk bekerja dengan hal 8.4, tapi saya tidak repot-repot untuk backpatch.)

CREATE OR REPLACE FUNCTION f_curr_instance(_id int, INOUT _t ANYELEMENT) AS
$func$
DECLARE
   _type text := pg_typeof(_t);
BEGIN
   EXECUTE
   (
   SELECT format
         ($f$
         SELECT *
         FROM   crosstab(
            $x$
            SELECT DISTINCT ON (1,2)
                   unique_id, column_name, value
            FROM   changes
            WHERE  table_name = %1$L
            AND    unique_id  = %2$s
            ORDER  BY 1, 2, updated_at DESC;
            $x$    
          , $y$
            SELECT attname
            FROM   pg_catalog.pg_attribute
            WHERE  attrelid = %1$L::regclass
            AND    attnum > 0
            AND    NOT attisdropped
            AND    attname <> 'unique_id'
            ORDER  BY attnum
            $y$) AS ct (%3$s)
         $f$
          , _type, _id
          , string_agg(attname || ' ' || atttypid::regtype::text
                     , ', ' ORDER BY attnum)  -- must be in order
         )
   FROM   pg_catalog.pg_attribute
   WHERE  attrelid = _type::regclass
   AND    attnum > 0
   AND    NOT attisdropped
   )
   INTO _t;
END
$func$  LANGUAGE plpgsql;

Panggil (menyediakan jenis tabel dengan NULL::public.instances :

SELECT * FROM f_curr_instance(3, NULL::public.instances);

Terkait:

  • Memfaktorkan ulang fungsi PL/pgSQL untuk mengembalikan output dari berbagai kueri SELECT
  • Cara menyetel nilai bidang variabel komposit menggunakan SQL dinamis



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Dapatkan Jumlah Hari dalam Sebulan di PostgreSQL

  2. Tidak bisa begitu saja menggunakan nama tabel PostgreSQL (relasi tidak ada)

  3. Pencocokan partisi tingkat lanjut untuk penggabungan partisi

  4. Beralih peran setelah terhubung ke database

  5. Heroku psql:FATAL:slot koneksi yang tersisa dicadangkan untuk koneksi superuser non-replikasi