Làm cách nào để tôi truyền một chuỗi thành số nguyên và có 0 trong trường hợp có lỗi trong quá trình truyền với PostgreQuery?


127

Trong PostgreSQL tôi có một bảng với một cột varchar. Dữ liệu được coi là số nguyên và tôi cần nó ở dạng số nguyên trong một truy vấn. Một số giá trị là chuỗi rỗng. Sau đây là

SELECT myfield::integer FROM mytable

sản lượng ERROR: invalid input syntax for integer: ""

Làm thế nào tôi có thể truy vấn một diễn viên và có 0 trong trường hợp có lỗi trong quá trình truyền trong postgres?

Câu trả lời:


160

Bản thân tôi chỉ đang vật lộn với một vấn đề tương tự, nhưng không muốn chi phí hoạt động. Tôi đã đưa ra các truy vấn sau đây:

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

Postgres rút ngắn các điều kiện của nó, vì vậy bạn không nên nhận bất kỳ số nguyên nào không nhấn vào biểu thức :: số nguyên của mình. Nó cũng xử lý các giá trị NULL (chúng sẽ không khớp với biểu thức chính quy).

Nếu bạn muốn số không thay vì không chọn, thì câu lệnh CASE sẽ hoạt động:

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

14
Tôi thực sự khuyên bạn nên đi với đề nghị của Matthew. Giải pháp này có vấn đề với các chuỗi trông giống như số nhưng lớn hơn giá trị tối đa bạn có thể đặt trong một số nguyên.
pilif

4
Tôi nhận xét thứ hai của pilif. giá trị tối đa đó là một lỗi đang chờ xảy ra. điểm không ném lỗi là không ném lỗi khi dữ liệu không hợp lệ. câu trả lời được chấp nhận này KHÔNG giải quyết điều đó. cảm ơn Matthew công việc tuyệt vời
Shawn Kovac

3
Tuyệt vời như câu trả lời của Matthew là, tôi chỉ cần một cách xử lý nhanh chóng và bẩn thỉu để kiểm tra một số dữ liệu. Tôi cũng thừa nhận rằng kiến ​​thức của riêng tôi hiện đang thiếu trong việc xác định các hàm trong SQL. Tôi chỉ quan tâm đến các số từ 1 đến 5 chữ số, vì vậy tôi đã thay đổi regex thành E'\\d{1,5}$'.
Bobort

3
Vâng, vâng, giải pháp này tương đối nhanh và bẩn, nhưng trong trường hợp của tôi, tôi biết dữ liệu mình có và bảng tương đối ngắn. Nó dễ dàng hơn nhiều so với việc viết (và gỡ lỗi) toàn bộ một chức năng. @ {1,5}Giới hạn của Bobort ở trên các chữ số có thể là một ý tưởng tốt nếu bạn lo ngại về việc tràn, nhưng nó sẽ che giấu các số lớn hơn, điều này có thể gây rắc rối nếu bạn chuyển đổi bảng. Cá nhân tôi muốn có lỗi truy vấn ở phía trước và biết rằng một số "số nguyên" của tôi rất khó hiểu (Bạn cũng có thể chọn E'\\d{6,}$'trước để đảm bảo).
Anthony Briggs

1
@Anthony Briggs: Điều này sẽ không hoạt động nếu myfield chứa "'" hoặc "," hoặc "." Hoặc' - '
Stefan Steiger

100

Bạn cũng có thể tạo chức năng chuyển đổi của riêng mình, bên trong bạn có thể sử dụng các khối ngoại lệ:

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;

Kiểm tra:

=# 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
trái ngược với câu trả lời được chấp nhận, giải pháp này ở đây đúng hơn vì nó cũng có thể xử lý tốt các số quá lớn để khớp với một số nguyên và nó cũng có thể nhanh hơn vì nó không hoạt động xác thực trong trường hợp chung (= chuỗi hợp lệ )
pilif

Làm thế nào bạn có thể truyền chuỗi thành số nguyên trên các trường cụ thể bằng cách sử dụng hàm của bạn trong khi trong INSERTcâu lệnh?
sk

27

Tôi có cùng loại nhu cầu và thấy điều này hoạt động tốt với tôi (postgres 8.4):

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

Một số trường hợp thử nghiệm để chứng minh:

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"

Nếu bạn cần xử lý khả năng trường có văn bản không phải là số (chẳng hạn như "100bad"), bạn có thể sử dụng biểu thức chính quy để loại bỏ các ký tự không phải là số trước khi truyền.

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

Sau đó, các giá trị văn bản / varchar như "b3ad5" cũng sẽ đưa ra số

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

Để giải quyết mối quan tâm của Chris Cogdon với giải pháp không đưa ra 0 cho tất cả các trường hợp, bao gồm cả trường hợp như "xấu" (không có ký tự chữ số nào), tôi đã đưa ra tuyên bố điều chỉnh này:

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

Nó hoạt động tương tự như các giải pháp đơn giản hơn, ngoại trừ sẽ cho 0 khi giá trị cần chuyển đổi chỉ là các ký tự không có chữ số, chẳng hạn như "xấu":

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

Tại sao bạn cần '0' || ? Từ các tài liệu: "Hàm COALESCE trả về đối số đầu tiên không phải là null." Vì vậy, nếu bạn có giá trị null, Coalesce sẽ thoát khỏi nó.
Amala

@Amala Đúng. Bắt đẹp. Đã chỉnh sửa.
ghbarratt

1
Giải pháp chỉ hoạt động nếu đầu vào là một số nguyên hoặc NULL. Câu hỏi được yêu cầu chuyển đổi bất kỳ loại đầu vào nào và sử dụng 0 nếu không thể chuyển đổi.
Chris Cogdon

