Kiểm tra xem một mảng Postgres JSON có chứa một chuỗi hay không


119

Tôi có một bảng để lưu trữ thông tin về những con thỏ của tôi. Nó trông như thế này:

create table rabbits (rabbit_id bigserial primary key, info json not null);
insert into rabbits (info) values
  ('{"name":"Henry", "food":["lettuce","carrots"]}'),
  ('{"name":"Herald","food":["carrots","zucchini"]}'),
  ('{"name":"Helen", "food":["lettuce","cheese"]}');

Làm thế nào tôi nên tìm những con thỏ thích cà rốt? Tôi đã nghĩ ra điều này:

select info->>'name' from rabbits where exists (
  select 1 from json_array_elements(info->'food') as food
  where food::text = '"carrots"'
);

Tôi không thích truy vấn đó. Đó là một mớ hỗn độn.

Là một người nuôi thỏ toàn thời gian, tôi không có thời gian để thay đổi lược đồ cơ sở dữ liệu của mình. Tôi chỉ muốn cho thỏ ăn đúng cách. Có cách nào dễ đọc hơn để thực hiện truy vấn đó không?


1
Câu hỏi thú vị. Tôi đã thử với nó, nhưng sau đó tôi chợt nhận ra, tôi không chắc bạn muốn nói gì về "tốt hơn". Bạn đang đánh giá câu trả lời của mình dựa trên tiêu chí nào? Khả năng đọc? Hiệu quả? Khác?
David S

@DavidS: (Tôi đã cập nhật câu hỏi.) Tôi thích khả năng đọc hơn hiệu quả. Tôi chắc chắn không mong đợi điều gì tốt hơn việc quét toàn bộ bảng, vì tôi đang giữ lược đồ đã được sửa.
Snowball

11
Có sai không khi tôi ủng hộ câu hỏi này vì những con thỏ?
osman

3
Tôi vừa ủng hộ câu hỏi này vì thỏ và sau đó nhìn thấy nhận xét của bạn @osman
1valdis

Tôi đã xem bình luận của bạn và sau đó nhận ra rằng tôi cần
ủng hộ

Câu trả lời:


185

Kể từ PostgreSQL 9.4, bạn có thể sử dụng ?toán tử :

select info->>'name' from rabbits where (info->'food')::jsonb ? 'carrots';

Bạn thậm chí có thể lập chỉ mục ?truy vấn trên "food"khóa nếu bạn chuyển sang loại jsonb thay thế:

alter table rabbits alter info type jsonb using info::jsonb;
create index on rabbits using gin ((info->'food'));
select info->>'name' from rabbits where info->'food' ? 'carrots';

Tất nhiên, bạn có thể không có thời gian cho việc đó với tư cách là người nuôi thỏ toàn thời gian.

Cập nhật: Dưới đây là minh chứng về những cải thiện hiệu suất trên bàn 1.000.000 con thỏ, nơi mỗi con thỏ thích hai loại thức ăn và 10% trong số chúng thích cà rốt:

d=# -- Postgres 9.3 solution
d=# explain analyze select info->>'name' from rabbits where exists (
d(# select 1 from json_array_elements(info->'food') as food
d(#   where food::text = '"carrots"'
d(# );
 Execution time: 3084.927 ms

d=# -- Postgres 9.4+ solution
d=# explain analyze select info->'name' from rabbits where (info->'food')::jsonb ? 'carrots';
 Execution time: 1255.501 ms

d=# alter table rabbits alter info type jsonb using info::jsonb;
d=# explain analyze select info->'name' from rabbits where info->'food' ? 'carrots';
 Execution time: 465.919 ms

d=# create index on rabbits using gin ((info->'food'));
d=# explain analyze select info->'name' from rabbits where info->'food' ? 'carrots';
 Execution time: 256.478 ms

làm thế nào để có được hàng nơi mảng thức ăn bên trong json là không có sản phẩm nào, ví dụ nếu chúng ta có thể xem xét, họ là JSON, nơi mảng thực phẩm cũng trống rỗng, có thể giúp bạn giúp đỡ
Bravo

1
@Bravoselect * from rabbits where info->'food' != '[]';
Snowball

1
Có ai biết cách này hoạt động như thế nào trong trường hợp bạn cần chọn một số nguyên thay vì một chuỗi / văn bản không?
Rotareti

3
@Rotareti Bạn có thể sử dụng @> điều hành : create table t (x jsonb); insert into t (x) values ('[1,2,3]'), ('[2,3,4]'), ('[3,4,5]'); select * from t where x @> '2';. Lưu ý rằng đó '2'là một số JSON; không bị đánh lừa bởi các dấu ngoặc kép.
Snowball

@Snowball, truy vấn này chọn thông tin - >> 'tên' từ thỏ ở đâu (thông tin -> 'thức ăn') :: jsonb? 'cà rốt'; đang hoạt động hoàn hảo cho từ tìm kiếm từ JSON. Nhưng làm thế nào tôi có thể nhận được tất cả các bản ghi không chứa từ 'cà rốt'?
Milan

23

Bạn có thể sử dụng toán tử @> để thực hiện điều này như

SELECT info->>'name'
FROM rabbits
WHERE info->'food' @> '"carrots"';

1
Này rất hữu ích khi mục là null cũng
Lucio

2
Hãy chắc chắn rằng bạn chú ý đến 'dấu tích xung quanh "củ cà rốt" ... nó sẽ vỡ nếu bạn bỏ chúng đi, ngay cả khi bạn đang kiểm tra một số nguyên. (dành 3 giờ cố gắng tìm một số nguyên, có nó một cách kỳ diệu làm việc bằng cách gói 've xung quanh số)
skplunkerin

@skplunkerin Nó phải là giá trị json được bao quanh bởi 'dấu tích để tạo thành một chuỗi, bởi vì mọi thứ đều là một chuỗi cho SQL ở kiểu JSONB. Ví dụ, boolean: 'true', chuỗi: '"example"', số nguyên: '123'.
1valdis

22

Không thông minh hơn nhưng đơn giản hơn:

select info->>'name' from rabbits WHERE info->>'food' LIKE '%"carrots"%';

13

Một biến thể nhỏ nhưng không có gì mới. Nó thực sự thiếu một tính năng ...

select info->>'name' from rabbits 
where '"carrots"' = ANY (ARRAY(
    select * from json_array_elements(info->'food'))::text[]);
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.