خانه/ وبلاگ/ تفاوت WHERE و HAVING در SQL و نحوه‌ی کار GROUP BY
آموزش SQL

تفاوت WHERE و HAVING در SQL و نحوه‌ی کار GROUP BY

آموزش SQL ۵ آبان ۱۴۰۵ 9 دقیقه مطالعه

WHERE و HAVING هر دو شرط می‌گذارند، اما در دو مرحله‌ی متفاوت. اگر این تفاوت را بفهمی، خطای معروف «Column is invalid in the select list» هم برایت روشن می‌شود. در این مقاله با یک جدول فروش همه‌چیز را قدم‌به‌قدم می‌بینیم.

قاعده‌ی ساده: WHERE ردیف‌ها را قبل از گروه‌بندی فیلتر می‌کند؛ HAVING گروه‌ها را بعد از گروه‌بندی فیلتر می‌کند.

جدول مثال

sql
CREATE TABLE Sales (
  SaleId INT PRIMARY KEY,
  City   NVARCHAR(30),
  Product NVARCHAR(30),
  Amount INT
);

INSERT INTO Sales VALUES
 (1, N'Tehran',  N'Laptop', 900), (2, N'Tehran', N'Mouse', 20),
 (3, N'Tehran',  N'Laptop', 950), (4, N'Shiraz', N'Laptop', 880),
 (5, N'Shiraz',  N'Mouse',  25),  (6, N'Isfahan', N'Mouse', 22);

ترتیب منطقی اجرای SELECT

نوشتن دستور از SELECT شروع می‌شود، اما SQL Server آن را با ترتیب دیگری ارزیابی می‌کند:

مرحلهعبارتکار
۱FROM / JOINانتخاب جدول‌ها و وصل کردن آن‌ها
۲WHEREفیلتر ردیف‌های تکی
۳GROUP BYساخت گروه‌ها
۴HAVINGفیلتر گروه‌ها (روی نتیجه‌ی تجمیع)
۵SELECTمحاسبه‌ی ستون‌ها و Aliasها
۶ORDER BYمرتب‌سازی نتیجه

به همین دلیل Alias تعریف‌شده در SELECT را نمی‌توان در WHERE یا HAVING استفاده کرد، اما در ORDER BY می‌توان.

GROUP BY چه می‌کند؟

GROUP BY ردیف‌هایی را که مقدار ستون (ستون‌ها)ی مشخصی دارند در یک گروه جمع می‌کند و برای هر گروه یک ردیف می‌دهد:

sql
SELECT City, COUNT(*) AS SaleCount, SUM(Amount) AS Total
FROM Sales
GROUP BY City;
CitySaleCountTotal
Tehran31870
Shiraz2905
Isfahan122

WHERE: فیلتر قبل از گروه‌بندی

فقط فروش لپ‌تاپ را می‌خواهیم، سپس بر اساس شهر گروه‌بندی کنیم:

sql
SELECT City, SUM(Amount) AS Total
FROM Sales
WHERE Product = N'Laptop'
GROUP BY City;
CityTotal
Tehran1850
Shiraz880

HAVING: فیلتر بعد از گروه‌بندی

حالا فقط شهرهایی که جمع فروششان بیشتر از ۱۰۰۰ است:

sql
SELECT City, SUM(Amount) AS Total
FROM Sales
GROUP BY City
HAVING SUM(Amount) > 1000;
CityTotal
Tehran1870

نمی‌توان نوشت WHERE SUM(Amount) > 1000؛ در مرحله‌ی WHERE هنوز مجموعی ساخته نشده و SQL Server خطا می‌دهد.

ترکیب WHERE و HAVING

هر دو را می‌توان با هم به‌کار برد: اول ردیف‌ها فیلتر می‌شوند، بعد گروه‌ها.

sql
SELECT City, COUNT(*) AS Cnt, AVG(Amount) AS AvgAmount
FROM Sales
WHERE Amount >= 20            -- ردیف‌های ارزان‌تر از ۲۰ حذف می‌شوند
GROUP BY City
HAVING COUNT(*) >= 2          -- فقط شهرهای با حداقل ۲ فروش
ORDER BY AvgAmount DESC;

خطای رایج: Column is invalid in the select list

اگر ستونی در SELECT بیاید که نه در GROUP BY است و نه داخل تابع تجمیعی، SQL Server این خطا را می‌دهد:

text
SELECT City, Product, SUM(Amount)   -- Product نه در GROUP BY است نه تجمیعی
FROM Sales
GROUP BY City;

-- Msg 8120: Column 'Sales.Product' is invalid in the select list
-- because it is not contained in either an aggregate function
-- or the GROUP BY clause.

دلیل: در هر گروه شهر، چند محصول مختلف داریم و SQL Server نمی‌داند کدام را نشان دهد. راه‌حل: یا Product را به GROUP BY اضافه کن، یا آن را در تابعی مثل MAX بگذار، یا از SELECT حذف کن:

sql
SELECT City, Product, SUM(Amount) AS Total
FROM Sales
GROUP BY City, Product;

COUNT(*) یا COUNT(ستون)؟

  • COUNT(*) تعداد همه‌ی ردیف‌های گروه را می‌شمارد.
  • COUNT(Column) فقط ردیف‌هایی را می‌شمارد که آن ستون NULL نیست.
  • COUNT(DISTINCT Column) مقدارهای متفاوت را می‌شمارد.

جمع‌بندی

WHEREHAVING
اجراقبل از GROUP BYبعد از GROUP BY
فیلتر رویردیف‌های تکیگروه‌ها
تابع تجمیعیمجاز نیستمجاز است
کاراییداده را زودتر کم می‌کندبعد از ساخت گروه‌ها

برای تمرین بیشتر، درس‌های GROUP BY و HAVING را در Playground حل کن.

سوالات متداول درباره‌ی این موضوع

می‌توانم در WHERE از تابع تجمیعی مثل COUNT استفاده کنم؟

نه. WHERE قبل از گروه‌بندی اجرا می‌شود و هنوز مقدار تجمیعی وجود ندارد. شرط روی COUNT، SUM و مانند آن باید در HAVING بیاید.

آیا HAVING بدون GROUP BY کار می‌کند؟

بله؛ در این حالت کل جدول یک گروه حساب می‌شود، مثلاً SELECT COUNT(*) FROM Orders HAVING COUNT(*) > 10. اما در عمل تقریباً همیشه همراه GROUP BY استفاده می‌شود.

چرا WHERE از HAVING سریع‌تر است؟

چون WHERE ردیف‌ها را پیش از گروه‌بندی کم می‌کند و داده‌ی کمتری برای تجمیع باقی می‌ماند. هر شرطی که روی ستون‌های عادی است باید در WHERE باشد، نه HAVING.

مصطفی موسویسازنده و نویسنده‌ی SQLFarsi. اگر اشتباهی در مقاله دیدی یا پیشنهادی داری، از طریق mousavi.dev خبر بده.

می‌خوای SQL را از پایه و با تمرین یاد بگیری؟

دوره‌ی رایگان SQLFarsi را با Playground و پروژه‌های واقعی دنبال کن.

شروع یادگیری