เหตุใดจำนวนเต็มที่ไม่ได้ลงชื่อจึงไม่มีใน PostgreSQL


115

ฉันเจอโพสต์นี้ ( ความแตกต่างระหว่าง tinyint, smallint, mediumint, bigint และ int ใน MySQL คืออะไร ) และตระหนักว่า PostgreSQL ไม่รองรับจำนวนเต็มที่ไม่ได้ลงชื่อ

ใครช่วยอธิบายได้ไหมว่าทำไมถึงเป็นเช่นนั้น?

ส่วนใหญ่ฉันใช้จำนวนเต็มที่ไม่ได้ลงชื่อเป็นคีย์หลักที่เพิ่มขึ้นโดยอัตโนมัติใน MySQL ในการออกแบบเช่นนี้ฉันจะเอาชนะสิ่งนี้ได้อย่างไรเมื่อฉันพอร์ตฐานข้อมูลจาก MySQL ไปยัง PostgreSQL

ขอบคุณ.


ยังไม่ใช่เร็ว ๆ นี้และเรากำลังพิจารณาที่จะย้ายไปที่ PostgreSQL
Adrian Hoe

4
ฉันไม่คิดว่านี่เป็นสถานที่ที่ดีที่สุดในการถามว่าเหตุใดจึงมีการตัดสินใจบางอย่างหนึ่งในรายชื่ออีเมล PostgreSQL อาจเหมาะสมกว่า หากคุณต้องการเพิ่มค่าอัตโนมัติให้ใช้serial(1 ถึง 2147483647) หรือbigserial(1 ถึง 9223372036854775807) จำนวนเต็ม 64 บิตที่ลงนามอาจมีพื้นที่มากเกินพอ
สั้นเกินไป

4
ขอบคุณ @muistooshort นั่นตอบปัญหาคีย์หลัก แต่ประเภทจำนวนเต็มที่ไม่ได้ลงชื่อซึ่งไม่ได้เพิ่มขึ้นอัตโนมัติหรือคีย์หลักล่ะ? ฉันมีคอลัมน์ที่เก็บจำนวนเต็มที่ไม่ได้ลงชื่อซึ่งมีช่วงตั้งแต่ 0 ถึง 2 ^ 32
Adrian Hoe

4
การเรียกใช้เอกสาร PostgreSQL อย่างรวดเร็ว ( postgresql.org/docs/current/interactive/index.html ) อาจเป็นประโยชน์ในการช่วยให้คุณเข้าใจได้ดีขึ้นว่า PostgreSQL สามารถทำอะไรได้บ้าง เหตุผลเดียวที่ฉันใช้ MySQL ในทุกวันนี้คือถ้าฉันลงทุนไปเยอะแล้ว: PostgreSQL นั้นรวดเร็วเต็มไปด้วยคุณสมบัติที่มีประโยชน์และสร้างขึ้นโดยผู้คนที่ค่อนข้างหวาดระแวงเกี่ยวกับข้อมูลของพวกเขา IMO แน่นอน :)
มูสั้นเกินไป

ขอบคุณอีกครั้ง @muistooshort สำหรับคำแนะนำ
Adrian Hoe

คำตอบ:


48

มีคำตอบแล้วว่าทำไม postgresql ถึงไม่มีประเภทที่ไม่ได้ลงนาม อย่างไรก็ตามฉันขอแนะนำให้ใช้โดเมนสำหรับประเภทที่ไม่ได้ลงชื่อ

http://www.postgresql.org/docs/9.4/static/sql-createdomain.html

 CREATE DOMAIN name [ AS ] data_type
    [ COLLATE collation ]
    [ DEFAULT expression ]
    [ constraint [ ... ] ]
 where constraint is:
 [ CONSTRAINT constraint_name ]
 { NOT NULL | NULL | CHECK (expression) }

โดเมนก็เหมือนกับประเภทหนึ่ง แต่มีข้อ จำกัด เพิ่มเติม

สำหรับตัวอย่างที่เป็นรูปธรรมคุณสามารถใช้ได้

CREATE DOMAIN uint2 AS int4
   CHECK(VALUE >= 0 AND VALUE < 65536);

นี่คือสิ่งที่ psql ให้เมื่อฉันพยายามละเมิดประเภท

