PostgreSQL: التعامل مع بيانات JSON و JSONB في PostgreSQL

آخر تحديث: 2026-08-26

1. ما ستتعلمه


2. القصة

Bob هو مهندس خلفية في منصة SaaS. تحتاج المنصة إلى تخزين تكوينات المستخدم وسمات المنتج، لكن هذه الحقول تختلف لكل عميل:

مع النموذج العلائقي التقليدي، كل حقل جديد يتطلب ALTER TABLE. اختار Bob تخزين هذه الحقول الديناميكية في جدول واحد باستخدام JSONB — مرن وفعال.


3. المفهوم: JSON مقابل JSONB

(1) مقارنة نوعي JSON

البعد JSON JSONB
التخزين مخزن كنص، محفوظ حرفيًا مخزن كثنائي، محلل ثم مخزن
سرعة الكتابة أسرع (بدون تحليل) أبطأ (يحتاج تحليل وتحويل)
سرعة الاستعلام أبطأ (يحلل عند كل استعلام) سريع جدًا (محلل بالفعل إلى شجرة)
دعم الفهرس لا فهرس محلي يدعم فهرس GIN
المسافات البيضاء/الترتيب يحافظ على المسافات الأصلية وترتيب المفاتيح غير محفوظ؛ المفاتيح مرتبة أبجديًا
المفاتيح المكررة جميع المفاتيح المكررة محفوظة فقط آخر قيمة محفوظة
موصى به لـ تخزين فقط، بدون استعلام الغالبية العظمى من الحالات

(1) ▶ مثال

SQL
SELECT '{"name": "Alice", "age": 30}'::json;
-- {"name": "Alice", "age": 30}

SELECT '{"name": "Alice", "age": 30}'::jsonb;
-- {"age": 30, "name": "Alice"}

Output:

TEXT 📖 للعرض فقط
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

(2) ▶ مثال

SQL
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:

TEXT 📖 للعرض فقط
INSERT 0 1

4. المفهوم: معاملات JSON

(1) معاملات الاستخراج الأساسية

المعامل المعامل الأيمن نوع الإرجاع الوصف ▶�ثال
-> int JSON/JSONB عنصر مصفوفة حسب الفهرس '[1,2,3]'::jsonb -> 12
-> text JSON/JSONB قيمة كائن حسب المفتاح '{"a":1}'::jsonb -> 'a'1
->> int text عنصر مصفوفة حسب الفهرس (نص) '[1,2,3]'::jsonb ->> 1"2"
->> text text قيمة كائن حسب المفتاح (نص) '{"a":1}'::jsonb ->> 'a'"1"

(3) ▶ مثال

SQL
SELECT profile -> 'theme' AS theme_json,
       profile ->> 'theme' AS theme_text
FROM users
WHERE username = 'alice';

Output:

TEXT 📖 للعرض فقط
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

(2) معاملات استخراج المسار

المعامل المعامل الأيمن نوع الإرجاع الوصف
#> text[] JSON/JSONB قيمة حسب المسار (تنسيق JSON)
#>> text[] text قيمة حسب المسار (تنسيق نص)

(4) ▶ مثال

SQL
SELECT profile #> '{address,city}' AS city_json,
       profile #>> '{address,city}' AS city_text
FROM users
WHERE profile ? 'address';

Output:

TEXT 📖 للعرض فقط
 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) ▶ مثال

SQL
SELECT username, profile
FROM users
WHERE profile @> '{"theme": "dark"}';

Output:

TEXT 📖 للعرض فقط
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

(6) ▶ مثال

SQL
SELECT username, profile ->> 'timezone' AS tz
FROM users
WHERE profile ? 'timezone';

Output:

TEXT 📖 للعرض فقط
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

(7) ▶ مثال

SQL
SELECT username
FROM users
WHERE profile ?| array['timezone', 'currency'];

Output:

TEXT 📖 للعرض فقط
 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) ▶ مثال

