دليل شامل لـ GROUP BY في SQL: الدوال التجميعية (COUNT, SUM, AVG) وشرط HAVING

دقائق القراءة: 14

مقدمة إلى جملة GROUP BY في SQL: قوة التحليل والتجميع

تُعد جملة GROUP BY في لغة الاستعلامات الهيكلية (SQL) أداة بالغة القوة، لكنها قد تكون محيرة بعض الشيء عند استخدامها لأول مرة. حتى بعد سنوات من الخبرة، يتطلب الأمر أحيانًا التوقف والتفكير بعمق في آليتها لضمان الاستخدام الأمثل. في هذا المقال، سنتعمق في كيفية بناء جملة GROUP BY، وكيف تؤثر على استعلاماتك، وكيف يمكنك توظيفها لإجراء عمليات التجميع واستخلاص رؤى قيمة من بياناتك.

سنغطي المحاور التالية:

  • إعداد قاعدة البيانات الخاصة بك.
  • إعداد بيانات الأمثلة (إنشاء سجلات مبيعات).
  • كيف تعمل جملة GROUP BY؟
  • كتابة استعلامات GROUP BY.
  • دوال التجميع (COUNT، SUM، AVG).
  • التعامل مع مجموعات متعددة.
  • استخدام الدوال في جملة GROUP BY.
  • تصفية المجموعات باستخدام HAVING.
  • التجميعات ذات التجميع الضمني.

إعداد بيئة قاعدة البيانات

قبل أن نتمكن من كتابة استعلاماتنا، نحتاج إلى إعداد قاعدة البيانات. لأغراض هذه الأمثلة، سنستخدم نظام PostgreSQL، ولكن المفاهيم والاستعلامات المعروضة هنا قابلة للتطبيق بسهولة على أي نظام قواعد بيانات حديث آخر (مثل MySQL، SQL Server، وغيرها).

للعمل مع قاعدة بيانات PostgreSQL الخاصة بنا، يمكننا استخدام psql، وهو برنامج سطر الأوامر التفاعلي لـ PostgreSQL. إذا كنت تفضل استخدام عميل قاعدة بيانات آخر، فلا بأس بذلك.

لنبدأ بإنشاء قاعدة بيانات. بعد تثبيت PostgreSQL، يمكننا تشغيل الأمر createdb <database-name> في الطرفية لإنشاء قاعدة بيانات جديدة. لقد أطلقت على قاعدتي اسم fcc:

$ createdb fcc

بعد ذلك، لنبدأ تشغيل وحدة التحكم التفاعلية باستخدام الأمر psql، ثم نتصل بقاعدة البيانات التي أنشأناها للتو باستخدام \c <database-name>:

$ psql
psql (11.5)
Type "help" for help.
john=# \c fcc
You are now connected to database "fcc" as user "john".
fcc=#

ملاحظة: لقد قمت بتبسيط مخرجات psql في هذه الأمثلة لتسهيل القراءة، لذا لا تقلق إذا لم تكن المخرجات المعروضة هنا مطابقة تمامًا لما تراه في طرفيتك. أشجعك على متابعة هذه الأمثلة وتشغيل الاستعلامات بنفسك. ستتعلم وتتذكر الكثير من خلال التطبيق العملي بدلاً من مجرد القراءة.

إعداد بيانات الأمثلة: إنشاء سجلات المبيعات

لأغراض أمثلتنا، سنستخدم جدولًا يخزن سجلات المبيعات لمنتجات مختلفة عبر مواقع متاجر متنوعة. سنطلق على هذا الجدول اسم sales، وسيكون تمثيلاً بسيطًا لمبيعات المتجر: اسم الموقع (location)، اسم المنتج (product)، السعر (price)، ووقت البيع (sold_at).

إذا كنا نبني هذا الجدول في تطبيق حقيقي، فسنقوم بإعداد مفاتيح خارجية (foreign keys) لجداول أخرى (مثل locations أو products). ولكن لتوضيح مفاهيم GROUP BY، سنستخدم أعمدة نصية بسيطة (TEXT columns).

لنقم بإنشاء الجدول وإدخال بعض بيانات المبيعات:

CREATE TABLE sales(
  location TEXT,
  product TEXT,
  price DECIMAL,
  sold_at TIMESTAMP
);