DS1 = # เลือก (346346 :: uint2);

ข้อผิดพลาด: ค่าสำหรับโดเมน uint2 ละเมิดข้อ จำกัด การตรวจสอบ "uint2_check"


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

ปัญหาเดียวของแนวทางนี้คือคุณกำลัง "เสีย" พื้นที่เก็บข้อมูล 15 บิตซึ่งไม่ได้ใช้งาน ไม่ต้องพูดถึงการตรวจสอบยังมีค่าใช้จ่ายเล็กน้อยสำหรับประสิทธิภาพ ทางออกที่ดีกว่าคือ Postgres เพิ่มไม่ได้ลงนามเป็นประเภทชั้นหนึ่ง ในตารางที่มีข้อมูล 20 ล้านเรกคอร์ดที่มีและจัดทำดัชนีฟิลด์เช่นนี้คุณกำลังเสียพื้นที่ 40MB ไปกับบิตที่ไม่ได้ใช้ หากคุณใช้ในทางที่ผิดในอีก 20 ตารางตอนนี้คุณกำลังเสียพื้นที่ 800MB
tpartee

85

ไม่อยู่ในมาตรฐาน SQL ดังนั้นความต้องการทั่วไปในการใช้งานจึงต่ำกว่า

การมีประเภทจำนวนเต็มที่แตกต่างกันมากเกินไปทำให้ระบบการแก้ปัญหาประเภทมีความเปราะบางมากขึ้นดังนั้นจึงมีความต้านทานต่อการเพิ่มประเภทอื่น ๆ ในการผสม

ที่กล่าวมาไม่มีเหตุผลว่าทำไมถึงทำไม่ได้ ก็แค่งานเยอะ


35
คำถามนี้ได้รับความนิยมมากพอที่ฉันได้ตั้งเป้าไว้ว่าจะแก้ไข: github.com/petere/pguint
Peter Eisentraut

การมีการแปลงอินพุต / เอาต์พุตสำหรับตัวอักษรจำนวนเต็มที่ไม่ได้ลงชื่อจะเป็นประโยชน์อย่างยิ่ง หรือแม้แต่to_charรูปแบบ
Bergi

37

คุณสามารถใช้ข้อ จำกัด การตรวจสอบเช่น:

CREATE TABLE products (
    product_no integer,
    name text,
    price numeric CHECK (price > 0)
);

นอกจากนี้ PostgreSQL มีsmallserial, serialและbigserialประเภทสำหรับรถยนต์ที่เพิ่มขึ้น


2
สิ่งหนึ่งที่จะกล่าวถึงคุณไม่มี NULL ในคอลัมน์ที่ใช้ CHECK
Minutis

1
@Minutis คุณแน่ใจหรือไม่ว่าคุณมี x เป็นโมฆะหรือ x ระหว่าง 4 และ 40
jgmjgm

และสิ่งนี้ไม่ได้ให้ความละเอียดเท่ากับที่คุณทำหากไม่ได้ลงนาม int ความหมาย int ไม่ได้ลงนามสามารถไปถึง2^32-1ได้ลงนามในขณะเดียวกัน ints 2^31-1สามารถไปได้ถึง
JukesOnYou

2
NULLและCHECKมีมุมฉากอย่างสมบูรณ์ คุณสามารถมีNULL/ NOT NULLคอลัมน์โดยมีหรือไม่มีCHECKก็ได้ ทราบเพียงว่าตามเอกสารที่postgresql.org/docs/9.4/ddl-constraints.html , CHECKกลับมาประเมินเป็นโมฆะเป็น TRUE ดังนั้นหากคุณต้องการจริงๆที่จะป้องกันไม่ให้ NULLs แล้วใช้NOT NULLแทน (หรือนอกเหนือไปCHECK)
flaviovs

การใช้ CHECK ไม่อนุญาตให้ฉันเก็บที่อยู่ ipv4 ไว้ในinteger(ไม่ใช่โดยไม่ต้องมีพวกเขาสุ่มบวกหรือลบอย่างน้อย .. )
hanshenrik

5

