الدرس 31 دوال SQL

دوال النصوص String Functions

تُعد دوال النصوص من أهم الأدوات في SQL للتعامل مع البيانات النصية. سنتعلم كيفية دمج النصوص، استخراج أجزاء منها، البحث والاستبدال، وتنسيق البيانات النصية بطرق متعددة.

⏱️ مدة القراءة: 35 دقيقة
📊 المستوى: متوسط
🎯 الوحدة: دوال SQL المتقدمة

مقدمة في دوال النصوص

البيانات النصية موجودة في كل مكان - الأسماء، العناوين، الأوصاف، والمزيد. SQL يوفر مجموعة غنية من الدوال للتعامل مع هذه البيانات:

📝 أنواع دوال النصوص
  • دوال الدمج: CONCAT, CONCAT_WS
  • دوال الاستخراج: SUBSTRING, LEFT, RIGHT
  • دوال البحث: LOCATE, POSITION, INSTR
  • دوال التحويل: UPPER, LOWER, INITCAP
  • دوال التنظيف: TRIM, LTRIM, RTRIM
  • دوال الاستبدال: REPLACE, TRANSLATE
  • دوال القياس: LENGTH, CHAR_LENGTH

دمج النصوص CONCAT

دالة CONCAT تُستخدم لدمج نصين أو أكثر معاً في نص واحد.

الصيغة الأساسية

SQL
-- MySQL, PostgreSQL, SQL Server
SELECT CONCAT(string1, string2, ...) AS result;

-- Oracle (استخدام ||)
SELECT string1 || string2 AS result FROM dual;

أمثلة عملية

SQL - دمج الاسم الأول والأخير
-- جدول الموظفين
SELECT
    first_name,
    last_name,
    CONCAT(first_name, ' ', last_name) AS full_name
FROM employees;

-- النتيجة:
-- first_name | last_name | full_name
-- أحمد       | محمد      | أحمد محمد
-- سارة       | علي       | سارة علي
-- خالد       | عبدالله   | خالد عبدالله

CONCAT_WS - الدمج مع فاصل

CONCAT_WS (With Separator) تدمج النصوص مع فاصل محدد بينها:

SQL - دمج العنوان
-- بناء عنوان كامل
SELECT
    CONCAT_WS(', ',
        street,
        city,
        country
    ) AS full_address
FROM customers;

-- النتيجة:
-- شارع الملك فهد, الرياض, السعودية

-- دمج رقم الهاتف مع رمز الدولة
SELECT
    CONCAT_WS('-',
        country_code,
        area_code,
        phone_number
    ) AS formatted_phone
FROM contacts;

-- النتيجة:
-- 966-11-1234567
⚠️ التعامل مع NULL

في معظم قواعد البيانات، إذا كان أي جزء من CONCAT يحتوي على NULL، تكون النتيجة NULL. لكن CONCAT_WS تتجاهل القيم الفارغة:

SELECT CONCAT('Hello', NULL, 'World'); -- NULL في MySQL
SELECT CONCAT_WS('-', 'A', NULL, 'B'); -- A-B (تتجاهل NULL)

استخراج النصوص SUBSTRING

دوال الاستخراج تسمح لك بأخذ جزء من النص.

دالة SUBSTRING

SQL - استخراج جزء من النص
-- الصيغة: SUBSTRING(string, start, length)
-- الترقيم يبدأ من 1

SELECT SUBSTRING('Hello World', 1, 5);  -- 'Hello'
SELECT SUBSTRING('Hello World', 7, 5);  -- 'World'
SELECT SUBSTRING('Hello World', 7);     -- 'World' (حتى النهاية)

-- استخراج السنة من تاريخ نصي
SELECT SUBSTRING('2025-01-15', 1, 4) AS year;  -- '2025'

-- استخراج الشهر
SELECT SUBSTRING('2025-01-15', 6, 2) AS month; -- '01'

دوال LEFT و RIGHT