INSERT INTO sales(location, product, price, sold_at) VALUES
  ('HQ', 'Coffee', 2, NOW()),
  ('HQ', 'Coffee', 2, NOW() - INTERVAL '1 hour'),
  ('Downtown', 'Bagel', 3, NOW() - INTERVAL '2 hour'),
  ('Downtown', 'Coffee', 2, NOW() - INTERVAL '1 day'),
  ('HQ', 'Bagel', 2, NOW() - INTERVAL '2 day'),
  ('1st Street', 'Bagel', 3, NOW() - INTERVAL '2 day' - INTERVAL '1 hour'),
  ('1st Street', 'Coffee', 2, NOW() - INTERVAL '3 day'),
  ('HQ', 'Bagel', 3, NOW() - INTERVAL '3 day' - INTERVAL '1 hour');

لدينا ثلاثة مواقع: HQ، Downtown، و 1st Street. ولدينا منتجان: Coffee و Bagel. قمنا بإدخال هذه المبيعات بقيم sold_at مختلفة لتمثيل بيع العناصر في أيام وأوقات مختلفة. هناك بعض المبيعات اليوم، وبعضها بالأمس، وبعضها من اليوم الذي سبقه.

فهم آلية عمل جملة GROUP BY

لتوضيح كيفية عمل جملة GROUP BY، دعنا نتناول مثالاً بسيطاً. تخيل أن لدينا غرفة مليئة بالأشخاص الذين ولدوا في بلدان مختلفة. إذا أردنا معرفة متوسط طول الأشخاص في الغرفة لكل بلد، فسنطلب أولاً من هؤلاء الأشخاص الانفصال إلى مجموعات بناءً على بلد ميلادهم. بمجرد أن يتم تقسيمهم إلى مجموعاتهم، يمكننا بعد ذلك حساب متوسط الطول داخل كل مجموعة.

هذه هي الطريقة التي تعمل بها جملة GROUP BY. أولاً، نحدد كيفية تجميع الصفوف معًا، ثم يمكننا إجراء العمليات الحسابية أو التجميعات على هذه المجموعات.

التعامل مع مجموعات فرعية متعددة

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

الآن يمكننا إيجاد متوسط الطول داخل كل من هذه المجموعات الأصغر، وسيكون لدينا نتيجة أكثر تحديدًا: متوسط الطول لكل بلد ولكل لون عين.

غالبًا ما تُستخدم جملة GROUP BY في المواقف التي يمكنك فيها استخدام عبارة “لكل شيء” أو “عن كل شيء”، مثل:

  • متوسط الطول لكل بلد ميلاد.
  • العدد الإجمالي للأشخاص لكل تركيبة لون عين وشعر.
  • إجمالي المبيعات لكل منتج.

صياغة استعلامات GROUP BY

جملة GROUP BY سهلة الكتابة للغاية؛ كل ما علينا فعله هو استخدام الكلمات المفتاحية GROUP BY ثم تحديد الحقول التي نريد التجميع بناءً عليها:

SELECT ...
FROM sales
GROUP BY location;

يقوم هذا الاستعلام البسيط بتجميع بيانات جدول sales بناءً على عمود location. لقد قمنا بالتجميع، ولكن ماذا نضع في جملة SELECT الخاصة بنا؟

الشيء الواضح الذي يجب تحديده هو عمود location؛ فنحن نقوم بالتجميع بناءً عليه، لذا نريد على الأقل رؤية أسماء المجموعات التي أنشأناها:

SELECT location
FROM sales
GROUP BY location;

النتيجة هي مواقعنا الثلاثة الفريدة:

 location
------------
 1st Street
 HQ
 Downtown
(3 rows)

إذا نظرنا إلى بيانات جدولنا الأصلية (SELECT * FROM sales;)، فسنرى أن لدينا أربعة صفوف بموقع HQ، وصفين بموقع Downtown، وصفين بموقع 1st Street:

 product  |  location  | price |          sold_at
----------+------------+-------+----------------------------
 Coffee   | HQ         |     2 | 2020-09-01 09:42:33.085995
 Coffee   | HQ         |     2 | 2020-09-01 08:42:33.085995
 Bagel    | Downtown   |     3 | 2020-09-01 07:42:33.085995
 Coffee   | Downtown   |     2 | 2020-08-31 09:42:33.085995
 Bagel    | HQ         |     2 | 2020-08-30 09:42:33.085995
 Bagel    | 1st Street |     3 | 2020-08-30 08:42:33.085995
 Coffee   | 1st Street |     2 | 2020-08-29 09:42:33.085995
 Bagel    | HQ         |     3 | 2020-08-29 08:42:33.085995