การพูดคุยเกี่ยวกับ DOMAINS นั้นน่าสนใจ แต่ไม่เกี่ยวข้องกับที่มาที่เป็นไปได้เพียงประการเดียวของคำถามนั้น ความปรารถนาสำหรับ ints ที่ไม่ได้ลงนามคือการเพิ่มช่วงของ ints เป็นสองเท่าด้วยจำนวนบิตเท่ากันมันเป็นอาร์กิวเมนต์ที่มีประสิทธิภาพไม่ใช่ความปรารถนาที่จะยกเว้นตัวเลขเชิงลบทุกคนรู้วิธีเพิ่มข้อ จำกัด ในการตรวจสอบ

เมื่อมีคนถามเกี่ยวกับเรื่องนี้ Tome Lane กล่าวว่า:

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

"POLA" คืออะไร? Google ให้ผม 10 ผลลัพธ์ที่มีความหมาย ไม่แน่ใจว่าเป็นความคิดที่ไม่ถูกต้องทางการเมืองจึงถูกเซ็นเซอร์ เหตุใดข้อความค้นหานี้จึงไม่ให้ผลลัพธ์ใด ๆ ก็ตาม.

คุณสามารถใช้ ints ที่ไม่ได้ลงชื่อเป็นประเภทส่วนขยายได้โดยไม่มีปัญหามากเกินไป หากคุณใช้ฟังก์ชัน C จะไม่มีบทลงโทษเกี่ยวกับประสิทธิภาพเลย คุณไม่จำเป็นต้องขยายตัวแยกวิเคราะห์เพื่อจัดการกับตัวอักษรเนื่องจาก PgSQL มีวิธีง่ายๆในการตีความสตริงเป็นตัวอักษรเพียงเขียน '4294966272' :: uint4 เป็นตัวอักษรของคุณ การร่ายไม่ควรเป็นเรื่องใหญ่เช่นกัน คุณไม่จำเป็นต้องทำข้อยกเว้นช่วงคุณสามารถจัดการความหมายของ '4294966273' :: uint4 :: int เป็น -1024 ได้ หรือคุณสามารถโยนข้อผิดพลาด

ถ้าฉันต้องการสิ่งนี้ฉันจะทำมัน แต่เนื่องจากฉันใช้ Java ในอีกด้านหนึ่งของ SQL สำหรับฉันแล้วมันมีค่าเพียงเล็กน้อยเนื่องจาก Java ไม่มีจำนวนเต็มที่ไม่ได้ลงชื่อ ดังนั้นฉันจึงไม่ได้รับอะไรเลย ฉันรู้สึกรำคาญอยู่แล้วถ้าฉันได้รับ BigInteger จากคอลัมน์ bigint เมื่อมันควรจะยาว

อีกประการหนึ่งถ้าฉันมีความจำเป็นในการจัดเก็บประเภท 32 บิตหรือ 64 บิตฉันสามารถใช้ PostgreSQL int4 หรือ int8 ตามลำดับเพียงแค่จำไว้ว่าลำดับธรรมชาติหรือเลขคณิตจะไม่ทำงานอย่างน่าเชื่อถือ แต่การจัดเก็บและการเรียกคืนจะไม่ได้รับผลกระทบจากสิ่งนั้น


นี่คือวิธีที่ฉันสามารถใช้ int8 แบบธรรมดาที่ไม่ได้ลงชื่อ:

ก่อนอื่นฉันจะใช้

CREATE TYPE name (
    INPUT = uint8_in,
    OUTPUT = uint8_out
    [, RECEIVE = uint8_receive ]
    [, SEND = uint8_send ]
    [, ANALYZE = uint8_analyze ]
    , INTERNALLENGTH = 8
    , PASSEDBYVALUE ]
    , ALIGNMENT = 8
    , STORAGE = plain
    , CATEGORY = N
    , PREFERRED = false
    , DEFAULT = null
)

น้อยที่สุด 2 ฟังก์ชั่นuint8_inและuint8_outฉันต้องกำหนดก่อน

CREATE FUNCTION uint8_in(cstring)
    RETURNS uint8
    AS 'uint8_funcs'
    LANGUAGE C IMMUTABLE STRICT;

CREATE FUNCTION uint64_out(complex)
    RETURNS cstring
    AS 'uint8_funcs'
    LANGUAGE C IMMUTABLE STRICT;

