ทำไมฉันต้องส่ง NULL ไปเป็นประเภทคอลัมน์


10

ฉันมีผู้ช่วยที่กำลังสร้างรหัสเพื่อทำการอัปเดตจำนวนมากสำหรับฉันและสร้าง SQL ที่มีลักษณะดังนี้:

(ทั้งประเภทที่ใช้งานอยู่และเขตข้อมูลหลักเป็นประเภทboolean)

UPDATE fields as t set "active" = new_values."active","core" = new_values."core"
FROM (values 
(true,NULL,3419),
(false,NULL,3420)
) as new_values("active","core","id") WHERE new_values.id = t.id;

อย่างไรก็ตามมันล้มเหลวด้วย:

ERROR: column "core" is of type boolean but expression is of type text

ฉันสามารถทำให้มันทำงานได้โดยเพิ่ม::booleanโมฆะ แต่มันดูแปลกทำไม NULL ถึงถูกพิจารณาว่าเป็นประเภทTEXTใด

นอกจากนี้ยังเป็นการยากที่จะร่ายเพราะมันต้องการการทำใหม่ของโค้ดเพื่อให้รู้ว่าควรใช้ NULL แบบใด (รายการของคอลัมน์และค่าปัจจุบันถูกสร้างอัตโนมัติจากอาร์เรย์ของวัตถุ JSON แบบง่าย ๆ ) .

เหตุใดจึงมีความจำเป็นและมีโซลูชันที่หรูหรากว่าที่ไม่ต้องการรหัสการสร้างเพื่อทราบชนิดของ NULLs

หากมีความเกี่ยวข้องฉันกำลังใช้sequelizeบน Node.JS เพื่อทำสิ่งนี้ แต่ฉันยังได้ผลลัพธ์เดียวกันในไคลเอนต์บรรทัดคำสั่ง Postgres

คำตอบ:


16

นี่คือการค้นพบที่น่าสนใจ โดยปกติแล้ว NULL จะไม่มีประเภทข้อมูลที่สันนิษฐานดังที่คุณเห็นที่นี่:

SELECT pg_typeof(NULL);

 pg_typeof 
───────────
 unknown

การเปลี่ยนแปลงนี้เมื่อVALUESตารางเข้ามาในรูปภาพ:

SELECT pg_typeof(core) FROM (
    VALUES (NULL)
) new_values (core);

 pg_typeof 
───────────
 text

พฤติกรรมนี้อธิบายไว้ในซอร์สโค้ดที่https://doxygen.postgresql.org/parse__coerce_8c.html#l01373 :

 /*
  * If all the inputs were UNKNOWN type --- ie, unknown-type literals ---
  * then resolve as type TEXT.  This situation comes up with constructs
  * like SELECT (CASE WHEN foo THEN 'bar' ELSE 'baz' END); SELECT 'foo'
  * UNION SELECT 'bar'; It might seem desirable to leave the construct's
  * output type as UNKNOWN, but that really doesn't work, because we'd
  * probably end up needing a runtime coercion from UNKNOWN to something
  * else, and we usually won't have it.  We need to coerce the unknown
  * literals while they are still literals, so a decision has to be made
  * now.
  */

(ใช่ซอร์สโค้ดของ PostgreSQL นั้นค่อนข้างง่ายต่อการทำความเข้าใจและสถานที่ส่วนใหญ่ด้วยความเห็นที่ยอดเยี่ยม)

อย่างไรก็ตามทางออกอาจเป็นดังต่อไปนี้ สมมติว่าคุณกำลังสร้างรายการVALUESที่ตรงกับคอลัมน์ทั้งหมดของตารางที่ระบุ (ดูหมายเหตุที่สองด้านล่างสำหรับกรณีอื่น ๆ ) จากตัวอย่างของคุณเคล็ดลับเล็ก ๆ อาจช่วยได้:

SELECT (x).* FROM (VALUES ((TRUE, NULL, 1234)::fields)) t(x);

 active  core   id  
────────┼──────┼──────
 t             1234

ที่นี่คุณใช้นิพจน์แถวที่เลือกใช้กับประเภทของตารางแล้วแตกออกเป็นตาราง

ขึ้นอยู่กับข้างต้นคุณUPDATEอาจมีลักษณะเช่น

UPDATE fields AS t set active = (x).active, core = (x).core
FROM ( VALUES
           ((true, NULL, 3419)::fields),
           ((false, NULL, 3420)::fields)
     ) AS new_values(x) WHERE (x).id = t.id;

หมายเหตุ:

  • ฉันลบเครื่องหมายคำพูดคู่เพื่อให้มนุษย์อ่านง่ายขึ้น แต่คุณสามารถเก็บไว้ได้เพราะมันช่วยเมื่อสร้างชื่อ (คอลัมน์)
  • หากคุณต้องการเพียงชุดย่อยของคอลัมน์คุณสามารถสร้างประเภทที่กำหนดเองสำหรับวัตถุประสงค์นี้ ใช้พวกเขาในลักษณะเดียวกับที่คุณต้องการข้างต้น (ที่ฉันใช้ประเภทที่สร้างขึ้นโดยอัตโนมัติด้วยตารางโดยถือโครงสร้างแถวหลัง)

ดูสิ่งที่ทั้งการทำงานในdbfiddle


ขอบคุณนี้เป็นสิ่งที่น่าสนใจ แต่สำหรับฉันรหัสข้างต้นก่อให้เกิดCannot cast type boolean to bigint in column 1(ข้อผิดพลาดชี้ที่ :: ระหว่างคำสั่งแรกของเขตข้อมูล)
Christopher Christopher

1
@ChristopherJ คำตอบสมมติว่าตารางที่เรียกว่าfieldsมี 3 คอลัมน์(active, core, id)พร้อมกับบูลีนบูลีนและ int / bigint ตารางของคุณมีคอลัมน์หรือประเภทที่แตกต่างกันมากขึ้นหรือมีการกำหนดคอลัมน์ตามลำดับที่แตกต่างกันหรือไม่
ypercubeᵀᴹ

อ่าฉันเข้าใจแล้วขอบคุณมีคอลัมน์มากขึ้นและเรียงตามลำดับที่แตกต่างกัน รับมันขอบคุณ
ChristopherJ
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.