Tuesday, March 13, 2012

SQL Server 2008 New TSQL Programmability Features at a glance


SQL Server 2008 New TSQL Programmability Features at a glance

1 New Data Types

A   Date and Time Data types

DATE
Defines the date.
DECLARE @MyDate DATE
CREATE TABLE Table1 (Column1 DATE)
*** YYYY-MM-DD
*** Follows Gregorian calendar
*** Default value is 1900-01-01
*** Date range 0001-01-01 to 9999-12-31
*** Accuracy 100 nano seconds
*** Storage size 3 bytes fixed

DECLARE @d DATE 
SET @d = GETDATE() 
SELECT @d
-- output '2012-03-12'
 
SET @d = '1234-03-12 16:30:00' 
SELECT @d
-- output '1234-03-22'  

TIME (Fractional Second Precision)
Defines the time of a day.
We can specify the optional precision (default seven digits) during declaration.
DECLARE @MyTime TIME(7)
CREATE TABLE Table1 (Column1 TIME(7) )
*** hh:mm:ss [.nnnnnnn]
*** Default 00:00:00
*** Accuracy 100 nano seconds
*** Storage size 5 bytes fixed

CREATE TABLE #times( 
    Time_DefaultPrecision   TIME, 
    Time_Precision1  TIME(1), 
    Time_Precision5  TIME(5), 
    Time_Precision7  TIME(7) 
    ) 
 
INSERT INTO #times VALUES ( 
    '16:30:01.7654321', 
    '16:30:01.7654321', 
    '16:30:01.7654321', 
    '16:30:01.7654321') 
 
SELECT * FROM #times 
DROP TABLE #times 

--output 
Time_DefaultPrecision Time_Precision1  Time_Precision5  Time_Precision7
--------------------- ---------------- ---------------- ----------------
16:30:01.7654321      16:30:01.8       16:30:01.76543   16:30:01.7654321


DATETIME2 (Fractional Second Precision)
It is an extension to the DATETIME data type.
It has extended precision up to seven digits.
We can specify the optional precision (default seven digits) during declaration.
DECLARE @MyDatetime2 DATETIME2(7)
CREATE TABLE Table1 (Column1 DATETIME2(7) )
*** YYYY-MM-DD hh:mm:ss [.nnnnnnn]
*** Follows Gregorian calendar
*** Default value for DATETIME2 is 1900-01-01
*** Date range 0001-01-01 to 9999-12-31
*** Accuracy 100 nano seconds
*** Storage size
Precision < 3    -   6 bytes
Precision 3, 4   -   7 bytes
Precision > 4    -   8 bytes

DECLARE @dateA DATETIME2 = '2012-03-12 16:30:01.7654321' 
PRINT @dateA -- output 2012-03-12 16:30:01.7654321 
DECLARE @dateB DATETIME2(4) = '2012-03-12 16:30:01.7654321' 
PRINT @dateB -- output 2012-03-12 16:30:01.7654



DATETIMEOFFSET (Fractional Second Precision)
It is an extension to the DATETIME data type.
It has extended precision up to seven digits.
We can specify the optional precision (default seven digits) during declaration.
DECLARE @MyDatetime2 DATETIME2(7)
CREATE TABLE Table1 (Column1 DATETIME2(7) )
*** YYYY-MM-DD hh:mm:ss [.nnnnnnn]
*** Follows Gregorian calendar
*** Default value for DATETIME2 is 1900-01-01
*** Date range 0001-01-01 to 9999-12-31
*** Accuracy 100 nano seconds
*** Storage size 10 Bytes fixed

SET NOCOUNT ON
-- Time Stamp on Central Daylight Time 
DECLARE @today DATETIMEOFFSET = '2012-03-12 01:02:03.1234567-05:00' 
DECLARE @today1 DATETIMEOFFSET = '2012-03-12T01:02:03.1234567-05:00' 
DECLARE @today2 DATETIMEOFFSET(2) = '2012-03-12 01:02:03.1234567-05:00' 
DECLARE @today3 DATETIMEOFFSET(2) = '2012-03-12T01:02:03.1234567-05:00' 
SELECT @today  -- output 2012-03-12 01:02:03.1234567 -05:00  ,
, @today1 -- output 2012-03-12 01:02:03.1234567 -05:00 
, @today2 -- output 2012-03-12 01:02:03.12 -05:00
, @today3 -- output 2012-03-12 01:02:03.12 -05:00
GO