จำเป็นต้องใช้สิ่งนี้ใน C uint8_funcs.c ดังนั้นฉันจะใช้ตัวอย่างที่ซับซ้อนจากที่นี่และทำให้มันง่าย:

PG_FUNCTION_INFO_V1(complex_in);

Datum complex_in(PG_FUNCTION_ARGS) {
    char       *str = PG_GETARG_CSTRING(0);
    uint64_t   result;

    if(sscanf(str, "%llx" , &result) != 1)
        ereport(ERROR,
                (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                 errmsg("invalid input syntax for uint8: \"%s\"", str)));

    return (Datum)SET_8_BYTES(result);
}

อาดีหรือคุณสามารถเพียงแค่พบว่ามันทำมาแล้ว


1
ฉันเดาว่า POLA เป็น "หลักการของความประหลาดใจอย่างน้อยที่สุด" แสดงให้เห็นว่าการเปลี่ยนแปลงนี้มีศักยภาพในการเปลี่ยนแปลงพฤติกรรมที่มีอยู่ในรูปแบบที่ไม่คาดคิด
Doctor Eval

1

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

https://www.postgresql.org/docs/10/datatype-numeric.html

integer     4 bytes     typical choice for integer  -2147483648 to +2147483647
serial  4 bytes     autoincrementing integer    1 to 2147483647

0

Postgres OIDจะมีชนิดจำนวนเต็มไม่ได้ลงนามที่เป็นถิ่นหลาย

oidชนิดถูกนำมาใช้ในปัจจุบันเป็นจำนวนเต็มสี่ไบต์ […]

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

แม้ว่าจะไม่ใช่ประเภทตัวเลขและการพยายามทำเลขคณิตใด ๆ (หรือแม้แต่การดำเนินการแบบบิต) ก็จะล้มเหลว นอกจากนี้ยังเป็นเพียง 4 ไบต์ ( INTEGER) ไม่มีBIGINTประเภทที่ไม่ได้ลงนาม8 ไบต์ ( ) ที่ตรงกัน

ดังนั้นจึงไม่ใช่ความคิดที่ดีที่จะใช้สิ่งนี้ด้วยตัวเองและฉันเห็นด้วยกับคำตอบอื่น ๆ ทั้งหมดที่ในการออกแบบฐานข้อมูล Postgresql คุณควรใช้คอลัมน์INTEGERหรือBIGINTคอลัมน์สำหรับคีย์หลักแบบอนุกรมของคุณเสมอโดยเริ่มต้นด้วยค่าลบ ( MINVALUE) หรืออนุญาต เพื่อตัดรอบ ( CYCLE) หากคุณต้องการใช้โดเมนแบบเต็ม

อย่างไรก็ตามมันมีประโยชน์มากสำหรับการแปลงอินพุต / เอาต์พุตเช่นการย้ายข้อมูลจาก DBMS อื่น การแทรกค่า2147483648ลงในคอลัมน์จำนวนเต็มจะนำไปสู่ ​​" ข้อผิดพลาด: จำนวนเต็มอยู่นอกช่วง " ในขณะที่การใช้นิพจน์2147483648::OIDก็ใช้ได้ดี
ในทำนองเดียวกันเมื่อเลือกคอลัมน์จำนวนเต็มเป็นข้อความmycolumn::TEXTคุณจะได้รับค่าลบ ณ จุดใดจุดหนึ่ง แต่mycolumn::OID::TEXTคุณจะได้จำนวนธรรมชาติเสมอ

ดูตัวอย่างที่ dbfiddle.uk


หากคุณไม่ต้องการการดำเนินการค่าเดียวจากการใช้ OID คือลำดับการจัดเรียงของคุณทำงานได้ ถ้านั่นคือสิ่งที่คุณต้องการก็ดี แต่ในไม่ช้าใครบางคนก็ต้องการ uint8 แล้วพวกเขาก็หลงทางเช่นกัน บรรทัดล่างคือการจัดเก็บค่า 32 บิตหรือ 64 บิตคุณสามารถใช้ int4 และ int8 ตามลำดับเพียงแค่ต้องระมัดระวังกับการดำเนินการ แต่เขียนส่วนขยายได้ง่าย
Gunther Schadow
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.