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

Hasilkan nilai DEFAULT dalam CTE UPSERT menggunakan PostgreSQL 9.3

Postgres 9.5 menerapkan UPSERT . Lihat di bawah.

Postgres 9.4 atau lebih lama

Ini adalah masalah yang rumit. Anda mengalami pembatasan ini (per dokumentasi):

Dalam VALUES daftar yang muncul di tingkat atas INSERT , ekspresi dapat diganti dengan DEFAULT untuk menunjukkan bahwa nilai default kolom tujuan harus dimasukkan. DEFAULT tidak dapat digunakan saatVALUES muncul dalam konteks lain.

Penekanan saya yang berani. Nilai default tidak ditentukan tanpa tabel untuk dimasukkan. Jadi tidak ada langsung solusi untuk pertanyaan Anda, tetapi ada beberapa kemungkinan rute alternatif, tergantung pada persyaratan yang tepat .

Ambil nilai default dari katalog sistem?

Anda bisa ambil dari katalog sistem pg_attrdef like @Patrick berkomentar atau dari information_schema.columns . Instruksi lengkap di sini:

  • Dapatkan nilai default kolom tabel di Postgres?

Tapi kemudian Anda tetap hanya memiliki daftar baris dengan representasi teks dari ekspresi untuk memasak nilai default. Anda harus membangun dan mengeksekusi pernyataan secara dinamis untuk mendapatkan nilai untuk digunakan. Membosankan dan berantakan. Sebagai gantinya, kita dapat membiarkan fungsi Postgres bawaan melakukannya untuk kita :

Pintasan sederhana

Sisipkan baris dummy dan kembalikan untuk menggunakan default yang dihasilkan:

INSERT INTO playlist_items DEFAULT VALUES RETURNING *;

Masalah / cakupan solusi

  • Ini hanya dijamin berfungsi untuk STABLE atau IMMUTABLE ekspresi default . Kebanyakan VOLATILE fungsi akan bekerja dengan baik, tetapi tidak ada jaminan. current_timestamp keluarga fungsi memenuhi syarat sebagai stabil, karena nilainya tidak berubah dalam suatu transaksi.
    Khususnya, ini memiliki efek samping pada serial kolom (atau gambar default lainnya dari urutan). Tapi itu seharusnya tidak menjadi masalah, karena Anda biasanya tidak menulis ke serial kolom secara langsung. Itu tidak boleh dicantumkan di INSERT pernyataan sama sekali.
    Sisa kekurangan untuk serial kolom:urutannya masih dimajukan oleh satu panggilan untuk mendapatkan baris default, menghasilkan celah dalam penomoran. Sekali lagi, itu seharusnya tidak menjadi masalah, karena kesenjangan umumnya diharapkan dalam serial kolom.

Dua masalah lagi dapat diselesaikan:

  • Jika Anda memiliki kolom yang ditentukan NOT NULL , Anda harus memasukkan nilai dummy dan menggantinya dengan NULL dalam hasilnya.

  • Kami sebenarnya tidak ingin menyisipkan baris tiruan . Kita bisa menghapus nanti (dalam transaksi yang sama), tapi itu mungkin memiliki lebih banyak efek samping, seperti pemicu ON DELETE . Ada cara yang lebih baik:

Hindari baris tiruan

Mengkloning tabel sementara termasuk kolom default dan masukkan ke itu :

BEGIN;
CREATE TEMP TABLE tmp_playlist_items (LIKE playlist_items INCLUDING DEFAULTS)
   ON COMMIT DROP;  -- drop at end of transaction

INSERT INTO tmp_playlist_items DEFAULT VALUES RETURNING *;
...

Hasil yang sama, efek samping yang lebih sedikit. Karena ekspresi default disalin kata demi kata, kloning mengambil dari urutan yang sama jika ada. Tetapi efek samping lain dari baris atau pemicu yang tidak diinginkan dapat dihindari sepenuhnya.

Penghargaan untuk Igor atas idenya:

  • Postgresql, pilih baris "palsu"

