EduFlow
SQL للمحلّلين
⁦0%⁩

الوحدة 3 · SQL التحليلي

القمع والـ cohorts ومعدل الاحتفاظ

20 دقراءة 4 دتمرين SQL

الدرس في لمحة

ابنِ قمع تحويل (funnel) من أحداث الجلسات بالتجميع الشرطي، ثم جمّع العملاء في cohorts شهرية حسب طلبهم الأول وقِس كم منهم يعود في الأشهر اللاحقة.

  1. 1قمع من أحداث الجلسات
  2. 2الـ cohorts: تجميع العملاء حسب وقت بدايتهم
  3. 3«غير معروف» ليس صفرًا

لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.

طلبان في رأس القائمة. هدى: «ميزانية البحث المدفوع قيد المراجعة. أي مصادر الزيارات تحوّل الزيارات فعلًا إلى طلبات، وأين يتسرّب الناس؟». ورانيا: «اكتسبنا عملاء كثيرين في أشهر Black Friday. هل يعودون بالقدر نفسه الذي يعود به الآخرون؟». الأول قمع (funnel)، والثاني تحليل معدل الاحتفاظ لكل cohort. كلاهما مبني من أشياء تعرفها بالفعل: التجميع الشرطي، والتواريخ، والـ CTEs.

قمع من أحداث الجلسات

كل صف في web_sessions زيارة واحدة، مع علامات للخطوات التي بلغتها: added_to_cart وpurchased (0 أو 1). جمع علامة قيمتها 0/1 يعدّ الجلسات التي بلغت الخطوة، فيكون القمع GROUP BY واحدًا:

SQL
SELECT
  source,
  COUNT(*)            AS sessions,
  SUM(added_to_cart)  AS carts,
  SUM(purchased)      AS purchases,
  ROUND(100.0 * SUM(added_to_cart) / COUNT(*), 1)                  AS cart_rate,
  ROUND(100.0 * SUM(purchased) / NULLIF(SUM(added_to_cart), 0), 1) AS checkout_rate,
  ROUND(100.0 * SUM(purchased) / COUNT(*), 1)                      AS conversion
FROM web_sessions
WHERE started_at >= '2025-01-01'
GROUP BY source
ORDER BY conversion DESC;

البريد الإلكتروني هو الأفضل تحويلًا إجمالًا، 10.9% من الجلسات، والإحالات هي الأسوأ بـ 7.1%. لكن معدلات الخطوات تحكي القصة الأنفع. زوّار الإحالات نادرًا ما يضيفون إلى السلة (13.3%)، لكن من يضيف منهم يتمّ الشراء أكثر من أي فئة أخرى (53.6%). والبريد الإلكتروني عكس ذلك. هاتان مشكلتان مختلفتان وإصلاحهما مختلف: الإحالات تحتاج إلى صفحات هبوط أفضل، والبريد يحتاج إلى عملية دفع أسلس.

لهذا يعرض القمع معدلات الخطوات، أي كل خطوة مقسومة على السابقة، لا التحويل من البداية إلى النهاية وحده. اجعل كل مقام صريحًا في اسم العمود؛ فـ«معدل التحويل» بلا مقام هو أكثر رقم يُتجادل حوله في أي اجتماع تسويق. ويحمي NULLIF القسمة لمصدر ليس فيه سلال.

استبدل source بـ device فيظهر اكتشاف أوضح: جلسات الحاسوب المكتبي التي تبلغ السلة تتمّ الشراء في 56% من الحالات، وجلسات الجوال في 37% فقط. والجوال يجلب أكبر قدر من الزيارات، فهذه الفجوة أثمن من أي تعديل على ميزانية الإعلانات.

الـ cohorts: تجميع العملاء حسب وقت بدايتهم

الـ cohort مجموعة من العملاء يتشاركون فترة بداية واحدة. وهي هنا شهر أول طلب غير ملغى لهم. ثم يسأل معدل الاحتفاظ: من العملاء الذين بدأوا في الشهر M، ما نسبة من طلبوا مجددًا بعد شهر أو شهرين أو ثلاثة؟

ثلاث خطوات، وثلاثة CTEs. أولًا، أعطِ كل طلب مؤشّر شهر، year * 12 + month، حتى تكون فروق الأشهر عملية طرح بسيطة حتى عبر حدود السنة. ثانيًا، اعثر على الشهر الأول لكل عميل. ثالثًا، اعرض الأشهر النشطة لكل عميل نسبةً إلى الـ cohort الخاص به:

