ฉันจะสร้างสตริงเป็นจำนวนเต็มและมี 0 ได้อย่างไรในกรณีที่เกิดข้อผิดพลาดในการแคสต์ด้วย PostgreSQL


128

ใน PostgreSQL ฉันมีตารางที่มีคอลัมน์ varchar ข้อมูลควรเป็นจำนวนเต็มและฉันต้องการให้เป็นจำนวนเต็มในแบบสอบถาม ค่าบางค่าเป็นสตริงว่าง ดังต่อไปนี้:

SELECT myfield::integer FROM mytable

อัตราผลตอบแทน ERROR: invalid input syntax for integer: ""

ฉันจะสอบถามนักแสดงและมี 0 ได้อย่างไรในกรณีที่เกิดข้อผิดพลาดระหว่างการแคสต์ใน postgres

คำตอบ:


161

ฉันแค่ต่อสู้กับปัญหาที่คล้ายกันด้วยตัวเอง แต่ไม่ต้องการค่าใช้จ่ายของฟังก์ชัน ฉันคิดคำถามต่อไปนี้:

SELECT myfield::integer FROM mytable WHERE myfield ~ E'^\\d+$';

Postgres ช็อตคัทตามเงื่อนไขดังนั้นคุณไม่ควรได้รับใด ๆ ที่ไม่ใช่จำนวนเต็มกดปุ่ม :: integer cast ของคุณ นอกจากนี้ยังจัดการค่า NULL (ไม่ตรงกับ regexp)

หากคุณต้องการเลขศูนย์แทนที่จะไม่เลือกคำสั่ง CASE ควรใช้งานได้:

SELECT CASE WHEN myfield~E'^\\d+$' THEN myfield::integer ELSE 0 END FROM mytable;

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

4
ฉันความคิดเห็นที่สองของพิลิฟ ค่าสูงสุดนั้นเป็นจุดบกพร่องที่รอให้เกิดขึ้น จุดสำคัญของการไม่ส่งข้อผิดพลาดคืออย่าโยนข้อผิดพลาดเมื่อข้อมูลไม่ถูกต้อง คำตอบที่ยอมรับนี้ไม่สามารถแก้ปัญหานั้นได้ ขอบคุณ Matthew! การทำงานที่ดี!
Shawn Kovac

3
ฉันแค่ต้องการวิธีจัดการที่รวดเร็วและสกปรกในการตรวจสอบข้อมูลบางอย่าง ฉันยอมรับด้วยว่าตอนนี้ความรู้ของตัวเองยังขาดในการกำหนดฟังก์ชันใน SQL ผมมีความสนใจเฉพาะในตัวเลขระหว่าง 1 ถึง 5 หลักดังนั้นผมเปลี่ยน regex E'\\d{1,5}$'ไป
Bobort

3
ใช่ใช่วิธีแก้ปัญหานี้ค่อนข้างรวดเร็วและสกปรก แต่ในกรณีของฉันฉันรู้ว่าฉันมีข้อมูลอะไรและตารางค่อนข้างสั้น ง่ายกว่าการเขียน (และแก้จุดบกพร่อง) ทั้งฟังก์ชัน {1,5}ขีด จำกัดของ @ Bobort ด้านบนของตัวเลขอาจเป็นความคิดที่ดีหากคุณกังวลเกี่ยวกับการล้น แต่จะปิดบังตัวเลขที่มากขึ้นซึ่งอาจทำให้เกิดปัญหาหากคุณกำลังแปลงตาราง โดยส่วนตัวแล้วฉันอยากจะมีข้อผิดพลาดในการค้นหาอยู่ข้างหน้าและรู้ว่า "จำนวนเต็ม" บางส่วนของฉันเป็นจำนวนเต็ม (คุณสามารถเลือกด้วยE'\\d{6,}$'ก่อนเพื่อให้แน่ใจ)
Anthony Briggs

1
@Anthony Briggs: สิ่งนี้จะไม่ทำงานหาก myfield มี "" "" "หรือ". "หรือ" - "
Stefan Steiger

100

คุณยังสามารถสร้างฟังก์ชันการแปลงของคุณเองซึ่งคุณสามารถใช้บล็อกข้อยกเว้นได้:

CREATE OR REPLACE FUNCTION convert_to_integer(v_input text)
RETURNS INTEGER AS $$
DECLARE v_int_value INTEGER DEFAULT NULL;
BEGIN
    BEGIN
        v_int_value := v_input::INTEGER;
    EXCEPTION WHEN OTHERS THEN
        RAISE NOTICE 'Invalid integer value: "%".  Returning NULL.', v_input;
        RETURN NULL;
    END;