Hapus NOT NULL kendala

Anda harus memberikan nilai dummy untuk NOT NULL kolom, karena (per dokumentasi):

Batasan bukan nol selalu disalin ke tabel baru.

Baik mengakomodasi mereka yang ada di INSERT pernyataan atau (lebih baik) menghilangkan kendala:

ALTER TABLE tmp_playlist_items
   ALTER COLUMN foo DROP NOT NULL
 , ALTER COLUMN bar DROP NOT NULL;

Ada cara cepat dan kotor dengan hak pengguna super:

UPDATE pg_attribute
SET    attnotnull = FALSE
WHERE  attrelid = 'tmp_playlist_items'::regclass
AND    attnotnull
AND    attnum > 0;

Ini hanya tabel sementara tanpa data dan tujuan lain, dan akan dihapus di akhir transaksi. Jadi jalan pintasnya menggoda. Namun, aturan dasarnya adalah:jangan pernah mengutak-atik katalog sistem secara langsung.

Jadi, mari kita lihat cara bersih :Otomatiskan dengan SQL dinamis dalam DO penyataan. Anda hanya perlu hak istimewa biasa Anda dijamin memilikinya sejak peran yang sama membuat tabel sementara.

DO $$BEGIN
EXECUTE (
   SELECT 'ALTER TABLE tmp_playlist_items ALTER '
       || string_agg(quote_ident(attname), ' DROP NOT NULL, ALTER ')
       || ' DROP NOT NULL'
   FROM   pg_catalog.pg_attribute
   WHERE  attrelid = 'tmp_playlist_items'::regclass
   AND    attnotnull
   AND    attnum > 0
   );
END$$

Jauh lebih bersih dan masih sangat cepat. Jalankan perawatan dengan perintah dinamis dan waspadai injeksi SQL. Pernyataan ini aman. Saya telah memposting beberapa jawaban terkait dengan lebih banyak penjelasan.

Solusi umum (9.4 dan yang lebih lama)

BEGIN;

CREATE TEMP TABLE tmp_playlist_items
   (LIKE playlist_items INCLUDING DEFAULTS) ON COMMIT DROP;

DO $$BEGIN
EXECUTE (
   SELECT 'ALTER TABLE tmp_playlist_items ALTER '
       || string_agg(quote_ident(attname), ' DROP NOT NULL, ALTER ')
       || ' DROP NOT NULL'
   FROM   pg_catalog.pg_attribute
   WHERE  attrelid = 'tmp_playlist_items'::regclass
   AND    attnotnull
   AND    attnum > 0
   );
END$$;

LOCK TABLE playlist_items IN EXCLUSIVE MODE;  -- forbid concurrent writes

WITH default_row AS (
   INSERT INTO tmp_playlist_items DEFAULT VALUES RETURNING *
   )
, new_values (id, playlist, item, group_name, duration, sort, legacy) AS (
   VALUES
      (651, 21, 30012, 'a', 30, 1, FALSE)
    , (NULL, 21, 1, 'b', 34, 2, NULL)
    , (668, 21, 30012, 'c', 30, 3, FALSE)
    , (7428, 21, 23068, 'd', 0, 4, FALSE)
   )
, upsert AS (  -- *not* replacing existing values in UPDATE (?)
   UPDATE playlist_items m
   SET   (  playlist,   item,   group_name,   duration,   sort,   legacy)
       = (n.playlist, n.item, n.group_name, n.duration, n.sort, n.legacy)
   --                                   ..., COALESCE(n.legacy, m.legacy)  -- see below
   FROM   new_values n
   WHERE  n.id = m.id
   RETURNING m.id
   )
INSERT INTO playlist_items
        (playlist,   item,   group_name,   duration,   sort, legacy)
SELECT n.playlist, n.item, n.group_name, n.duration, n.sort
                                   , COALESCE(n.legacy, d.legacy)