SELECT
     CAST('2012-03-12 16:30:00. 1234567 +10:11' AS TIME(7)) AS [Time]
    ,CAST('2012-03-12 16:30:00. 1234567 +10:11' AS DATE) AS [Date]
    ,CAST('2012-03-12 16:30:00.123' AS SMALLDATETIME) AS [SmallDateTime]
    ,CAST('2012-03-12 16:30:00.123' AS DATETIME) AS [DateTime]
    ,CAST('2012-03-12 16:30:00.1234567+10:11' AS DATETIME2(7)) AS [DateTime2]
    ,CAST('2012-03-12 16:30:00.1234567 +10:11' AS DATETIMEOFFSET(7)) AS [DateTimeOffset]
GO

B   Spatial Data types – Geometry and Geography:
SQL Server 2008 introduces the spatial data types to simplify the development process (like combining the spatial data with other data).
Geometry data type stores planar data like the (X, Y) co-ordinates on the surface of the earth to represent a point, line or a polygon in a two-dimensional space.
CREATE TABLE SpatialDemo
(
   DataID           INT IDENTITY(1,1)NOT NULL,
    --Using GEOMETRY data type similar to any other system defined data type
    SpatialData     GEOMETRY
 )
 GO
 INSERT INTO SpatialDemo (SpatialData)
 VALUES
--insert a point where X = 5 and Y = 10
 ('POINT(5 10)'),
--insert a line start from X = 0 and Y = 0
--to X = 5 and Y = 5
 ('LINESTRING(0 0, 5 5)'),
--insert a line start from X = 0 and Y = 0 to X = 5 and Y = 5 and then to X = 7 and Y = 3
 ('LINESTRING(0 0, 5 5, 7 3)'),
--insert a polygon with triangular shape
 ('POLYGON((0 0, 8 5, 8 5, 10 0, 0 0))')
GO
--Note

SELECT * FROM SpatialDemo
--A space is used to separate the coordinates of a point. A comma is used to separate points within a more complex type.
--Parentheses are used to group points into a single shape.
Geography data type stores ellipsoidal data like the latitudes and longitudes that represents a point, line or a polygon on earth’s surface.
*** Support special spatial indexes

C   HierarchyId
It is used to create tables with a hierarchical structure or to reference hierarchical structure of data in another location.
Hierarchical data is defined as a set of data items that are related to each other by hierarchical relationships. Hierarchical relationships are where one item of data is the parent of another item. Hierarchical data is common in databases.

Examples include the following:
*** An organizational structure
*** A file system
*** A set of tasks in a project
*** Taxonomy of language terms
*** A graph of links between Web pages

The methods used in the HierarchyId are
GetRoot - It returns root of the hierarchy tree.   HIERARCHYID::GetRoot()
GetAncestor - It is used to find nth ancestos (parents and grantparents) of the given node.
child.GetAncestor () (where represents the number of levels to go up in the hierarchy)
GetDescendant - It returns a child node of the given parent. It is mainly used to determine new descendant's position before adding it to tree.
Parent.GetDescendant (, )
GetLevel - It returns the depth of the node under the provided node/tree.
node.GetLevel ( )
IsDescendantOf - It returns true if child is a descendant of provided node. In other words, It is used to determine if a record or value is a child of a second record or value.
Parent.IsDescendantOf()
Parse     It is again a static method so it will be called by HIERARCHYID type itself. It is used to convert the canonical version of the data type to a HIERARCHYID value. Parse is called implicitly when a conversion from a string type to HIERARCHYID occurs. It acts as the opposite of ToString method discussed below.
HIERARCHYID::Parse ( input )
Read      This method is only available within .NET CLR code. It is used to read the binary representation of the HIERARCHYID value and cannot be called by using Transact-SQL.
void Read( BinaryReader r )
GetReparentedValue - This method helps to re-parent a node i.e. suppose a manager left a job and all his directs will now come under another manager, it will be much helpful in this kind of scenario. In other words, it can be used to modify the tree by moving nodes from oldRoot to newRoot.
node. GetReparentedValue ( , )
ToString - It gives string representation (logical representation) of the HIERARCHYID. The data type for returned value is nvarchar(4000).
node.ToString ( )
Write    Similar to the Read method as discussed above, this method is also available only within .NET CLR code and cannot be called by using Transact-SQL. It writes out a binary representation of SqlHierarchyId to the passed-in BinaryWriter.
void Write( BinaryWriter w )