SQL
WITH orders_ok AS (
  SELECT customer_id, order_date,
         CAST(strftime('%Y', order_date) AS INTEGER) * 12
           + CAST(strftime('%m', order_date) AS INTEGER) AS month_index
  FROM orders
  WHERE status <> 'cancelled'
),
firsts AS (
  -- grain: one row per customer
  SELECT customer_id,
         MIN(month_index) AS cohort_index,
         strftime('%Y-%m', MIN(order_date)) AS cohort
  FROM orders_ok
  GROUP BY customer_id
),
activity AS (
  -- grain: one row per customer per active month
  SELECT DISTINCT f.cohort, f.cohort_index, o.customer_id,
         o.month_index - f.cohort_index AS month_number
  FROM orders_ok AS o
  JOIN firsts AS f ON f.customer_id = o.customer_id
)
SELECT
  cohort,
  COUNT(*) FILTER (WHERE month_number = 0) AS customers,
  ROUND(100.0 * COUNT(*) FILTER (WHERE month_number = 1)
        / COUNT(*) FILTER (WHERE month_number = 0), 1) AS m1_pct,
  CASE WHEN MAX(cohort_index) + 2 <= 2025 * 12 + 12 THEN
    ROUND(100.0 * COUNT(*) FILTER (WHERE month_number = 2)
          / COUNT(*) FILTER (WHERE month_number = 0), 1)
  END AS m2_pct
FROM activity
WHERE cohort >= '2025-07'
GROUP BY cohort
ORDER BY cohort;

يحوّل الـ SELECT الأخير أرقام الأشهر إلى أعمدة باستخدام FILTER. والـ DISTINCT في activity ضروري: معدل الاحتفاظ يعدّ أشخاصًا، والعميل الذي يطلب ثلاث مرات في شهر واحد عميل نشط واحد.

الـ cohorts من سبتمبر إلى ديسمبر 2025 مقابل الأشهر من 0 إلى 3. لسبتمبر الأشهر الأربعة كلها مقيسة؛ ولأكتوبر ثلاثة؛ ولنوفمبر اثنان؛ ولديسمبر الشهر 0 فقط. الخانات التي تقع بعد نهاية البيانات قيمتها غير معروفة، لا صفر. m0 m1 m2 m3 2025-09 2025-10 2025-11 2025-12 100% 43.5% 39.1% 30.4% 100% 44.4% 27.8% لم يحن بعد 100% 36.2% لم يحن بعد لم يحن بعد 100% لم يحن بعد لم يحن بعد لم يحن بعد
جدول الـ cohorts مثلث: الـ cohorts الحديثة لم تعش بما يكفي لقياسها

«غير معروف» ليس صفرًا

انظر إلى صف ديسمبر 2025 في نتيجة الـ query: قيمة m1_pct هي 0.0. هذا ليس انقطاعًا. الشهر 1 لـ cohort ديسمبر هو يناير 2026، والبيانات تنتهي في 31 ديسمبر. يعرض العمود m2_pct الإصلاح: تعبير CASE يفحص هل عاش الـ cohort ذلك الشهر (أي هل ما زال cohort_index + 2 داخل ديسمبر 2025) ويعيد NULL في غير ذلك. مخطط الاحتفاظ ذو الخانات الفارغة الصادقة مثلث؛ أما المخطط ذو الأصفار فيُظهر انهيارًا وهميًا في كل cohort حديث.

نعود إلى سؤال رانيا. غيّر الشرط إلى 2024، فتجد أن cohort نوفمبر 2024، وهو أول جمهور Black Friday لدى Cartwheel وأكبر cohort في تلك السنة بـ 35 عميلًا، لا يحتفظ إلا بـ 14.3% في الشهر 1، وهذا أقل من معظم cohorts عام 2024. عملاء الخصومات الكبيرة يأتون عادةً من أجل الصفقة. هذه إجابة تستطيع استخدامها حين تخطّط للعرض الترويجي القادم. ويطرح التمرين سؤالًا قريب الصلة: ما نسبة من يعودون من كل cohort أصلًا خلال ثلاثة أشهر؟

بهذا تكتمل أدوات التحليل. أما القسم الأخير فيدور حول الثقة: التأكد من أن البيانات والـ queries التي تكتبها تستحقها.

