GROUP BY for ANSI SQL
This topic provides reference information about migrating from Microsoft SQL Server 2019 to Amazon Aurora PostgreSQL, focusing on GROUP BY, CUBE, ROLLUP, and GROUPING SETS functionalities. You can use this guide to understand the similarities and differences between these database systems when working with aggregate functions and grouping operations.
| Feature compatibility | Amazon SCT / Amazon DMS automation level | Amazon SCT action code index | Key differences |
|---|---|---|---|
|
|
|
N/A |
N/A |
SQL Server Usage
GROUP BY is an ANSI SQL query clause used to group individual rows that have passed the WHERE filter clause into groups to be passed on to the HAVING filter and then to the SELECT list. This grouping supports the use of aggregate functions such as SUM, MAX, AVG, and others.
Syntax
ANSI compliant GROUP BY Syntax.
GROUP BY [ROLLUP | CUBE] <Column Expression> ...n [GROUPING SETS (<Grouping Set>)...n
Backward compatibility syntax.
GROUP BY [ ALL ] <Column Expression> ...n [ WITH CUBE | ROLLUP ]
The basic ANSI syntax for GROUP BY supports multiple grouping expressions, the CUBE and ROLLUP keywords, and the GROUPING SETS clause; all used to add super-aggregate rows to the output.
Up to SQL Server 2008 R2, the database engine supported a legacy, proprietary syntax (not ANSI Compliant) using the WITH CUBE and WITH ROLLUP clauses. These clauses added super-aggregates to the output.
Also, up to SQL Server 2008 R2, SQL Server supported the GROUP BY ALL syntax, which was used to create an empty group for rows that failed the WHERE clause.
SQL Server supports the following aggregate functions: AVG, CHECKSUM_AGG, COUNT, COUNT_BIG, GROUPING, GROUPING_ID, STDEV, STDEVP, STRING_AGG, SUM, MIN, MAX, VAR, VARP.
Examples
Legacy CUBE and ROLLUP Syntax
CREATE TABLE Orders ( OrderID INT IDENTITY(1,1) NOT NULL PRIMARY KEY, Customer VARCHAR(20) NOT NULL, OrderDate DATE NOT NULL );
INSERT INTO Orders(Customer, OrderDate)
VALUES ('John', '20180501'), ('John', '20180502'), ('John', '20180503'),
('Jim', '20180501'), ('Jim', '20180503'), ('Jim', '20180504')
SELECT Customer, OrderDate, COUNT(*) AS NumOrders FROM Orders AS O GROUP BY Customer, OrderDate WITH ROLLUP
The preceding example produces the following results.
Customer OrderDate NumOrders Jim 2018-05-01 1 Jim 2018-05-03 1 Jim 2018-05-04 1 Jim NULL 3 John 2018-05-01 1 John 2018-05-02 1 John 2018-05-03 1 John NULL 3 NULL NULL 6
The rows with NULL were added as a result of the WITH ROLLUP clause and contain super aggregates for the following:
-
All orders for Jim and John regardless of OrderDate.
-
A super aggregated for all customers and all dates.
Using CUBE instead of ROLLUP adds super aggregates in all possible combinations, not only in GROUP BY expression order.
SELECT Customer, OrderDate, COUNT(*) AS NumOrders FROM Orders AS O GROUP BY Customer, OrderDate WITH CUBE
The preceding example produces the following results.
Customer OrderDate NumOrders Jim 2018-05-01 1 John 2018-05-01 1 NULL 2018-05-01 2 John 2018-05-02 1 NULL 2018-05-02 1 Jim 2018-05-03 1 John 2018-05-03 1 NULL 2018-05-03 2 Jim 2018-05-04 1 NULL 2018-05-04 1 NULL NULL 6 Jim NULL 3 John NULL 3
Four additional rows were added by the CUBE. They provide super aggregates for every date for all customers that were not part of the ROLLUP results in the preceding example.
Legacy GROUP BY ALL
Use the Orders table from the previous example.
SELECT Customer, OrderDate, COUNT(*) AS NumOrders FROM Orders AS O WHERE OrderDate <= '20180503' GROUP BY ALL Customer, OrderDate
The preceding example produces the following results.
Customer OrderDate NumOrders Jim 2018-05-01 1 John 2018-05-01 1 John 2018-05-02 1 Jim 2018-05-03 1 John 2018-05-03 1 Jim 2018-05-04 0 Warning: Null value is eliminated by an aggregate or other SET operation.
The last row failed the WHERE clause and was returned as an empty group as indicated by the warning for the empty COUNT(*) = 0.
Use GROUPING SETS
The following query uses the ANSI compliant GROUPING SETS syntax to provide all possible aggregate combinations for the Orders table, similar to the result of the CUBE syntax. This syntax requires specifying each dimension that needs to be aggregated.
SELECT Customer, OrderDate, COUNT(*) AS NumOrders FROM Orders AS O GROUP BY GROUPING SETS ( (Customer, OrderDate), (Customer), (OrderDate), () )
The preceding example produces the following results.
Customer OrderDate NumOrders Jim 2018-05-01 1 John 2018-05-01 1 NULL 2018-05-01 2 John 2018-05-02 1 NULL 2018-05-02 1 Jim 2018-05-03 1 John 2018-05-03 1 NULL 2018-05-03 2 Jim 2018-05-04 1 NULL 2018-05-04 1 NULL NULL 6 Jim NULL 3 John NULL 3
For more information, see Aggregate Functions (Transact-SQL)
PostgreSQL Usage
Amazon Aurora PostgreSQL-Compatible Edition (Aurora PostgreSQL) supports the basic ANSI syntax for GROUP BY and also supports GROUPING SETS CUBE, and ROLLUP.
In Aurora PostgreSQL, you can use ROLLUP and ORDER BY clauses in the same query, but the syntax is different from SQL Server. There is no WITH clause in the statement.
SELECT Customer, OrderDate, COUNT(*) AS NumOrders FROM Orders AS O GROUP BY ROLLUP (Customer, OrderDate)
The main difference is the need to move from writing the column to GROUP BY after the ROLLUP.
For the CUBE option, it’s the same change.
SELECT Customer, OrderDate, COUNT(*) AS NumOrders FROM Orders AS O GROUP BY CUBE (Customer, OrderDate);
For the GROUPING SET, use the following query.
SELECT Customer, OrderDate, COUNT(*) AS NumOrders FROM Orders AS O GROUP BY GROUPING SETS ( (Customer, OrderDate), (Customer), (OrderDate), () );
For more information, see Table Expressions
Syntax
SELECT <Select List> FROM <Table Source> WHERE <Row Filter> GROUP BY [ROLLUP | CUBE | GROUPING SETS] <Column Name> | <Expression> | <Position>
Migration Considerations
The GROUP BY functionality exists except for the ALL option.
Convert every query to use the column name after the GROUP BY option, such as CUBE, ROLLUP, or CUBE.
Examples
Rewrite SQL Server WITH CUBE modifier for migration.
CREATE TABLE Orders ( OrderID serial NOT NULL PRIMARY KEY, Customer VARCHAR(20) NOT NULL, OrderDate DATE NOT NULL );
INSERT INTO Orders(Customer, OrderDate)
VALUES ('John', '20180501'), ('John', '20180502'), ('John', '20180503'),
('Jim', '20180501'), ('Jim', '20180503'), ('Jim', '20180504');
SELECT Customer, OrderDate, COUNT(*) AS NumOrders FROM Orders AS O GROUP BY CUBE (Customer, OrderDate);
The preceding example produces the following results.
Customer OrderDate NumOrders Jim 2018-05-01 1 Jim 2018-05-03 1 Jim 2018-05-04 1 Jim NULL 3 John 2018-05-01 1 John 2018-05-02 1 John 2018-05-03 1 John NULL 3 NULL NULL 6 NULL 2018-05-01 2 NULL 2018-05-02 1 NULL 2018-05-03 2 NULL 2018-05-04 1
Rewrite SQL Server GROUP BY ALL for migration.
SELECT Customer, OrderDate, COUNT(*) AS NumOrders FROM Orders AS O WHERE OrderDate <= '20180503' GROUP BY Customer, OrderDate UNION ALL -- Add the empty groups SELECT DISTINCT Customer, OrderDate, 0 FROM Orders AS O WHERE OrderDate > '20180503';
The preceding example produces the following results.
Customer OrderDate NumOrders Jim 2018-05-01 1 Jim 2018-05-03 1 John 2018-05-01 1 John 2018-05-02 1 John 2018-05-03 1 Jim 2018-05-04 0
Summary
The following table shows similarities, differences, and key migration considerations.
| SQL Server feature | Aurora PostgreSQL feature | Comments |
|---|---|---|
|
|
|
In Aurora PostgreSQL, |
|
|
N/A |
Use a loop to calculate checksums. |
|
|
|
Reconsider the query logic to avoid having NULL groups that are ambiguous with the super aggregates. |
|
|
|
Rewrite keywords only. |
|
|
|
|
|
|
|
Remove |
|
|
|
Remove |
|
|
|
For more information, see Aggregate Functions