FROM   new_values n, default_row d   -- single row can be cross-joined
WHERE  NOT EXISTS (SELECT 1 FROM upsert u WHERE u.id = n.id)
RETURNING id;

COMMIT;

Anda hanya perlu LOCK jika Anda memiliki transaksi bersamaan yang mencoba menulis ke tabel yang sama.

Seperti yang diminta, ini hanya menggantikan nilai NULL di kolom legacy di baris input untuk INSERT kasus. Dapat dengan mudah diperluas untuk bekerja untuk kolom lain atau di UPDATE kasus juga. Misalnya, Anda dapat UPDATE juga secara kondisional:hanya jika nilai inputnya NOT NULL . Saya menambahkan baris komentar ke UPDATE di atas.

Selain:Anda tidak perlu melempar nilai di baris mana pun kecuali yang pertama dalam VALUES ekspresi, karena tipe diturunkan dari pertama baris.

Postgres 9.5

mengimplementasikan UPSERT dengan INSERT .. ON CONFLICT .. DO NOTHING | UPDATE . Ini sangat menyederhanakan operasi:

INSERT INTO playlist_items AS m (id, playlist, item, group_name, duration, sort, legacy)
VALUES (651, 21, 30012, 'a', 30, 1, FALSE)
,      (DEFAULT, 21, 1, 'b', 34, 2, DEFAULT)  -- !
,      (668, 21, 30012, 'c', 30, 3, FALSE)
,      (7428, 21, 23068, 'd', 0, 4, FALSE)
ON CONFLICT (id) DO UPDATE
SET (playlist, item, group_name, duration, sort, legacy)
 = (EXCLUDED.playlist, EXCLUDED.item, EXCLUDED.group_name
  , EXCLUDED.duration, EXCLUDED.sort, EXCLUDED.legacy)
-- (...,  COALESCE(l.legacy, EXCLUDED.legacy))  -- see below
RETURNING m.id;

Kami dapat melampirkan VALUES klausa ke INSERT secara langsung, yang memungkinkan DEFAULT kata kunci. Dalam kasus pelanggaran unik pada (id) , pembaruan Postgres sebagai gantinya. Kita dapat menggunakan baris yang dikecualikan di UPDATE . Panduan:

SET dan WHERE klausa dalam ON CONFLICT DO UPDATE memiliki akses ke baris yang ada menggunakan nama tabel (atau alias), dan ke baris yang diusulkan untuk disisipkan menggunakan excluded khusus tabel.

Dan:

Perhatikan bahwa efek dari semua per-baris BEFORE INSERT pemicu dicerminkan dalam nilai yang dikecualikan, karena efek tersebut mungkin telah menyebabkan baris dikecualikan dari penyisipan.

Sisa casing sudut

Anda memiliki berbagai opsi untuk UPDATE :Anda bisa ...

  • ... tidak memperbarui sama sekali:tambahkan WHERE klausa ke UPDATE untuk hanya menulis ke baris yang dipilih.
  • ... hanya perbarui kolom yang dipilih.
  • ... hanya perbarui jika kolom saat ini NULL:COALESCE(l.legacy, EXCLUDED.legacy)
  • ... hanya perbarui jika nilai baru adalah NOT NULL :COALESCE(EXCLUDED.legacy, l.legacy)

Tetapi tidak ada cara untuk membedakan DEFAULT nilai dan nilai yang sebenarnya disediakan di INSERT . Hanya menghasilkan EXCLUDED baris terlihat. Jika Anda membutuhkan perbedaan, kembali ke solusi sebelumnya, di mana Anda memiliki keduanya.




  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Postgres/JSON - perbarui semua elemen array

  2. Perbaiki 'ERROR:  kolom "colname" tidak ada' di PostgreSQL saat menggunakan UNION, KECUALI, atau INTERSECT

  3. Postgres TIDAK dalam array

  4. Pemantauan PostgreSQL Penting - Bagian 1

  5. Bagaimana cara menghapus sejumlah baris tetap dengan penyortiran di PostgreSQL?