PostgreSQL: التعامل مع بيانات JSON و JSONB في PostgreSQL
آخر تحديث: 2026-08-26
1. ما ستتعلمه
- فهم الفرق بين JSON و JSONB، ومزايا JSONB
- استخدام معاملات JSON لاستخراج البيانات وتصفيتها
- استخدام دوال JSONB للاستعلام عن بيانات JSON وتعديلها وتوليدها
- إنشاء فهارس GIN لتسريع استعلامات JSONB
- استخدام JSONPATH (معيار SQL/JSON) للاستعلامات المعقدة
- أنماط التصميم الهجين التي تجمع بين JSONB والبيانات العلائقية
2. القصة
Bob هو مهندس خلفية في منصة SaaS. تحتاج المنصة إلى تخزين تكوينات المستخدم وسمات المنتج، لكن هذه الحقول تختلف لكل عميل:
- تكوين مستخدم العميل A يحتوي على
themeوlanguageوnotifications - تكوين مستخدم العميل B يحتوي على
timezoneوcurrencyوdashboard_layout - سمات المنتج تختلف أكثر:: الملابس لها
size/color، الإلكترونيات لهاwarranty/voltage
مع النموذج العلائقي التقليدي، كل حقل جديد يتطلب ALTER TABLE. اختار Bob تخزين هذه الحقول الديناميكية في جدول واحد باستخدام JSONB — مرن وفعال.
3. المفهوم: JSON مقابل JSONB
(1) مقارنة نوعي JSON
| البعد | JSON | JSONB |
|---|---|---|
| التخزين | مخزن كنص، محفوظ حرفيًا | مخزن كثنائي، محلل ثم مخزن |
| سرعة الكتابة | أسرع (بدون تحليل) | أبطأ (يحتاج تحليل وتحويل) |
| سرعة الاستعلام | أبطأ (يحلل عند كل استعلام) | سريع جدًا (محلل بالفعل إلى شجرة) |
| دعم الفهرس | لا فهرس محلي | يدعم فهرس GIN |
| المسافات البيضاء/الترتيب | يحافظ على المسافات الأصلية وترتيب المفاتيح | غير محفوظ؛ المفاتيح مرتبة أبجديًا |
| المفاتيح المكررة | جميع المفاتيح المكررة محفوظة | فقط آخر قيمة محفوظة |
| موصى به لـ | تخزين فقط، بدون استعلام | الغالبية العظمى من الحالات |
(1) ▶ مثال
SELECT '{"name": "Alice", "age": 30}'::json;
-- {"name": "Alice", "age": 30}
SELECT '{"name": "Alice", "age": 30}'::jsonb;
-- {"age": 30, "name": "Alice"}
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(2) ▶ مثال
CREATE TABLE users (
user_id SERIAL PRIMARY KEY,
username TEXT NOT NULL,
profile JSONB NOT NULL DEFAULT '{}'
);
INSERT INTO users (username, profile) VALUES
('alice', '{"theme": "dark", "language": "en", "notifications": true}'),
('bob', '{"timezone": "UTC-5", "currency": "USD", "dashboard_layout": "grid"}');
Output:
INSERT 0 1
4. المفهوم: معاملات JSON
(1) معاملات الاستخراج الأساسية
| المعامل | المعامل الأيمن | نوع الإرجاع | الوصف | ▶�ثال |
|---|---|---|---|---|
-> |
int | JSON/JSONB | عنصر مصفوفة حسب الفهرس | '[1,2,3]'::jsonb -> 1 → 2 |
-> |
text | JSON/JSONB | قيمة كائن حسب المفتاح | '{"a":1}'::jsonb -> 'a' → 1 |
->> |
int | text | عنصر مصفوفة حسب الفهرس (نص) | '[1,2,3]'::jsonb ->> 1 → "2" |
->> |
text | text | قيمة كائن حسب المفتاح (نص) | '{"a":1}'::jsonb ->> 'a' → "1" |
(3) ▶ مثال
SELECT profile -> 'theme' AS theme_json,
profile ->> 'theme' AS theme_text
FROM users
WHERE username = 'alice';
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(2) معاملات استخراج المسار
| المعامل | المعامل الأيمن | نوع الإرجاع | الوصف |
|---|---|---|---|
#> |
text[] | JSON/JSONB | قيمة حسب المسار (تنسيق JSON) |
#>> |
text[] | text | قيمة حسب المسار (تنسيق نص) |
(4) ▶ مثال
SELECT profile #> '{address,city}' AS city_json,
profile #>> '{address,city}' AS city_text
FROM users
WHERE profile ? 'address';
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(3) معاملات الاحتواء والوجود (JSONB فقط)
| المعامل | الوصف | مثال |
|---|---|---|
@> |
ما إذا كان الأيسر يحتوي الأيمن | '{"a":1,"b":2}'::jsonb @> '{"a":1}' → true |
<@ |
ما إذا كان الأيسر محتوى من الأيمن | '{"a":1}'::jsonb <@ '{"a":1,"b":2}' → true |
? |
ما إذا كان المفتاح موجودًا | '{"a":1}'::jsonb ? 'a' → true |
| `? | ` | ما إذا كان أي مفتاح موجودًا |
?& |
ما إذا كانت جميع المفاتيح موجودة | '{"a":1}'::jsonb ?& array['a','b'] → false |
(5) ▶ مثال
SELECT username, profile
FROM users
WHERE profile @> '{"theme": "dark"}';
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(6) ▶ مثال
SELECT username, profile ->> 'timezone' AS tz
FROM users
WHERE profile ? 'timezone';
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(7) ▶ مثال
SELECT username
FROM users
WHERE profile ?| array['timezone', 'currency'];
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
5. المفهوم: دوال JSONB
(1) دوال الاستعلام والاستخراج
| الدالة | نوع الإرجاع | الوصف |
|---|---|---|
jsonb_path_query(data, path) |
setof jsonb | استعلام بـ JSONPATH، إعادة جميع التطابقات |
jsonb_array_elements(data) |
setof jsonb | توسيع المصفوفة إلى مجموعة صفوف |
jsonb_each(data) |
setof (key, value) | توسيع الكائن إلى أزواج مفتاح-قيمة |
jsonb_object_keys(data) |
setof text | إعادة جميع مفاتيح المستوى الأعلى |
jsonb_typeof(data) |
text | إعادة نوع قيمة JSON |
(8) ▶ مثال
CREATE TABLE products (
product_id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
attributes JSONB NOT NULL DEFAULT '{}'
);
INSERT INTO products (name, attributes) VALUES
('T-Shirt', '{"colors": ["red", "blue", "green"], "sizes": ["S", "M", "L"]}'),
('Laptop', '{"colors": ["silver", "black"], "warranty_years": 2}');
SELECT product_id, name,
jsonb_array_elements_text(attributes -> 'colors') AS color
FROM products;
Output:
INSERT 0 1
(9) ▶ مثال
SELECT username,
(jsonb_each(profile)).key AS config_key,
(jsonb_each(profile)).value AS config_value
FROM users;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(2) دوال التعديل
| الدالة | الوصف |
|---|---|
jsonb_set(target, path, new_value) |
تعيين القيمة في مسار محدد |
jsonb_insert(target, path, new_value [, before]) |
إدراج قيمة جديدة في مسار محدد |
target - key |
حذف مفتاح من المستوى الأعلى |
target - path_array |
حذف مسار محدد |
jsonb_pretty(data) |
إخراج منسق بشكل جميل |
(10) ▶ مثال
-- إضافة أو تحديث حقل
UPDATE users
SET profile = jsonb_set(profile, '{language}', '"zh"')
WHERE username = 'alice';
-- إضافة حقل متداخل
UPDATE users
SET profile = jsonb_set(profile, '{address,city}', '"New York"')
WHERE username = 'alice';
-- حذف حقل
UPDATE users
SET profile = profile - 'notifications'
WHERE username = 'alice';
Output:
-- SQL statement executed successfully
(11) ▶ مثال
-- إلحاق بالنهاية (المسار يجب أن يشير إلى مصفوفة موجودة، إدراج بعد آخر عنصر)
UPDATE products
SET attributes = jsonb_set(
attributes, '{colors}',
(attributes -> 'colors') || '"yellow"'
)
WHERE name = 'T-Shirt';
Output:
INSERT 0 1
(12) ▶ مثال
SELECT jsonb_pretty(profile) FROM users WHERE username = 'alice';
{
"theme": "dark",
"language": "zh",
"address": {
"city": "New York"
}
}
6. المفهوم: فهارس JSONB
(1) فهرس GIN يسرع استعلامات JSONB
| نوع فهرس GIN | المعاملات المدعومة | الوصف |
|---|---|---|
jsonb_ops (افتراضي) |
@> ? `? |
?&` |
jsonb_path_ops |
@> |
فهرس أصغر وأسرع — يدعم استعلامات الاحتواء فقط |
(13) ▶ مثال
-- فهرس GIN افتراضي (يدعم @>, ?, ?|, ?&)
CREATE INDEX idx_users_profile ON users USING gin (profile);
-- فهرس GIN مسار (أصغر، أسرع لـ @> فقط)
CREATE INDEX idx_users_profile_path ON users USING gin (profile jsonb_path_ops);
Output:
CREATE TABLE
(14) ▶ مثال
-- بدون فهرس: مسح تسلسلي
EXPLAIN ANALYZE
SELECT * FROM users WHERE profile @> '{"theme": "dark"}';
-- بعد إنشاء فهرس GIN: مسح فهرس نقطي
CREATE INDEX idx_users_profile ON users USING gin (profile);
EXPLAIN ANALYZE
SELECT * FROM users WHERE profile @> '{"theme": "dark"}';
Output:
CREATE TABLE
| طريقة الاستعلام | تستخدم فهرس GIN؟ | الوصف |
|---|---|---|
profile @> '{"theme":"dark"}' |
نعم | استعلام احتواء — أفضل حالة لـ GIN |
profile ->> 'theme' = 'dark' |
لا | استخراج ثم مقارنة — يحتاج فهرس تعبير B-tree |
profile ? 'theme' |
نعم | استعلام وجود مفتاح |
(15) ▶ مثال
-- للاستعلامات التي تستخدم المعامل ->>
CREATE INDEX idx_users_theme ON users ((profile ->> 'theme'));
SELECT * FROM users WHERE profile ->> 'theme' = 'dark'; -- يستخدم الفهرس
Output:
CREATE TABLE
7. المفهوم: JSONPATH (معيار SQL/JSON)
(1) صيغة JSONPATH
PostgreSQL 12+ يدعم معيار SQL/JSON JSONPATH، مشابه لـ XPath، للاستعلامات المعقدة على JSON.
| الصيغة | الوصف | مثال |
|---|---|---|
$.key |
مفتاح كائن جذر | $.theme |
$.array[*] |
تكرار على المصفوفة | $.colors[*] |
$.nested.key |
وصول متداخل | $.address.city |
? (condition) |
مرشح | $.items[*] ? (@.price > 100) |
@ |
العنصر الحالي | @.name |
(16) ▶ مثال
SELECT jsonb_path_query(profile, '$.theme') AS theme
FROM users
WHERE username = 'alice';
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(17) ▶ مثال
-- منتجات بضمان > سنة واحدة
SELECT name,
jsonb_path_query(attributes, '$.warranty_years') AS warranty
FROM products
WHERE jsonb_path_exists(attributes, '$.warranty_years ? (@ > 1)');
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(18) ▶ مثال
SELECT name,
jsonb_path_query(attributes, '$.colors[*]') AS color
FROM products;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
| الدالة | نوع الإرجاع | الوصف |
|---|---|---|
jsonb_path_query(data, path) |
setof jsonb | إعادة جميع التطابقات |
jsonb_path_query_array(data, path) |
jsonb | إعادة التطابقات كمصفوفة JSON |
jsonb_path_query_first(data, path) |
jsonb | إعادة أول تطابق |
jsonb_path_exists(data, path) |
منطقي | ما إذا كان أي تطابق موجودًا |
8. المفهوم: التصميم الهجين باستخدام JSONB والبيانات العلائقية
(1) متى تستخدم JSONB، ومتى تستخدم الأعمدة العلائقية
| السيناريو | موصى به | السبب |
|---|---|---|
| الحقول التي يتم الاستعلام عنها/ترتيبها/ربطها بشكل متكرر | عمود علائقي + فهرس B-tree | أفضل أداء |
| الحقول ذات الهيكل الثابت التي تشارك في منطق الأعمال | عمود علائقي | آمن النوع، قيود كاملة |
| الحقول التي يختلف هيكلها حسب العميل | JSONB + فهرس GIN | مرن، لا حاجة لـ ALTER TABLE |
| استعلامات معلومات تكميلية عرضية | JSONB | لا يلوث هيكل الجدول الرئيسي |
| الحقول الديناميكية التي تحتاج قيود نوع دقيقة | JSONB + قيد CHECK | يوازن المرونة والأمان |
(19) ▶ مثال
CREATE TABLE products_v2 (
product_id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
category TEXT NOT NULL, -- عمود ثابت، مفهرس
price NUMERIC(10,2) NOT NULL, -- عمود ثابت، مفهرس
stock INT NOT NULL DEFAULT 0, -- عمود ثابت، مفهرس
attributes JSONB NOT NULL DEFAULT '{}', -- سمات ديناميكية
metadata JSONB DEFAULT '{}' -- معلومات وصفية نادرًا ما تستعلم عنها
);
CREATE INDEX idx_products_category ON products_v2 (category);
CREATE INDEX idx_products_attrs ON products_v2 USING gin (attributes jsonb_path_ops);
Output:
CREATE TABLE
(20) ▶ مثال
ALTER TABLE products_v2
ADD CONSTRAINT chk_attributes_schema
CHECK (
jsonb_typeof(attributes -> 'colors') = 'array'
AND attributes ? 'colors'
);
Output:
-- SQL statement executed successfully
(21) ▶ مثال
-- إيجاد الطلبات حيث المنتج له سمة محددة
SELECT o.order_id, o.customer_id, p.name
FROM orders o
JOIN products_v2 p ON o.product_id = p.product_id
WHERE p.attributes @> '{"warranty_years": 2}';
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
9. تدفق تخزين واستعلام JSON
flowchart TD
A[إدخال نص JSON] --> B{النوع الهدف؟}
B -->|json| C[تخزين كما هو<br/>بدون عبء تحليل]
B -->|jsonb| D[تحليل وتحويل<br/>إلى شجرة ثنائية]
D --> E[تخزين كـ JSONB<br/>مفاتيح مرتبة، بدون مسافات]
E --> F{نوع الاستعلام؟}
F -->|@> يحتوي| G[مسح فهرس GIN<br/>مسار سريع]
F -->|->> استخراج + مقارنة| H[فهرس تعبير B-tree<br/>أو مسح تسلسلي]
F -->|jsonpath| I[محرك JSONPATH<br/>PG 12+]
G --> J[إعادة النتائج]
H --> J
I --> J
C --> K[تحليل عند كل استعلام<br/>بطيء، بدون فهرس]
K --> J
10. تطبيق عملي: نظام تكوين مستخدم وسمات منتج لمنصة SaaS
يحتاج Bob إلى تنفيذ حل تخزين بيانات كامل لمنصة SaaS، يدعم تكوين مستخدم مرن وإدارة سمات منتج.
-- الخطوة 1: إنشاء الجداول الأساسية مع JSONB
CREATE TABLE saas_users (
user_id SERIAL PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
plan TEXT NOT NULL DEFAULT 'free',
config JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMP DEFAULT now()
);
CREATE TABLE saas_products (
product_id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
category TEXT NOT NULL,
price NUMERIC(10,2) NOT NULL,
specs JSONB NOT NULL DEFAULT '{}',
tags JSONB NOT NULL DEFAULT '[]'
);
-- الخطوة 2: إدراج بيانات عينة
INSERT INTO saas_users (email, name, plan, config) VALUES
('alice@corp.com', 'Alice', 'pro',
'{"theme":"dark","language":"en","notifications":{"email":true,"sms":false},"sidebar":["dashboard","reports"]}'),
('bob@corp.com', 'Bob', 'enterprise',
'{"theme":"light","language":"zh","notifications":{"email":true,"sms":true},"sidebar":["dashboard","admin","billing"]}');
INSERT INTO saas_products (name, category, price, specs, tags) VALUES
('Pro Widget', 'widget', 49.99,
'{"weight_kg":0.5,"colors":["red","blue"],"warranty_years":3}',
'["popular","new"]'),
('Mega Gadget', 'gadget', 199.99,
'{"weight_kg":2.0,"colors":["silver","black"],"voltage":"220V"}',
'["premium","bestseller"]');
-- الخطوة 3: إنشاء الفهارس
CREATE INDEX idx_saas_users_config ON saas_users USING gin (config);
CREATE INDEX idx_saas_products_specs ON saas_products USING gin (specs jsonb_path_ops);
CREATE INDEX idx_saas_products_tags ON saas_products USING gin (tags);
CREATE INDEX idx_saas_products_category ON saas_products (category);
-- الخطوة 4: أمثلة استعلام
-- إيجاد المستخدمين الذين لديهم إشعارات بريد إلكتروني مفعلة
SELECT name, config ->> 'theme' AS theme
FROM saas_users
WHERE config @> '{"notifications":{"email":true}}';
-- إيجاد المنتجات المتاحة باللون الأحمر
SELECT name, price
FROM saas_products
WHERE specs -> 'colors' @> '["red"]';
-- إيجاد المنتجات بعلامات محددة
SELECT name
FROM saas_products
WHERE tags @> '["premium"]';
-- تحديث تكوين المستخدم (إضافة حقل جديد)
UPDATE saas_users
SET config = jsonb_set(config, '{timezone}', '"America/New_York"')
WHERE email = 'alice@corp.com';
-- إزالة حقل تكوين
UPDATE saas_users
SET config = config - 'language'
WHERE email = 'bob@corp.com';
-- توسيع علامات المنتج للتحليلات
SELECT name, jsonb_array_elements_text(tags) AS tag
FROM saas_products;
-- تنسيق جميل لتكوين المستخدم
SELECT name, jsonb_pretty(config) FROM saas_users WHERE plan = 'pro';
❓ أسئلة شائعة
📖 ملخص
- JSONB هو JSON مخزن ثنائيًا — أسرع في الاستعلام، يدعم الفهارس — موصى به كخيار افتراضي
->يعيد نوع JSON،->>يعيد نوع نص،#>/#>>يستخرجان حسب المسار- معامل الاحتواء
@>مع فهرس GIN هو أفضل تركيبة لاستعلامات JSONB jsonb_set/jsonb_insert/-تتعامل مع الإضافة/التعديل/الحذف؛jsonb_array_elements/jsonb_eachتتعامل مع التوسيع- JSONPATH (PG 12+) يوفر قدرات استعلام معقدة بمعيار SQL/JSON
- التصميم الهجين: الحقول الثابتة تستخدم أعمدة علائقية، الحقول الديناميكية تستخدم JSONB، مع قيود CHECK لضمان الجودة
📝 تمارين
-
⭐ أنشئ جدول
app_settingsبأعمدةapp_name(TEXT) وsettings(JSONB)؛ أدرج صفين، ثم استخدم->>للاستعلام عن قيمة عنصر تكوين. -
⭐⭐ أنشئ فهرس GIN على جدول
saas_products؛ اكتب استعلامًا يجد أسماء جميع المنتجات التيspecsلهاwarranty_years > 2، واستخدمjsonb_prettyلتنسيقspecsبشكل جميل. -
⭐⭐⭐ صمم مخطط JSONB هجين لجدول طلبات: أعمدة ثابتة لـ
order_id/customer_id/total_amount/status/created_at، وعمود JSONBextraيخزن معلومات القسيمة (coupon_code/discount_percent) وملاحظات التوصيل (delivery_notes). اكتب: إدراج طلب معextra، استخدام@>لإيجاد الطلبات التي استخدمت قسيمة محددة، واستخدامjsonb_setلإلحاقgift_wrap: trueلطلب موجود.