ptg 1564 CHAPTER 42 What’s New for Transact-SQL in SQL Server 2008 Listing 42.3 demonstrates how to use the GROUPING SETS operator to perform three group- ings on three individual columns in a single query. LISTING 42.3 GROUPING SETS Example /*** ** Perform a grouping by type, grouping by pub_id, and grouping by price ***/ SELECT type, pub_id, price, sum(isnull(ytd_sales, 0)) AS ytd_sales FROM titles where pub_id < ‘9’ GROUP BY GROUPING SETS ( type, pub_id, price) go type pub_id price ytd_sales NULL NULL NULL 0 NULL NULL 0.0006 111 NULL NULL 0.0017 750 NULL NULL 14.3279 4095 NULL NULL 14.595 18972 NULL NULL 14.9532 14294 NULL NULL 14.9611 4095 NULL NULL 15.894 40968 NULL NULL 15.9329 3336 NULL NULL 17.0884 2045 NULL NULL 17.1675 8780 NULL 0736 NULL 28286 NULL 0877 NULL 44219 NULL 1389 NULL 24941 business NULL NULL 30788 mod_cook NULL NULL 24278 popular_comp NULL NULL 12875 psychology NULL NULL 9939 trad_cook NULL NULL 19566 In the output in Listing 42.3, the first 11 rows are the results grouped by price, the next 3 rows are grouped by pub_id, and the bottom 5 rows are grouped by type. Now, you can modify this query to include a super-aggregate for all rows by adding a null field list, as shown in Listing 42.4. ptg 1565 GROUP BY Clause Enhancements 42 LISTING 42.4 GROUPING SETS Example with Null Field List to Generate Super-Aggregate SELECT type, pub_id, price, sum(isnull(ytd_sales, 0)) AS ytd_sales FROM titles where pub_id < ‘9’ GROUP BY GROUPING SETS ( type, pub_id, price, () ) go type pub_id price ytd_sales NULL NULL NULL 0 NULL NULL 0.0006 111 NULL NULL 0.0017 750 NULL NULL 14.3279 4095 NULL NULL 14.595 18972 NULL NULL 14.9532 14294 NULL NULL 14.9611 4095 NULL NULL 15.894 40968 NULL NULL 15.9329 3336 NULL NULL 17.0884 2045 NULL NULL 17.1675 8780 NULL NULL NULL 97446 NULL 0736 NULL 28286 NULL 0877 NULL 44219 NULL 1389 NULL 24941 business NULL NULL 30788 mod_cook NULL NULL 24278 popular_comp NULL NULL 12875 psychology NULL NULL 9939 trad_cook NULL NULL 19566 If you look closely at the results in Listing 42.4, you see there are two rows with NULL values for all three columns for type, pub_id, and price. How can you determine definitively which row is the super-aggregate of all three rows, and which is a row grouped by price where the value of price is NULL? This is where the new grouping_id() function comes in. The grouping_id() Function The grouping_id() function, new in SQL Server 2008, can be used to determine the level of grouping in a query using GROUPING SETS or the CUBE and ROLLUP operators. Unlike the GROUPING() function, which takes only a single column expression as an argument and returns a 1 or 0 to indicate whether that individual column is being aggregated, the grouping_id() function accepts multiple column expressions and returns a bitmap to indicate which columns are being aggregated for that row. ptg 1566 CHAPTER 42 What’s New for Transact-SQL in SQL Server 2008 For example, you can add the grouping_id() and grouping() functions to the query in Listing 42.4 and examine the results (see Listing 42.5). LISTING 42.5 Using the grouping_id() Function SELECT type, pub_id, price, sum(isnull(ytd_sales, 0)) AS ytd_sales, grouping_id(type, pub_id, price) as grping_id, grouping(type) type_rlp, grouping(pub_id) pub_id_rlp, grouping(price) price_rlp FROM titles where pub_id < ‘9’ GROUP BY GROUPING SETS ( type, pub_id, price, () ) go type pub_id price ytd_sales grping_id type_rlp pub_id_rlp price_rlp NULL NULL NULL 0 6 1 1 0 NULL NULL 0.0006 111 6 1 1 0 NULL NULL 0.0017 750 6 1 1 0 NULL NULL 14.3279 4095 6 1 1 0 NULL NULL 14.595 18972 6 1 1 0 NULL NULL 14.9532 14294 6 1 1 0 NULL NULL 14.9611 4095 6 1 1 0 NULL NULL 15.894 40968 6 1 1 0 NULL NULL 15.9329 3336 6 1 1 0 NULL NULL 17.0884 2045 6 1 1 0 NULL NULL 17.1675 8780 6 1 1 0 NULL NULL NULL 97446 7 1 1 1 NULL 0736 NULL 28286 5 1 0 1 NULL 0877 NULL 44219 5 1 0 1 NULL 1389 NULL 24941 5 1 0 1 business NULL NULL 30788 3 0 1 1 mod_cook NULL NULL 24278 3 0 1 1 popular_comp NULL NULL 12875 3 0 1 1 psychology NULL NULL 9939 3 0 1 1 trad_cook NULL NULL 19566 3 0 1 1 Unlike the grouping() function, which takes only a single column name as an argument, the grouping_id() function accepts all columns that participate in any grouping set. The grouping_id() function produces an integer result that is a bitmap, where each bit repre- sents a different column, producing a unique integer for each grouping set. The bits in the bitmap indicate whether the columns are being aggregated in the grouping set (bit value is 1) or if the column is used to determine the grouping set (bit value is 0) used to calculate the aggregate value. ptg 1567 GROUP BY Clause Enhancements 42 The bit values are assigned to columns from right to left in the order the columns are listed in the grouping_id() function. For example, in the query in Listing 42.5, price is the rightmost bit value, bit 1; pub_id is assigned the next bit value, bit 2, and type is assigned the leftmost bit value, bit 3. When the grouping_id() value equals 6, that means the bits 2 and 3 are turned on (4 + 2 + 0 = 6). This indicates that the type and pub_id columns are being aggregated in the grouping set, and the price column defines the grouping set. The grouping_id() column can thus be used to determine which of the two rows where type, pub_id, and price are all NULL is the row with the super-aggregate of all three columns ( grouping_id = 7), and which row is an aggregate rolled up where the value of price is NULL (grouping_id = 6). The values returned by the grouping_id() function can also be used for further filtering your grouping set results or for sorting your grouping set results, as shown in Listing 42.6. LISTING 42.6 Using the grouping_id() Function to Sort Results SELECT type, pub_id, price, sum(isnull(ytd_sales, 0)) AS ytd_sales, grouping_id(type, pub_id, price) as grping_id FROM titles where pub_id < ‘9’ GROUP BY GROUPING SETS ( type, pub_id, price, () ) order by grping_id go type pub_id price ytd_sales grping_id business NULL NULL 30788 3 mod_cook NULL NULL 24278 3 popular_comp NULL NULL 12875 3 psychology NULL NULL 9939 3 trad_cook NULL NULL 19566 3 NULL 0736 NULL 28286 5 NULL 0877 NULL 44219 5 NULL 1389 NULL 24941 5 NULL NULL NULL 0 6 NULL NULL 0.0006 111 6 NULL NULL 0.0017 750 6 NULL NULL 14.3279 4095 6 NULL NULL 14.595 18972 6 NULL NULL 14.9532 14294 6 NULL NULL 14.9611 4095 6 NULL NULL 15.894 40968 6 NULL NULL 15.9329 3336 6 ptg 1568 CHAPTER 42 What’s New for Transact-SQL in SQL Server 2008 NULL NULL 17.0884 2045 6 NULL NULL 17.1675 8780 6 NULL NULL NULL 97446 7 Variable Assignment in DECLARE Statement In SQL Server 2008, you can now set a variable’s initial value at the same time you declare it. For example, the following line of code declares a variable named @ctr of type int and set its value to 100: DECLARE @ctr int = 100 Previously, this functionality was only possible with stored procedure parameters. Assigning an initial value to a variable required a separate SET or SELECT statement. This new syntax simply streamlines the process of assigning an initial value to a variable. The value specified can be a constant or a constant expression, as in the following: DECLARE @start_time datetime = getdate() You can even assign the initial value via a subquery, as long as the subquery returns only a single value, as in the following example: declare @max_price money = (select MAX(price) from titles) The value being assigned to the variable must be of the same type as the variable or be implicitly convertible to that type. Compound Assignment Operators Another new feature that streamlines and improves the efficiency of your T-SQL code is compound operators. This is a concept that has been around in many other programming languages for a long time, but has now finally found its way into T-SQL. Compound oper- ators are used when you want to apply an arithmetic operation on a variable and assign the value back into the variable. For example, the += operator adds the specified value to the variable and then assigns the new value back into the variable. For example, SET @ctr += 1 is functionally the same as SET @ctr = @ctr + 1 ptg 1569 Row Constructors 42 The compound operators are a quicker to type, and they offer a cleaner piece of finished code. Following is the complete list of compound operators provided in SQL Server 2008: += Add and assign -= Subtract and assign *= Multiply and assign /= Divide and assign %= Modulo and assign &= Bitwise AND and assign ^= Bitwise XOR and assign |= Bitwise OR and assign Row Constructors SQL Server 2008 provides a new method to insert data to SQL Server tables, referred to as row constructors. Row constructors are a feature that can be used to simplify data insertion, allowing multiple rows of data to be specified in a single DML statement. Row construc- tors are used to specify a set of row value expressions to be constructed into a data row. Row constructors can be specified in the VALUES clause of the INSERT statement, in the USING clause of the MERGE statement, and in the definition of a derived table in the FROM clause. The general syntax of the row constructor is as follows: VALUES ( { expression | DEFAULT | NULL |} [ , n ] ) [ , n ] Each column of data defined in the VALUES clause is separated from the next using a comma. Multiple rows (which may also contain multiple columns) are separated from each other using parentheses and a comma. When multiple rows are specified, the corre- sponding column values must be of the same data type or implicitly convertible data type. The following example shows the row constructor VALUES clause being used within a SELECT statement to define a set of rows and columns with explicit values: SELECT a, b FROM (VALUES (1, 2), (3, 4), (5, 6), (7, 8), (9, 10) ) AS MyTable(a, b); GO a b 1 2 3 4 ptg 1570 CHAPTER 42 What’s New for Transact-SQL in SQL Server 2008 5 6 7 8 9 10 The VALUES clause is commonly used in this manner to populate temporary tables but can also be used in a view, as shown in Listing 42.7. LISTING 42.7 Using the VALUES Clause in a View create view book_types as SELECT type, description FROM (VALUES (‘mod_cook’, ‘Modern Cooking’), (‘trad_cook’, ‘Traditional Cooking’), (‘popular_comp’, ‘Popular Computing’), (‘biography’, ‘Biography’), (‘business’, ‘Business Development’), (‘children’, ‘Children’’s Literature’), (‘fiction’, ‘Fiction’), (‘nonfiction’, ‘NonFiction’), (‘psychology’, ‘Psychology and Self Help’), (‘drama’, ‘Drama and Theater’), (‘lit crit’, ‘Literay Criticism’) ) AS type_lookup(type, description) go Defining a view in this manner can be useful as a code lookup table: select top 10 convert(varchar(50), title) as title, description from titles t inner join book_types bt on t.type = bt.type order by title_id desc go title description ————————————————————————— ———————————— Sushi, Anyone? Traditional Cooking Fifty Years in Buckingham Palace Kitchens Traditional Cooking Onions, Leeks, and Garlic: Cooking Secrets of the Traditional Cooking Emotional Security: A New Algorithm Psychology and Self Help Prolonged Data Deprivation: Four Case Studies Psychology and Self Help Life Without Fear Psychology and Self Help Is Anger the Enemy? Psychology and Self Help ptg 1571 Row Constructors 42 Computer Phobic AND Non-Phobic Individuals: Behavi Psychology and Self Help Net Etiquette Popular Computing Secrets of Silicon Valley Popular Computing The advantage of this approach is that unlike a permanent code table, the view with the VALUES clause doesn’t really take up any space; it’s materialized only when it’s referenced. Maintaining it involves simply dropping and re-creating the view rather than having to perform inserts, updates, and deletes as you would for a permanent table. The primary use of row constructors is to insert multiple rows of data in a single INSERT statement. Essentially, if you have multiple rows to insert, you can specify multiple rows in the VALUES clause. The maximum number of rows that can be specified in the VALUES clause is 1000. The following example shows how to use the row constructor VALUES clause in a single INSERT statement to insert five rows: insert sales (stor_id, ord_num, ord_date, qty, payterms, title_id) VALUES (‘6380’, ‘1234’, ‘3/26/2010’, 50, ‘Net 30’, ‘BU1032’), (‘6380’, ‘1234’, ‘3/26/2010’, 150, ‘Net 30’, ‘PS2091’), (‘6380’, ‘1234’, ‘3/26/2010’, 25, ‘Net 30’, ‘CH2480’), (‘6380’, ‘1234’, ‘3/26/2010’, 30, ‘Net 30’, ‘FI2046’), (‘6380’, ‘1234’, ‘3/26/2010’, 10, ‘Net 30’, ‘FI6318’) As you can see, this new syntax is much more concise and simple than having to issue five individual INSERT statements as you would have had to do in versions of SQL Server prior to SQL Server 2008. The VALUES clause can also be used in the MERGE statement as the source table. Listing 42.8 uses the VALUES clause to define five rows as the source data to perform INSERT/UPDATE operations on the store_inventory table defined in Listing 42.1. LISTING 42.8 Using the VALUES Clause in a MERGE Statement MERGE INTO store_inventory as s USING (VALUES (‘A011’, ‘CH3348’, 41 , getdate()), (‘A011’, ‘CH2480’, 125 , getdate()), (‘A011’, ‘FI0392’, 1100 , getdate()), (‘A011’, ‘FI2046’, 1476 , getdate()), (‘A011’, ‘FI1872’, 520 , getdate()) ) as i (stor_id, title_id, qty, update_dt) ON s.stor_id = i.stor_id and s.title_id = i.title_id WHEN MATCHED and s.qty <> i.qty THEN UPDATE SET s.qty = i.qty, update_dt = getdate() ptg 1572 CHAPTER 42 What’s New for Transact-SQL in SQL Server 2008 WHEN NOT MATCHED THEN INSERT (stor_id, title_id, qty, update_dt) VALUES (i.stor_id, i.title_id, i.qty, getdate()) OUTPUT $action, isnull(inserted.title_id, ‘’) as src_titleid, isnull(str(inserted.qty, 5), ‘’) as src_qty, isnull(deleted.title_id, ‘’) as tgt_titleid, isnull(str(deleted.qty, 5), ‘’) as tgt_qty ; go $action src_titleid src_qty tgt_titleid tgt_qty INSERT CH2480 125 UPDATE CH3348 41 CH3348 24 UPDATE FI0392 1100 FI0392 1176 UPDATE FI1872 520 FI1872 540 INSERT FI2046 1476 New date and time Data Types and Functions SQL Server 2008 introduces four new date and time data types: . date . time (precision) . datetime2 (precision) . datetimeoffset (precision) Two of the most welcome of these new types are the new date and time data types. These new data types allow you to store date-only and time-only values. In previous versions of SQL Server, the datetime and smalldatetime data types were the only available types for storing date or time values, and they always store both the date and time. This made date- only or time-only comparisons tricky at times because you always had to account for the other component (for more detailed examples on working with datetime values in SQL Server, see Chapter 43). In addition, the datetime stored date values range only from 1/1/1753 to 12/31/9999, with accuracy only to 3.33 milliseconds. The smalldatetime stored date values range only from 1/1/1900 to 6/6/2079, with accuracy of only 1 minute. The new date data type stores only the date component without the time component, and stores date values ranging from 1/1/0001 to 12/31/9999. The new time data type stores only the time component with accuracy that can be specified down to seven decimal places (100 nanoseconds). The default is seven decimal places. ptg 1573 New date and time Data Types and Functions 42 The datetime2 data type stores both date and time components, similar to datetime, increases the range of allowed values to 1/1/0001 to 12/31/9999, also with accuracy down to seven decimal places (100 ns). The default precision is seven decimal places. The datetimeoffset data type also stores both date and time components just like datetime2, but includes the time zone offset from Universal Time Coordinates (UTC). The time zone offset ranges from -14:00 to +14:00. Along with the new date and time data types, SQL Server 2008 also introduces some new date and time functions for returning the current system date and time in different formats: . SYSDATETIME()—Returns the current system datetime as a DATETIME2(7) value . SYSDATETIMEOFFSET()—Returns the current system datetime as a DATETIMEOFFSET(7) value . SYSUTCDATETIME—Returns the current system datetime as a DATETIME2(7) value representing the current UTC time . SWITCHOFFSET (DATETIMEOFFSET,time_zone)—Changes the DATETIMEOFFSET value from the stored time zone offset to the specified time zone . TODATETIMEOFFSET (datetime, time_zone)—Applies the specified time zone to the datetime value that does not reflect time zone difference from UTC Listing 42.9 demonstrates the use of some of the new data types and functions. Notice the difference in the specified decimal precision returned for the time values. LISTING 42.9 Using the new date and time Data Types and Functions declare @date date, @time time, @time3 time(3), @datetime2 datetime2(7), @datetimeoffset datetimeoffset, @datetime datetime, @utcdatetime datetime2(7) select @datetime = getdate(), @date = getdate(), @time = sysdatetime(), @time3 = sysdatetime(), @datetime2 = SYSDATETIME(), @datetimeoffset = SYSDATETIMEOFFSET(), @utcdatetime = SYSUTCDATETIME() select @datetime as ‘datetime’, @date as ‘date’, @time as ‘time’, . What’s New for Transact -SQL in SQL Server 2008 NULL NULL 17.0884 2045 6 NULL NULL 17.1675 8780 6 NULL NULL NULL 97446 7 Variable Assignment in DECLARE Statement In SQL Server 2008, you can now set. Bitwise XOR and assign |= Bitwise OR and assign Row Constructors SQL Server 2008 provides a new method to insert data to SQL Server tables, referred to as row constructors. Row constructors are. issue five individual INSERT statements as you would have had to do in versions of SQL Server prior to SQL Server 2008. The VALUES clause can also be used in the MERGE statement as the source table.