Cara meneruskan tipe tabel dengan bidang array ke fungsi di postgresql

8

saya punya meja yang disebut buku

CREATE TABLE book
(
  id smallint NOT NULL DEFAULT 0,       
  bname text,       
  btype text,
  bprices numeric(11,2)[],
  CONSTRAINT key PRIMARY KEY (id )
)

dan fungsi save_book

CREATE OR REPLACE FUNCTION save_book(thebook book)
  RETURNS text AS
$BODY$
DECLARE 
myoutput text :='Nothing has occured';
BEGIN

    update book set 
    bname=thebook.bname,
    btype=thebook.btype,bprices=thebook.bprices  WHERE id=thebook.id;

    IF FOUND THEN
        myoutput:= 'Record with PK[' || thebook.id || '] successfully updated';
        RETURN myoutput;
    END IF;

    BEGIN
        INSERT INTO book values(thebook.id,thebook.bname,thebook.btype,
        thebook.bprices);
        myoutput:= 'Record successfully added';           
    END;
 RETURN myoutput;

    END;
$BODY$
  LANGUAGE plpgsql VOLATILE
  COST 100;

sekarang ketika saya memanggil fungsi

SELECT save_book('(179,the art of war,fiction,{190,220})'::book);

saya mendapatkan kesalahan

ERROR: malformed array literal: "{190"
SQL state: 22P02
Character: 18

saya tidak mengerti karena saya tidak melihat kesalahan dalam format array, ada bantuan?