أهم النقاط

  • القمع (funnel) يعدّ كم وحدة تصل إلى كل خطوة ويقسم كل خطوة على التي قبلها، فترى بالضبط أين يتسرّب الناس.
  • صرّح دائمًا بمقام أي معدل؛ معدل إتمام الشراء (المشتريات لكل سلة) ومعدل التحويل (المشتريات لكل جلسة) يجيبان عن سؤالين مختلفين.
  • الـ cohort يجمع العملاء حسب فترة حدثهم الأول، ومعدل الاحتفاظ (retention) يعدّ العملاء النشطين بعد N فترات، كلًّا مرة واحدة، مقسومًا على حجم الـ cohort.
  • الأشهر التي لم يعشها cohort حديث بعد قيمتها غير معروفة لا صفر؛ اعرضها NULL حتى لا يقرأها أحد على أنها انقطاع.

قراءات إضافية

تحقّق من فهمك

4 أسئلة

السؤال 1 من 4

جلسات البريد الإلكتروني: 404 جلسات، و107 إضافات إلى السلة، و44 عملية شراء. أي رقم هو معدل إتمام الشراء؟

السؤال 2 من 4

لماذا يستخدم الـ query الخاص بالـ cohorts العبارة SELECT DISTINCT cohort, customer_id, month_number قبل العدّ؟

السؤال 3 من 4

يعرض cohort ديسمبر 2025 معدل احتفاظ 0% في الشهر 1. تنتهي البيانات في 2025-12-31. ماذا يجب أن يعرض الجدول؟

السؤال 4 من 4

لحساب «الأشهر منذ الطلب الأول» عبر حدود السنة، لماذا تحوّل التواريخ إلى year * 12 + month بدل طرح قيم strftime('%m', ...)؟

تدريب

تمرين SQL

معدل تكرار الشراء خلال ثلاثة أشهر لكل cohort

تريد رانيا رقمًا واحدًا لكل cohort: من العملاء الذين كان طلبهم الأول في شهر معيّن، ما النسبة المئوية لمن طلبوا مجددًا في أي من الأشهر الثلاثة التالية (أرقام الأشهر 1 أو 2 أو 3)؟

أعِد cohort (‘YYYY-MM’) وcustomers (حجم الـ cohort) وrepeat_3m_pct (مقرّبًا إلى منزلة عشرية واحدة) للـ cohorts من 2025-01 إلى 2025-09، وهي التي رُصدت أشهرها الثلاثة كلها. رتّب حسب cohort. الترتيب مهم.

الـ CTEs صحيحة. أما الـ SELECT الأخير فيعدّ الشيء الخطأ.

الجداول
customers
(customer_id, first_name, last_name, email, country, city, signup_date, segment, referred_by)
orders
(order_id, customer_id, order_date, status, channel, coupon_code, shipping_fee)
order_items
(order_item_id, order_id, product_id, quantity, unit_price, discount)
products
(product_id, name, category_id, unit_price, unit_cost, launched_on, is_active)
categories
(category_id, name, parent_id)
returns
(return_id, order_item_id, return_date, reason, refund_amount)
employees
(employee_id, first_name, last_name, title, department, manager_id, hire_date, salary)
support_tickets
(ticket_id, customer_id, order_id, opened_at, closed_at, priority, channel, satisfaction, agent_id)
web_sessions
(session_id, customer_id, started_at, source, device, pages_viewed, added_to_cart, purchased)
-- Share of each 2025 cohort that ordered again within 3 months
WITH orders_ok AS (
  SELECT customer_id,
         order_date,
         CAST(strftime('%Y', order_date) AS INTEGER) * 12
           + CAST(strftime('%m', order_date) AS INTEGER) AS month_index
  FROM orders
  WHERE status <> 'cancelled'
),
firsts AS (
  SELECT customer_id,
         MIN(month_index) AS cohort_index,
         strftime('%Y-%m', MIN(order_date)) AS cohort
  FROM orders_ok
  GROUP BY customer_id
),
activity AS (
  SELECT f.cohort, o.customer_id, o.month_index - f.cohort_index AS month_number
  FROM orders_ok AS o
  JOIN firsts AS f ON f.customer_id = o.customer_id
)
SELECT
  cohort,
  COUNT(*) AS customers,
  ROUND(100.0 * COUNT(*) FILTER (WHERE month_number BETWEEN 1 AND 3)
        / COUNT(*), 1) AS repeat_3m_pct
FROM activity
WHERE cohort BETWEEN '2025-01' AND '2025-09'
GROUP BY cohort
ORDER BY cohort;جارٍ تحميل المحرّر…

Ctrl/⌘ + Enter للتشغيل · يعمل في بيئة معزولة داخل متصفّحك. لا شيء يغادر جهازك.