SQL - استخراج من اليمين واليسار
-- LEFT: أول n حرف
SELECT LEFT('Hello World', 5);  -- 'Hello'

-- RIGHT: آخر n حرف
SELECT RIGHT('Hello World', 5); -- 'World'

-- استخراج رمز الدولة من رقم الهاتف
SELECT
    phone_number,
    LEFT(phone_number, 3) AS country_code
FROM customers;

-- استخراج امتداد الملف
SELECT
    filename,
    RIGHT(filename, 3) AS extension
FROM files;
-- 'document.pdf' → 'pdf'
-- 'image.jpg'    → 'jpg'

مثال عملي: تنسيق رقم البطاقة

SQL - إخفاء أرقام البطاقة الائتمانية
-- إظهار آخر 4 أرقام فقط
SELECT
    card_number,
    CONCAT('****-****-****-', RIGHT(card_number, 4)) AS masked_card
FROM payments;

-- النتيجة:
-- card_number      | masked_card
-- 4532123456789012 | ****-****-****-9012

دوال البحث تساعدك في العثور على موقع نص داخل نص آخر.

دالة LOCATE / POSITION / INSTR

SQL - البحث عن موقع نص
-- MySQL: LOCATE(substring, string)
SELECT LOCATE('World', 'Hello World');  -- 7

-- PostgreSQL: POSITION(substring IN string)
SELECT POSITION('World' IN 'Hello World');  -- 7

-- Oracle: INSTR(string, substring)
SELECT INSTR('Hello World', 'World') FROM dual;  -- 7

-- البحث مع موقع البداية
SELECT LOCATE('o', 'Hello World', 5);  -- 8 (يبدأ البحث من الموقع 5)

-- إذا لم يُوجد يرجع 0
SELECT LOCATE('xyz', 'Hello World');  -- 0

استخدام البحث مع SUBSTRING

SQL - استخراج اسم النطاق من البريد
-- استخراج النطاق من البريد الإلكتروني
SELECT
    email,
    SUBSTRING(email, LOCATE('@', email) + 1) AS domain
FROM users;

-- النتيجة:
-- email                  | domain
-- ahmed@gmail.com        | gmail.com
-- sara@company.com       | company.com

-- استخراج اسم المستخدم
SELECT
    email,
    LEFT(email, LOCATE('@', email) - 1) AS username
FROM users;

-- النتيجة:
-- email                  | username
-- ahmed@gmail.com        | ahmed

تحويل حالة الأحرف

هذه الدوال مفيدة للغة الإنجليزية وبعض اللغات الأخرى.

SQL - تحويل الحالة
-- تحويل لأحرف كبيرة
SELECT UPPER('hello world');  -- 'HELLO WORLD'

-- تحويل لأحرف صغيرة
SELECT LOWER('HELLO WORLD');  -- 'hello world'

-- أول حرف كبير (PostgreSQL, Oracle)
SELECT INITCAP('hello world');  -- 'Hello World'

-- تطبيق عملي: البحث بدون حساسية للحالة
SELECT *
FROM products
WHERE LOWER(product_name) = LOWER('IPhone');

-- تنسيق الأسماء
SELECT
    CONCAT(
        UPPER(LEFT(first_name, 1)),
        LOWER(SUBSTRING(first_name, 2))
    ) AS formatted_name
FROM employees;
💡 نصيحة

للبحث بدون حساسية للحالة، يمكن استخدام COLLATE أو ILIKE في PostgreSQL بدلاً من تحويل الحالة في كل مرة.

تنظيف النصوص TRIM

دوال TRIM تزيل الفراغات أو أحرف محددة من بداية و/أو نهاية النص.

SQL - إزالة الفراغات
-- إزالة الفراغات من الطرفين
SELECT TRIM('   Hello World   ');  -- 'Hello World'

-- إزالة من اليسار فقط
SELECT LTRIM('   Hello World   '); -- 'Hello World   '

-- إزالة من اليمين فقط
SELECT RTRIM('   Hello World   '); -- '   Hello World'

