ตรวจสอบว่าอาร์เรย์ Postgres JSON มีสตริงหรือไม่


122

ฉันมีโต๊ะสำหรับเก็บข้อมูลเกี่ยวกับกระต่ายของฉัน ดูเหมือนว่า:

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"]}');

ฉันจะหากระต่ายที่ชอบแครอทได้อย่างไร? ฉันคิดสิ่งนี้:

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

ฉันไม่ชอบข้อความค้นหานั้น มันเป็นเรื่องยุ่ง

ในฐานะผู้ดูแลกระต่ายเต็มเวลาฉันไม่มีเวลาเปลี่ยนสคีมาฐานข้อมูล ฉันแค่อยากเลี้ยงกระต่ายของฉันอย่างถูกต้อง มีวิธีที่อ่านง่ายขึ้นในการทำแบบสอบถามนั้นหรือไม่?


1
คำถามที่น่าสนใจ ฉันเล่นกับมันแล้ว แต่มันก็เริ่มขึ้นฉันไม่แน่ใจว่าคุณหมายถึงอะไร "ดีกว่า" คุณใช้เกณฑ์อะไรในการตัดสินคำตอบของคุณ การอ่าน? ประสิทธิภาพ? อื่น ๆ ?
David S

@DavidS: (ฉันอัปเดตคำถามแล้ว) ฉันต้องการความสามารถในการอ่านมากกว่าประสิทธิภาพ ฉันไม่คาดหวังว่าจะมีอะไรดีไปกว่าการสแกนแบบเต็มตารางเนื่องจากฉันกำลังแก้ไขสคีมา
Snowball

12
ผิดไหมที่ฉันโหวตคำถามนี้เพราะกระต่าย?
osman

3
ฉันเพิ่งโหวตคำถามนี้เพราะกระต่ายแล้วเห็นความคิดเห็นของคุณ @osman
1valdis

ฉันเห็นความคิดเห็นของคุณแล้วก็รู้ว่าฉันต้องโหวตเพราะกระต่าย
Peter Aron Zentai

คำตอบ:


187

ตั้งแต่ PostgreSQL 9.4 คุณสามารถใช้ตัว?ดำเนินการ :

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

คุณสามารถจัดทำดัชนี?แบบสอบถามบน"food"คีย์ได้หากคุณเปลี่ยนไปใช้ประเภทjsonbแทน:

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

แน่นอนคุณอาจไม่มีเวลาสำหรับสิ่งนั้นในฐานะผู้ดูแลกระต่ายเต็มเวลา

อัปเดต:นี่คือการสาธิตการปรับปรุงประสิทธิภาพบนโต๊ะของกระต่าย 1,000,000 ตัวที่กระต่ายแต่ละตัวชอบอาหารสองอย่างและ 10% ของพวกมันชอบแครอท:

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

วิธีรับแถวที่อาร์เรย์อาหารภายใน json ไม่ว่างเปล่าตัวอย่างเช่นถ้าเราสามารถพิจารณาได้ว่าเป็น JSON ที่อาร์เรย์อาหารว่างเปล่าคุณช่วยได้ไหม
Bravo

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

1
มีใครทราบวิธีการทำงานในกรณีที่คุณต้องเลือกจำนวนเต็มแทนสตริง / ข้อความ?
Rotareti

3
@Rotareti คุณสามารถใช้> ผู้ประกอบการ @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'; : โปรดทราบว่า'2'เป็นหมายเลข JSON อย่าเข้าใจผิดโดยคำพูด
Snowball

@Snowball แบบสอบถามนี้เลือกข้อมูล - >> 'ชื่อ' จากกระต่ายที่ไหน (ข้อมูล -> 'อาหาร') :: jsonb? 'แครอท'; ทำงานได้อย่างสมบูรณ์แบบสำหรับคำค้นหาจาก JSON แต่ฉันจะได้รับบันทึกทั้งหมดที่ไม่มีคำว่า 'แครอท' ได้อย่างไร?
มิลาน

23

คุณสามารถใช้ตัวดำเนินการ @> เพื่อทำสิ่งนี้ได้

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

1
สิ่งนี้มีประโยชน์เมื่อรายการเป็นโมฆะเช่นกัน
Lucio

2
ตรวจสอบให้แน่ใจว่าคุณใส่ใจกับ'เห็บรอบ ๆ "แครอท" ... มันจะแตกถ้าคุณปล่อยมันออกไปแม้ว่าคุณจะกำลังตรวจหาจำนวนเต็มก็ตาม (ใช้เวลา 3 ชั่วโมงในการหาจำนวนเต็มทำให้มันทำงานได้อย่างน่าอัศจรรย์โดยการห่อ'เห็บรอบ ๆ ตัวเลข)
skplunkerin

@skplunkerin ควรเป็นค่า json ที่ล้อมรอบด้วย'เห็บเพื่อสร้างสตริงเนื่องจากทุกอย่างเป็นสตริงสำหรับ SQL ในประเภท JSONB ยกตัวอย่างเช่นบูล: 'true'สตริง: จำนวนเต็ม:'"example"' '123'
1valdis


13

รูปแบบเล็กน้อย แต่ไม่มีข้อมูลใหม่ มันขาดคุณสมบัติจริงๆ ...

select info->>'name' from rabbits 
where '"carrots"' = ANY (ARRAY(
    select * from json_array_elements(info->'food'))::text[]);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.