PostgreSQL: معاملات وتعابير PostgreSQL
آخر تحديث: 2026-08-26
1. ما ستتعلمه
- المعاملات الحسابية
+ - * / %وسلوك التجاوز والدقة - معاملات المقارنة والمنطق
- دمج السلاسل
||ومعاملات مطابقة الأنماط - تحويلات الأنواع
::وCAST() - منشئو الصفوف
ROW()ومعاملات النطاق@> <@ &< &> - - تعابير الاستعلام الفرعي
IN / EXISTS / ANY / ALL
2. القصة
Charlie هو محلل مالي في منصة SaaS، مسؤول عن:
- حساب المبلغ بعد الضريبة لكل طلب (
total * 1.08) - التحقق مما إذا كان الطلب يقع ضمن نطاق تاريخ معين
- تحويل التواريخ المنسقة كنصوص إلى نوع DATE للمقارنة
- التحقق مما إذا كانت نطاقات الأسعار تتداخل
هذه المهام تتطلب منه إتقان معاملات وتعابير PostgreSQL المختلفة.
3. المفهوم: المعاملات الحسابية
(1) المعاملات الحسابية الأساسية
| المعامل | المعنى | مثال | النتيجة |
|---|---|---|---|
+ |
جمع | 100 + 8 |
108 |
- |
طرح | 500 - 50 |
450 |
* |
ضرب | 29.99 * 3 |
89.97 |
/ |
قسمة صحيحة (اقتطاع) | 7 / 2 |
3 |
/ |
قسمة عشرية (بعلامة عشرية) | 7.0 / 2 |
3.5 |
% |
باقي القسمة | 10 % 3 |
1 |
^ |
أس | 2 ^ 10 |
1024 |
| ` | /` | جذر تربيعي | ` |
| ` | /` | جذر تكعيبي | |
! |
عاملي | 5 ! |
120 |
@ |
قيمة مطلقة | @ -15 |
15 |
(1) ▶ مثال
SELECT
order_id,
total_amount,
total_amount * 0.08 AS tax,
total_amount * 1.08 AS total_with_tax
FROM orders
WHERE order_status = 'completed';
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(2) ▶ مثال
SELECT
7 / 2 AS int_div,
7.0 / 2 AS numeric_div,
7 / 2.0 AS numeric_div2,
CAST(7 AS NUMERIC) / 2 AS cast_div;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(2) أولوية المعاملات
| الأولوية | المعامل | الوصف |
|---|---|---|
| 1 (أعلى) | . |
وصول العضو |
| 2 | :: |
تحويل النوع |
| 3 | [ |
فهرس المصفوفة |
| 4 | - (أحادي)، @، ! |
عمليات أحادية |
| 5 | ^ |
أس |
| 6 | *، /، % |
ضرب / قسمة / باقي |
| 7 | +، - |
جمع / طرح |
| 8 | ` | |
| 9 | معاملات المقارنة | = <> < > <= >= |
| 10 | IS، IN، BETWEEN |
مسندات |
| 11 | NOT |
نفي منطقي |
| 12 | AND |
و منطقي |
| 13 (أدنى) | OR |
أو منطقي |
(3) ▶ مثال
SELECT
2 + 3 * 4 AS no_parens,
(2 + 3) * 4 AS with_parens;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(3) التجاوز والدقة
نطاق INTEGER في PostgreSQL من حوالي -2.1 مليار إلى 2.1 مليار؛ تجاوزه يثير خطأ. لحسابات الأموال، استخدم NUMERIC(dقة, مقياس) أو DECIMAL.
| النوع | النطاق | الدقة | الأنسب لـ |
|---|---|---|---|
SMALLINT |
-32768 ~ 32767 | صحيح | تعدادات صغيرة النطاق |
INTEGER |
±2.1 مليار | صحيح | أعداد صحيحة عامة |
BIGINT |
±9.2 كوينتيليون | صحيح | معرفات كبيرة النطاق |
NUMERIC(p,s) |
غير محدود | دقة اعتباطية | حسابات الأموال |
(4) ▶ مثال
SELECT
order_id,
total_amount::NUMERIC(12,2) AS amount,
(total_amount::NUMERIC(12,2) * 0.08)::NUMERIC(12,2) AS tax,
(total_amount::NUMERIC(12,2) * 1.08)::NUMERIC(12,2) AS total_with_tax
FROM orders;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
4. المفهوم: معاملات المقارنة
(1) المقارنة الأساسية
| المعامل | المعنى | 1 <> 2 |
NULL = NULL |
|---|---|---|---|
= |
يساوي | FALSE | NULL |
<> / != |
لا يساوي | TRUE | NULL |
< |
أصغر من | TRUE | NULL |
> |
أكبر من | FALSE | NULL |
<= |
أصغر من أو يساوي | TRUE | NULL |
>= |
أكبر من أو يساوي | FALSE | NULL |
أي مقارنة مع NULL تعطي NULL، وليس TRUE أو FALSE.
(5) ▶ مثال
SELECT product_name, unit_price
FROM products
WHERE unit_price >= 100
AND unit_price < 500
AND category = 'Electronics';
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(2) BETWEEN والمقارنات المركبة
| التعبير | المكافئ |
|---|---|
x BETWEEN a AND b |
x >= a AND x <= b |
x NOT BETWEEN a AND b |
x < a OR x > b |
(6) ▶ مثال
SELECT order_id, total_amount, order_date
FROM orders
WHERE total_amount BETWEEN 100 AND 1000
AND order_date BETWEEN '2025-01-01' AND '2025-12-31';
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(3) IS DISTINCT FROM
IS DISTINCT FROM يعامل NULL كقيمة قابلة للمقارنة، متجنبًا مأزق NULL في المقارنات القياسية.
| التعبير | NULL = NULL |
NULL IS DISTINCT FROM NULL |
|---|---|---|
| النتيجة | NULL | FALSE |
1 IS DISTINCT FROM NULL |
— | TRUE |
(7) ▶ مثال
SELECT order_id, old_status, new_status
FROM order_status_log
WHERE old_status IS DISTINCT FROM new_status;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
5. المفهوم: المعاملات المنطقية
(1) جدول حقيقة المنطق ثلاثي القيم
تستخدم PostgreSQL منطقًا ثلاثي القيم (TRUE / FALSE / NULL)؛ عندما يشارك NULL في عملية منطقية، يمكن أن تكون النتيجة غير بديهية.
| a | b | a AND b | a OR b | |---|---|---|---|---------|--------| | T | T | T | T | | T | F | F | T | | T | N | N | T | | F | T | F | T | | F | F | F | F | | F | N | F | N | | N | T | N | T | | N | F | F | N | | N | N | N | N |
(8) ▶ مثال
SELECT
TRUE AND NULL AS and_result,
TRUE OR NULL AS or_result,
FALSE AND NULL AS and_false,
FALSE OR NULL AS or_null,
NOT NULL AS not_result;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(9) ▶ مثال
-- هذا يستبعد الصفوف حيث discount_rate هو NULL
SELECT product_name, discount_rate
FROM products
WHERE discount_rate > 0 OR discount_rate = 0;
-- صفوف NULL غير مضمنة لأن NULL > 0 هو NULL، NULL = 0 هو NULL
-- إصلاح: معالجة NULL صراحة
SELECT product_name, discount_rate
FROM products
WHERE discount_rate > 0 OR discount_rate = 0 OR discount_rate IS NULL;
Output:
count
-------
5
(1 row)
6. المفهوم: معاملات السلاسل
(1) دمج السلاسل ||
|| هو معامل دمج السلاسل في PostgreSQL، على عكس + في SQL Server.
| المعامل | المعنى | مثال |
|---|---|---|
| ` | ` |
(10) ▶ مثال
SELECT
customer_id,
first_name || ' ' || last_name AS full_name,
'Order #' || order_id || ': $' || total_amount::TEXT AS order_summary
FROM orders o
JOIN customers c ON c.customer_id = o.customer_id;
Output:
result
----------
42.50
(1 row)
(2) دمج السلاسل مع NULL
دمج أي سلسلة مع NULL يعطي NULL؛ استخدم COALESCE للتعامل معه.
| التعبير | النتيجة |
|---|---|
| `'A' | |
| `'A' |
(11) ▶ مثال
SELECT
product_name || COALESCE(' - ' || description, '') AS product_info
FROM products;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
7. المفهوم: تحويلات الأنواع
(1) :: مقابل CAST()
| الصيغة | مثال | SQL قياسي | الوصف |
|---|---|---|---|
::type |
'2025-01-01'::DATE |
لا (خاص بـ PG) | موجز، موصى به في PG |
CAST(expr AS type) |
CAST('2025-01-01' AS DATE) |
نعم | قابل للنقل، مطول |
(12) ▶ مثال
SELECT
order_id,
order_date::DATE AS date_only,
'2025-06-15'::DATE AS literal_date,
CAST('2025-06-15 14:30:00' AS TIMESTAMP) AS full_timestamp
FROM orders
WHERE order_date::DATE >= '2025-01-01'::DATE;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(13) ▶ مثال
SELECT
total_amount::NUMERIC(12,2) AS rounded,
total_amount::INTEGER AS truncated,
total_amount::TEXT AS as_text
FROM orders;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(2) سيناريوهات التحويل الشائعة
| المصدر → الهدف | الاستخدام |
|---|---|
| TEXT → DATE | '2025-01-01'::DATE |
| TEXT → TIMESTAMP | CAST(ts AS TIMESTAMP) |
| NUMERIC → TEXT | 123.45::TEXT |
| INTEGER → NUMERIC | id::NUMERIC |
| TEXT → INTEGER | '42'::INTEGER |
(14) ▶ مثال
SELECT order_id, total_amount, order_date
FROM orders
WHERE order_date >= '2025-01-01'::DATE
AND order_date < '2025-07-01'::DATE
AND total_amount::NUMERIC(12,2) > 500;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
8. المفهوم: منشئو الصفوف وتعابير الاستعلام الفرعي
(1) منشئ ROW
ROW(val1, val2, ...) يبني قيمة صف مجهولة، مفيدة لمقارنات مستوى الصف.
(15) ▶ مثال
SELECT order_id, order_status, total_amount
FROM orders
WHERE ROW(order_status, total_amount) = ROW('completed', 500);
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(2) مقارنة الصف و IN
يمكن إقران منشئ الصف مع IN للمطابقة متعددة الأعمدة.
(16) ▶ مثال
SELECT order_id, customer_id, order_status
FROM orders
WHERE (order_status, customer_id) IN (
('completed', 1001),
('completed', 1002),
('pending', 1003)
);
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(3) مقارنة تعبير الاستعلام الفرعي
| التعبير | الدلالة | نوع الإرجاع |
|---|---|---|
col IN (subquery) |
يساوي أي قيمة في نتيجة الاستعلام الفرعي | BOOLEAN |
col = ANY(subquery) |
نفس IN | BOOLEAN |
col > ALL(subquery) |
أكبر من جميع القيم في الاستعلام الفرعي | BOOLEAN |
EXISTS (subquery) |
ما إذا كان الاستعلام الفرعي أعاد أي صفوف | BOOLEAN |
(17) ▶ مثال
SELECT product_name, unit_price, category
FROM products
WHERE unit_price > ALL (
SELECT AVG(unit_price) FROM products GROUP BY category
);
Output:
result
----------
42.50
(1 row)
9. المفهوم: معاملات النطاق
(1) أنواع النطاق
لدى PostgreSQL أنواع نطاق مدمجة مثل int4range و int8range و numrange و daterange و tstzrange.
(18) ▶ مثال
SELECT daterange('2025-01-01'::DATE, '2025-06-30'::DATE) AS h1_range;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(2) مرجع معاملات النطاق
| المعامل | المعنى | مثال |
|---|---|---|
@> |
يحتوي عنصر | '[1,5]'::int4range @> 3 → TRUE |
<@ |
محتوى من | 3 <@ '[1,5]'::int4range → TRUE |
&& |
يتداخل | '[1,5]'::int4range && '[3,8]'::int4range → TRUE |
&< |
لا يمتد يمين | '[1,3]'::int4range &< '[2,5]'::int4range → TRUE |
&> |
لا يمتد يسار | '[3,5]'::int4range &> '[1,3]'::int4range → TRUE |
| `- | -` | مجاور لـ |
+ |
اتحاد | '[1,3]'::int4range + '[3,6]'::int4range → [1,6) |
* |
تقاطع | '[1,5]'::int4range * '[3,8]'::int4range → [3,5) |
- |
فرق | '[1,5]'::int4range - '[3,8]'::int4range → [1,3) |
(19) ▶ مثال
SELECT o1.order_id, o2.order_id
FROM orders o1, orders o2
WHERE o1.customer_id = o2.customer_id
AND o1.order_id < o2.order_id
AND daterange(o1.order_date, o1.delivery_date) &&
daterange(o2.order_date, o2.delivery_date);
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(20) ▶ مثال
SELECT product_name, unit_price
FROM products
WHERE numrange(100, 500) @> unit_price::NUMERIC;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
10. مخطط تدفق: قرار تحويل النوع
flowchart TD
A[تحتاج تحويل نوع؟] --> B{النوع الهدف؟}
B -->|تاريخ/وقت| C["::DATE / ::TIMESTAMP"]
B -->|رقمي| D["::NUMERIC / ::INTEGER"]
B -->|سلسلة| E["::TEXT"]
B -->|تحتاج قابلية نقل| F["CAST(expr AS type)"]
C --> G{هل المصدر له تنسيق؟}
G -->|تنسيق قياسي| H[تحويل :: مباشر]
G -->|تنسيق مخصص| I["TO_DATE() / TO_TIMESTAMP()"]
D --> J[انتبه لفقدان الدقة]
E --> K[انتبه لدمج NULL]
H --> L[تم]
I --> L
J --> L
K --> L
F --> L
style A fill:#e1f5fe
style L fill:#c8e6c9
11. مثال شامل
أكمل Charlie جميع الحسابات التي تطلبها تحليله المالي.
-- الخطوة 1: حساب الضريبة مع التحكم في الدقة
SELECT
order_id,
total_amount::NUMERIC(12,2) AS amount,
(total_amount::NUMERIC(12,2) * 0.08)::NUMERIC(12,2) AS tax,
(total_amount::NUMERIC(12,2) * 1.08)::NUMERIC(12,2) AS total_with_tax
FROM orders
WHERE order_status = 'completed'
AND order_date >= '2025-01-01'::DATE;
-- الخطوة 2: فحص نطاق التاريخ بـ daterange
SELECT order_id, customer_id, order_date, total_amount
FROM orders
WHERE daterange('2025-01-01'::DATE, '2025-07-01'::DATE) @> order_date::DATE
AND order_status <> 'cancelled';
-- الخطوة 3: تحويل سلسلة إلى تاريخ وتنسيق
SELECT
order_id,
'Order #' || order_id || ' - ' ||
COALESCE(customer_name, 'Unknown') ||
' ($' || total_amount::TEXT || ')' AS order_label
FROM orders o
LEFT JOIN customers c ON c.customer_id = o.customer_id
WHERE o.order_date::DATE >= '2025-01-01'::DATE
ORDER BY o.total_amount DESC;
-- الخطوة 4: منشئ صف وتداخل نطاق
SELECT
p1.product_name AS product_a,
p2.product_name AS product_b,
numrange(p1.unit_price, p1.unit_price * 1.2) AS price_range_a,
numrange(p2.unit_price, p2.unit_price * 1.2) AS price_range_b
FROM products p1
JOIN products p2 ON p1.product_id < p2.product_id
WHERE p1.category = p2.category
AND numrange(p1.unit_price, p1.unit_price * 1.2) &&
numrange(p2.unit_price, p2.unit_price * 1.2)
AND p1.is_active = true AND p2.is_active = true;
❓ أسئلة شائعة
:: أم CAST()؟:: أكثر إيجازًا وهو صيغة خاصة بـ PostgreSQL؛ CAST() هو الصيغة القياسية في SQL. لمشاريع PostgreSQL الخالصة، يوصى بـ ::؛ استخدم CAST() عندما تحتاج توافقًا عبر قواعد البيانات.TRUE AND NULL = NULL (ليس TRUE)، FALSE OR NULL = NULL (ليس FALSE)، NOT NULL = NULL. في WHERE، يعامل NULL على أنه ليس TRUE، لذا يتم استبعاد الصفوف ذات الصلة.COALESCE(col, '') قبل الدمج.WHERE (status, amount) = ROW('completed', 500) — إنها أنظف من مقارنة عمودين منفصلين، وتعمل أيضًا مع IN متعدد الأعمدة.@> <@ && تعمل على أنواع Range المدمجة في PostgreSQL (int4range, numrange, daterange، إلخ). يجب أولاً تحويل الأعمدة العادية إلى قيمة Range قبل المقارنة.^ له أولوية أعلى من الضرب/القسمة؟^ يرتبط أقوى من * / %. 2^3^2 = 2^(3^2) = 512 (ترابطي يمين)، ليس (2^3)^2 = 64.IS DISTINCT FROM و <>؟<>، NULL <> NULL يعيد NULL (ليس TRUE ولا FALSE). IS DISTINCT FROM يعامل NULL كقيمة قابلة للمقارنة: NULL IS DISTINCT FROM NULL يعيد FALSE، و 1 IS DISTINCT FROM NULL يعيد TRUE.📖 ملخص
- المعاملات الحسابية: انتبه لاقتطاع القسمة الصحيحة والدقة؛ استخدم
NUMERIC(p,s)للأموال - معاملات المقارنة تنتج NULL عند المقارنة مع NULL؛ استخدم
IS DISTINCT FROMللمقارنة الآمنة - في المنطق ثلاثي القيم،
NULL AND TRUE = NULLوNULL OR FALSE = NULL - دمج السلاسل
||يعيد NULL على NULL؛ تعامل معه بـCOALESCE ::هي صيغة تحويل خاصة بـ PG؛CAST()هو معيار SQL- منشئو الصفوف
ROW()يدعمون المقارنة متعددة الأعمدة و IN متعدد الأعمدة - معاملات النطاق
@> <@ &&توفر فحوصات فعالة لاحتواء وتداخل النطاق
📝 تمارين
-
⭐ اكتب استعلامًا يحسب
unit_price * quantityلكل صف فيorder_items، ويحول النتيجة إلىNUMERIC(10,2)، ويرتب تنازليًا حسب مبلغ السطر. -
⭐⭐ اكتب استعلامًا يستخدم
daterangeوالمعامل@>لإيجاد الطلبات التي يقعorder_dateفيها في الربع الأول 2025 (1 يناير – 31 مارس)، واستخدم||لبناء سلسلة ملخص طلب (تنسيق:"Order #id - customer_name - $amount")، مع التعامل مع أسماء العملاء NULL. -
⭐⭐⭐ اكتب استعلامًا يجد أزواج المنتجات في نفس الفئة التي تتداخل نطاقات أسعارها (ابنِ النطاق بـ
numrange(unit_price, unit_price * 1.2)، وتحقق من التداخل بـ&&)، وأظهر ما إذا كانت مقارنة منشئ الصفROW(a.price, a.category) = ROW(b.price, b.category)متساوية لكل زوج.