{"id":"l-sql-1-4","courseId":"sql-for-analysts","lang":"ar","kind":"lesson","summary":"صفِّ الزمن وجمّعه وقِسه في SQLite باستخدام نصوص التواريخ بصيغة ISO، والنطاقات نصف المفتوحة، وstrftime() لأجزاء التاريخ، وjulianday() للمُدد، دون أن تفقد آخر يوم في الفترة.","html":"<p>يُعدّ مارك إيفانز، نائب الرئيس للعمليات، مراجعة لشهر نوفمبر. يريد رقمين: كم طلبًا استقبلت Cartwheel في نوفمبر 2025، وبأي سرعة حلّ فريق الدعم التذاكر. كلاهما سؤال عن التواريخ، وSQLite يتعامل مع التواريخ بطريقة تختلف عن معظم قواعد البيانات. حين تفهم طريقته، تجدها بسيطة ويمكن التنبؤ بها.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">التواريخ نصوص، ولا بأس في ذلك</h2>\n<p>لا يملك SQLite نوعًا مستقلًا للتواريخ. تخزّن Cartwheel العمود <code dir=\"ltr\">order_date</code> كنص بصيغة ISO-8601: <code dir=\"ltr\">'2025-11-28 19:55:30'</code>. ولأن الصيغة تبدأ بالوحدة الأكبر وتنتهي بالأصغر، فإن الترتيب الأبجدي <em>هو</em> الترتيب الزمني. مقارنة النصوص تقارن لحظات زمنية، والـ index على العمود يعمل مع نطاقات التواريخ.</p>\n<p>هذا هو الـ query البديهي لشهر نوفمبر:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"sql\" data-run=\"sql\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">SQL</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"sql\" data-index=\"0\" aria-label=\"شغّل هذا المثال\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>تشغيل</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">SELECT</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> COUNT</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">*</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> november_orders</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> orders</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">WHERE</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> order_date </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">BETWEEN</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> '2025-11-01'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> AND</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> '2025-11-30'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span></code></pre><div class=\"run-output\" data-run-output=\"0\" hidden></div></div>\n<p>336 طلبًا. وهو خاطئ بفارق 11.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 720 200\" role=\"img\" aria-labelledby=\"l-sql-1-4-t1\"><defs><marker id=\"arrow-l-sql-1-4-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-sql-1-4-t1\">خط زمني من 29 نوفمبر إلى 1 ديسمبر. الشرط BETWEEN '2025-11-01' AND '2025-11-30' يتوقف عند منتصف الليل في بداية 30 نوفمبر، فيفوّت طلبًا في الساعة 19:55 من ذلك اليوم. أما النطاق نصف المفتوح، أصغر من '2025-12-01'، فيغطي 30 نوفمبر كله.</title>\n  <path class=\"d-line\" d=\"M40 90 L680 90\"/>\n  <path class=\"d-line\" d=\"M120 80 L120 100\"/>\n  <path class=\"d-line\" d=\"M360 80 L360 100\"/>\n  <path class=\"d-line\" d=\"M600 80 L600 100\"/>\n  <text class=\"d-code\" x=\"120\" y=\"125\" text-anchor=\"middle\">11-29</text>\n  <text class=\"d-code\" x=\"360\" y=\"125\" text-anchor=\"middle\">11-30 00:00</text>\n  <text class=\"d-code\" x=\"600\" y=\"125\" text-anchor=\"middle\">12-01 00:00</text>\n  <circle class=\"d-dot\" cx=\"560\" cy=\"90\" r=\"7\"/>\n  <text class=\"d-label\" x=\"560\" y=\"70\" text-anchor=\"middle\">طلب 19:55</text>\n  <rect class=\"d-box-warn\" x=\"40\" y=\"140\" width=\"320\" height=\"26\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"200\" y=\"158\" text-anchor=\"middle\">BETWEEN … AND '2025-11-30'</text>\n  <rect class=\"d-box-success\" x=\"40\" y=\"170\" width=\"560\" height=\"26\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"320\" y=\"188\" text-anchor=\"middle\">&gt;= '2025-11-01' AND &lt; '2025-12-01'</text>\n  <rect class=\"d-box-primary\" x=\"40\" y=\"20\" width=\"200\" height=\"30\" rx=\"6\"/>\n  <text class=\"d-label\" x=\"140\" y=\"40\" text-anchor=\"middle\">الزمن يتقدّم يمينًا</text>\n</svg><figcaption>الحدّ الأعلى بلا وقت يعني منتصف الليل في بداية اليوم الأخير</figcaption></figure>\n<p>النص <code dir=\"ltr\">'2025-11-30'</code> أقصر من <code dir=\"ltr\">'2025-11-30 19:55:30'</code>، والنص الأقصر الذي يطابق البداية يأتي أولًا في الترتيب. لذا فالحدّ الأعلى فعليًا هو منتصف الليل في <em>بداية</em> 30 نوفمبر، وكل ما بعده في ذلك اليوم يقع خارج النطاق. الإصلاح هو النطاق نصف المفتوح: اشمل البداية، واستبعد بداية الفترة التالية.</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"sql\" data-run=\"sql\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">SQL</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"sql\" data-index=\"1\" aria-label=\"شغّل هذا المثال\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>تشغيل</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">SELECT</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> COUNT</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">*</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> november_orders</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> orders</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">WHERE</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> order_date </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">>=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> '2025-11-01'</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  AND</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> order_date </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">&#x3C;</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">  '2025-12-01'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span></code></pre><div class=\"run-output\" data-run-output=\"1\" hidden></div></div>\n<p>347، وهو الرقم الحقيقي. في أكثر أيام عطلة نهاية أسبوع Black Friday ازدحامًا، كانت هذه الطلبات الـ 11 المفقودة ستترك فجوة واضحة في رسم مارك البياني.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">خطأ شائع</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>BETWEEN على قيم التاريخ والوقت</span></p>\n<p><code dir=\"ltr\">BETWEEN '2025-11-01' AND '2025-11-30'</code> يبدو صحيحًا، وهو خاطئ بصمت كلما احتوى العمود على وقت. اجعل النطاقات نصف المفتوحة خيارك الافتراضي لكل شرط على فترة: فهي تعمل مع الأيام والأشهر والسنوات، مع الوقت أو بدونه، وفي كل قاعدة بيانات.</p>\n</aside>\n<h2 id=\"sec-2\" tabindex=\"-1\">تفكيك التواريخ</h2>\n<p>تعيد <code dir=\"ltr\">strftime(format, value)</code> أجزاء التاريخ <strong>كنص</strong>:</p>\n<div class=\"table-wrap\" role=\"region\" aria-label=\"جدول قابل للتمرير\" tabindex=\"0\"><table><thead>\n<tr>\n<th>الصيغة</th>\n<th>تعيد</th>\n<th>مثال</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code dir=\"ltr\">'%Y-%m'</code></td>\n<td>السنة والشهر</td>\n<td><code dir=\"ltr\">'2025-11'</code></td>\n</tr>\n<tr>\n<td><code dir=\"ltr\">'%Y'</code></td>\n<td>السنة</td>\n<td><code dir=\"ltr\">'2025'</code></td>\n</tr>\n<tr>\n<td><code dir=\"ltr\">'%w'</code></td>\n<td>يوم الأسبوع، الأحد = 0</td>\n<td><code dir=\"ltr\">'5'</code></td>\n</tr>\n<tr>\n<td><code dir=\"ltr\">'%H'</code></td>\n<td>الساعة، 00–23</td>\n<td><code dir=\"ltr\">'19'</code></td>\n</tr>\n</tbody>\n</table></div><p><code dir=\"ltr\">'%Y-%m'</code> هي الصيغة الأكثر استخدامًا: تحوّل الطابع الزمني إلى تسمية شهر يمكنك التجميع حسبها في الدرس التالي. ولأن النتيجة نص، قارنها بنص: <code dir=\"ltr\">strftime('%w', order_date) = '0'</code> يجد طلبات يوم الأحد، بينما <code dir=\"ltr\">= 0</code> لا يجد شيئًا.</p>\n<p>تُوحّد <code dir=\"ltr\">date()</code> و<code dir=\"ltr\">datetime()</code> صيغة القيمة وتقبلان <strong>مُعدِّلات</strong> (modifiers) تجعل حدود الفترات سهلة:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"sql\" data-run=\"sql\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">SQL</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"sql\" data-index=\"2\" aria-label=\"شغّل هذا المثال\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>تشغيل</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">SELECT</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  date</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'2025-11-28 19:55:30'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)                                   </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> just_the_date,</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  date</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'2025-11-28'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'start of month'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)                          </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> month_start,</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  date</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'2025-11-28'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'start of month'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'+1 month'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)              </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> next_month_start,</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  date</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'2025-11-28'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'start of month'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'+1 month'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'-1 day'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)    </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> month_end,</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  date</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'2025-11-28'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'-6 days'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)                                 </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> week_ago;</span></span></code></pre><div class=\"run-output\" data-run-output=\"2\" hidden></div></div>\n<p>تُطبَّق المُعدِّلات من اليسار إلى اليمين. و<code dir=\"ltr\">'start of month', '+1 month'</code> هو بالضبط الحدّ الأعلى غير المشمول الذي يحتاجه النطاق نصف المفتوح.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">النوافذ المتحركة وأيام الأسبوع</h2>\n<p>«الطلبات في آخر 30 يومًا» نافذة متحركة، وتخفي سؤالًا عن الخطأ بفارق واحد: هل تشمل «آخر 30 يومًا» حتى 31 ديسمبر اليوم 31 نفسه؟ عادةً نعم، ما يعني أن النافذة تبدأ في 2 ديسمبر، قبل 29 يومًا. ابنِ الحدّين كليهما من تاريخ الارتكاز حتى تتحرك النافذة معه:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"sql\" data-run=\"sql\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">SQL</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"sql\" data-index=\"3\" aria-label=\"شغّل هذا المثال\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>تشغيل</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">SELECT</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> COUNT</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">*</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> orders_last_30_days</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> orders</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">WHERE</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> order_date </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">>=</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> date</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'2025-12-31'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'-29 days'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  AND</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> order_date </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">&#x3C;</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  date</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'2025-12-31'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'+1 day'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span></code></pre><div class=\"run-output\" data-run-output=\"3\" hidden></div></div>\n<p>189 طلبًا. الحدّ الأعلى هو بداية اليوم <em>التالي</em> لتاريخ الارتكاز، وهي فكرة النطاق نصف المفتوح نفسها. اكتب أول يوم وآخر يوم في النافذة في تعليق فوق الـ query؛ فهذا بالضبط ما سيتحقق منه من يراجعه.</p>\n<p>يجمع تحليل أيام الأسبوع بين <code dir=\"ltr\">strftime('%w', ...)</code> وتعبيرات CASE من الدرس السابق. ربط ‘0’ و’6’ بتسمية عطلة نهاية الأسبوع خطوة أولى شائعة حين يسأل التسويق هل تستحق حملات عطلة نهاية الأسبوع العناء:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"sql\" data-run=\"sql\" data-index=\"4\"><div class=\"code-head\"><span class=\"code-title\">SQL</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"sql\" data-index=\"4\" aria-label=\"شغّل هذا المثال\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>تشغيل</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">SELECT</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  order_id,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  order_date,</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  CASE</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> strftime(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'%w'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, order_date)</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    WHEN</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> '0'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> THEN</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'weekend'</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    WHEN</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> '6'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> THEN</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'weekend'</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    ELSE</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'weekday'</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  END</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> day_type</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> orders</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ORDER BY</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> order_date </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">DESC</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">LIMIT</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 5</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span></code></pre><div class=\"run-output\" data-run-output=\"4\" hidden></div></div>\n<p>يستخدم هذا الـ CASE الصيغة <em>البسيطة</em>، <code dir=\"ltr\">CASE value WHEN x THEN ...</code>، التي تقارن قيمة واحدة بقائمة. وهي مفيدة لعمليات الربط كهذه؛ أما الصيغة الشرطية (searched)، <code dir=\"ltr\">CASE WHEN condition THEN ...</code>، فاستخدمها كلما احتاج فرعٌ إلى شرط حقيقي.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">قياس المُدد</h2>\n<p>تحوّل <code dir=\"ltr\">julianday()</code> التاريخ إلى عدد من الأيام (مع كسور للوقت). اطرح قيمتين فتحصل على مدة بالأيام؛ واضرب في 24 لتحصل على الساعات. هذه تذاكر ديسمبر العاجلة مع أوقات حلّها:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"sql\" data-run=\"sql\" data-index=\"5\"><div class=\"code-head\"><span class=\"code-title\">SQL</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"sql\" data-index=\"5\" aria-label=\"شغّل هذا المثال\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>تشغيل</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">SELECT</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  ticket_id,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  opened_at,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  closed_at,</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  ROUND</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((julianday(closed_at) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> julianday(opened_at)) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">*</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 24</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> hours_to_close</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> support_tickets</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">WHERE</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> priority </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'urgent'</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  AND</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> opened_at </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">>=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> '2025-12-01'</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ORDER BY</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> opened_at;</span></span></code></pre><div class=\"run-output\" data-run-output=\"5\" hidden></div></div>\n<p>التذكرة 691 بلا <code dir=\"ltr\">closed_at</code>، لذا مدّتها NULL. وهذا صحيح: إنها ليست صفر ساعات، بل لم تنتهِ بعد، وأي متوسط تحسبه لاحقًا سيتخطّاها بدل أن يجمّل أداء الفريق.</p>\n<aside class=\"callout callout-tip\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">نصيحة</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>ثبّت تاريخ الارتكاز</span></p>\n<p>تتغيّر <code dir=\"ltr\">julianday('now')</code> كل ثانية، فينجرف أي مقياس لمدة البقاء أو «الأيام منذ آخر طلب» في كل تشغيل. تنتهي بيانات Cartwheel في 2025-12-31؛ اكتب <code dir=\"ltr\">julianday('2025-12-31')</code> (أو CTE واحدًا يحمل تاريخ الارتكاز، كما سترى لاحقًا) حتى تكون النتائج قابلة لإعادة الإنتاج.</p>\n</aside>\n<p>أصبحت قادرًا على تصفية الزمن وقياسه. أما سؤال مارك الآخر، متوسط وقت الحل <em>لكل أولوية</em>، فيحتاج إلى فكرة إضافية: ضغط صفوف كثيرة في صف واحد لكل مجموعة. وهذا هو الدرس التالي.</p>\n","toc":[{"id":"sec-1","text":"التواريخ نصوص، ولا بأس في ذلك","level":2},{"id":"sec-2","text":"تفكيك التواريخ","level":2},{"id":"sec-3","text":"النوافذ المتحركة وأيام الأسبوع","level":2},{"id":"sec-4","text":"قياس المُدد","level":2}],"takeaways":["يخزّن SQLite التواريخ كنص بصيغة ISO-8601، وهو نص يُرتَّب زمنيًا، لذا تعمل مقارنة النصوص العادية كشرط على التاريخ.","صفِّ أي فترة بنطاق نصف مفتوح (&gt;= البداية AND &lt; بداية الفترة التالية)؛ أما BETWEEN بحدّ أعلى بلا وقت فيُسقط كل ما بعد منتصف الليل في اليوم الأخير.","تستخرج strftime() أجزاءً مثل الشهر (‘%Y-%m’) أو يوم الأسبوع (‘%w’، والأحد = 0) أو الساعة (‘%H’) كنص.","يعطي julianday(later) - julianday(earlier) مدة بالأيام مع الكسور؛ اضربها في 24 لتحصل على الساعات.","اربط حسابات «حتى تاريخ كذا» بتاريخ ثابت بدل ‘now’ حتى يعطي الـ query نفسه الإجابة نفسها غدًا."],"further":[{"title":"SQLite date and time functions","url":"https://www.sqlite.org/lang_datefunc.html"},{"title":"PostgreSQL date/time functions and operators","url":"https://www.postgresql.org/docs/current/functions-datetime.html"}],"quiz":[{"q":"<p>يحمل <code dir=\"ltr\">order_date</code> قيمًا مثل ‘2025-11-30 19:55:30’. أي شرط يعيد كل طلب في نوفمبر 2025؟</p>","options":[{"text":"<code dir=\"ltr\">order_date BETWEEN '2025-11-01' AND '2025-11-30'</code>","why":"‘2025-11-30 19:55:30’ يأتي بعد ‘2025-11-30’ في الترتيب، فيُسقَط كل طلب في 30 نوفمبر بعد منتصف الليل."},{"text":"<code dir=\"ltr\">order_date &gt;= '2025-11-01' AND order_date &lt; '2025-12-01'</code>","why":"صحيح. النطاق نصف المفتوح يشمل اليوم الأخير كله أيًّا كان الوقت، ويعمل بالطريقة نفسها مع أي طول للفترة."},{"text":"<code dir=\"ltr\">order_date &gt; '2025-11-01' AND order_date &lt; '2025-11-30'</code>","why":"هذا يخسر يوم 30 نوفمبر كاملًا: كل قيمة ‘2025-11-30 …’ تأتي في الترتيب بعد ‘2025-11-30’، فيرفضها الشرط <code dir=\"ltr\">&lt; '2025-11-30'</code>. أما اليوم الأول فينجو فقط لأن القيم التي تحمل وقتًا تأتي بعد التاريخ المجرّد."},{"text":"<code dir=\"ltr\">order_date = '2025-11'</code>","why":"المساواة تقارن النص كاملًا، ولا توجد قيمة تساوي ‘2025-11’ بالضبط."}],"answer":1},{"q":"<p>ماذا يعيد <code dir=\"ltr\">strftime('%m', '2025-03-09 14:00:00')</code>؟</p>","options":[{"text":"الرقم 3","why":"strftime تعيد نصًا دائمًا. لهذا تفشل مقارنتها بالعدد الصحيح 3؛ قارنها بـ ‘03’."},{"text":"النص ‘03’","why":"صحيح. ‘%m’ هو الشهر برقمين كنص. استخدم <code dir=\"ltr\">CAST(... AS INTEGER)</code> إن احتجت إلى رقم."},{"text":"النص ‘March’","why":"لا تملك strftime في SQLite صيغة لاسم الشهر؛ ستبني الأسماء بتعبير CASE."}],"answer":1},{"q":"<p>فُتحت تذكرة في ‘2025-12-04 17:56:18’ وأُغلقت في ‘2025-12-05 01:56:18’. ماذا يعيد <code dir=\"ltr\">(julianday(closed_at) - julianday(opened_at)) * 24</code>؟</p>","options":[{"text":"1، لأن بين التاريخين يومًا واحدًا","why":"تحتفظ julianday بوقت اليوم، فيكون الفرق كسرًا من يوم، لا عددًا من أيام التقويم."},{"text":"0.33","why":"هذا هو الفرق بالأيام (8 / 24). الضرب في 24 يحوّله إلى ساعات."},{"text":"8.0","why":"صحيح. الفرق ثلث يوم، وبضربه في 24 يصبح 8 ساعات."},{"text":"خطأ، لأنك لا تستطيع طرح النصوص","why":"تحوّل julianday() النص أولًا إلى عدد من الأيام، فيصبح الطرح عدديًا."}],"answer":2},{"q":"<p>تحسب مدة بقاء العميل بـ <code dir=\"ltr\">julianday('now') - julianday(signup_date)</code>. لماذا قد يعترض زميلك؟</p>","options":[{"text":"لأن julianday() لا تقبل ‘now’.","why":"بل تقبلها؛ ‘now’ قيمة زمنية صالحة في كل دوال التاريخ في SQLite."},{"text":"لأن النتيجة تتغيّر كل يوم، فلا يمكن إعادة إنتاج تقرير الأسبوع الماضي.","why":"صحيح. في مجموعة بيانات تنتهي في 2025-12-31، اربط الحساب بهذا التاريخ (أو بمعامل) حتى يعني الرقم الشيء نفسه في كل تشغيل."},{"text":"لأن مدة البقاء يجب أن تُحسب بالأشهر لا بالأيام.","why":"الوحدة اختيار في طريقة العرض؛ الأيام مقبولة وتحويلها سهل."}],"answer":1}],"exercise":{"kind":"sql","starter":"-- November 2025 tickets and how long they took to close\nSELECT\n  ticket_id,\n  priority,\n  ROUND(julianday(closed_at) - julianday(opened_at), 1) AS hours_to_close\nFROM support_tickets\nWHERE opened_at BETWEEN '2025-11-01' AND '2025-11-30'\nORDER BY ticket_id;","solution":"SELECT\n  ticket_id,\n  priority,\n  ROUND((julianday(closed_at) - julianday(opened_at)) * 24, 1) AS hours_to_close\nFROM support_tickets\nWHERE opened_at >= '2025-11-01'\n  AND opened_at < '2025-12-01'\nORDER BY ticket_id;","orderMatters":true,"title":"أوقات الحل في نوفمبر","prompt":"<p>يريد مارك كل تذكرة <strong>فُتحت في نوفمبر 2025</strong> مع وقت حلّها <strong>بالساعات</strong>.</p>\n<p>أعِد <code dir=\"ltr\">ticket_id</code> و<code dir=\"ltr\">priority</code> و<code dir=\"ltr\">hours_to_close</code> (مقرّبًا إلى منزلة عشرية واحدة؛ واتركه NULL للتذاكر التي ما زالت مفتوحة)، مرتّبة حسب <code dir=\"ltr\">ticket_id</code>. الترتيب مهم.</p>\n<p>في الكود المبدئي خطآن: أحدهما يُسقط تذاكر، والآخر يخطئ في الوحدة.</p>\n","hints":["قارن عدد صفوف الكود المبدئي بعدد يستخدم <code dir=\"ltr\">opened_at &lt; '2025-12-01'</code>. أي يوم يختفي؟","استبدل BETWEEN بنطاق نصف مفتوح: <code dir=\"ltr\">&gt;= '2025-11-01' AND &lt; '2025-12-01'</code>.","فروق <code dir=\"ltr\">julianday()</code> بالأيام. اضرب الفرق في 24 قبل التقريب."],"explanation":"<p>مع BETWEEN يأتي الحدّ الأعلى ‘2025-11-30’ في الترتيب قبل أي وقت في 30 نوفمبر، فتختفي التذاكر المفتوحة في ذلك اليوم: 112 صفًا بدل 116. النطاق نصف المفتوح يُبقيها. و<code dir=\"ltr\">julianday(closed_at) - julianday(opened_at)</code> كسر من يوم، فالضرب في 24 يعطي الساعات. التذاكر المفتوحة قيمة <code dir=\"ltr\">closed_at</code> فيها NULL، والعملية الحسابية تمرّر هذه الـ NULL كما هي، وهذه هي الإجابة الصادقة لتذكرة لم تنتهِ.</p>\n","checkLabels":{}},"words":635,"readMinutes":4}