@ChrisCogdon Tôi đã thêm vào giải pháp để giải quyết mối quan tâm của bạn mà không phải luôn luôn đưa ra số 0 nếu giá trị cần chuyển đổi là "không thể chuyển đổi." Phiên bản tinh chỉnh của giải pháp này sẽ trả về 0 khi một chuỗi không có ký tự chữ số được đưa ra làm giá trị cần chuyển đổi.
ghbarratt

22

Điều này có thể là một phần của hack, nhưng nó đã hoàn thành công việc trong trường hợp của chúng tôi:

(0 || myfield)::integer

Giải thích (Đã thử nghiệm trên Postgres 8.4):

Biểu thức được đề cập ở trên mang lại NULLgiá trị NULL trong myfield0cho các chuỗi trống (Hành vi chính xác này có thể hoặc không phù hợp với trường hợp sử dụng của bạn).

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

Dữ liệu kiểm tra:

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');

Truy vấn sẽ mang lại kết quả như sau:

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

Trong khi đó chỉ chọn values::integersẽ dẫn đến một thông báo lỗi.

Hi vọng điêu nay co ich.


3

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

Tôi chưa từng làm việc với PostgreSQL nhưng tôi đã kiểm tra hướng dẫn sử dụng cú pháp chính xác của các câu lệnh IF trong các truy vấn CHỌN.


Điều đó làm việc cho bảng như bây giờ. Tôi hơi sợ rằng trong tương lai nó có thể chứa các giá trị không phải là số. Tôi đã thích một giải pháp giống như thử / bắt, nhưng đây là một mẹo nhỏ. Cảm ơn.
silviot

Có lẽ bạn có thể sử dụng các biểu thức thông thường postgresql.org/docs/8.4/interactive/fifts-matching.html nhưng điều đó có thể tốn kém. Đồng thời chấp nhận câu trả lời nếu đó là giải pháp :)
Jan Hančič

3

Câu trả lời của Matthew là tốt. Nhưng nó có thể đơn giản và nhanh hơn. Và câu hỏi yêu cầu chuyển đổi các chuỗi rỗng ( '') thành 0, nhưng không phải đầu vào "cú pháp đầu vào không hợp lệ" hoặc "ngoài phạm vi" khác:

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;

Điều này trả về 0cho một chuỗi trống và NULLcho bất kỳ đầu vào không hợp lệ khác.
Nó có thể dễ dàng được điều chỉnh cho bất kỳ chuyển đổi loại dữ liệu .

Bước vào một khối ngoại lệ là đắt hơn đáng kể. Nếu các chuỗi rỗng là phổ biến thì nên nắm bắt trường hợp đó trước khi đưa ra một ngoại lệ.
Nếu các chuỗi rỗng rất hiếm, nó trả tiền để chuyển kiểm tra sang mệnh đề ngoại lệ.


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

Hàm này sẽ luôn trả về 0nếu không có chữ số trong chuỗi đầu vào.

SELECT parse_int('test12_3test');

sẽ trở lại 123


Bạn đã thực hiện bất kỳ kiểm tra hiệu suất cho chức năng regex vs chuỗi? Ngoài ra, làm thế nào điều này xử lý null? Nó sẽ trả về 0 hoặc NULL như mong đợi? Cảm ơn!
vol7ron


1

SUBSTRING có thể giúp cho một số trường hợp, bạn có thể giới hạn kích thước của int.

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

0

Nếu dữ liệu được coi là số nguyên và bạn chỉ cần các giá trị đó là số nguyên, tại sao bạn không đi cả dặm và chuyển đổi cột thành cột số nguyên?

Sau đó, bạn có thể thực hiện việc chuyển đổi các giá trị bất hợp pháp này thành số không chỉ một lần, tại điểm của hệ thống nơi dữ liệu được chèn vào bảng.

Với chuyển đổi ở trên, bạn buộc Postgres phải chuyển đổi các giá trị đó nhiều lần cho mỗi hàng trong mỗi truy vấn cho bảng đó - điều này có thể làm giảm hiệu suất nghiêm trọng nếu bạn thực hiện nhiều truy vấn đối với cột này trong bảng này.


Về nguyên tắc bạn đúng, nhưng trong kịch bản cụ thể này, tôi phải tối ưu hóa một truy vấn chậm duy nhất trong một ứng dụng. Tôi không biết làm thế nào mã xử lý dữ liệu đầu vào hoạt động. Tôi không muốn chạm vào nó. Cho đến nay truy vấn viết lại của tôi hoạt động, nhưng tôi muốn nó không bị phá vỡ trong các trường hợp không lường trước được. Kiến trúc lại ứng dụng không phải là một lựa chọn, ngay cả khi nó có vẻ là điều hợp lý nhất.
silviot

0

Các chức năng sau đây

  • sử dụng một giá trị mặc định ( error_result) cho các kết quả không thể cast, ví dụ abchoặc999999999999999999999999999999999999999999
  • giữ nullnhưnull
  • cắt đi khoảng trắng và khoảng trắng khác trong đầu vào
  • các giá trị được chọn là hợp lệ bigintsđược so sánh với lower_boundví dụ chỉ thực thi các giá trị dương
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

Tôi cũng có nhu cầu tương tự nhưng hoạt động với JPA 2.0 và Hibernate 5.0.2:

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

Công trình kỳ diệu. Tôi nghĩ rằng nó hoạt động với THÍCH quá.


-3

Điều này cũng sẽ thực hiện công việc nhưng điều này là trên toàn SQL và không phải là postgres cụ thể.

select avg(cast(mynumber as numeric)) from my table
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.