Chỉ mục để tìm một phần tử trong mảng JSON


84

Tôi có một bảng trông như thế này:

CREATE TABLE tracks (id SERIAL, artists JSON);

INSERT INTO tracks (id, artists) 
  VALUES (1, '[{"name": "blink-182"}]');

INSERT INTO tracks (id, artists) 
  VALUES (2, '[{"name": "The Dirty Heads"}, {"name": "Louis Richards"}]');

Có một số cột khác không liên quan đến câu hỏi này. Có lý do để lưu trữ chúng dưới dạng JSON.

Những gì tôi đang cố gắng làm là tra cứu một bản nhạc có tên nghệ sĩ cụ thể (khớp chính xác).

Tôi đang sử dụng truy vấn này:

SELECT * FROM tracks 
  WHERE 'ARTIST NAME' IN
    (SELECT value->>'name' FROM json_array_elements(artists))

ví dụ

SELECT * FROM tracks
  WHERE 'The Dirty Heads' IN 
    (SELECT value->>'name' FROM json_array_elements(artists))

Tuy nhiên, điều này thực hiện quét toàn bộ bảng và nó không nhanh lắm. Tôi đã thử tạo chỉ mục GIN bằng một hàm names_as_array(artists)và được sử dụng 'ARTIST NAME' = ANY names_as_array(artists), tuy nhiên chỉ mục này không được sử dụng và truy vấn thực sự chậm hơn đáng kể.


Tôi đã thực hiện một câu hỏi theo dõi dựa trên này một: dba.stackexchange.com/questions/71546/...
Ken Li

Câu trả lời:


138

jsonb trong Postgres 9.4+

Với kiểu dữ liệu JSON nhị phân mới jsonb, Postgres 9.4 đã giới thiệu các tùy chọn chỉ mục được cải thiện đáng kể . Bây giờ bạn có thể có chỉ mục GIN trên một jsonbmảng trực tiếp:

CREATE TABLE tracks (id serial, artists jsonb);
CREATE INDEX tracks_artists_gin_idx ON tracks USING gin (artists);

Không cần hàm để chuyển đổi mảng. Điều này sẽ hỗ trợ một truy vấn:

SELECT * FROM tracks WHERE artists @> '[{"name": "The Dirty Heads"}]';

@>jsonbtoán tử "chứa" mới , có thể sử dụng chỉ mục GIN. (Không dành cho loại json, chỉjsonb !)

Hoặc bạn sử dụng lớp toán tử GIN chuyên biệt hơn, không mặc định jsonb_path_opscho chỉ mục:

CREATE INDEX tracks_artists_gin_idx ON tracks
USING  gin (artists jsonb_path_ops);

Cùng một truy vấn.

Hiện tại jsonb_path_opschỉ hỗ trợ @>nhà mạng. Nhưng nó thường nhỏ hơn và nhanh hơn nhiều. Có thêm tùy chọn chỉ mục, chi tiết trong sách hướng dẫn .


Nếu artists chỉ giữ các tên như được hiển thị trong ví dụ, sẽ hiệu quả hơn khi lưu trữ giá trị JSON ít dư thừa hơn để bắt đầu bằng: chỉ các giá trị dưới dạng văn bản gốckhóa dự phòng có thể nằm trong tên cột.

Lưu ý sự khác biệt giữa các đối tượng JSON và các kiểu nguyên thủy:

CREATE TABLE tracks (id serial, artistnames jsonb);
INSERT INTO tracks  VALUES (2, '["The Dirty Heads", "Louis Richards"]');

CREATE INDEX tracks_artistnames_gin_idx ON tracks USING gin (artistnames);

Truy vấn:

SELECT * FROM tracks WHERE artistnames ? 'The Dirty Heads';

?không hoạt động cho các giá trị đối tượng , chỉ là các khóacác phần tử mảng .
Hoặc (hiệu quả hơn nếu tên được lặp lại thường xuyên):

CREATE INDEX tracks_artistnames_gin_idx ON tracks
USING  gin (artistnames jsonb_path_ops);

Truy vấn:

SELECT * FROM tracks WHERE artistnames @> '"The Dirty Heads"'::jsonb;

json trong Postgres 9.3+

Điều này sẽ hoạt động với một IMMUTABLE chức năng :

CREATE OR REPLACE FUNCTION json2arr(_j json, _key text)
  RETURNS text[] LANGUAGE sql IMMUTABLE AS
'SELECT ARRAY(SELECT elem->>_key FROM json_array_elements(_j) elem)';

Tạo chỉ mục chức năng này :

CREATE INDEX tracks_artists_gin_idx ON tracks
USING  gin (json2arr(artists, 'name'));

Và sử dụng một truy vấn như thế này. Biểu thức trong WHEREmệnh đề phải khớp với biểu thức trong chỉ mục:

SELECT * FROM tracks
WHERE  '{"The Dirty Heads"}'::text[] <@ (json2arr(artists, 'name'));

Cập nhật thông tin phản hồi trong nhận xét. Chúng ta cần sử dụng toán tử mảng để hỗ trợ chỉ mục GIN.
Các "được chứa bởi" nhà điều hành<@ trong trường hợp này.

Ghi chú về sự biến động của chức năng

Bạn có thể khai báo chức năng của mình IMMUTABLEngay cả khi json_array_elements() không có.
Hầu hết các JSONchức năng được sử dụng để duy nhất STABLE, không IMMUTABLE. Đã có một cuộc thảo luận về danh sách tin tặc để thay đổi điều đó. Hầu hết là IMMUTABLEbây giờ. Kiểm tra với:

SELECT p.proname, p.provolatile
FROM   pg_proc p
JOIN   pg_namespace n ON n.oid = p.pronamespace
WHERE  n.nspname = 'pg_catalog'
AND    p.proname ~~* '%json%';

Chỉ mục chức năng chỉ hoạt động với các IMMUTABLEchức năng.


2
Điều này không hoạt động vì SETOFkhông thể sử dụng trả về trong một chỉ mục. Loại bỏ điều đó, tôi có thể tạo chỉ mục, tuy nhiên nó không được sử dụng bởi trình lập kế hoạch truy vấn. Ngoài ra, cả json_array_elements và array_agg đềuIMMUTABLE
JeffS

2
@Tony: Xin lỗi, tôi đang trộn tên cột và tên khóa. Đã sửa và bổ sung thêm.
Erwin Brandstetter

1
@PyWebDesign: Các truy vấn chứa jsonb nói chung phải khớp với cùng một cấu trúc với đối tượng chứa (vì vậy tìm kiếm một đối tượng bên trong một mảng có nghĩa là bạn phải truy vấn bằng một đối tượng bên trong một mảng). Có một ngoại lệ đặc biệt cho các kiểu nguyên thủy bên trong một mảng; thêm chi tiết tại đây: stackoverflow.com/a/29947194/818187
potatosalad

3
@PyWebDesign: Tôi thấy bây giờ, lớp mảng đã bị thiếu trong một ví dụ. Đã sửa. Chỉ mục sẽ chỉ được sử dụng trong một bảng đủ lớn để nó rẻ hơn cho Postgres so với quét tuần tự.
Erwin Brandstetter

2
@PyWebDesign: Chạy trong phiên của bạn SET enable_seqscan = off;(chỉ dành cho mục đích gỡ lỗi) stackoverflow.com/questions/14554302/… .
Erwin Brandstetter
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.