indago
sumber
Fungsi Anda terlihat terlalu rumit bagi saya, tetapi bagaimanapun juga. Sebuah literal Array harus diapit oleh tanda kutip tunggal, jadi coba berikut ini: ave_book((179,the art of war,fiction,'{190,220}')::book. Baris yang dibangun tidak membutuhkan tanda kutip.
dezso
ketika saya menjalankan itu saya mendapatkan kesalahan ERROR: syntax error at or near "art"
indago
1
Masing-masing string literal harus tetap diapit tanda kutip.
Andriy M
Maaf, yang benar adalah ave_book((179, 'the art of war', 'fiction', '{190,220}')::book, seperti yang dikatakan Andriy.
dezso
@dezso: Tampaknya ada jawaban untuk saya. :)
Andriy M

Jawaban:

7

Hal semacam ini menjadi rumit. Saya sedang mengerjakan beberapa proyek terkait saat ini. Tweak dasarnya adalah PostgreSQL menggunakan format yang menggunakan tanda kutip ganda secara internal dalam representasi tuple untuk mewakili nilai literal, jadi:

SELECT save_book('(179,the art of war,fiction,"{190,220}")'::book);

harus bekerja. Intinya trik yang rapi adalah membuat csv dan menyertakan pengidentifikasi tuple atau array. Masalah besar adalah bahwa Anda harus berurusan dengan melarikan diri (menggandakan penawaran di setiap level sesuai kebutuhan). Jadi yang berikut ini persis sama:

SELECT save_book('(179,"the art of war","fiction","{""190"",""220""}")'::book);

Pendekatan kedua adalah menggunakan konstruktor baris:

SELECT save_book(row(179,'the art of war','fiction', array[190,220])::book);

Solusi pertama memiliki keuntungan yang jelas karena dapat memanfaatkan kerangka kerja pemrograman yang ada untuk pembuatan dan pelepasan CSV. Yang kedua adalah terbersih dalam SQL. Mereka dapat dicampur dan dicocokkan.

Chris Travers
sumber
+1 untuk menggunakan konstruktor array, melarikan diri bisa menjadi mimpi buruk
Jack mengatakan coba topanswers.xyz
@ Chris, Solusi pertama tampaknya bagus dan bagi saya saya pikir adalah yang terbaik!
indago
@JackDouglas, satu hal yang kami lihat sedang dilakukan adalah pergi rute pertama di LSMB khusus karena kami dapat menggunakan kerangka kerja CSV yang sudah berfungsi.
Chris Travers
6

Jika Anda pernah bertanya-tanya tentang sintaks yang benar untuk tipe baris, tanyakan pada Postgres. Seharusnya tahu:

SELECT b FROM book b LIMIT 1;  -- or: WHERE id = 179;

Yang akan mengembalikan representasi teks dari baris Anda dalam format yang valid:

(179,"the art of war",fiction,"{190,220}")
  • Nilai kolom direpresentasikan sebagai daftar tanpa tanda kutip, dipisahkan oleh koma, terlampir dalam paragraf.

  • Kutipan ganda digunakan di sekitar nilai, jika ada ambiguitas - termasuk teks dengan spasi putih. Sementara dalam kasus khusus ini tanda kutip ganda di sekitar "the art of war"adalah opsional, tanda kutip ganda sekitar "{190,220}"diperlukan untuk sebuah array.

Lampirkan string dalam tanda kutip tunggal, modifikasi, dan uji:

SELECT '(333,the art of war,fiction,"{191,220,235}")'::book

Fungsi ditinjau

Pertimbangkan apa yang kita diskusikan dalam pertanyaan terkait sebelumnya:
Masalah dengan tipe komposit dalam fungsi UPSERT

Sebuah terpisah blok ( BEGIN .. END;) hanya berguna jika Anda ingin menangkap EXCEPTIONsebuah INSERTkekuatan kenaikan gaji. Karena sebuah blok dengan pengecualian membawa beberapa overhead, masuk akal untuk memiliki blok terpisah yang mungkin tidak pernah dimasukkan:

CREATE OR REPLACE FUNCTION save_book(thebook book)
  RETURNS text AS
$BODY$
BEGIN
   UPDATE book
   SET    bname =   thebook.bname
         ,btype =   thebook.btype
         ,bprices = thebook.bprices
   WHERE  id = thebook.id;

   IF FOUND THEN
      RETURN format('Record with PK[%s] successfully updated', thebook.id);
   END IF;

   BEGIN
      INSERT INTO book SELECT (thebook).*;
      RETURN format('Record with PK[%s] successfully inserted', thebook.id);

   EXCEPTION WHEN unique_violation THEN
      UPDATE book
      SET    bname =   thebook.bname
            ,btype =   thebook.btype
            ,bprices = thebook.bprices
      WHERE  id = thebook.id;
   END;

   RETURN format('Record with PK[%s] successfully updated', thebook.id);
END
$BODY$  LANGUAGE plpgsql

Lain, sederhanakan :

CREATE OR REPLACE FUNCTION save_book(thebook book)
  RETURNS text AS
$BODY$
BEGIN
   UPDATE book
   SET    bname =   thebook.bname
         ,btype =   thebook.btype
         ,bprices = thebook.bprices
   WHERE  id = thebook.id;

   IF FOUND THEN
      RETURN format('Record with PK[%s] successfully updated', thebook.id);
   END IF;

   INSERT INTO book SELECT (thebook).*;
   RETURN format('Record with PK[%s] successfully inserted', thebook.id);
END
$BODY$  LANGUAGE plpgsql

Saya juga menyederhanakan INSERTpernyataan Anda . Aman untuk menghilangkan daftar kolom dari INSERT dalam kondisi tertentu.

Erwin Brandstetter
sumber
3

Sementara saya tidak melihat keuntungan nyata dari solusi Anda, maksud saya melewati baris ke fungsi alih-alih melewati nilai individual seperti pada

CREATE OR REPLACE FUNCTION save_book2(
      integer
    , text
    , text
    , integer[]
)
RETURNS text AS
...

Bagaimanapun, solusi Anda juga berfungsi jika Anda memanggil fungsi dengan benar:

SELECT ave_book((179, 'the art of war', 'fiction', '{190,220}')::book);

Artinya, ekspresi rekaman tidak perlu mengutip, sedangkan nilai teks dan array literal lakukan.

dezso
sumber
Saya membuat fungsi itu untuk menangani semua sisipan dan pembaruan yang melindungi aplikasi agar tidak langsung berhubungan dengan tabel, apakah Anda berpikir bahwa memiliki fungsi itu tidak menambah keuntungan?
indago
Saya $ 0,02 pada keputusan desain. Saya pikir ini memiliki keuntungan besar asalkan Anda memiliki kode aplikasi untuk mencari struktur tipe yang terlibat dan membangun argumen untuk Anda. Ini memberi Anda API yang dapat ditemukan yang sangat membantu. Tanpa itu, bagaimanapun, itu berarti bahwa jika struktur tabel Anda berubah, Anda memiliki tempat tambahan untuk membuat perubahan yang jelas tidak baik. Saya mengatakan ini sebagai orang yang menggerakkan perkembangan saya ke arah yang Anda tuju, dan secara keseluruhan saya pikir ini adalah ide yang bagus. Itu memang memiliki bahaya.
Chris Travers