SQL
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:

TEXT 📖 للعرض فقط
INSERT 0 1

(9) ▶ مثال

SQL
SELECT username,
       (jsonb_each(profile)).key AS config_key,
       (jsonb_each(profile)).value AS config_value
FROM users;

Output:

TEXT 📖 للعرض فقط
 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) ▶ مثال

SQL
-- إضافة أو تحديث حقل
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:

TEXT 📖 للعرض فقط
-- SQL statement executed successfully

(11) ▶ مثال

SQL
-- إلحاق بالنهاية (المسار يجب أن يشير إلى مصفوفة موجودة، إدراج بعد آخر عنصر)
UPDATE products
SET attributes = jsonb_set(
  attributes, '{colors}',
  (attributes -> 'colors') || '"yellow"'
)
WHERE name = 'T-Shirt';

Output:

TEXT 📖 للعرض فقط
INSERT 0 1

(12) ▶ مثال

SQL
SELECT jsonb_pretty(profile) FROM users WHERE username = 'alice';
TEXT 📖 للعرض فقط
{
    "theme": "dark",
    "language": "zh",
    "address": {
        "city": "New York"
    }
}

6. المفهوم: فهارس JSONB

(1) فهرس GIN يسرع استعلامات JSONB

نوع فهرس GIN المعاملات المدعومة الوصف
jsonb_ops (افتراضي) @> ? `? ?&`
jsonb_path_ops @> فهرس أصغر وأسرع — يدعم استعلامات الاحتواء فقط

(13) ▶ مثال

SQL
-- فهرس 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:

TEXT 📖 للعرض فقط
CREATE TABLE

(14) ▶ مثال

SQL
-- بدون فهرس: مسح تسلسلي
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:

TEXT 📖 للعرض فقط
CREATE TABLE
طريقة الاستعلام تستخدم فهرس GIN؟ الوصف
profile @> '{"theme":"dark"}' نعم استعلام احتواء — أفضل حالة لـ GIN
profile ->> 'theme' = 'dark' لا استخراج ثم مقارنة — يحتاج فهرس تعبير B-tree
profile ? 'theme' نعم استعلام وجود مفتاح

(15) ▶ مثال

SQL
-- للاستعلامات التي تستخدم المعامل ->>
CREATE INDEX idx_users_theme ON users ((profile ->> 'theme'));
SELECT * FROM users WHERE profile ->> 'theme' = 'dark'; -- يستخدم الفهرس

Output:

TEXT 📖 للعرض فقط
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) ▶ مثال

SQL
SELECT jsonb_path_query(profile, '$.theme') AS theme
FROM users
WHERE username = 'alice';

Output:

TEXT 📖 للعرض فقط
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

(17) ▶ مثال

SQL
-- منتجات بضمان > سنة واحدة
SELECT name,
       jsonb_path_query(attributes, '$.warranty_years') AS warranty
FROM products
WHERE jsonb_path_exists(attributes, '$.warranty_years ? (@ > 1)');

Output:

TEXT 📖 للعرض فقط
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

(18) ▶ مثال

SQL
SELECT name,
       jsonb_path_query(attributes, '$.colors[*]') AS color
FROM products;

Output:

TEXT 📖 للعرض فقط
 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) ▶ مثال

SQL
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:

TEXT 📖 للعرض فقط
CREATE TABLE

(20) ▶ مثال

SQL
ALTER TABLE products_v2
ADD CONSTRAINT chk_attributes_schema
CHECK (
  jsonb_typeof(attributes -> 'colors') = 'array'
  AND attributes ? 'colors'
);

Output:

TEXT 📖 للعرض فقط
-- SQL statement executed successfully

(21) ▶ مثال

SQL
-- إيجاد الطلبات حيث المنتج له سمة محددة
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:

TEXT 📖 للعرض فقط
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

9. تدفق تخزين واستعلام JSON

100%
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، يدعم تكوين مستخدم مرن وإدارة سمات منتج.