RETURN v_int_value;
END;
$$ LANGUAGE plpgsql;

การทดสอบ:

=# select convert_to_integer('1234');
 convert_to_integer 
--------------------
               1234
(1 row)

=# select convert_to_integer('');
NOTICE:  Invalid integer value: "".  Returning NULL.
 convert_to_integer 
--------------------

(1 row)

=# select convert_to_integer('chicken');
NOTICE:  Invalid integer value: "chicken".  Returning NULL.
 convert_to_integer 
--------------------

(1 row)

8
เมื่อเทียบกับคำตอบที่ยอมรับวิธีนี้จะถูกต้องมากกว่าเนื่องจากสามารถจัดการกับตัวเลขที่ใหญ่เกินกว่าที่จะใส่เป็นจำนวนเต็มได้ดีพอ ๆ กันและยังมีแนวโน้มที่จะเร็วกว่าเนื่องจากไม่มีการตรวจสอบความถูกต้องในกรณีทั่วไป (= สตริงที่ถูกต้อง )
pilif

คุณจะโยนสตริงเป็นจำนวนเต็มในฟิลด์เฉพาะโดยใช้ฟังก์ชันขณะอยู่ในINSERTคำสั่งได้อย่างไร
sk

27

ฉันมีความต้องการแบบเดียวกันและพบว่าสิ่งนี้ใช้ได้ดีสำหรับฉัน (postgres 8.4):

CAST((COALESCE(myfield,'0')) AS INTEGER)

การทดสอบบางกรณีเพื่อแสดง:

db=> select CAST((COALESCE(NULL,'0')) AS INTEGER);
 int4
------
    0
(1 row)

db=> select CAST((COALESCE('','0')) AS INTEGER);
 int4
------
    0
(1 row)

db=> select CAST((COALESCE('4','0')) AS INTEGER);
 int4
------
    4
(1 row)

db=> select CAST((COALESCE('bad','0')) AS INTEGER);
ERROR:  invalid input syntax for integer: "bad"

หากคุณต้องการจัดการกับความเป็นไปได้ของฟิลด์ที่มีข้อความที่ไม่ใช่ตัวเลข (เช่น "100bad") คุณสามารถใช้ regexp_replace เพื่อดึงอักขระที่ไม่ใช่ตัวเลขก่อนแคสต์

CAST(REGEXP_REPLACE(COALESCE(myfield,'0'), '[^0-9]+', '', 'g') AS INTEGER)

จากนั้นค่า text / varchar เช่น "b3ad5" จะให้ตัวเลขด้วย

db=> select CAST(REGEXP_REPLACE(COALESCE('b3ad5','0'), '[^0-9]+', '', 'g') AS INTEGER);
 regexp_replace
----------------
             35
(1 row)

เพื่อจัดการกับข้อกังวลของ Chris Cogdon เกี่ยวกับการแก้ปัญหาที่ไม่ให้ 0 สำหรับทุกกรณีรวมถึงกรณีเช่น "ไม่ดี" (ไม่มีอักขระหลักเลย) ฉันได้ปรับคำสั่งนี้:

CAST((COALESCE(NULLIF(REGEXP_REPLACE(myfield, '[^0-9]+', '', 'g'), ''), '0')) AS INTEGER);

ทำงานคล้ายกับโซลูชันที่ง่ายกว่ายกเว้นจะให้ 0 เมื่อค่าที่จะแปลงเป็นอักขระที่ไม่ใช่ตัวเลขเท่านั้นเช่น "ไม่ดี":

db=> select CAST((COALESCE(NULLIF(REGEXP_REPLACE('no longer bad!', '[^0-9]+', '', 'g'), ''), '0')) AS INTEGER);
     coalesce
----------
        0
(1 row)

ทำไมคุณถึงต้องการ '0' || ? จากเอกสาร: "ฟังก์ชัน COALESCE ส่งคืนอาร์กิวเมนต์แรกที่ไม่เป็นค่าว่าง" ดังนั้นถ้าคุณมีค่า null เป็นค่าของคุณ Coalesce จะกำจัดมันออกไป
Amala

@ อำลาแท้. รับได้สวย. แก้ไข
ghbarratt