-- our Categories table 
CREATE TABLE #Categories ( 
    CategoryID INT IDENTITY(1,1), 
    CategoryNode HIERARCHYID NOT NULL, 
    CategName NVARCHAR(40) NOT NULL 
    ) 

-- the root category 
DECLARE @root HIERARCHYID = hierarchyid::GetRoot() 
INSERT INTO #Categories (CategoryNode, CategName)  
    VALUES (@root, 'All #Categories') 
 
-- insert the 'Electronics' category 
DECLARE @electronics HIERARCHYID 
SELECT @electronics = @root.GetDescendant(NULL, NULL) 
INSERT INTO #Categories (CategoryNode, CategName)  
    VALUES (@electronics, 'Electronics') 
 
-- insert the 'Music' category after 'Electronics' 
DECLARE @music HIERARCHYID 
SELECT @music = @root.GetDescendant(NULL, @electronics) 
INSERT INTO #Categories (CategoryNode, CategName)  
    VALUES (@music, 'Music') 
 
-- insert the 'Apparel' category between 'Electronics' and 'Music' 
SELECT @music = @root.GetDescendant(@music, @electronics) 
INSERT INTO #Categories (CategoryNode, CategName)  
    VALUES (@music, 'Apparel') 
 
-- insert some children under 'Electronics' 
DECLARE @video HIERARCHYID 
--   We could do a simple @category.GetDescendant() but, let's 
--      show something that is more likely to happen 
SELECT @video = CategoryNode.GetDescendant(NULL, NULL) 
  FROM #Categories WHERE CategName ='Electronics' 
INSERT INTO #Categories (CategoryNode, CategName)  
    VALUES (@video, 'Video Equipment') 
 
-- insert some children under 'Video Equipment' 
DECLARE @tvs HIERARCHYID 
SELECT @tvs = @video.GetDescendant(NULL, NULL) 
INSERT INTO #Categories (CategoryNode, CategName)  
    VALUES (@tvs, 'Televisions') 
 
DECLARE @players HIERARCHYID 
SELECT @players = @video.GetDescendant(NULL, @tvs) 
INSERT INTO #Categories (CategoryNode, CategName)  
    VALUES (@players, 'DVD - BluRay') 
--When we query the table, the output from the CategoryNode column reflects the position of the record in the hierarchy
--, similar to directory paths.

SELECT  
    CategoryID, CategName,  
    CategoryNode,  
    CategoryNode.ToString() AS Path 
FROM #Categories

DROP TABLE #Categories
GO

2 Table Valued Parameter

A tabular data can be passed as a parameter to the routines. This mechanism minimizes a lot of effort where previously one by one parameter set is used to be passed.
*** Efficient transport of data from client to Server
*** Need to create a User-defined table type to use the TVP
*** Limitations of User-defined table types
Once created, their definition cannot be modified
We cannot perform DML operations on them once they are passed to the routine (READONLY)
Cannot be used as OUTPUT variables
SQL Server does not store statistics for them

CREATE TABLE TVPTest(Id INT, Name VARCHAR(50))
INSERT INTO TVPTest
SELECT 1, 'abc' UNION ALL
SELECT 2, 'bcd' UNION ALL
SELECT 3, 'cde' UNION ALL
SELECT 4, 'def' UNION ALL
SELECT 5, 'efg'