SQL
-- الخطوة 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';

❓ أسئلة شائعة

س أيهما أختار، JSON أم JSONB؟
ج للغالبية العظمى من الحالات، اختر JSONB. JSONB أسرع في الاستعلام، يدعم الفهرسة، ولديه معاملات أغنى. استخدم JSON فقط عندما تحتاج الحفاظ على تنسيق النص الأصلي (المسافات، ترتيب المفاتيح، المفاتيح المكررة)، أو للتخزين فقط بدون استعلام.
س هل يمكن لـ JSONB استبدال الجداول العلائقية؟
ج ليس بالكامل. الحقول التي يتم الاستعلام عنها/ترتيبها/ربطها بشكل متكرر يجب أن تستخدم أعمدة علائقية. JSONB يناسب البيانات التكميلية متغيرة الهيكل؛ التصميم الهجين هو أفضل ممارسة.
س ما الفرق بين jsonb_set و jsonb_insert؟
ج jsonb_set يستبدل القيمة في مسار موجود، وينشئها إذا لم يكن المسار موجودًا. jsonb_insert يدرج عنصرًا جديدًا في موضع محدد في مصفوفة (يتحكم معامل before في الأمام/الخلف)، ولا يستبدل إذا كان المفتاح موجودًا بالفعل.
س كيف أختار بين فهرس GIN وفهرس تعبير B-tree؟
ج استخدم GIN لاستعلامات الاحتواء (@>)، وفهرس تعبير B-tree لاستعلامات المساواة (->> 'key' = 'value'). يمكن أن يتواجد الاثنان معًا، لتغطية أنماط استعلام مختلفة.
س كيف أختار بين JSONPATH والمعاملات التقليدية؟
ج المعاملات أكثر إيجازًا للاستعلامات البسيطة؛ JSONPATH أكثر قوة للاستعلامات المتداخلة المعقدة وشروط المرشح. JSONPATH هو معيار SQL/JSON، لذا فهو أكثر قابلية للنقل.
س هل يمكن أن يكون لحقل JSONB قيد CHECK؟
ج نعم. استخدم jsonb_typeof()، المعامل ?، إلخ في قيد CHECK للتحقق من هيكل JSONB — على سبيل المثال CHECK (jsonb_typeof(attributes -> 'colors') = 'مصفوفة').
س هل تسبب أعداد كبيرة من تحديثات JSONB مشاكل أداء؟
ج نعم. تحديث JSONB يستبدل القيمة بأكملها (MVCC ينشئ نسخة جديدة)، وتحديث قيم JSONB كبيرة بشكل متكرر ينتج العديد من الصفوف الميتة. يوصى بتقسيم الحقول المحدثة بشكل متكرر إلى أعمدة علائقية، وترك JSONB لتخزين البيانات التكميلية منخفضة التردد في التحديث.

📖 ملخص


📝 تمارين

  1. ⭐ أنشئ جدول app_settings بأعمدة app_name (TEXT) و settings (JSONB)؛ أدرج صفين، ثم استخدم ->> للاستعلام عن قيمة عنصر تكوين.

  2. ⭐⭐ أنشئ فهرس GIN على جدول saas_products؛ اكتب استعلامًا يجد أسماء جميع المنتجات التي specs لها warranty_years > 2، واستخدم jsonb_pretty لتنسيق specs بشكل جميل.

  3. ⭐⭐⭐ صمم مخطط JSONB هجين لجدول طلبات: أعمدة ثابتة لـ order_id/customer_id/total_amount/status/created_at، وعمود JSONB extra يخزن معلومات القسيمة (coupon_code/discount_percent) وملاحظات التوصيل (delivery_notes). اكتب: إدراج طلب مع extra، استخدام @> لإيجاد الطلبات التي استخدمت قسيمة محددة، واستخدام jsonb_set لإلحاق gift_wrap: true لطلب موجود.

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%