(8 rows)

من خلال التجميع بناءً على عمود location، تأخذ قاعدة البيانات هذه الصفوف المدخلة وتحدد المواقع الفريدة من بينها؛ هذه المواقع الفريدة تعمل كمجموعاتنا. ولكن ماذا عن الأعمدة الأخرى في جدولنا؟ إذا حاولنا تحديد عمود مثل product لم نقم بالتجميع بناءً عليه…

SELECT location, product
FROM sales
GROUP BY location;

…سنواجه هذا الخطأ:

ERROR: column "sales.product" must appear in the GROUP BY clause or be used in an aggregate function

المشكلة هنا هي أننا أخذنا ثمانية صفوف وقمنا بضغطها أو تقطيرها إلى ثلاثة. لا يمكننا ببساطة إرجاع بقية الأعمدة بشكل طبيعي؛ كان لدينا ثمانية صفوف، والآن لدينا ثلاثة. ماذا نفعل بالصفوف الخمسة المتبقية من البيانات؟ أي من بيانات الصفوف الثمانية يجب عرضها على هذه الصفوف الثلاثة المميزة للمواقع؟ لا توجد إجابة واضحة ومحددة هنا. لاستخدام بقية بيانات الجدول لدينا، يجب علينا أيضًا تقطير البيانات من هذه الأعمدة المتبقية إلى مجموعات المواقع الثلاثة. هذا يعني أنه يجب علينا تجميع أو إجراء عملية حسابية لإنتاج نوع من المعلومات الموجزة حول بياناتنا المتبقية.

دوال التجميع (COUNT، SUM، AVG)

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

للبدء، دعنا نجد عدد المبيعات لكل موقع. نظرًا لأن كل سجل في جدول sales لدينا يمثل عملية بيع واحدة، فإن عدد المبيعات لكل موقع سيكون عدد الصفوف داخل كل مجموعة موقع. للقيام بذلك، سنستخدم دالة التجميع COUNT() لعد عدد الصفوف داخل كل مجموعة:

SELECT location, COUNT(*) AS number_of_sales
FROM sales
GROUP BY location;

نستخدم COUNT(*) التي تعد جميع الصفوف المدخلة لمجموعة. (تعمل COUNT() أيضًا مع التعبيرات، ولكن لها سلوك مختلف قليلاً). إليك كيفية تنفيذ قاعدة البيانات لهذا الاستعلام:

  • FROM sales — أولاً، استرداد جميع السجلات من جدول sales.
  • GROUP BY location — بعد ذلك، تحديد مجموعات location الفريدة.
  • SELECT ... — أخيرًا، تحديد اسم الموقع وعدد الصفوف في تلك المجموعة.

نعطي هذا العدد من الصفوف اسمًا مستعارًا باستخدام AS number_of_sales لجعل المخرجات أكثر قابلية للقراءة. تبدو النتيجة كالتالي:

 location  | number_of_sales
------------+-----------------
 1st Street |               2
 HQ         |               4
 Downtown   |               2
(3 rows)

يحتوي موقع 1st Street على عمليتي بيع، و HQ على أربع، و Downtown على اثنتين. هنا يمكننا أن نرى كيف أخذنا بيانات الأعمدة المتبقية من صفوفنا الثمانية المستقلة وقمنا بتقطيرها إلى معلومات موجزة مفيدة لكل موقع: عدد المبيعات.

دالة SUM: حساب الإجماليات

بطريقة مماثلة، بدلاً من عد عدد الصفوف في مجموعة، يمكننا جمع المعلومات داخل المجموعة، مثل إجمالي المبلغ المالي المكتسب من تلك المواقع. للقيام بذلك، سنستخدم دالة SUM():

SELECT location, SUM(price) AS total_revenue
FROM sales
GROUP BY location;

بدلاً من عد عدد الصفوف في كل مجموعة، نقوم بجمع قيمة السعر لكل عملية بيع، وهذا يوضح لنا إجمالي الإيرادات لكل موقع:

 location  | total_revenue
------------+---------------
 1st Street |             5
 HQ         |             9
 Downtown   |             5
