การสืบค้น JSONB ใน PostgreSQL


14

ฉันมีตารางpersonsซึ่งมีสองคอลัมน์และคอลัมน์ที่idใช้ JSONB data(ตารางนี้เพิ่งสร้างขึ้นเพื่อวัตถุประสงค์ในการสาธิตเพื่อเล่นกับการสนับสนุน JSON ของ PostgreSQL)

ตอนนี้มันควรจะมีสองบันทึก:

1, { name: 'John', age: 30 }
2, { name: 'Jane', age: 20 }

ตอนนี้ฉันควรจะได้ชื่อของทุกคนที่อายุมากกว่า 25 แล้วสิ่งที่ฉันได้ลองคือ:

select data->'name' as name from persons where data->'age' > 25

น่าเสียดายที่นี่ทำให้เกิดข้อผิดพลาด ฉันสามารถแก้ไขได้โดยใช้->>แทน->แต่จากนั้นการเปรียบเทียบไม่ทำงานตามที่คาดไว้อีกต่อไปเนื่องจากไม่ใช่การเปรียบเทียบตัวเลข แต่การแสดงเป็นสตริง

select data->'name' as name from persons where data->>'age' > '25'

จากนั้นฉันก็พบว่าฉันสามารถแก้ปัญหาได้จริงโดยใช้->และนักแสดงไปที่int:

select data->'name' as name from persons where cast(data->'age' as int) > 25

ใช้งานได้ แต่มันก็ไม่ดีที่ฉันต้องรู้ประเภทจริง (ชนิดของageในเอกสาร JSON อยู่numberแล้วดังนั้นทำไม PostgreSQL จึงไม่สามารถรู้ได้ด้วยตัวเอง?)

จากนั้นผมก็คิดว่าถ้าผมด้วยตนเองแปลงtextโดยใช้::ไวยากรณ์การทำงานทุกอย่างเป็นไปตามคาดเกินไป - แม้ว่าตอนนี้เรากำลังเปรียบเทียบสตริงอีกครั้ง

select data->'name' as name from persons where data->'age'::text > '25'

ถ้าฉันลองใช้ชื่อแทนอายุมันจะไม่ทำงาน:

select data->'name' as name from persons where data->'name'::text > 'Jenny'

สิ่งนี้ส่งผลให้เกิดข้อผิดพลาด:

ไวยากรณ์อินพุตที่ไม่ถูกต้องสำหรับประเภท json

เห็นได้ชัดว่าฉันไม่ได้อะไรเลย น่าเสียดายที่มันค่อนข้างยากที่จะพบตัวอย่างจริง ๆ ของการใช้ JSON กับ PostgreSQL

คำใบ้ใด ๆ


1
ในdata->'name'::textคุณกำลังหล่อ'name'สตริงเป็นข้อความไม่ใช่ผลลัพธ์ คุณไม่ได้รับข้อผิดพลาดเมื่อเปรียบเทียบกับ'25'เพราะ25เป็นตัวอักษร JSON ที่ถูกต้อง แต่Jennyไม่ใช่ (แม้ว่า"Jenny"จะเป็น)
chirlu

ขอบคุณนั่นคือทางออก :-) ฉันสับสนกับ'Jenny' '"Jenny"'
Golo Roden

คำตอบ:


15

นี้ไม่ได้ทำงานเพราะมันพยายามที่จะโยนความคุ้มค่าให้ jsonbinteger

select data->'name' as name from persons where cast(data->'age' as int) > 25

สิ่งนี้ใช้ได้จริง:

SELECT data->'name' AS name FROM persons WHERE cast(data->>'age' AS int) > 25;

หรือสั้นกว่า:

SELECT data->'name' AS name FROM persons WHERE (data->>'age')::int > 25;

และนี่:

SELECT data->'name' AS name FROM persons WHERE data->>'name' > 'Jenny';

ดูเหมือนว่าความสับสนกับสองผู้ประกอบการ->และ->>และความสำคัญประกอบ นักแสดง::ผูกขึ้นแข็งแกร่งกว่าตัวดำเนินการ json (b)

คิดออกประเภทแบบไดนามิก

นี่เป็นส่วนที่น่าสนใจของคำถามของคุณ:

ประเภทอายุในเอกสาร JSON เป็นตัวเลขอยู่แล้วเหตุใด PostgreSQL จึงไม่สามารถคำนวณได้ด้วยตัวเอง

SQL เป็นภาษาที่พิมพ์อย่างเคร่งครัดมันไม่อนุญาตให้มีการแสดงออกเดียวกันในการประเมินintegerในหนึ่งแถวและtextในถัดไป แต่เนื่องจากคุณสนใจเฉพาะbooleanผลลัพธ์ของการทดสอบคุณสามารถหลีกเลี่ยงข้อ จำกัด นี้โดยใช้CASEนิพจน์ที่ใช้ขึ้นอยู่กับผลลัพธ์ของjsonb_typeof():

SELECT data->'name'
FROM   persons
WHERE  CASE jsonb_typeof(data->'age')
        WHEN 'number'  THEN (data->>'age')::numeric > '25' -- treated as numeric
        WHEN 'string'  THEN data->>'age' > 'age_level_3'   -- treated as text
        WHEN 'boolean' THEN (data->>'age')::bool           -- use boolean directly (example)
        ELSE FALSE                                         -- remaining: array, object, null
       END;

สตริงตัวอักษรที่ไม่ได้พิมพ์ทางด้านขวาของ>โอเปอเรเตอร์จะถูก coerced กับประเภทของค่าที่เกี่ยวข้องทางซ้ายโดยอัตโนมัติ หากคุณใส่ค่าที่พิมพ์ไว้ประเภทนั้นจะต้องตรงกันหรือคุณต้องทำการแปลงอย่างชัดเจน - เว้นแต่จะมีการส่งสัญญาณโดยนัยที่เพียงพอลงทะเบียนในระบบ

หากคุณรู้ว่าค่าตัวเลขทั้งหมดเป็นจริงintegerคุณสามารถ:

... (data->>'age')::int > 25 ...

การแสดงออกหลัก sqlalchemy คือสิ่งที่สำหรับการเปรียบเทียบข้างต้นของคำสั่งเลือกเช่น s = select ([ประเด็น]) โดยที่ (problems.c.id == กลาง) .select_from (ปัญหา, ..... outerjoin (problems.c.data ['type_id'] == mtypes.c.id) ) ... นี่คือปัญหา data.c.data jsonb ชนิดข้อมูลและจะถูกเปรียบเทียบกับ mtypes.c.id ประเภทจำนวนเต็ม
user956424
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.