-- إزالة أحرف محددة (MySQL, PostgreSQL)
SELECT TRIM(BOTH '-' FROM '---Hello---');  -- 'Hello'
SELECT TRIM(LEADING '0' FROM '00012345');  -- '12345'
SELECT TRIM(TRAILING '.' FROM 'Hello...');  -- 'Hello'

مثال عملي: تنظيف البيانات المستوردة

SQL - تنظيف بيانات العملاء
-- تنظيف وتوحيد البيانات
UPDATE customers
SET
    first_name = TRIM(first_name),
    last_name = TRIM(last_name),
    email = LOWER(TRIM(email)),
    phone = TRIM(REPLACE(phone, ' ', ''))
WHERE
    first_name LIKE ' %'
    OR first_name LIKE '% '
    OR email != LOWER(email);

الاستبدال REPLACE

دالة REPLACE تستبدل كل ظهور لنص بنص آخر.

SQL - استبدال النصوص
-- الصيغة: REPLACE(string, old_text, new_text)

SELECT REPLACE('Hello World', 'World', 'SQL');
-- 'Hello SQL'

-- استبدال متعدد
SELECT REPLACE(REPLACE(phone, '-', ''), ' ', '') AS clean_phone
FROM customers;
-- '966-11-123 4567' → '966111234567'

-- تحديث روابط قديمة
UPDATE articles
SET content = REPLACE(content, 'http://', 'https://')
WHERE content LIKE '%http://%';

-- إزالة حرف بالاستبدال بفراغ
SELECT REPLACE('A-B-C-D', '-', '');  -- 'ABCD'

TRANSLATE - استبدال متعدد

SQL - PostgreSQL/Oracle TRANSLATE
-- استبدال حرف بحرف
-- TRANSLATE(string, from_chars, to_chars)

SELECT TRANSLATE('Hello', 'elo', 'aio');
-- 'Haiii' (e→a, l→i, o→i)

-- إزالة أحرف خاصة
SELECT TRANSLATE(
    phone_number,
    '()-. ',
    ''
) AS digits_only
FROM contacts;
-- '(966) 11-123.4567' → '966111234567'

قياس طول النص

SQL - دوال الطول
-- عدد الأحرف
SELECT LENGTH('Hello');        -- 5 (MySQL)
SELECT CHAR_LENGTH('Hello');   -- 5
SELECT LEN('Hello');           -- 5 (SQL Server)

-- ملاحظة مهمة للعربية:
SELECT LENGTH('مرحبا');        -- قد يُرجع عدد البايتات
SELECT CHAR_LENGTH('مرحبا');   -- 5 (عدد الأحرف الصحيح)

-- التحقق من طول كلمة المرور
SELECT
    username,
    CASE
        WHEN CHAR_LENGTH(password_hash) < 8 THEN 'ضعيفة'
        WHEN CHAR_LENGTH(password_hash) < 12 THEN 'متوسطة'
        ELSE 'قوية'
    END AS password_strength
FROM users;

استخدامات عملية

SQL - فحص البيانات
-- العثور على سجلات بأسماء قصيرة جداً
SELECT * FROM customers
WHERE CHAR_LENGTH(TRIM(first_name)) < 2;

-- إحصائيات عن طول الأوصاف
SELECT
    MIN(CHAR_LENGTH(description)) AS min_length,
    MAX(CHAR_LENGTH(description)) AS max_length,
    AVG(CHAR_LENGTH(description)) AS avg_length
FROM products;

-- اقتطاع الأوصاف الطويلة
SELECT
    product_name,
    CASE
        WHEN CHAR_LENGTH(description) > 100
        THEN CONCAT(LEFT(description, 97), '...')
        ELSE description
    END AS short_description
FROM products;

إضافة حشو LPAD/RPAD

دوال الحشو تضيف أحرفاً لتصل النص لطول معين.

SQL - حشو النصوص
-- LPAD: إضافة من اليسار
SELECT LPAD('123', 6, '0');    -- '000123'
SELECT LPAD('42', 5, '*');     -- '***42'