1
โซลูชันจะใช้งานได้เฉพาะเมื่ออินพุตเป็นจำนวนเต็มหรือ NULL คำถามคือขอให้แปลงอินพุตประเภทใดก็ได้และใช้ 0 หากไม่สามารถแปลงได้
Chris Cogdon

@ChrisCogdon ฉันได้เพิ่มวิธีแก้ปัญหาเพื่อจัดการกับข้อกังวลของคุณโดยไม่ให้ศูนย์เสมอไปหากค่าที่จะแปลงเป็น "ไม่สามารถแปลงได้" โซลูชันเวอร์ชันที่ปรับแต่งนี้จะส่งคืน 0 เมื่อสตริงที่ไม่มีอักขระหลักถูกกำหนดเป็นค่าที่จะแปลง
ghbarratt

22

นี่อาจจะเป็นการแฮ็กบ้าง แต่ก็ทำให้งานสำเร็จในกรณีของเรา:

(0 || myfield)::integer

คำอธิบาย (ทดสอบใน Postgres 8.4):

นิพจน์ที่กล่าวถึงข้างต้นให้ผลNULLสำหรับค่า NULL ในmyfieldและ0สำหรับสตริงว่าง (ลักษณะการทำงานนี้อาจเหมาะสมหรือไม่เหมาะกับกรณีการใช้งานของคุณ

SELECT id, (0 || values)::integer from test_table ORDER BY id

ข้อมูลการทดสอบ:

CREATE TABLE test_table
(
  id integer NOT NULL,
  description character varying,
  "values" character varying,
  CONSTRAINT id PRIMARY KEY (id)
)

-- Insert Test Data
INSERT INTO test_table VALUES (1, 'null', NULL);
INSERT INTO test_table VALUES (2, 'empty string', '');
INSERT INTO test_table VALUES (3, 'one', '1');

แบบสอบถามจะให้ผลลัพธ์ดังต่อไปนี้:

 ---------------------
 |1|null        |NULL|
 |2|empty string|0   |
 |3|one         |1   |
 ---------------------

ในขณะที่เลือกเท่านั้น values::integerจะส่งผลให้เกิดข้อความแสดงข้อผิดพลาด

หวังว่านี่จะช่วยได้


3

SELECT CASE WHEN myfield="" THEN 0 ELSE myfield::integer END FROM mytable

ฉันไม่เคยทำงานกับ PostgreSQL แต่ฉันตรวจสอบคู่มือสำหรับไวยากรณ์ที่ถูกต้องของคำสั่ง IF ในการสืบค้น SELECT


ที่ใช้ได้กับตารางเหมือนตอนนี้ ฉันค่อนข้างกลัวว่าในอนาคตอาจมีค่าที่ไม่ใช่ตัวเลข ฉันต้องการวิธีแก้ปัญหาแบบลอง / จับได้ แต่นี่เป็นเคล็ดลับ ขอบคุณ
silviot

บางทีคุณอาจใช้นิพจน์ทั่วไปpostgresql.org/docs/8.4/interactive/functions-matching.htmlแต่อาจมีค่าใช้จ่ายสูง ยอมรับคำตอบด้วยถ้าเป็นทางออก :)
ม.ค. Hančič

3

@ แมทธิวตอบได้ดี แต่สามารถทำได้ง่ายและเร็วขึ้น และคำถามขอให้แปลงสตริงว่าง ( '') เป็น0แต่ไม่ใช่อินพุต "ไวยากรณ์อินพุตที่ไม่ถูกต้อง" หรือ "อยู่นอกช่วง" อื่น ๆ :

CREATE OR REPLACE FUNCTION convert_to_int(text)
  RETURNS int AS
$func$
BEGIN
   IF $1 = '' THEN  -- special case for empty string like requested
      RETURN 0;
   ELSE
      RETURN $1::int;
   END IF;

EXCEPTION WHEN OTHERS THEN
   RETURN NULL;  -- NULL for other invalid input

END
$func$  LANGUAGE plpgsql IMMUTABLE;

สิ่งนี้ส่งกลับ0สำหรับสตริงว่างและNULLสำหรับอินพุตอื่น ๆ ที่ไม่ถูกต้อง
มันสามารถนำมาปรับใด ๆการแปลงชนิดข้อมูลการแปลงชนิดข้อมูล

การป้อนบล็อกข้อยกเว้นมีราคาแพงกว่ามาก หากสตริงว่างเป็นเรื่องปกติควรจับกรณีนั้นก่อนที่จะเพิ่มข้อยกเว้น
หากสตริงว่างหายากมากจะต้องจ่ายเพื่อย้ายการทดสอบไปที่ข้อยกเว้น