CREATE TYPE TVPtbl AS TABLE (Id INT, Name VARCHAR(50))
GO

CREATE PROCEDURE dbo.TVPTestProc(@tvp_variable TVPtbl READONLY)
AS
BEGIN
      UPDATE T SET T.Name = V.Name
      FROM TVPTest AS T
      INNER JOIN @tvp_variable AS V ON T.Id = V.Id
END
GO

SELECT * FROM TVPTest

DECLARE @tbl AS TVPtbl
INSERT INTO @tbl
SELECT 1, 'aaa' UNION ALL
SELECT 2, 'bbb' UNION ALL
SELECT 3, 'ccc' UNION ALL
SELECT 4, 'ddd' UNION ALL
SELECT 5, 'eee'

EXEC dbo.TVPTestProc @tvp_variable = @tbl

SELECT * FROM TVPTest

DROP PROCEDURE TVPTestProc
DROP TYPE TVPtbl
DROP TABLE TVPTest
GO

3   Merge Statement

Merge Statement has the ability to perform multiple DML (INS/ UPD/ DEL) operations in a single statement.
*** Useful to synchronize target with the sources
*** Can perform UPSERTS (INSERT + UPDATE)
*** Since all the DML can be processed in a single statement, it increases the performance of the query

MERGE INTO Taget AS T USING Source AS S ON Condition
WHEN MATCHED AND (T.Col2 != S.Col2 OR T.Col3 != S.Col3) THEN -- Row Exists, data is different
UPDATE SET T.Col2 = S.Col2, T.Col3 = S.Col3
WHEN NOT MATCHED THEN -- Row exists in source but not in target
INSERT VALUES(S.Col1, S.Col2....,S.Coln)
WHEN SOURCE NOT MATCHED THEN -- Row not in Source but found in target
DELETE;


CREATE TABLE MergeTarget (ProductId INT, TotalSale INT)
CREATE TABLE MergeSource (ProductId INT, QtySoldToday INT)

INSERT MergeSource
SELECT 1, 12 UNION ALL
SELECT 2, 20 UNION ALL
SELECT 3, 15 UNION ALL
SELECT 4, 27 UNION ALL
SELECT 5, 15 UNION ALL
SELECT 6, 25 UNION ALL
SELECT 7, 12

INSERT MergeTarget
SELECT 1, 8 UNION ALL
SELECT 2, 20 UNION ALL
SELECT 3, 15 UNION ALL
SELECT 4, 27 UNION ALL
SELECT 5, 15 UNION ALL
SELECT 6, 12 UNION ALL
SELECT 10, 99

SELECT * FROM MergeSource
SELECT * FROM MergeTarget
SELECT * INTO #T FROM MergeTarget WHERE 1 = 0 -- Temp table to track the changes

CREATE TABLE #T (Action VARCHAR(50), ProductId INT, NewTotalQty INT, PreviousTotalQty INT)

MERGE MergeTarget AS T USING MergeSource AS S ON T.ProductId = S.ProductId
WHEN MATCHED THEN
UPDATE SET T.TotalSale += S.QtySoldToday
WHEN NOT MATCHED THEN
INSERT VALUES (S.ProductId, QtySoldToday)
WHEN NOT MATCHED BY SOURCE THEN
DELETE
OUTPUT $action , Inserted.ProductID, Inserted.TotalSale AS NewQty, Deleted.TotalSale AS PreviousQty INTO #T;

SELECT * FROM #T
SELECT * FROM MergeSource
SELECT * FROM MergeTarget


DROP TABLE MergeSource
DROP TABLE MergeTarget
DROP TABLE #T

4   Large UDTs

Large user-defined types allow users to expand the size of defined data types by eliminating the 8-KB limit. SQL Server 2005 allowed for user defined types (UDTs) and user defined aggregates in the CLR up to 8000 bytes only, but SQL Server 2008 increases this all the way to 2GB.