(3 rows)

دالة AVG: حساب المتوسطات

للعثور على متوسط سعر البيع لكل موقع، ما علينا سوى استبدال دالة SUM() بدالة AVG():

SELECT location, AVG(price) AS average_revenue_per_sale
FROM sales
GROUP BY location;

العمل مع مجموعات متعددة في GROUP BY

حتى الآن، كنا نعمل مع مجموعة واحدة فقط: location. ماذا لو أردنا تقسيم هذه المجموعة الفرعية بشكل أكبر؟ على غرار سيناريو “بلدان الميلاد ولون العين” الذي بدأنا به، ماذا لو أردنا معرفة عدد المبيعات لكل منتج لكل موقع؟

للقيام بذلك، كل ما نحتاجه هو إضافة شرط التجميع الثاني إلى جملة GROUP BY الخاصة بنا:

SELECT ...
FROM sales
GROUP BY location, product;

بإضافة عمود ثانٍ في جملة GROUP BY، نقوم بتقسيم مجموعات المواقع لدينا إلى مجموعات مواقع لكل منتج. نظرًا لأننا نقوم الآن بالتجميع أيضًا حسب عمود product، يمكننا الآن إرجاعه في جملة SELECT الخاصة بنا! (سأضيف بعض جمل ORDER BY إلى هذه الاستعلامات لجعل المخرجات أسهل في القراءة).

SELECT location, product
FROM sales
GROUP BY location, product
ORDER BY location, product;

بالنظر إلى نتيجة تجميعنا الجديد، يمكننا رؤية مجموعات الموقع/المنتج الفريدة لدينا:

  location  | product
------------+---------
 1st Street | Bagel
 1st Street | Coffee
 Downtown   | Bagel
 Downtown   | Coffee
 HQ         | Bagel
 HQ         | Coffee
(6 rows)

الآن بعد أن أصبح لدينا مجموعاتنا، ماذا نريد أن نفعل ببقية بيانات أعمدتنا؟ حسنًا، يمكننا إيجاد عدد المبيعات لكل منتج لكل موقع باستخدام نفس دوال التجميع كما كان من قبل:

SELECT location, product, COUNT(*) AS number_of_sales
FROM sales
GROUP BY location, product
ORDER BY location, product;
  location  | product | number_of_sales
------------+---------+-----------------
 1st Street | Bagel   |               1
 1st Street | Coffee  |               1
 Downtown   | Bagel   |               1
 Downtown   | Coffee  |               1
 HQ         | Bagel   |               2
 HQ         | Coffee  |               2
(6 rows)

كتمرين للقارئ: ابحث عن إجمالي الإيرادات (المجموع) لكل منتج لكل موقع.

استخدام الدوال في جملة GROUP BY

بعد ذلك، دعنا نحاول إيجاد العدد الإجمالي للمبيعات لكل يوم. إذا اتبعنا نمطًا مشابهًا لما فعلناه مع مواقعنا وقمنا بالتجميع حسب عمود sold_at

SELECT sold_at, COUNT(*) AS sales_per_day
FROM sales
GROUP BY sold_at
ORDER BY sold_at;

…قد نتوقع أن تكون كل مجموعة هي كل يوم فريد، ولكن بدلاً من ذلك نرى هذا:

          sold_at           | sales_per_day
----------------------------+---------------
 2020-08-29 08:42:33.085995 |             1
 2020-08-29 09:42:33.085995 |             1
 2020-08-30 08:42:33.085995 |             1
 2020-08-30 09:42:33.085995 |             1
 2020-08-31 09:42:33.085995 |             1
 2020-09-01 07:42:33.085995 |             1
 2020-09-01 08:42:33.085995 |             1
 2020-09-01 09:42:33.085995 |             1
(8 rows)

يبدو أن بياناتنا غير مجمعة على الإطلاق؛ نحصل على كل صف على حدة. ولكن، بياناتنا مجمعة بالفعل! المشكلة هي أن قيمة sold_at لكل صف هي قيمة فريدة، لذا يحصل كل صف على مجموعته الخاصة! تعمل جملة GROUP BY بشكل صحيح، ولكن هذه ليست النتيجة التي نريدها.

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

  • 2020-09-01 08:42:33.085995 => 2020-09-01
  • 2020-09-01 09:42:33.085995 => 2020-09-01

عند التحويل إلى تاريخ، ستُرجع جميع الطوابع الزمنية في نفس اليوم نفس قيمة التاريخ، وبالتالي سيتم وضعها في نفس المجموعة. للقيام بذلك، سنقوم بتحويل قيمة الطابع الزمني sold_at إلى تاريخ:

SELECT sold_at::DATE AS date, COUNT(*) AS sales_per_day
FROM sales
GROUP BY sold_at::DATE
ORDER BY sold_at::DATE;

في جملة GROUP BY، نستخدم ::DATE لاقتطاع جزء الطابع الزمني إلى “اليوم” فقط. هذا يقطع بفعالية الساعات/الدقائق/الثواني من الطابع الزمني ويرجع اليوم فقط. في جملة SELECT، نُرجع نفس التعبير ونعطيه اسمًا مستعارًا لتجميل المخرجات.

ولنفس السبب الذي لم نتمكن من إرجاع product بدونه، لا تسمح لنا قاعدة البيانات بإرجاع sold_at فقط؛ يجب أن يكون كل شيء في جملة SELECT إما في جملة GROUP BY أو نوعًا من التجميع على المجموعات الناتجة.

النتيجة هي المبيعات لكل يوم التي أردنا رؤيتها في الأصل:

    date    | sales_per_day
------------+---------------
 2020-08-29 |             2
 2020-08-30 |             2
 2020-08-31 |             1
 2020-09-01 |             3
(4 rows)

تصفية المجموعات باستخدام HAVING

بعد ذلك، دعنا نلقي نظرة على كيفية تصفية صفوفنا المجمعة. للقيام بذلك، دعنا نحاول إيجاد الأيام التي كان لدينا فيها أكثر من عملية بيع واحدة. بدون تجميع، كنا عادةً نقوم بتصفية صفوفنا باستخدام جملة WHERE. على سبيل المثال:

SELECT *
FROM sales
WHERE product = 'Coffee';

مع مجموعاتنا، قد نرغب في القيام بشيء كهذا لتصفية مجموعاتنا بناءً على عدد الصفوف…

SELECT sold_at::DATE AS date, COUNT(*) AS sales_per_day
FROM sales
WHERE COUNT(*) > 1 -- filter the groups?
GROUP BY sold_at::DATE;

للأسف، هذا لا يعمل ونتلقى هذا الخطأ:

ERROR: aggregate functions are not allowed in WHERE

دوال التجميع غير مسموح بها في جملة WHERE لأن جملة WHERE يتم تقييمها قبل جملة GROUP BY؛ لا توجد مجموعات بعد لإجراء العمليات الحسابية عليها. ولكن، هناك نوع من الجمل يسمح لنا بالتصفية، وإجراء التجميعات، ويتم تقييمه بعد جملة GROUP BY: وهي جملة HAVING.

جملة HAVING تشبه جملة WHERE لمجموعاتك. للعثور على الأيام التي كان لدينا فيها أكثر من عملية بيع واحدة، يمكننا إضافة جملة HAVING تتحقق من عدد الصفوف في المجموعة:

SELECT sold_at::DATE AS date, COUNT(*) AS sales_per_day
FROM sales
GROUP BY sold_at::DATE
HAVING COUNT(*) > 1;

تقوم جملة HAVING هذه بتصفية أي صفوف لا يكون فيها عدد الصفوف في تلك المجموعة أكبر من واحد، ونرى ذلك في مجموعة نتائجنا:

    date    | sales_per_day
------------+---------------
 2020-09-01 |             3
 2020-08-29 |             2
 2020-08-30 |             2
(3 rows)

لأغراض الاكتمال، إليك ترتيب تنفيذ جميع أجزاء عبارة SQL:

  1. FROM — استرداد جميع الصفوف من الجدول المحدد.
  2. JOIN — تنفيذ أي عمليات ربط (JOINs).
  3. WHERE — تصفية الصفوف الفردية بناءً على الشروط.
  4. GROUP BY — تشكيل المجموعات من الصفوف المتبقية.
  5. HAVING — تصفية المجموعات بناءً على الشروط التجميعية.
  6. SELECT — تحديد البيانات المراد إرجاعها.
  7. ORDER BY — ترتيب الصفوف الناتجة.
  8. LIMIT — إرجاع عدد معين من الصفوف.