1
CREATE OR REPLACE FUNCTION parse_int(s TEXT) RETURNS INT AS $$
BEGIN
  RETURN regexp_replace(('0' || s), '[^\d]', '', 'g')::INT;
END;
$$ LANGUAGE plpgsql;

ฟังก์ชันนี้จะส่งคืนเสมอ0หากไม่มีตัวเลขในสตริงอินพุต

SELECT parse_int('test12_3test');

จะกลับมา 123


คุณได้ทำการทดสอบประสิทธิภาพสำหรับฟังก์ชัน regex vs string หรือไม่ นอกจากนี้สิ่งนี้จัดการกับโมฆะอย่างไร มันจะกลับเป็น 0 หรือ NULL ตามที่คาดไว้หรือไม่? ขอบคุณ!
vol7ron

1

ฉันพบว่ารหัสต่อไปนี้ใช้งานง่าย คำตอบเดิมอยู่ที่นี่https://www.postgresql.org/message-id/371F1510.F86C876B@sferacarta.com

prova=> create table test(t text, i integer);
CREATE

prova=> insert into test values('123',123);
INSERT 64579 1

prova=> select cast(i as text),cast(t as int)from test;
text|int4
----+----
123| 123
(1 row)

หวังว่ามันจะช่วยได้


1

SUBSTRING อาจช่วยได้ในบางกรณีคุณสามารถ จำกัด ขนาดของ int ได้

SELECT CAST(SUBSTRING('X12312333333333', '([\d]{1,9})') AS integer);

0

หากข้อมูลควรเป็นจำนวนเต็มและคุณต้องการเพียงค่าเหล่านั้นเป็นจำนวนเต็มทำไมคุณไม่ไปทั้งไมล์และแปลงคอลัมน์เป็นคอลัมน์จำนวนเต็ม

จากนั้นคุณสามารถทำการแปลงค่าที่ผิดกฎหมายนี้ให้เป็นศูนย์เพียงครั้งเดียว ณ จุดของระบบที่ข้อมูลถูกแทรกลงในตาราง

ด้วยการแปลงด้านบนคุณกำลังบังคับให้ Postgres แปลงค่าเหล่านั้นซ้ำแล้วซ้ำอีกสำหรับแต่ละแถวในแต่ละคิวรีสำหรับตารางนั้นซึ่งอาจทำให้ประสิทธิภาพลดลงอย่างมากหากคุณทำแบบสอบถามจำนวนมากกับคอลัมน์นี้ในตารางนี้


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

0

ฟังก์ชั่นต่อไปนี้ทำ

  • ใช้ค่าเริ่มต้น ( error_result) สำหรับผลลัพธ์ที่ไม่สามารถแคสได้เช่นabcหรือ999999999999999999999999999999999999999999
  • ช่วยให้ nullเป็นnull
  • ตัดช่องว่างและช่องว่างอื่น ๆ ในการป้อนข้อมูล
  • ค่าที่ระบุว่าถูกต้องbigintsจะถูกเปรียบเทียบกับlower_boundเช่นบังคับใช้ค่าบวกเท่านั้น
CREATE OR REPLACE FUNCTION cast_to_bigint(text) 
RETURNS BIGINT AS $$
DECLARE big_int_value BIGINT DEFAULT NULL;
DECLARE error_result  BIGINT DEFAULT -1;
DECLARE lower_bound   BIGINT DEFAULT 0;
BEGIN
    BEGIN
        big_int_value := CASE WHEN $1 IS NOT NULL THEN GREATEST(TRIM($1)::BIGINT, lower_bound) END;
    EXCEPTION WHEN OTHERS THEN
        big_int_value := error_result;
    END;
RETURN big_int_value;
END;

-1

ฉันก็มีความต้องการเหมือนกัน แต่ใช้ได้กับ JPA 2.0 และ Hibernate 5.0.2:

SELECT p FROM MatchProfile p WHERE CONCAT(p.id, '') = :keyword

ทำงานได้อย่างมหัศจรรย์ ฉันคิดว่ามันใช้ได้กับ LIKE ด้วย


-3

สิ่งนี้ควรใช้งานได้เช่นกัน แต่นี่เป็นการข้าม SQL และไม่ใช่ postgres เฉพาะ

select avg(cast(mynumber as numeric)) from my table
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.