If the UDT value does not exceed 8,000 bytes, the database system treats it as an inline value as in SQL Server 2005. If it exceeds 8,000 bytes, the system treats it as a large object and reports its size as “unlimited.”

5   SQL CLR Enhancements

*** Large Aggregates - SQL Server 2008 allows for user defined aggregates in the CLR all the way to 2GB erasing the 8K limit.
*** Easy access to .Net Framework functionality
*** Ability to register static methods as UDFs
*** Multi input aggregates
*** Specifying sort order and uniqueness to TVFs

6   Grouping Sets

It is an extension to the GROUP BY clause. It gives the ability to perform multiple groupings in the same query that gives the result set UNION ALL of the groupings made individually on the query.

*** Makes aggregation querying and reporting much faster


CREATE TABLE #Cars (Id INT, MakeYear INT, Colour VARCHAR(30))

INSERT INTO #Cars
SELECT 1, 2005, 'Blue' UNION ALL
SELECT 2, 2005, 'Red' UNION ALL
SELECT 3, 2006, 'Red' UNION ALL
SELECT 4, 2006, 'Blue' UNION ALL
SELECT 5, 2007, 'Red' UNION ALL
SELECT 6, 2007, 'Blue' UNION ALL
SELECT 7, 2008, 'Red' UNION ALL
SELECT 8, 2008, 'Red'

SELECT COUNT(*) AS Knt, MakeYear FROM #Cars GROUP BY MakeYear

SELECT COUNT(*) AS Knt, Colour FROM #Cars GROUP BY Colour

SELECT COUNT(*) AS Knt, Colour, MakeYear
FROM #Cars
GROUP BY GROUPING SETS ((Colour)
                                    , (MakeYear)
                                    )
DROP TABLE #Cars
GO

7   Object Dependencies Enhancements

*** Reliable discovery of dependencies between the objects
*** Applies to both schema-bound (where an object cannot be deleted as another object depends on it) and non-schema-bound objects (where an object can be deleted and the other object still depends on it).

*** Discover all objects that
                * Depends on a given object
                * A given object depends
                * Depends on another database
                * Perform distributed queries using four-part names

Sys.sql_expression_dependencies

* New ctatalog view that replaces sys.sql_dependencies
* Traces schema-bound and non-schema-bound              dependencies
* Traces cross-database and cross-server references

SELECT * FROM Sys.sql_expression_dependencies
WHERE referenced_id = OBJECT_ID(N'Production.Product')

Sys.dm_sql_referenced_entities

                * New Dynamic management view
                * Replaces sp_depends
                * Returns a row for each entity referenced by a given object

SELECT referenced_schema_name, referenced_entity_name, referenced_id, referenced_class_desc, is_caller_dependent
FROM sys.dm_sql_referenced_entities ('MyDBObject', 'OBJECT');

Sys.dm_sql_referencing_entities

                * New Dynamic management view
                * Replaces sp_depends
                * Returns a row for each entity referenced by a given object

SELECT referencing_schema_name, referencing_entity_name, referencing_id, referencing_class_desc, is_caller_dependent
FROM sys.dm_sql_referencing_entities ('MyDBObject', 'OBJECT');

8   T-SQL Delighters

Variable Initialization during Declaration

DECLARE @int INT = 6, @char VARCHAR(20) = 'String'
SELECT @int AS Innteger, @char AS String
GO

Insert multiple rows through VALUE clause of a single insert statement

CREATE TABLE #MusicInstruments(Id INT, Instrument VARCHAR(50))

INSERT INTO #MusicInstruments
VALUES(1, 'Guitar')
,(2, 'Flute')
,(3, 'Piano')
,(4, 'Table')
,(5, 'Veena')

SELECT * FROM #MusicInstruments

DROP TABLE #MusicInstruments
GO

Extending the CONVERT functionality – to convert between the binary hexadecimal data and character data.
SELECT CONVERT(VARCHAR(MAX), SpatialData) AS ConvertedHex FROM SpatialDemo

New collations (for say to add culture – correct weight etc) are introduced.