إتقان SQL وتحسين الاستعلامات

أنماط SQL الشائعة في المقابلات

4 دقيقة للقراءة

أتقن هذه الأنماط المسؤول عنها بشكل متكرر في SQL للتفوق في مقابلات هندسة البيانات. كل نمط يتضمن نوع المشكلة ونهج الحل والتنويعات الشائعة.

النمط 1: إيجاد المكررات

أحد أكثر أسئلة المقابلات شيوعاً.

المكررات الأساسية

-- إيجاد البريد الإلكتروني المكرر
SELECT email, COUNT(*) as count
FROM users
GROUP BY email
HAVING COUNT(*) > 1;

-- الحصول على كل السجلات بالبريد الإلكتروني المكرر
SELECT *
FROM users
WHERE email IN (
    SELECT email
    FROM users
    GROUP BY email
    HAVING COUNT(*) > 1
);

احتفظ بواحد، احذف الباقي

-- حذف المكررات، احتفظ بأقل id
DELETE FROM users
WHERE id NOT IN (
    SELECT MIN(id)
    FROM users
    GROUP BY email
);

-- باستخدام CTE (PostgreSQL, SQL Server)
WITH duplicates AS (
    SELECT id,
        ROW_NUMBER() OVER (PARTITION BY email ORDER BY id) as rn
    FROM users
)
DELETE FROM users
WHERE id IN (SELECT id FROM duplicates WHERE rn > 1);

النمط 2: تحليل الفجوات

إيجاد القيم المفقودة في التسلسلات.

إيجاد الأرقام المفقودة

-- إيجاد الفجوات في معرفات الطلبات
WITH all_ids AS (
    SELECT generate_series(
        (SELECT MIN(order_id) FROM orders),
        (SELECT MAX(order_id) FROM orders)
    ) as id
)
SELECT a.id as missing_id
FROM all_ids a
LEFT JOIN orders o ON a.id = o.order_id
WHERE o.order_id IS NULL;

إيجاد فجوات التاريخ

-- إيجاد التواريخ بدون طلبات
WITH date_range AS (
    SELECT generate_series(
        '2024-01-01'::date,
        '2024-12-31'::date,
        '1 day'::interval
    )::date as date
)
SELECT d.date as missing_date
FROM date_range d
LEFT JOIN orders o ON d.date = DATE(o.order_date)
WHERE o.order_id IS NULL;

النمط 3: السجلات المتتالية

إيجاد السلاسل والتسلسلات.

أيام تسجيل الدخول المتتالية

-- إيجاد المستخدمين مع 7+ أيام تسجيل دخول متتالية
WITH login_groups AS (
    SELECT
        user_id,
        login_date,
        login_date - (ROW_NUMBER() OVER (
            PARTITION BY user_id
            ORDER BY login_date
        ))::int as grp
    FROM (SELECT DISTINCT user_id, DATE(login_time) as login_date
          FROM logins) t
)
SELECT user_id, MIN(login_date), MAX(login_date), COUNT(*) as streak
FROM login_groups
GROUP BY user_id, grp
HAVING COUNT(*) >= 7;

شرح النمط

الخطوة الغرض
1. احصل على تواريخ مميزة لكل مستخدم إزالة تسجيلات الدخول المتعددة في اليوم
2. عيّن ROW_NUMBER بالتاريخ ترقيم تسلسلي
3. اطرح من التاريخ التواريخ المتتالية تحصل على نفس المجموعة
4. جمّع بالمجموعة المحسوبة إيجاد طول السلسلة

النمط 4: الحسابات التراكمية

المجاميع من بداية السنة

SELECT
    month,
    revenue,
    SUM(revenue) OVER (
        PARTITION BY YEAR(date)
        ORDER BY month
    ) as ytd_revenue
FROM monthly_revenue;

النسبة المئوية من الإجمالي

SELECT
    product_id,
    sales,
    ROUND(sales * 100.0 / SUM(sales) OVER (), 2) as pct_of_total,
    ROUND(sales * 100.0 / SUM(sales) OVER (
        PARTITION BY category_id
    ), 2) as pct_of_category
FROM product_sales;

النمط 5: مشاكل القيمة الأولى/الأخيرة

أول شراء لكل عميل

-- الطريقة 1: ROW_NUMBER
WITH ranked AS (
    SELECT *,
        ROW_NUMBER() OVER (
            PARTITION BY customer_id
            ORDER BY order_date
        ) as rn
    FROM orders
)
SELECT * FROM ranked WHERE rn = 1;

