SQL Data Types, Aggregations, and Grouping Rules
SQL Data Types, Explicit Casting, and Precision
Northwind Database Architecture:
Northwindis a relational database representing a fictional retail business.- Key tables include:
customers,orders,order_details,products,categories, andemployees. - The
order_detailstable contains key attributes such asorder_id,product_id,unit_price,quantity, anddiscount.
Integer Data Type Variations:
integer(or standardint): Standard 4-byte integer used for whole numbers.smallint: A smaller integer variation designed with a strict limit on maximum value to minimize storage footprint.- Historically,
smallintandtinyintwere heavily utilized in older database schemas to conserve expensive disk storage and memory. - In modern database systems equipped with abundant, low-cost storage and compute power, specifying
smallintis rarely necessary, and standardintegeror higher is preferred.
Floating-Point vs. Exact Numeric Data Types:
real: A floating-point numeric type. It is inherently imprecise due to how binary floating-point calculations are handled under IEEE standards.numeric/decimal: Exact numeric types that preserve fixed decimal precision. In SQL,numericanddecimalare completely equivalent; creating a column asnumericproduces the exact same result as defining it asdecimal.- Legacy floating-point data types like
realanddouble precisionare holdovers from 1980s and 1990s database architecture when storage efficiency outweighed strict mathematical exactness.
Arithmetic Precision and Explicit Casting:
- Multiplying fixed numeric types (e.g.,
unit_price) by integer types (e.g.,quantity) in SQL queries can produce unexpected floating-point artifacts or excessive decimal places. - The Standard
CAST()Function:CAST(expression AS data_type)is the ANSI SQL standard function supported across all relational database management systems (RDBMS) including PostgreSQL, MySQL, Microsoft SQL Server, and Oracle.- Syntax example:
CAST(unit_price * quantity AS numeric(10,2)). - In
numeric(10,2), the first argument () defines the scale (total number of stored digits), and the second argument () defines the precision (number of digits to the right of the decimal point). This allows values up to
- Multiplying fixed numeric types (e.g.,
PostgreSQL Double Colon (
::) Operator:- PostgreSQL provides a non-standard shorthand notation for type conversion known as the double colon or "bang bang" syntax.
- Syntax example:
(unit_price * quantity)::numeric(10,2). - This eliminates the need for explicit
CAST()phrasing and yields the exact same execution state in a PostgreSQL environment.
The
ROUND()Function Constraints:- Syntax:
ROUND(numeric_expression, decimal_places). ROUND()explicitly requires anumericdata type input when passing a secondary decimal place argument.- Aggregate functions such as
AVG()over certain numeric fields return adouble precisionfloating-point data type. ExecutingROUND(AVG(freight), 2)directly on adouble precisionoutput will fail with a syntax/type error becauseROUND()cannot process precision arguments for floating-point types. - To resolve this, explicitly cast the aggregated result to
numericbefore wrapping it inROUND(): - If
numericis declared without explicit scale or precision parameters (e.g.,::numeric), PostgreSQL dynamically assigns the most permissive numeric type based on the input data.
- Syntax:
Aggregating Data with GROUP BY
Definition and Core Function:
- The
GROUP BYclause collapses rows sharing common categorical values into single summary rows, enabling the computation of aggregate statistics across discrete categories.
- The
The Split-Apply-Combine Pattern:
- The database engine processes
GROUP BYoperations through a three-stage execution pattern:- Split: The input table is partitioned into distinct sub-tables based on the unique categorical values of the specified column(s).
- Apply: An aggregate transformation function (e.g.,
COUNT(),SUM(),AVG(),MIN(),MAX()) is evaluated independently on each split partition. - Combine: The calculated summary results are assigned to their respective categorical row labels and merged into a single output dataset.
- The database engine processes
Filtering Data: WHERE vs. HAVING Clauses
Filtering Sequence and Execution Order:
WHEREfilters individual rows before any grouping or aggregation takes place.HAVINGfilters group summary rows after theGROUP BYclause has aggregated the data.
Logical SQL Execution Order:
FROM(identifies source tables)WHERE(filters raw individual rows)GROUP BY(collapses filtered rows into categorical partitions)HAVING(filters summary rows based on aggregate metrics)SELECT(evaluates expressions, functions, and column aliases)ORDER BY(sorts the final output set)
Aggregate Filter Violations:
- Attempting to use an aggregate function inside a
WHEREclause (e.g.,WHERE COUNT(*) > 10) causes an error:aggregate functions are not allowed in WHERE clause. - Aggregate conditions must strictly be placed inside the
HAVINGclause (e.g.,HAVING COUNT(*) > 10).
- Attempting to use an aggregate function inside a
Common GROUP BY Errors and Dialect Features
The Non-Aggregated Column Rule:
- Every non-aggregated column present in the
SELECTlist must explicitly appear in theGROUP BYclause. - Error Example: Selecting
category_id, COUNT(*)without declaringGROUP BY category_idtriggers the error:column products.category_id must appear in the GROUP BY clause or be used in an aggregate function. - Standalone Exception:
COUNT(*)can be evaluated without aGROUP BYclause only if no non-aggregated categorical columns exist in theSELECTlist.
- Every non-aggregated column present in the
Referencing Column Aliases:
- Standard SQL rules prohibit referencing
SELECTcolumn aliases insideGROUP BYorHAVINGclauses becauseSELECTis logically evaluated afterGROUP BYandHAVING. - PostgreSQL Dialect Shortcut: PostgreSQL, MySQL, and SQLite allow grouping directly by column aliases defined in
SELECT(e.g.,GROUP BY year). Microsoft SQL Server does not support alias grouping and requires re-stating the full expression (e.g.,GROUP BY EXTRACT(year FROM order_date)). - HAVING Restriction: Even in PostgreSQL, column aliases cannot be referenced inside the
HAVINGclause; full aggregate expressions must be re-stated explicitly (e.g.,HAVING COUNT(*) > 10).
- Standard SQL rules prohibit referencing
The
GROUP BY ALLSyntax:- A modern database extension supported by select modern engines that automatically includes all non-aggregated
SELECTcolumns into theGROUP BYclause without requiring manual enumeration.
- A modern database extension supported by select modern engines that automatically includes all non-aggregated
Advanced Aggregations: ROLLUP and CUBE
Overview and Use Cases:
ROLLUPandCUBEextendGROUP BYby automatically calculating hierarchical subtotals and grand totals directly within SQL.- Useful for generating reporting datasets or pre-aggregated summary tables downstream for business intelligence tools like Power BI or Tableau.
GROUP BY ROLLUP(a, b):- Calculates hierarchical subtotals based on the left-to-right order of listed columns.
- For
ROLLUP(ship_country, ship_city), it returns:- Subtotals for each unique
(ship_country, ship_city)pair. - Subtotals for each
ship_country(whereship_cityisNULL). - A grand total across all rows (where both
ship_countryandship_cityareNULL).
- Subtotals for each unique
GROUP BY CUBE(a, b):- Calculates a complete cross-tabulation of all possible column combinations.
- For
CUBE(ship_country, ship_city), it returns:- Subtotals for each
(ship_country, ship_city)pair. - Subtotals for each
ship_countryon its own. - Subtotals for each
ship_cityon its own (independent of country). - A grand total row (where both columns are
NULL).
- Subtotals for each
CUBEalways produces a larger dataset with more rows thanROLLUPdue to unconstrained permutation processing.
Formatting Subtotals with
COALESCE():COALESCE(val1, val2)returns the first non-null argument.- Because
ROLLUPandCUBEinsertNULLvalues to represent aggregate levels, wrapping categorical outputs inCOALESCE()replacesNULLmarkers with human-readable labels like'Grand Total'or'Subtotal'.
Practical SQL Exercises and Queries
Exercise 1.1: Standard Explicit Casting with
CAST()- Requirement: Calculate line total (
unit_price * quantity) explicit cast tonumeric(10,2)limited to 10 rows. - Query:
sql SELECT order_id, product_id, CAST(unit_price * quantity AS numeric(10,2)) AS line_total FROM order_details LIMIT 10; - Expected Verification: Order ID
10248returns aline_totalof168.00(or168).
- Requirement: Calculate line total (
Exercise 1.2: PostgreSQL Syntax (
::) Conversion- Requirement: Re-write Exercise 1.1 using PostgreSQL double-colon casting syntax.
- Query:
sql SELECT order_id, product_id, (unit_price * quantity)::numeric(10,2) AS line_total FROM order_details LIMIT 10;
Exercise 1.3: Date Extraction and Aggregation
- Requirement: Count the number of orders placed in each year.
- ANSI Standard Query:
sql SELECT EXTRACT(year FROM order_date) AS year, COUNT(*) AS order_count FROM orders GROUP BY EXTRACT(year FROM order_date) ORDER BY year; - PostgreSQL Shorthand Query:
sql SELECT EXTRACT(year FROM order_date) AS year, COUNT(*) AS order_count FROM orders GROUP BY year ORDER BY year; - Benchmark Results:
- Year
2022: 152 orders - Year
2023: 408 orders - Year
2024: 270 orders
- Year
Exercise 2.1: Basic Categorical Aggregation
- Requirement: Group products by
category_id, count total products per category, and sort descending by count. - Query:
sql SELECT category_id, COUNT(*) AS product_count FROM products GROUP BY category_id ORDER BY product_count DESC;
- Requirement: Group products by
Exercise 2.2: Aggregated Filtering with
HAVING- Requirement: Filter product categories to include only those with more than 10 products.
- Query:
sql SELECT category_id, COUNT(*) AS product_count FROM products GROUP BY category_id HAVING COUNT(*) > 10 ORDER BY product_count DESC;
Exercise 2.3: Nested Aggregate Math and Rounding
- Requirement: Calculate rounded total revenue per product accounting for discount rates.
- Mathematical Formula:
- Query:
sql SELECT product_id, ROUND(CAST(SUM(unit_price * quantity * (1 - discount)) AS numeric), 2) AS revenue FROM order_details GROUP BY product_id;
Exercise 2.5: Aggregating Distinct Values
- Requirement: Count total orders alongside distinct customer counts handled by each employee.
- Query:
sql SELECT employee_id, COUNT(DISTINCT customer_id) AS distinct_customers, COUNT(*) AS total_orders FROM orders GROUP BY employee_id; - Benchmark Metric: Employee ID
4managed75distinct customers across156total orders.
Exercise 3.1: Subtotals via ROLLUP
- Requirement: Calculate order counts for cities in USA, Canada, and Mexico with subtotals and grand totals.
- Query:
sql SELECT COALESCE(ship_country, 'Grand Total') AS ship_country, COALESCE(ship_city, 'Subtotal') AS ship_city, COUNT(*) AS order_count FROM orders WHERE ship_country IN ('USA', 'Canada', 'Mexico') GROUP BY ROLLUP(ship_country, ship_city) ORDER BY ship_country NULLS FIRST; - Result Metric: Exactly 20 summary rows returned.
Exercise 3.2: Cross-Tabulation via CUBE
- Requirement: Calculate order metrics across all permutations of ship country and city.
- Query:
sql SELECT COALESCE(ship_country, 'Grand Total') AS ship_country, COALESCE(ship_city, 'Subtotal') AS ship_city, COUNT(*) AS order_count FROM orders WHERE ship_country IN ('USA', 'Canada', 'Mexico') GROUP BY CUBE(ship_country, ship_city);
Questions & Technical Discussion
Clarification on Floating-Point Imprecision vs. Numeric:
- Question: What is the definition and technical nature of
double precision? - Explanation:
double precisionis an IEEE floating-point data type stored in binary format. Because decimal numbers cannot always be represented exactly in binary floating-point arithmetic, mathematical operations are inherently imprecise compared to exact numeric/decimal data types. In enterprise database design, exactnumericfields should always be selected over floating-point types for monetary and precise values.
- Question: What is the definition and technical nature of
Query Formatting Strategy: Leading vs. Trailing Commas:
- Leading Comma Convention:
sql SELECT order_id , product_id , unit_price FROM order_details; - Technical Rationale: Placing commas at the beginning of subsequent lines allows developer comments (
--) to disable or comment out the final line in aSELECTlist without leaving a stray trailing comma that causes a syntax syntax execution failure.
- Leading Comma Convention: