Trước hết, bạn không thể truy cập các giá trị mảng JSON như vậy. Đối với một giá trị json đã cho
[{"event_slug":"test_1","start_time":"2014-10-08","end_time":"2014-10-12"},
{"event_slug":"test_2","start_time":"2013-06-24","end_time":"2013-07-02"},
{"event_slug":"test_3","start_time":"2014-03-26","end_time":"2014-03-30"}]
Kiểm tra hợp lệ đối với phần tử mảng đầu tiên sẽ là:
WHERE e->0->>'event_slug' = 'test_1'
Nhưng có thể bạn không muốn giới hạn tìm kiếm của mình ở phần tử đầu tiên của mảng. Với jsonb
kiểu dữ liệu trong Postgres 9.4 bạn có thêm toán tử và hỗ trợ chỉ mục. Để lập chỉ mục các phần tử của một mảng, bạn cần có chỉ mục GIN.
Các lớp toán tử cài sẵn cho chỉ mục GIN không hỗ trợ toán tử "lớn hơn" hoặc "nhỏ hơn" . Điều này đúng với > >= < <=
jsonb
cũng như, nơi bạn có thể chọn giữa hai lớp toán tử. Theo tài liệu:
Name Indexed Data Type Indexable Operators
...
jsonb_ops jsonb ? ?& ?| @>
jsonb_path_ops jsonb @>
(jsonb_ops
là mặc định.) Bạn có thể bao gồm kiểm tra bình đẳng, nhưng cả hai toán tử đó đều không bao gồm yêu cầu của bạn đối với >=
sự so sánh. Bạn sẽ cần một chỉ mục btree.
Giải pháp cơ bản
Để hỗ trợ việc kiểm tra sự bình đẳng với một chỉ mục:
CREATE INDEX locations_events_gin_idx ON locations
USING gin (events jsonb_path_ops);
SELECT * FROM locations WHERE events @> '[{"event_slug":"test_1"}]';
Điều này có thể đủ tốt nếu bộ lọc đủ chọn lọc.
Giả sử end_time >= start_time
, vì vậy chúng tôi không cần hai lần kiểm tra. Chỉ kiểm tra end_time
rẻ hơn và tương đương:
SELECT l.*
FROM locations l
, jsonb_array_elements(l.events) e
WHERE l.events @> '[{"event_slug":"test_1"}]'
AND (e->>'end_time')::timestamp >= '2014-10-30 14:04:06 -0400'::timestamptz;
Sử dụng JOIN LATERAL
ngầm . Chi tiết (chương cuối):
- PostgreSQL unnest () với số phần tử
Cẩn thận với các loại dữ liệu khác nhau ! Những gì bạn có trong giá trị JSON trông giống như timestamp [without time zone]
, trong khi các vị từ của bạn sử dụng timestamp with time zone
nghĩa đen. Dấu thời gian timestamp
giá trị được diễn giải theo múi giờ hiện tại trong khi timestamptz
đã cho các chữ phải được truyền thành timestamptz
rõ ràng hoặc múi giờ sẽ bị bỏ qua! Truy vấn trên sẽ hoạt động như mong muốn. Giải thích chi tiết:
- Bỏ qua hoàn toàn các múi giờ trong Rails và PostgreSQL
Giải thích thêm cho jsonb_array_elements()
:
- Tham gia PostgreSQL bằng JSONB
Giải pháp nâng cao
Nếu điều trên không đủ tốt, tôi sẽ xem xét một MATERIALIZED VIEW
lưu trữ các thuộc tính có liên quan ở dạng chuẩn hóa. Điều này cho phép lập chỉ mục btree đơn giản.
Mã giả định rằng các giá trị JSON của bạn có định dạng nhất quán như được hiển thị trong câu hỏi.
Thiết lập:
CREATE TYPE event_type AS (
, event_slug text
, start_time timestamp
, end_time timestamp
);
CREATE MATERIALIZED VIEW loc_event AS
SELECT l.location_id, e.event_slug, e.end_time -- start_time not needed
FROM locations l, jsonb_populate_recordset(null::event_type, l.events) e;
Câu trả lời liên quan cho jsonb_populate_recordset()
:
- Cách chuyển đổi kiểu jsonb của PostgreSQL 9.4 thành float
CREATE INDEX loc_event_idx ON loc_event (event_slug, end_time, location_id);
Cũng bao gồm location_id
để cho phép chỉ quét chỉ mục . (Xem trang hướng dẫn sử dụng và Postgres Wiki.)
Truy vấn:
SELECT *
FROM loc_event
WHERE event_slug = 'test_1'
AND end_time >= '2014-10-30 14:04:06 -0400'::timestamptz;
Hoặc, nếu bạn cần các hàng đầy đủ từ các vị trí timestamp
bên dưới bảng:
SELECT l.*
FROM (
SELECT DISTINCT location_id
FROM loc_event
WHERE event_slug = 'test_1'
AND end_time >= '2014-10-30 14:04:06 -0400'::timestamptz
) le
JOIN locations l USING (location_id);