-- الطريقة 2: استعلام فرعي مرتبط
SELECT *
FROM orders o1
WHERE order_date = (
    SELECT MIN(order_date)
    FROM orders o2
    WHERE o2.customer_id = o1.customer_id
);

-- الطريقة 3: DISTINCT ON (PostgreSQL)
SELECT DISTINCT ON (customer_id) *
FROM orders
ORDER BY customer_id, order_date;

أحدث تغيير حالة

SELECT
    user_id,
    status,
    changed_at,
    LAG(status) OVER (PARTITION BY user_id ORDER BY changed_at) as prev_status,
    LAG(changed_at) OVER (PARTITION BY user_id ORDER BY changed_at) as prev_changed
FROM user_status_log;

النمط 6: تقسيم الجلسات

تجميع الأحداث في جلسات (شائع في التحليلات).

-- تعريف الجلسة كأحداث ضمن 30 دقيقة
WITH session_markers AS (
    SELECT
        user_id,
        event_time,
        CASE
            WHEN event_time - LAG(event_time) OVER (
                PARTITION BY user_id ORDER BY event_time
            ) > INTERVAL '30 minutes'
            THEN 1
            ELSE 0
        END as new_session
    FROM events
),
sessions AS (
    SELECT *,
        SUM(new_session) OVER (
            PARTITION BY user_id
            ORDER BY event_time
        ) as session_id
    FROM session_markers
)
SELECT
    user_id,
    session_id,
    MIN(event_time) as session_start,
    MAX(event_time) as session_end,
    COUNT(*) as events_in_session
FROM sessions
GROUP BY user_id, session_id;

النمط 7: التحويل/عكس التحويل

الصفوف إلى أعمدة (Pivot)

-- بدون كلمة PIVOT
SELECT
    user_id,
    MAX(CASE WHEN metric = 'views' THEN value END) as views,
    MAX(CASE WHEN metric = 'clicks' THEN value END) as clicks,
    MAX(CASE WHEN metric = 'conversions' THEN value END) as conversions
FROM user_metrics
GROUP BY user_id;

الأعمدة إلى صفوف (Unpivot)

-- باستخدام UNION ALL
SELECT user_id, 'views' as metric, views as value FROM user_stats
UNION ALL
SELECT user_id, 'clicks' as metric, clicks as value FROM user_stats
UNION ALL
SELECT user_id, 'conversions' as metric, conversions as value FROM user_stats;

-- باستخدام CROSS JOIN LATERAL (PostgreSQL)
SELECT u.user_id, x.metric, x.value
FROM user_stats u
CROSS JOIN LATERAL (
    VALUES ('views', views), ('clicks', clicks), ('conversions', conversions)
) as x(metric, value);

النمط 8: التسلسلات الهرمية التكرارية

قائمة المواد (BOM)

-- إيجاد الكمية الإجمالية المطلوبة لمنتج
WITH RECURSIVE bom AS (
    -- الأساس: منتج المستوى الأعلى
    SELECT
        component_id,
        parent_id,
        quantity,
        1 as level
    FROM components
    WHERE parent_id = 'PRODUCT_A'

    UNION ALL

    -- التكراري: المكونات الفرعية
    SELECT
        c.component_id,
        c.parent_id,
        c.quantity * b.quantity as quantity,
        b.level + 1
    FROM components c
    JOIN bom b ON c.parent_id = b.component_id
)
SELECT component_id, SUM(quantity) as total_needed
FROM bom
GROUP BY component_id;

مرجع سريع: أي نمط تستخدم

نوع المشكلة النمط
إيجاد سجلات متطابقة المكررات + GROUP BY HAVING
قيم مفقودة في تسلسل تحليل الفجوات + generate_series
السلاسل/التسلسلات المتتالية + حيلة ROW_NUMBER
المقاييس التراكمية الحسابات التراكمية + النافذة
الحصول على الأول/الأخير لكل مجموعة ROW_NUMBER أو DISTINCT ON
التجميع القائم على الوقت تقسيم الجلسات
إعادة تشكيل البيانات التحويل/عكس التحويل
تجاوز الشجرة/الرسم البياني CTE تكراري

نصيحة نجاح المقابلة: عندما تتلقى مشكلة SQL، أولاً حدد أي نمط تطابقه. هذا يساعدك على هيكلة حلك بسرعة وثقة. تدرب حتى يصبح التعرف على الأنماط تلقائياً.

الآن لننتقل إلى مفاهيم نمذجة البيانات والتخزين. :::

اختبار

الوحدة 2: إتقان SQL وتحسين الاستعلامات

خذ الاختبار