-- RPAD: إضافة من اليمين
SELECT RPAD('Hello', 10, '.'); -- 'Hello.....'
SELECT RPAD('99', 5, '0');     -- '99000'

-- تنسيق أرقام الفواتير
SELECT
    CONCAT('INV-', LPAD(invoice_id, 8, '0')) AS invoice_number
FROM invoices;
-- INV-00001234

-- تنسيق تقرير
SELECT
    RPAD(product_name, 30, '.'),
    LPAD(FORMAT(price, 2), 10, ' ')
FROM products;
-- Output:
-- iPhone 15 Pro............    1299.00
-- MacBook Air..............    999.00

أمثلة متقدمة

تحليل الروابط URL

SQL - تحليل URL
-- استخراج أجزاء الرابط
WITH url_parts AS (
    SELECT 'https://www.example.com/products/123?ref=home' AS url
)
SELECT
    url,
    -- استخراج البروتوكول
    LEFT(url, LOCATE('://', url) - 1) AS protocol,
    -- استخراج النطاق
    SUBSTRING_INDEX(
        SUBSTRING_INDEX(url, '://', -1),
        '/',
        1
    ) AS domain,
    -- استخراج المسار
    SUBSTRING(
        url,
        LOCATE('/', url, LOCATE('://', url) + 3)
    ) AS path
FROM url_parts;

تنسيق الأسماء

SQL - تنسيق متقدم
-- تحويل الاسم للاختصار
SELECT
    first_name,
    last_name,
    CONCAT(
        UPPER(LEFT(first_name, 1)),
        '.',
        UPPER(LEFT(last_name, 1)),
        '.'
    ) AS initials
FROM employees;

-- النتيجة:
-- first_name | last_name | initials
-- Ahmed      | Mohamed   | A.M.
-- Sara       | Ali       | S.A.

-- توليد اسم مستخدم فريد
SELECT
    LOWER(
        CONCAT(
            LEFT(first_name, 1),
            last_name,
            FLOOR(RAND() * 100)
        )
    ) AS username
FROM employees;
-- 'amohamed42', 'sali78'

البحث المتقدم

SQL - بحث مرن
-- بحث في عدة حقول
SELECT *
FROM products
WHERE
    LOWER(CONCAT_WS(' ',
        product_name,
        description,
        category
    )) LIKE LOWER('%laptop%');

-- البحث مع تسليط الضوء على النتائج
SELECT
    product_name,
    REPLACE(
        description,
        'laptop',
        '**laptop**'
    ) AS highlighted_description
FROM products
WHERE description LIKE '%laptop%';

مقارنة بين قواعد البيانات

الدالة MySQL PostgreSQL SQL Server Oracle
الدمج CONCAT() CONCAT() أو || CONCAT() أو + || أو CONCAT()
الطول LENGTH() LENGTH() LEN() LENGTH()
الاستخراج SUBSTRING() SUBSTRING() SUBSTRING() SUBSTR()
البحث LOCATE() POSITION() CHARINDEX() INSTR()
أول حرف كبير غير موجود INITCAP() غير موجود INITCAP()

اختبر معلوماتك

السؤال 1: ما نتيجة CONCAT('Hello', ' ', 'World')؟

السؤال 2: ما نتيجة SUBSTRING('Database', 5, 4)؟

السؤال 3: ما الدالة المستخدمة لإزالة الفراغات من طرفي النص؟

السؤال 4: ما نتيجة LPAD('42', 5, '0')؟

تمرين عملي

💪

تحدي: معالجة بيانات العملاء

اكتب استعلام SQL لتنسيق بيانات العملاء:

  • دمج الاسم الأول والأخير مع مسافة
  • استخراج النطاق من البريد الإلكتروني
  • تنسيق رقم الهاتف بإضافة أصفار في البداية ليصبح 10 أرقام

الجدول: customers (first_name, last_name, email, phone)