التجميعات ذات التجميع الضمني

الموضوع الأخير الذي سننظر إليه هو التجميعات التي يمكن إجراؤها بدون جملة GROUP BY، أو ربما بشكل أفضل، تلك التي تحتوي على تجميع ضمني. هذه التجميعات مفيدة في السيناريوهات التي تريد فيها إيجاد قيمة تجميعية معينة من جدول، مثل إجمالي الإيرادات أو أكبر أو أصغر قيمة لعمود.

على سبيل المثال، يمكننا إيجاد إجمالي الإيرادات عبر جميع المواقع بمجرد تحديد المجموع من الجدول بأكمله:

SELECT SUM(price) FROM sales;
 sum
-----
  19
(1 row)

حتى الآن، حققنا 19 دولارًا من المبيعات عبر جميع المواقع (يا للروعة!).

شيء آخر مفيد يمكننا الاستعلام عنه هو أول أو آخر شيء. على سبيل المثال، ما هو تاريخ أول عملية بيع لدينا؟ للعثور على هذا، نستخدم فقط دالة MIN():

SELECT MIN(sold_at)::DATE AS first_sale
FROM sales;
 first_sale
------------
 2020-08-29
(1 row)

(للعثور على تاريخ آخر عملية بيع، ما عليك سوى استبدال MAX() بـ MIN()).

استخدام MIN و MAX في الاستعلامات المركبة

بينما يمكن أن تكون هذه الاستعلامات البسيطة مفيدة كاستعلامات قائمة بذاتها، إلا أنها غالبًا ما تكون جزءًا من عوامل التصفية لاستعلامات أكبر. على سبيل المثال، دعنا نحاول إيجاد إجمالي المبيعات لآخر يوم كان لدينا فيه مبيعات. إحدى الطرق التي يمكننا بها كتابة هذا الاستعلام ستكون كالتالي:

SELECT SUM(price) FROM sales WHERE sold_at::DATE = '2020-09-01';

يعمل هذا الاستعلام، لكننا قمنا بترميز التاريخ 2020-09-01 بشكل صريح. قد يكون 01/09/2020 هو آخر تاريخ كان لدينا فيه عملية بيع، لكنه لن يكون دائمًا هذا التاريخ. نحتاج إلى حل ديناميكي. يمكن تحقيق ذلك من خلال الجمع بين هذا الاستعلام ودالة MAX() في استعلام فرعي (subquery):

SELECT SUM(price)
FROM sales
WHERE sold_at::DATE = (SELECT MAX(sold_at::DATE) FROM sales);

في جملة WHERE الخاصة بنا، نجد أكبر تاريخ في جدولنا باستخدام استعلام فرعي: SELECT MAX(sold_at::DATE) FROM sales. ثم نستخدم هذا التاريخ الأقصى كقيمة نقوم بتصفية الجدول بناءً عليها، ونجمع سعر كل عملية بيع.

التجميع الضمني

أقول إن هذه تجميعات ضمنية لأنه إذا حاولنا تحديد قيمة تجميعية مع عمود غير مجمع كهذا…

SELECT SUM(price), location FROM sales;

…نحصل على خطأنا المعتاد:

ERROR: column "sales.location" must appear in the GROUP BY clause or be used in an aggregate function

الخلاصة التقنية

تُعد جملة GROUP BY في SQL أداة أساسية لا غنى عنها لأي محلل بيانات أو مطور قواعد بيانات. إن فهم آليتها، خاصةً في سياق ترتيب تنفيذ الاستعلامات، هو مفتاح استخلاص رؤى ذات معنى من البيانات. القدرة على تجميع الصفوف بناءً على معيار واحد أو أكثر، ثم تطبيق دوال تجميعية مثل COUNT، SUM، و AVG، تفتح الباب أمام تحليلات معقدة وفعالة. كما أن التمييز بين WHERE و HAVING أمر حيوي؛ حيث تُستخدم الأولى لتصفية الصفوف قبل التجميع، بينما تُستخدم الثانية لتصفية المجموعات بعد تشكيلها، مما يمنحنا مرونة هائلة في تحديد نطاق التحليل. إتقان GROUP BY يتطلب ممارسة، لكنه يمثل قفزة نوعية في القدرة على التعامل مع البيانات الكبيرة وتحويلها إلى معلومات قابلة للاستخدام.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *