What's New in SQL Server 2012-2019

for the Developer

Jeff Taylor

ABOUT ME

ABOUT ME

Questions

  • What is your role?


     
  • Which version of SQL Server do you currently use?

SQL Server 2012

IIF

DECLARE @FirstArgument INT = 10;
DECLARE @SecondArgument INT = 20;
SELECT
    IIF(@FirstArgument > @SecondArgument, 'TRUE', 'FALSE') AS [TrueFalse],
    IIF(@FirstArgument > @SecondArgument, 1, 0) AS [BitResult];

IIF

DECLARE @StudentGrades TABLE 
(
	Name VARCHAR(100), 
	Mark INT
)
INSERT @StudentGrades VALUES('Dilbert',98)
INSERT @StudentGrades VALUES('Pointy-Haired Boss',64)
INSERT @StudentGrades VALUES('Dogbert',83)
INSERT @StudentGrades VALUES('Wally',75)
INSERT @StudentGrades VALUES('Asok',65)
 
SELECT 
	Name, 
	IIF( Mark BETWEEN 90 AND 100, 'A Grade',
	IIF( Mark BETWEEN 80 AND 89,  'B Grade',
	IIF( Mark BETWEEN 70 AND 79,  'C Grade',
	IIF( Mark BETWEEN 66 AND 69,  'D Grade',
	IIF( Mark < 65, 'Failed', 'E Grade' ))))) AS Grade
FROM 
	@StudentGrades

Choose

Similar concept to coding enums

SELECT CHOOSE(2, 'Female', 'Male', 'Unknown') AS [Gender];
SELECT CHOOSE(4, 'Female', 'Male', 'Unknown') AS [Gender];

Returns 'Male'

Returns NULL

SELECT TOP 5
    DATEPART(DD, l.[Date]) AS [Date],
    CHOOSE(DATEPART(MM, l.[Date]), 'January', 'February', 'March', 'April', 'May', 'June', 
                                   'July', 'August', 'September', 'October', 'November', 
                                   'December') [Month],
    DATEPART(YYYY, l.[Date]) AS [Year]
FROM
    dbo.[Log] AS l
ORDER BY
    l.Id DESC;

Choose

SELECT 
    COUNT(l.id) AS LoggedIssues,
    CHOOSE(DATEPART(MM, l.[Date]), 'January', 'February', 'March', 'April', 'May', 'June',
                                   'July', 'August', 'September', 'October', 'November', 
                                   'December') [Month],
    DATEPART(YYYY, l.[Date]) AS [Year],
    IIF(DATEPART(MM, l.[Date]) = 1 or DATEPART(MM, l.[Date]) BETWEEN 10 AND 12, 
                            'Holiday Season', 'Regular Season') AS HolidaySeason,
    CHOOSE(MONTH(l.[Date]),'Winter','Winter', 'Spring','Spring','Spring','Summer','Summer', 
                           'Summer','Autumn','Autumn','Autumn','Winter') AS WeatherSeason
FROM
    dbo.[Log] AS l
WHERE
	l.[Date] IS NOT NULL
GROUP BY
    DATEPART(MM, l.[Date]),
    DATEPART(YYYY, l.[Date])
ORDER BY
    1 desc

Choose

Declares @DaysOfWeek TABLE (DayOfWeek INT)
INSERT @DaysOfWeek (DayOfWeek) VALUES (1), (2), (3), (4), (5), (6), (7);

SELECT
	[DayOfWeek],
 
	CHOOSE([DayOfWeek],'Sunday','Monday', 'Tuesday', 'Wednesday','Thursday','Friday'
                           ,'Saturday') AS [Choose-Function],
 
	IIF([DayOfWeek] = 1, 'Sunday', 
		IIF([DayOfWeek] = 2, 'Monday', 
			IIF([DayOfWeek] = 3, 'Tuesday', 
				IIF([DayOfWeek] = 4, 'Wednesday', 
					IIF([DayOfWeek] = 5, 'Thursday', 
						IIF([DayOfWeek] = 6, 'Friday', 
                                                    'Saturday')))))) AS [IIF-Function],
 
	CASE [DayOfWeek]
		WHEN 1 THEN 'Sunday'
		WHEN 2 THEN 'Monday'
		WHEN 3 THEN 'Tuesday'
		WHEN 4 THEN 'Wednesday'
		WHEN 5 THEN 'Thursday'
		WHEN 6 THEN 'Friday'
		WHEN 7 THEN 'Saturday'
	END AS [Case-Function]
FROM
	@DaysOfWeek

Try_Convert()

SELECT 
    CASE WHEN TRY_CONVERT(float, 'test') IS NULL 
    THEN 'Cast failed'
    ELSE 'Cast succeeded'
END AS Result;
GO
SET DATEFORMAT dmy;
SELECT TRY_CONVERT(datetime2, '12/31/2010') AS Result;
GO

Cast failed

NULL

Try_PARSE()

SELECT id, somevalue FROM dbo.TestTry
SELECT CONVERT(INT, id) AS IntegerValue, somevalue FROM dbo.TestTry

SELECT CONVERT(INT, id) AS IntegerValue, somevalue FROM dbo.TestTry WHERE ISNUMERIC(id) = 1;
SELECT 
    CONVERT(INT, id) AS IntegerValue, somevalue 
FROM 
    dbo.TestTry 
WHERE 
    ISNUMERIC(TRY_PARSE(id AS int)) = 1; 

Format

SELECT
    FORMAT(GETDATE(), 'yyyy-MM-dd') AS [ISO Formatted Date],
    FORMAT(GETDATE(), 'MMMM dd, yyyy hh:mm:ss') AS [Custom Formatted Date],
    FORMAT(GETDATE(), 'yyyy-MM-dd hh:mm:ss') AS [Full ISO],
    FORMAT(GETDATE(), 'MMMM dd, yyyy') AS [Long-hand Date (EN)],
    FORMAT(GETDATE(), 'MMMM dd, yyyy', 'fr-FR') AS [French Date]

Dates

DECLARE @d DATETIME = '05/01/2015';
SELECT 
    FORMAT ( @d, 'D', 'en-US' ) AS 'US English Result',
    FORMAT ( @d, 'D', 'en-gb' ) AS 'Great Britain English Result',
    FORMAT ( @d, 'D', 'de-de' ) AS 'German Result',
    FORMAT ( @d, 'D', 'zh-cn' ) AS 'Chinese (Simplified PRC) Result'; 

Format

DECLARE @datePattern DATETIME
SET @datePattern=GETDATE()
 
SELECT 
    FORMAT(@datePattern, 'M') AS MonthDayPattern
    ,FORMAT(@datePattern, 'M', 'hr-HR') AS MmLanguage
    ,FORMAT(@datePattern, 'Y') AS MonthYear
    ,FORMAT(@datePattern, 's') AS SortableDateTime
    ,FORMAT(@datePattern, 'T') AS LongTime
    ,FORMAT(@datePattern, 't') AS ShortTime
    ,FORMAT(@datePattern, 'U') AS UniversalTimeFull
    ,FORMAT(@datePattern, 'u') AS UniversalTimeShort
    ,FORMAT(@datePattern, 'O') AS RoundTripPattern_SameOutputPattern
    ,FORMAT(@datePattern, 'R') AS RFC1123Pattern_SameOutputPattern

Dates

Format

DECLARE @Era DATETIME
SET @Era=GETDATE()
 
SELECT 
    FORMAT(@Era, 'MM/dd/yyyy g') AS Era
    ,FORMAT(@Era, 'MM/dd/yyyy g', 'fr-FR') as FrenchEra

Dates

Format

DECLARE @1224hour DATETIME
SET @1224hour='5/9/2001 4:54:08 PM'
SELECT 
    FORMAT(@1224hour, 'h ') AS [12hr-4]
    ,FORMAT(@1224hour, 'h:m:s') AS [12hr-h:m:s]
    ,FORMAT(@1224hour, 'h:m:s', 'fr-FR') AS [12hr-4:54:8]
    ,FORMAT(@1224hour, 'hh') AS [12hr-04]
    ,FORMAT(@1224hour, 'hh:m:s') AS [12hr-04:54:8]
    ,FORMAT(@1224hour, 'hh:m:s', 'fr-FR') AS [12hr-04:54:8]
SELECT
    FORMAT(@1224hour, 'H ') AS [24h-16]
    ,FORMAT(@1224hour, 'H:m:s') AS [24h-16:54:8]
    ,FORMAT(@1224hour, 'H:m:s', 'fr-FR') AS [24h-16:54:8]
    ,FORMAT(@1224hour, 'HH') AS [24h-16]
    ,FORMAT(@1224hour, 'HH:m:s') AS [24h-16:54:8]
    ,FORMAT(@1224hour, 'HH:m:s', 'fr-FR') AS [24h-16:54:8]

Time

Format

SELECT
    FORMAT(22.7, 'C', 'en-US') AS [US Currency],
    FORMAT(222460.7, 'C', 'en-US') AS [US Currency],
    FORMAT(1222460.7, 'C', 'en-US') AS [US Currency],
    FORMAT(22.7, 'C', 'en-GB') AS [UK Currency],
    FORMAT(222460.7, 'C', 'en-GB') AS [UK Currency],
    FORMAT(1222460.7, 'C', 'en-GB') AS [UK Currency]

Currency

Format

--Fixed Point to String
DECLARE @fp FLOAT = 18934.1879
SELECT 
    FORMAT(@fp, 'F') AS F,
    FORMAT(@fp, 'F1') AS F1,
    FORMAT(@fp, 'F2') AS F2,
    FORMAT(@fp, 'F3') AS F3,
    FORMAT(@fp, 'F5') AS F5,
    FORMAT(-@fp, 'F0') AS F0

Fix Point

Format

DECLARE @p FLOAT =.2468013
SELECT 
    FORMAT(@p, 'P') AS PercentValue,
    FORMAT(@p, 'P', 'hr-HR') AS CroatiaPercentValue

Percent/Decimal

DECLARE @dp FLOAT = 34.5
SELECT 
    FORMAT(@dp, '0.0') AS OneDecimalPlace,
    FORMAT(@dp, '00') AS NoDecimalPlace,
    FORMAT(@dp, '0000') AS PaddedNoDecimal,
    FORMAT(@dp, '0000.00') AS PaddedDecimalPlace

Format

DECLARE @h INT = 123456789
SELECT 
    FORMAT(@h, 'X') AS Hex,
    FORMAT(@h, 'x') AS HexLower,
    FORMAT(@h, 'X12') AS Hex12

HEX/Degrees/Number

DECLARE @t FLOAT = 68    
SELECT FORMAT(@t, '# "degrees"') AS [Degrees]
DECLARE @num FLOAT = 012.30
SELECT FORMAT(@num, '###.##') AS NumberFormat

Format

DECLARE @posValue FLOAT = 1234;
DECLARE @negValue FLOAT = -1234;
DECLARE @zeroValue FLOAT = 0;    
SELECT 
    FORMAT(@posValue, '##;(##)') AS Positive,
    FORMAT(@negValue, '##;(##)') AS Negative,
    FORMAT(@zeroValue, '##;(##);N/A') AS Zero

Pos & Neg/Phone

DECLARE @phonenumber INT = 1234567890
SELECT 
    FORMAT(@phonenumber, '(###) ###-####') AS PhoneNumberFormat1,
    FORMAT(@phonenumber, '###-###-####') AS PhoneNumberFormat2,
    FORMAT(@phonenumber, '###\.###\.####') AS PhoneNumberFormat3

Try/Catch

DECLARE @ErrorMessage NVARCHAR(4000), @ErrorSeverity INT, @ErrorState INT;

BEGIN TRANSACTION
BEGIN TRY
    DECLARE @RecordCount INT = 1, @Updatecount INT = 0;
    IF @RecordCount <> @Updatecount
        BEGIN
	    SELECT @ErrorMessage = 'You cannot divide by zero.', @ErrorSeverity = 16,
                   @ErrorState = 1
	    RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState);
	END
END TRY
BEGIN CATCH
    PRINT 'FAILURE. AUTOMATIC ROLLBACK TRANSACTION'
    ROLLBACK TRANSACTION
    SELECT @ErrorMessage = ERROR_MESSAGE(), @ErrorSeverity = ERROR_SEVERITY(), 
           @ErrorState = ERROR_STATE();
    RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState);
END CATCH

RaiseError - Old

Try/Catch

--Set Your own, not required to use sys.messages
--Use in CLR
DECLARE @ErrorMessage NVARCHAR(4000), @ErrorSeverity INT, @ErrorState INT;

BEGIN TRANSACTION
BEGIN TRY
    SELECT @ErrorMessage = 'Because I want to...', @ErrorSeverity = 500001, 
           @ErrorState = 1;
    THROW @ErrorSeverity, @ErrorMessage, @ErrorState;

    DECLARE @RecordCount INT = 1, @Updatecount INT = 0;
    IF @RecordCount <> @Updatecount
    BEGIN
    	SELECT @ErrorMessage = 'You cannot divide by zero.', 
               @ErrorSeverity = 500001, @ErrorState = 1;
	THROW @ErrorSeverity, @ErrorMessage, @ErrorState;
    END
END TRY
BEGIN CATCH
    PRINT 'FAILURE. AUTOMATIC ROLLBACK TRANSACTION'
    ROLLBACK TRANSACTION;
    THROW
END CATCH

Throw - New

Sequence

CREATE SEQUENCE dbo.LogSequence AS int
        START WITH 1 INCREMENT BY 1;
CREATE SEQUENCE [dbo].[LogSequence] 
 AS [INT]
 START WITH 1
 INCREMENT BY 1
 -2147483648 MINVALUE
 MAXVALUE 2147483647
 CACHE 
GO
DECLARE @NextSequence int
SET  @NextSequence = NEXT VALUE FOR dbo.LogSequence;

INSERT INTO dbo.LogTable (LogId, LogValue)
VALUES (@NextSequence, 
       '@NextSequence will have a value of '+ CAST(@NextSequence AS VARCHAR) + '.')
SELECT current_value FROM sys.sequences WHERE name = 'LogSequence';

Sequence

CREATE SEQUENCE dbo.TicketSequence AS int
        START WITH -2147483648 INCREMENT BY 1;
SELECT current_value FROM sys.sequences WHERE name = 'TicketSequence';
INSERT INTO dbo.ServiceTickets (Department) VALUES ('M')
CREATE TABLE dbo.ServiceTickets
(
 TicketID INT PRIMARY KEY CLUSTERED DEFAULT NEXT VALUE FOR TicketSequence,
 Department CHAR(1) NOT NULL CHECK (Department IN ('M', 'F'))
) ON [FGData];
CREATE SEQUENCE [dbo].[TicketSequence] 
 AS [INT]
 START WITH -2147483648
 INCREMENT BY 1
 -2147483648 MINVALUE
 MAXVALUE 2147483647
 CACHE 
GO

Sequence

USE [Utility];
GO
DECLARE
    @CurrentMax INT,
    @CreateSequence VARCHAR(MAX);
SELECT
    @CurrentMax = CAST(current_value AS INT)
FROM
    sys.sequences
WHERE
    name = 'LogSequence';
DROP SEQUENCE [dbo].[LogSequence];

SET @CreateSequence = 'CREATE SEQUENCE [dbo].[LogSequence] 
 AS [INT]
 START WITH ' + CAST(@CurrentMax AS VARCHAR) + '
 INCREMENT BY 1
 -2147483648 MINVALUE
 MAXVALUE 2147483647
 CACHE';

PRINT @CreateSequence;

EXEC(@CreateSequence);

Sequence

[ CYCLE | NO CYCLE ]

Property that specifies whether the sequence object should restart from the minimum value (or maximum for descending sequence objects) or throw an exception when its minimum or maximum value is exceeded. The default cycle option for new sequence objects is NO CYCLE.
Note that cycling restarts from the minimum or maximum value, not from the start value.

CREATE SEQUENCE [dbo].[LogSequence] 
 AS [INT]
 START WITH 1
 INCREMENT BY 1
 -2147483648 MINVALUE
 MAXVALUE 2147483647
 CACHE
 NO CYCLE
GO

Sequence

[ CACHE [<constant> ] | NO CACHE ]

Increases performance for applications that use sequence objects by minimizing the number of disk IOs that are required to generate sequence numbers. It defaults to CACHE.

For example, if a cache size of 50 is chosen, SQL Server does not keep 50 individual values cached. It only caches the current value and the number of values left in the cache. This means that the amount of memory required to store the cache is always two instances of the data type of the sequence object.

CREATE SEQUENCE [dbo].[LogSequence] 
 AS [INT]
 START WITH 1
 INCREMENT BY 1
 -2147483648 MINVALUE
 MAXVALUE 2147483647
 CACHE 50
GO

Contained Databases

  • Much of the metadata that describes a database is maintained in the database. (In addition to, or instead of, maintaining metadata in the master database.)
     
  • User authentication can be performed by the database. (This reduces the databases dependency on the logins of the SQL Server instance.)

Contained Databases

  • Contained database user with passwords - Contained database users with passwords are authenticated by the database.
     
  • Windows principals - Authorized Windows users and members of authorized Windows groups can connect directly to the database and do not need logins in the master database. The database trusts the authentication by Windows.

Contained user - There are two types of users for contained databases.

Contained Databases

  • Database Movement - No Orphaned SQL Users

  • Always On - Allows Users to directly connect to database

  • Initial Database Development  - Developer must consider possible environmental impacts on the new database and program accordingly.

  • Database Administration - Maintaining database settings in the database, instead of in the master database, lets each database owner have more control over their database, without giving the database owner sysadmin permission.

Benefits

Contained Databases

  • Replication, change data capture, and change tracking.
     
  • Numbered procedures - (Overloading SPs has been deprecated)
     
  • Schema-bound objects that depend on built-in functions with collation changes.
     
  • Binding change resulting from collation changes, including references to objects, columns, symbols, or types.

Limitations

Temporary stored procedures are currently permitted. Because temporary stored procedures breach containment, they are not expected to be supported in future versions of contained database.

Pageination

DECLARE @PageNumber INT = 1 , @PageSize INT = 10
;WITH LogTable AS
(
    SELECT
        l.Id, l.Date, l.Exception, l.HostName, l.Level, l.Logger, l.Message,
        l.NestedContext, l.Thread, l.Thread_Context_ID
	,ROW_NUMBER() OVER (ORDER BY l.Date) AS RowNumber
    FROM 
	dbo.[Log] AS l
	
)   
SELECT 
    nl.Id, nl.Date, nl.Exception, nl.HostName, nl.Level, nl.Logger, nl.Message,
    nl.NestedContext, nl.Thread, nl.Thread_Context_ID
FROM 
    LogTable AS nl
WHERE 
    RowNumber BETWEEN ((@PageNumber - 1) * @PageSize) + 1 AND (@PageNumber * @PageSize)
ORDER BY
    nl.Date DESC;

Old Method

Pageination

DECLARE @PageNumber INT = 1 , @PageSize INT = 10;
SELECT
    l.Id, l.Date, l.Exception, l.HostName, l.Level, l.Logger, l.Message,
    l.NestedContext, l.Thread, l.Thread_Context_ID
FROM
    dbo.[Log] AS l
ORDER BY
    l.Date DESC
    OFFSET @PageNumber ROWS
        FETCH NEXT @PageSize ROWS ONLY;

New Method

Pageination

Difference

Old

New

Column Store Index

  • rowstoreA rowstore is data that is logically organized as a table with rows and columns, and then physically stored in a row-wise data format. This has been the traditional way to store relational table data.
     
  • columnstoreA columnstore is data that is logically organized as a table with rows and columns, and physically stored in a column-wise data format.
     
  • columnstore indexA columnstore index is a technology for storing, retrieving and managing data by using a columnar data format, called a columnstore. SQL Server supports both clustered and nonclustered columnstore indexes. Both use the same in-memory columnstore technology, but they do have differences in purpose and in features they support.

What is it?

Column Store Index

  • rowgroups and column segmentsFor high performance and high compression rates, the columnstore index slices the table into groups of rows, called row groups, and then compresses each row group in a column-wise manner. The number of rows in the row group must be large enough to improve compression rates, and small enough to benefit from in-memory operations.
     
  • row groupA rowgroup is a group of rows that are compressed into columnstore format at the same time.
     
  • column segment - A column segment is a column of data from within the rowgroup.
    A rowgroup usually contains the maximum number of rows per rowgroup which is 1,048,576 rows.
    Each rowgroup contains one column segment for every column in the table.
    Each column segment is compressed together and stored on physical media.

What is it?

Column Store Index

  • Non-Clustered - Not updatable
  • Clustered - Updatable (2014)
  • Cannot have more than 1024 columns.
  • A table with a nonclustered columnstore index can have unique constraints, primary key constraints, or foreign key constraints, but the constraints cannot be included in the nonclustered columnstore index.
  • Cannot be created on a view or indexed view.
  • Cannot include a sparse column.
  • Cannot be changed by using the ALTER INDEX statement. To change the nonclustered index, you must drop and re-create the columnstore index instead. You can use ALTER INDEX to disable and rebuild a columnstore index.
  • Cannot be created by using the INCLUDE keyword.
  • Cannot include the ASC or DESC keywords for sorting the index. Columnstore indexes are ordered according to the compression algorithms. (Sorting would eliminate many of the performance benefits.)

Restrictions/Limitations

Column Store Index

  • ntext
  • text
  • image
  • varchar(max) (Can now use in SQL 2017)
  • nvarchar(max) (Can now use in SQL 2017)
  • rowversion
  • timestamp
  • sql_variant
  • CLR types (hierarchyid and spatial types)
  • xml
  • uniqueidentifier (Can now use in 2014)

Restrictions/Limitations

Columns that use any of the following data types cannot be included in a columnstore index:

Column Store Index

  • Page and row compression, and vardecimal storage format (A columnstore index is already compressed in a different format.)
  • Replication
  • Change tracking
  • Change data capture
  • Filestream

Restrictions/Limitations

Columnstore indexes cannot be combined with the following features:

Column Store Index

Example

CREATE CLUSTERED COLUMNSTORE INDEX [CCSI_Log] ON [dbo].[LogCS] 
WITH (DROP_EXISTING = OFF, DATA_COMPRESSION = COLUMNSTORE) ON [FGData]
GO
CREATE NONCLUSTERED COLUMNSTORE INDEX [NCCS_Master] ON [dbo].[LogCS]
(
    [Id],
    [Date],
    [Thread],
    [Level],
    [Logger],
    [NestedContext],
    [Message],
    [Exception],
    [HostName],
    [Thread_Context_ID]
)WITH (DROP_EXISTING = OFF) ON [FGCS]
GO
ALTER TABLE [dbo].[LogCS] REBUILD PARTITION = ALL 
WITH (DATA_COMPRESSION = COLUMNSTORE_ARCHIVE)
GO

Column Store Index

Performance Results

SELECT COUNT(*) AS TotalTableCount FROM dbo.Log;
Table 'Log'. Scan count 5, logical reads 40778, physical reads 0, read-ahead reads 0, 
lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Seconds To Execute: 2
Milliseconds To Execute: 1826
Clustered Column Store Index
Table 'LogCS'. Scan count 5, logical reads 135, physical reads 0, read-ahead reads 0, 
lob logical reads 718, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, 
lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Workfile'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, 
lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Seconds To Execute: 0
Milliseconds To Execute: 46

Test 1 - Log and LogCS tables contain 30,479,827 records.

  • Execute Query using clustered columnstore index.

SELECT COUNT(*) AS TotalTableCount FROM dbo.LogCS;
  • Table has Primary Key Clustered Index and two Non-Clustered Indexes

Column Store Index

Performance Results

SELECT COUNT(*) AS LoggerCount FROM dbo.Log WHERE Logger = 'some value';
No Index
Table 'Log'. Scan count 5, logical reads 40850, physical reads 0, read-ahead reads 0, 
lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Seconds To Execute: 2
Milliseconds To Execute: 2040
Non-Clustered Column Store Index
Table 'LogCS'. Scan count 4, logical reads 0, physical reads 0, read-ahead reads 0, 
lob logical reads 410, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, 
lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Workfile'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, 
lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Seconds To Execute: 0
Milliseconds To Execute: 106

Test 2 - Log and LogCS tables contain 30,479,827 records.

  • Execute Query on column with no index.

  • Execute Query using non-clustered columnstore index.

SELECT COUNT(*) AS LoggerCount FROM dbo.LogCS WHERE Logger = 'some value';

Column Store Index

Performance Results

SELECT COUNT(*) AS LevelCount FROM dbo.Log WHERE Level = 'Error';
Non-Clustered Index
Table 'Log'. Scan count 5, logical reads 18306, physical reads 0, read-ahead reads 0, 
lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Seconds To Execute: 1
Milliseconds To Execute: 510
Non-Clustered Column Store Index
Table 'LogCS'. Scan count 4, logical reads 0, physical reads 0, read-ahead reads 0, 
lob logical reads 390, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, 
lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Workfile'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, 
lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Seconds To Execute: 0
Milliseconds To Execute: 56

Test 3 - Log and LogCS tables contain 30,479,827 records.

  • Execute Query on column with non-clustered index.

  • Execute Query using clustered columnstore index.

SELECT COUNT(*) AS LevelCount FROM dbo.LogCS WHERE Level = 'Error';

Column Store Index

Space

63.1% Space Savings

89.7% Space Savings

End Of Month

Function

DECLARE @date DATETIME = GETDATE();
SELECT
    EOMONTH(@date) AS Result;
GO
DECLARE @date DATETIME = GETDATE();
SELECT
    EOMONTH(@date) AS 'This Month',
    EOMONTH(@date, 1) AS 'Next Month',
    EOMONTH(@date, -1) AS 'Last Month';
GO

SQL Server 2016

Split String

Function

DECLARE	@emails VARCHAR(400) = 'test@test.com,testing@mywebsite.com,demo@test.com';   
SELECT
	value
FROM
	STRING_SPLIT(@emails, ',');  

SELECT
	value
FROM
	STRING_SPLIT('My random sentance for this demo.', ' ');

SELECT 
	IndustryID, 
	IndustryName
FROM 
	dbo.Industries  
	INNER JOIN STRING_SPLIT('1,3,5,7,9,10,14,27,74,86', ',')   
		ON value = IndustryID;

Datediff_Big

Function

DECLARE @StartDate AS DATETIME2(0) = '1/1/2016', @EndDate AS DATETIME2(0) = GETDATE();
--DECLARE @StartDate AS DATETIME2(0) = DATEADD(YEAR,-19,GETDATE()), @EndDate AS DATETIME2(0) = GETDATE();
SELECT 
	DATEDIFF_BIG(YEAR, @StartDate, @EndDate) AS [Year],
	DATEDIFF_BIG(quarter, @StartDate, @EndDate) AS [Quarter],
	DATEDIFF_BIG(month, @StartDate, @EndDate) AS [Month],
	DATEDIFF_BIG(dayofyear, @StartDate, @EndDate) AS [DayOfYear],
	DATEDIFF_BIG(day, @StartDate, @EndDate) [Day],
	DATEDIFF_BIG(week, @StartDate, @EndDate) AS [Week],
	DATEDIFF_BIG(hour, @StartDate, @EndDate) AS [Hour],
	DATEDIFF_BIG(minute, @StartDate, @EndDate) AS [Minute],
	DATEDIFF_BIG(second, @StartDate, @EndDate) AS [Second],
	DATEDIFF_BIG(millisecond, @StartDate, @EndDate) AS [Millisecond];

If EXISTS

Condition

DROP PROCEDURE IF EXISTS dbo.MySP;

DROP INDEX IF EXISTS IX_MyTestTable_Index ON dbo.MyTestTable;

DROP TABLE IF EXISTS dbo.MyTestTable;

DROP SYNONYM IF EXISTS dbo.MySynonym;

DROP TRIGGER IF EXISTS dbo.MyTrigger;

DROP USER IF EXISTS [user_name];

DROP VIEW IF EXISTS dbo.MyView;

DROP TYPE IF EXISTS dbo.MyType;

DROP SCHEMA IF EXISTS Demo;

DROP FUNCTION IF EXISTS dbo.MyFunction;

ALTER TABLE dbo.MyTestTable DROP COLUMN IF EXISTS MyTestColumn;

ALTER TABLE dbo.MyTestTable DROP CONSTRAINT IF EXISTS PK_MyTestTable;

JSON

Native Support

SELECT TOP 10 [AccountId],[EmailHash],[DisplayName],[Age],[Location],[CreationDate],
              [LastAccessDate],[Reputation],[DownVotes],[UpVotes],[Views] 
FROM dbo.Users ORDER BY LastAccessDate DESC
FOR JSON AUTO
[  {
    "AccountId":5638794,
    "DisplayName":"RiaDess",
    "CreationDate":"2015-01-17T13:34:58",
    "LastAccessDate":"2015-03-07T23:51:32.930",
    "Reputation":1,
    "DownVotes":0,
    "UpVotes":0,
    "Views":0
  },
  {
    "AccountId":2348349,
    "DisplayName":"Ed Cottrell",
    "Age":36,
    "Location":"Houston, TX",
    "CreationDate":"2013-10-30T05:47:17",
    "LastAccessDate":"2015-03-07T16:02:03.720",
    "Reputation":103,
    "DownVotes":0,
    "UpVotes":0,
    "Views":1
  },
  {
    "AccountId":384624,
    "DisplayName":"THelper",
    "Location":"The Netherlands",
    "CreationDate":"2011-12-22T08:24:28",
    "LastAccessDate":"2015-03-07T15:19:56.533",
    "Reputation":2065,
    "DownVotes":0,
    "UpVotes":54,
    "Views":6
  },
  {
    "AccountId":45818,
    "DisplayName":"Adam",
    "CreationDate":"2011-07-16T13:39:07",
    "LastAccessDate":"2015-03-06T22:22:21.207",
    "Reputation":1264,
    "DownVotes":0,
    "UpVotes":2,
    "Views":1
  },
  {
    "AccountId":3074278,
    "DisplayName":"Biotechnologist",
    "CreationDate":"2014-01-21T23:49:45",
    "LastAccessDate":"2015-03-04T11:58:03.593",
    "Reputation":106,
    "DownVotes":0,
    "UpVotes":0,
    "Views":0
  },
  {
    "AccountId":5080390,
    "DisplayName":"Sue",
    "Location":"Central Massachusetts",
    "CreationDate":"2015-02-04T18:58:56",
    "LastAccessDate":"2015-03-04T02:28:21.943",
    "Reputation":101,
    "DownVotes":0,
    "UpVotes":2,
    "Views":0
  },
  {
    "AccountId":42117,
    "DisplayName":"Pops",
    "Age":64,
    "Location":"East Coast, United States",
    "CreationDate":"2013-08-26T15:35:51",
    "LastAccessDate":"2015-03-03T21:23:23.593",
    "Reputation":101,
    "DownVotes":0,
    "UpVotes":2,
    "Views":3
  },
  {
    "AccountId":178712,
    "DisplayName":"mehta",
    "Location":"India",
    "CreationDate":"2013-02-04T08:14:42",
    "LastAccessDate":"2015-03-03T19:26:11.760",
    "Reputation":301,
    "DownVotes":0,
    "UpVotes":0,
    "Views":0
  },
  {
    "AccountId":1275707,
    "DisplayName":"kircm",
    "Age":43,
    "Location":"New York",
    "CreationDate":"2012-11-18T22:39:24",
    "LastAccessDate":"2015-03-03T16:55:49.090",
    "Reputation":1,
    "DownVotes":0,
    "UpVotes":0,
    "Views":0
  },
  {
    "AccountId":4521361,
    "DisplayName":"Enthusiastic Student",
    "Age":27,
    "CreationDate":"2014-05-25T21:16:30",
    "LastAccessDate":"2015-03-03T08:54:19.967",
    "Reputation":145,
    "DownVotes":0,
    "UpVotes":0,
    "Views":0
  }]

JSON

Native Support

SELECT TOP 10 [AccountId] AS AccountNumber,[EmailHash],[DisplayName],[Age],
              [Location],[CreationDate], [LastAccessDate],[Reputation],[DownVotes],
              [UpVotes],[Views] 
FROM dbo.Users ORDER BY LastAccessDate DESC 
FOR JSON PATH
[  {
    "AccountNumber":5638794,
    "DisplayName":"RiaDess",
    "CreationDate":"2015-01-17T13:34:58",
    "LastAccessDate":"2015-03-07T23:51:32.930",
    "Reputation":1,
    "DownVotes":0,
    "UpVotes":0,
    "Views":0
  },
  {
    "AccountNumber":2348349,
    "DisplayName":"Ed Cottrell",
    "Age":36,
    "Location":"Houston, TX",
    "CreationDate":"2013-10-30T05:47:17",
    "LastAccessDate":"2015-03-07T16:02:03.720",
    "Reputation":103,
    "DownVotes":0,
    "UpVotes":0,
    "Views":1
  },
  {
    "AccountNumber":384624,
    "DisplayName":"THelper",
    "Location":"The Netherlands",
    "CreationDate":"2011-12-22T08:24:28",
    "LastAccessDate":"2015-03-07T15:19:56.533",
    "Reputation":2065,
    "DownVotes":0,
    "UpVotes":54,
    "Views":6
  },
  {
    "AccountNumber":45818,
    "DisplayName":"Adam",
    "CreationDate":"2011-07-16T13:39:07",
    "LastAccessDate":"2015-03-06T22:22:21.207",
    "Reputation":1264,
    "DownVotes":0,
    "UpVotes":2,
    "Views":1
  },
  {
    "AccountNumber":3074278,
    "DisplayName":"Biotechnologist",
    "CreationDate":"2014-01-21T23:49:45",
    "LastAccessDate":"2015-03-04T11:58:03.593",
    "Reputation":106,
    "DownVotes":0,
    "UpVotes":0,
    "Views":0
  },
  {
    "AccountNumber":5080390,
    "DisplayName":"Sue",
    "Location":"Central Massachusetts",
    "CreationDate":"2015-02-04T18:58:56",
    "LastAccessDate":"2015-03-04T02:28:21.943",
    "Reputation":101,
    "DownVotes":0,
    "UpVotes":2,
    "Views":0
  },
  {
    "AccountNumber":42117,
    "DisplayName":"Pops",
    "Age":64,
    "Location":"East Coast, United States",
    "CreationDate":"2013-08-26T15:35:51",
    "LastAccessDate":"2015-03-03T21:23:23.593",
    "Reputation":101,
    "DownVotes":0,
    "UpVotes":2,
    "Views":3
  },
  {
    "AccountNumber":178712,
    "DisplayName":"mehta",
    "Location":"India",
    "CreationDate":"2013-02-04T08:14:42",
    "LastAccessDate":"2015-03-03T19:26:11.760",
    "Reputation":301,
    "DownVotes":0,
    "UpVotes":0,
    "Views":0
  },
  {
    "AccountNumber":1275707,
    "DisplayName":"kircm",
    "Age":43,
    "Location":"New York",
    "CreationDate":"2012-11-18T22:39:24",
    "LastAccessDate":"2015-03-03T16:55:49.090",
    "Reputation":1,
    "DownVotes":0,
    "UpVotes":0,
    "Views":0
  },
  {
    "AccountNumber":4521361,
    "DisplayName":"Enthusiastic Student",
    "Age":27,
    "CreationDate":"2014-05-25T21:16:30",
    "LastAccessDate":"2015-03-03T08:54:19.967",
    "Reputation":145,
    "DownVotes":0,
    "UpVotes":0,
    "Views":0
  }]

JSON

Native Support

SELECT
    Users.accountid,
    Posts.Id,
    Posts.PostTypeId,
    ISNULL(Posts.title, LEFT(CAST(Posts.body AS VARCHAR(MAX)), 50)) AS Title
FROM
    dbo.Users AS Users
    INNER JOIN dbo.Posts AS Posts
        ON Users.Id = Posts.OwnerUserId
WHERE
    Users.id IN (SELECT TOP 10
                    u.id
                 FROM
                    dbo.users AS u
                 ORDER BY
                    u.LastAccessDate DESC)
ORDER BY
    Users.id
FOR
    JSON AUTO
[
{"accountid":45818,"Posts":[{}]},
{"accountid":384624,
    "Posts":[
        {"Id":192,"PostTypeId":1,"Title":"Why is there an \"untagged\" tag?"},
        {"Id":222,"PostTypeId":1,"Title":"Typo in 'self-managment' tag"},
        {"Id":230,"PostTypeId":1,"Title":"Merge software tag and application tag?"},
        {"Id":250,"PostTypeId":1,"Title":"How do we get this site out of beta?"},
        {"Id":323,"PostTypeId":2,"Title":"<p>I've decided to start keeping track of the PP s"},
        {"Id":346,"PostTypeId":1,"Title":"Remove [productivity] tag once again?"}]},
{"accountid":1275707,"Posts":[{}]},
{"accountid":178712,"Posts":[{}]},
{"accountid":42117,"Posts":[{}]},
{"accountid":2348349,"Posts":[{}]},
{"accountid":3074278,"Posts":[{}]},
{"accountid":4521361,"Posts":[{}]},
{"accountid":5638794,"Posts":[{}]},
{"accountid":5080390,"Posts":[{}]}
]

JSON

Native Support

DECLARE @info NVARCHAR(100) = '{"name":"John","skills":["C#","SQL"]}'
print @info

-- Update name
SET @info = JSON_MODIFY(@info, '$.name', 'Mike')
print @info

-- Insert surname
SET @info = JSON_MODIFY(@info, '$.surname', 'Smith')
print @info

-- Delete name
SET @info = JSON_MODIFY(@info, '$.name', NULL)
print @info

-- Add skill
SET @info = JSON_MODIFY(@info, 'append $.skills', 'Azure')
print @info
go
{"name":"John","skills":["C#","SQL"]}
{"name":"Mike","skills":["C#","SQL"]}
{"name":"Mike","skills":["C#","SQL"],"surname":"Smith"}
{"skills":["C#","SQL"],"surname":"Smith"}
{"skills":["C#","SQL","Azure"],"surname":"Smith"}

JSON

Native Support

DECLARE @info NVARCHAR(100) = '{"name":"John","skills":["C#","SQL"]}'
print @info
-- Multiple updates
SET @info = JSON_MODIFY(JSON_MODIFY(JSON_MODIFY(@info, '$.name', 'Mike'), '$.surname', 'Smith'), 'append $.skills', 'Azure')
print @info
GO

DECLARE @product NVARCHAR(100) = '{"price":49.99}'
print @product
-- Rename property
SET @product = 	JSON_MODIFY(JSON_MODIFY(@product, '$.Price', CAST(JSON_VALUE(@product, '$.price') AS numeric(4,2))), '$.price', NULL)
print @product
GO

DECLARE @stats NVARCHAR(100) = '{"click_count": 173}'
print @stats
-- Increment value
SET @stats = JSON_MODIFY(@stats, '$.click_count',CAST(JSON_VALUE(@stats, '$.click_count') as int) + 1)
print @stats
{"name":"John","skills":["C#","SQL"]}
{"name":"Mike","skills":["C#","SQL","Azure"],"surname":"Smith"}
{"price":49.99}
{"Price":49.99}
{"click_count": 173}
{"click_count": 174}

Compress/Decompress

Function

DECLARE @jsontext VARCHAR(MAX) = '[{"name": "Åland Islands", "code": "AX"},{"name": "Albania", "code": "AL"},{"name": "Algeria", "code": "DZ"},{"name": "American Samoa", "code": "AS"},{"name": "AndorrA", "code": "AD"},{"name": "Angola", "code": "AO"},{"name": "Anguilla", "code": "AI"},{"name": "Antarctica", "code": "AQ"},{"name": "Antigua and Barbuda", "code": "AG"},{"name": "Argentina", "code": "AR"},{"name": "Armenia", "code": "AM"},{"name": "Aruba", "code": "AW"},{"name": "Australia", "code": "AU"},{"name": "Austria", "code": "AT"},{"name": "Azerbaijan", "code": "AZ"},{"name": "Bahamas", "code": "BS"},{"name": "Bahrain", "code": "BH"},{"name": "Bangladesh", "code": "BD"},{"name": "Barbados", "code": "BB"},{"name": "Belarus", "code": "BY"},{"name": "Belgium", "code": "BE"},{"name": "Belize", "code": "BZ"},{"name": "Benin", "code": "BJ"},{"name": "Bermuda", "code": "BM"},{"name": "Bhutan", "code": "BT"},{"name": "Bolivia", "code": "BO"},{"name": "Bosnia and Herzegovina", "code": "BA"},{"name": "Botswana", "code": "BW"},{"name": "Bouvet Island", "code": "BV"},{"name": "Brazil", "code": "BR"},{"name": "British Indian Ocean Territory", "code": "IO"},{"name": "Brunei Darussalam", "code": "BN"},{"name": "Bulgaria", "code": "BG"},{"name": "Burkina Faso", "code": "BF"},{"name": "Burundi", "code": "BI"},{"name": "Cambodia", "code": "KH"},{"name": "Cameroon", "code": "CM"},{"name": "Canada", "code": "CA"},{"name": "Cape Verde", "code": "CV"},{"name": "Cayman Islands", "code": "KY"},{"name": "Central African Republic", "code": "CF"},{"name": "Chad", "code": "TD"},{"name": "Chile", "code": "CL"},{"name": "China", "code": "CN"},{"name": "Christmas Island", "code": "CX"},{"name": "Cocos (Keeling) Islands", "code": "CC"},{"name": "Colombia", "code": "CO"},{"name": "Comoros", "code": "KM"},{"name": "Congo", "code": "CG"},{"name": "Congo, The Democratic Republic of the", "code": "CD"},{"name": "Cook Islands", "code": "CK"},{"name": "Costa Rica", "code": "CR"},{"name": "Cote D"Ivoire", "code": "CI"},{"name": "Croatia", "code": "HR"},{"name": "Cuba", "code": "CU"},{"name": "Cyprus", "code": "CY"},{"name": "Czech Republic", "code": "CZ"},{"name": "Denmark", "code": "DK"},{"name": "Djibouti", "code": "DJ"},{"name": "Dominica", "code": "DM"},{"name": "Dominican Republic", "code": "DO"},{"name": "Ecuador", "code": "EC"},{"name": "Egypt", "code": "EG"},{"name": "El Salvador", "code": "SV"},{"name": "Equatorial Guinea", "code": "GQ"},{"name": "Eritrea", "code": "ER"},{"name": "Estonia", "code": "EE"},{"name": "Ethiopia", "code": "ET"},{"name": "Falkland Islands (Malvinas)", "code": "FK"},{"name": "Faroe Islands", "code": "FO"},{"name": "Fiji", "code": "FJ"},{"name": "Finland", "code": "FI"},{"name": "France", "code": "FR"},{"name": "French Guiana", "code": "GF"},{"name": "French Polynesia", "code": "PF"},{"name": "French Southern Territories", "code": "TF"},{"name": "Gabon", "code": "GA"},{"name": "Gambia", "code": "GM"},{"name": "Georgia", "code": "GE"},{"name": "Germany", "code": "DE"},{"name": "Ghana", "code": "GH"},{"name": "Gibraltar", "code": "GI"},{"name": "Greece", "code": "GR"},{"name": "Greenland", "code": "GL"},{"name": "Grenada", "code": "GD"},{"name": "Guadeloupe", "code": "GP"},{"name": "Guam", "code": "GU"},{"name": "Guatemala", "code": "GT"},{"name": "Guernsey", "code": "GG"},{"name": "Guinea", "code": "GN"},{"name": "Guinea-Bissau", "code": "GW"},{"name": "Guyana", "code": "GY"},{"name": "Haiti", "code": "HT"},{"name": "Heard Island and Mcdonald Islands", "code": "HM"},{"name": "Holy See (Vatican City State)", "code": "VA"},{"name": "Honduras", "code": "HN"},{"name": "Hong Kong", "code": "HK"},{"name": "Hungary", "code": "HU"},{"name": "Iceland", "code": "IS"},{"name": "India", "code": "IN"},{"name": "Indonesia", "code": "ID"},{"name": "Iran, Islamic Republic Of", "code": "IR"},{"name": "Iraq", "code": "IQ"},{"name": "Ireland", "code": "IE"},{"name": "Isle of Man", "code": "IM"},{"name": "Israel", "code": "IL"},{"name": "Italy", "code": "IT"},{"name": "Jamaica", "code": "JM"},{"name": "Japan", "code": "JP"},{"name": "Jersey", "code": "JE"},{"name": "Jordan", "code": "JO"},{"name": "Kazakhstan", "code": "KZ"},{"name": "Kenya", "code": "KE"},{"name": "Kiribati", "code": "KI"},{"name": "Korea, Democratic People"S Republic of", "code": "KP"},{"name": "Korea, Republic of", "code": "KR"},{"name": "Kuwait", "code": "KW"},{"name": "Kyrgyzstan", "code": "KG"},{"name": "Lao People"S Democratic Republic", "code": "LA"},{"name": "Latvia", "code": "LV"},{"name": "Lebanon", "code": "LB"},{"name": "Lesotho", "code": "LS"},{"name": "Liberia", "code": "LR"},{"name": "Libyan Arab Jamahiriya", "code": "LY"},{"name": "Liechtenstein", "code": "LI"},{"name": "Lithuania", "code": "LT"},{"name": "Luxembourg", "code": "LU"},{"name": "Macao", "code": "MO"},{"name": "Macedonia, The Former Yugoslav Republic of", "code": "MK"},{"name": "Madagascar", "code": "MG"},{"name": "Malawi", "code": "MW"},{"name": "Malaysia", "code": "MY"},{"name": "Maldives", "code": "MV"},{"name": "Mali", "code": "ML"},{"name": "Malta", "code": "MT"},{"name": "Marshall Islands", "code": "MH"},{"name": "Martinique", "code": "MQ"},{"name": "Mauritania", "code": "MR"},{"name": "Mauritius", "code": "MU"},{"name": "Mayotte", "code": "YT"},{"name": "Mexico", "code": "MX"},{"name": "Micronesia, Federated States of", "code": "FM"},{"name": "Moldova, Republic of", "code": "MD"},{"name": "Monaco", "code": "MC"},{"name": "Mongolia", "code": "MN"},{"name": "Montserrat", "code": "MS"},{"name": "Morocco", "code": "MA"},{"name": "Mozambique", "code": "MZ"},{"name": "Myanmar", "code": "MM"},{"name": "Namibia", "code": "NA"},{"name": "Nauru", "code": "NR"},{"name": "Nepal", "code": "NP"},{"name": "Netherlands", "code": "NL"},{"name": "Netherlands Antilles", "code": "AN"},{"name": "New Caledonia", "code": "NC"},{"name": "New Zealand", "code": "NZ"},{"name": "Nicaragua", "code": "NI"},{"name": "Niger", "code": "NE"},{"name": "Nigeria", "code": "NG"},{"name": "Niue", "code": "NU"},{"name": "Norfolk Island", "code": "NF"},{"name": "Northern Mariana Islands", "code": "MP"},{"name": "Norway", "code": "NO"},{"name": "Oman", "code": "OM"},{"name": "Pakistan", "code": "PK"},{"name": "Palau", "code": "PW"},{"name": "Palestinian Territory, Occupied", "code": "PS"},{"name": "Panama", "code": "PA"},{"name": "Papua New Guinea", "code": "PG"},{"name": "Paraguay", "code": "PY"},{"name": "Peru", "code": "PE"},{"name": "Philippines", "code": "PH"},{"name": "Pitcairn", "code": "PN"},{"name": "Poland", "code": "PL"},{"name": "Portugal", "code": "PT"},{"name": "Puerto Rico", "code": "PR"},{"name": "Qatar", "code": "QA"},{"name": "Reunion", "code": "RE"},{"name": "Romania", "code": "RO"},{"name": "Russian Federation", "code": "RU"},{"name": "RWANDA", "code": "RW"},{"name": "Saint Helena", "code": "SH"},{"name": "Saint Kitts and Nevis", "code": "KN"},{"name": "Saint Lucia", "code": "LC"},{"name": "Saint Pierre and Miquelon", "code": "PM"},{"name": "Saint Vincent and the Grenadines", "code": "VC"},{"name": "Samoa", "code": "WS"},{"name": "San Marino", "code": "SM"},{"name": "Sao Tome and Principe", "code": "ST"},{"name": "Saudi Arabia", "code": "SA"},{"name": "Senegal", "code": "SN"},{"name": "Serbia and Montenegro", "code": "CS"},{"name": "Seychelles", "code": "SC"},{"name": "Sierra Leone", "code": "SL"},{"name": "Singapore", "code": "SG"},{"name": "Slovakia", "code": "SK"},{"name": "Slovenia", "code": "SI"},{"name": "Solomon Islands", "code": "SB"},{"name": "Somalia", "code": "SO"},{"name": "South Africa", "code": "ZA"},{"name": "South Georgia and the South Sandwich Islands", "code": "GS"},{"name": "Spain", "code": "ES"},{"name": "Sri Lanka", "code": "LK"},{"name": "Sudan", "code": "SD"},{"name": "Suriname", "code": "SR"},{"name": "Svalbard and Jan Mayen", "code": "SJ"},{"name": "Swaziland", "code": "SZ"},{"name": "Sweden", "code": "SE"},{"name": "Switzerland", "code": "CH"},{"name": "Syrian Arab Republic", "code": "SY"},{"name": "Taiwan, Province of China", "code": "TW"},{"name": "Tajikistan", "code": "TJ"},{"name": "Tanzania, United Republic of", "code": "TZ"},{"name": "Thailand", "code": "TH"},{"name": "Timor-Leste", "code": "TL"},{"name": "Togo", "code": "TG"},{"name": "Tokelau", "code": "TK"},{"name": "Tonga", "code": "TO"},{"name": "Trinidad and Tobago", "code": "TT"},{"name": "Tunisia", "code": "TN"},{"name": "Turkey", "code": "TR"},{"name": "Turkmenistan", "code": "TM"},{"name": "Turks and Caicos Islands", "code": "TC"},{"name": "Tuvalu", "code": "TV"},{"name": "Uganda", "code": "UG"},{"name": "Ukraine", "code": "UA"},{"name": "United Arab Emirates", "code": "AE"},{"name": "United Kingdom", "code": "GB"},{"name": "United States", "code": "US"},{"name": "United States Minor Outlying Islands", "code": "UM"},{"name": "Uruguay", "code": "UY"},{"name": "Uzbekistan", "code": "UZ"},{"name": "Vanuatu", "code": "VU"},{"name": "Venezuela", "code": "VE"},{"name": "Viet Nam", "code": "VN"},{"name": "Virgin Islands, British", "code": "VG"},{"name": "Virgin Islands, U.S.", "code": "VI"},{"name": "Wallis and Futuna", "code": "WF"},{"name": "Western Sahara", "code": "EH"},{"name": "Yemen", "code": "YE"},{"name": "Zambia", "code": "ZM"},{"name": "Zimbabwe", "code": "ZW"}]'

DECLARE @TestTable TABLE (  
    Id INT PRIMARY KEY IDENTITY,  
    CompressedData VARBINARY(max),
    UnCompressedData VARCHAR(MAX),
    JSONData AS CAST(decompress(CompressedData) AS VARCHAR(MAX))  
);

INSERT INTO @TestTable (CompressedData, UnCompressedData)
VALUES (COMPRESS(@jsontext), @jsontext)

SELECT UnCompressedData, DATALENGTH(CompressedData) CompressedSize
FROM @TestTable

SELECT CAST(DECOMPRESS(CompressedData) AS VARCHAR(MAX)) AS Decompressed, DATALENGTH(UnCompressedData) UncompressedSize
FROM @TestTable

Live Execution Plan

Performance Results

  • Before 2016, you could only Query Plan after a query executed.

 

  • Now, watch in real-time using LQS.

 

  • Includes number of rows, elapsed time, operator progress and live warnings.

Live Execution Plan

Performance Results

Always Encrypted

Security

Always Encrypted

Security

Always Encrypted

Security

  • Still use TDE
  • It works inflight and at rest.
  • It is supported in .Net Framework 4.6.
  • Other frameworks/driver support coming soon.
  • Some redundancy exist when using TDE and Always Encrypted simultaneously.
  • Create Master Key on server.
  • Create Column Key and place in the certificate store on user machine or web/app server.
  • Big Issue with .Net Framework 4.6.2! (https://blogs.msdn.microsoft.com/sqlreleaseservices/net-4-6-2-framework-client-driver-for-always-encrypted-resulting-in-intermittent-failures-to-decrypt-individual-rows/)

Always Encrypted

Security

There will be two kinds of encryption available in SQL Server 2016

  • Randomized: if you encrypt the same value (“12345″) twice, you’ll get two different encrypted values. Great for super-duper-security, but useless if you need to join between tables or do GROUP BYs.
     
  • Deterministic encryption: if you encrypt the same value multiple times, you’ll always get the same encrypted values. Less secure, but lets you do equality operations, WHERE clauses, SELECT DISTINCT, GROUP BY, etc.

Always Encrypted

Security

CREATE COLUMN MASTER KEY DEFINITION [CMKD]
WITH
(
	KEY_STORE_PROVIDER_NAME = N'MSSQL_CERTIFICATE_STORE',
	KEY_PATH = N'CurrentUser/My/32714BF3CD4EBD97E1E952E8DE2BCC0231521E8C'
)
END
GO

Always Encrypted

Security

CREATE COLUMN ENCRYPTION KEY [CEK]
WITH VALUES
(
	COLUMN MASTER KEY DEFINITION = [CMKD],
	ALGORITHM = 'RSA_OAEP',
	ENCRYPTED_VALUE = 0x016E000001630075007200720065006E00740075007300650072002F006D0079002F0033003200370031003400620066003300630064003400650062006400390037006500310065003900350032006500380064006500320062006300630030003200330031003500320031006500380063007DF88AE1521092180EBD34C56614BD0B8A9F0EDB13C008715003AF315D6A43DEC5DF85B98585CEB4FC0E8B5B81465C1225A70A15016204650EC65B66A926365465D3929673560ABD13D7F1E8F5580DE8176F47D265C605EEF5F1CA85F8223FED54C66E4C4C487899A063BDDA3766764774E9D1A0643BADBC43BF01EE72227A436B7285D11A5F98F0EFD49F8EB9EC7A6A8699CC9AD0A5102878D711F1223BB91F5F284B095F895560C54FC2670A58A4F82AEC75382FF875EDD959FB0EE3D7CE3BB670531CDB0275E2D39C5FC2FF576D345DDBE61D70710BDDC2750AF960CDCE09E8B50DF89FCF2BE77A489B20DB0EF583CADCBDBD7D113FE14B9B73C4BB529B3F55A3FF4AA72C3B808E5F49DC7A98BBFDD2E0DE1F62E1AA65A708DF95F3DEF5668FE9AAB9ED85211B26ACC60810649C6007C73360C5110F961BA2D3039E173B7CB425D81D25FB211A961C262948C039F527B1AD609675DCC7723D05E40BF3A63EEF70E172577328D618624223530179C1224118F07B5AF971AC80EA5E989598D8F8F82B4AC9C48E54E280840A8796C3D109F7BB3F3576C8E4B256B7073DD321B6AAA7B88A7D2189B01E26DCA54F252902FEB0A98A717D00275EECBB9001C25E2EB8547A102D83C874CB682B98EEBA4A1E259D16AA5337E26E159A9C06395A68965EBC621BD8CF4D27F76A2FAF3D2E073DE33FD0C1B662E784678A477F31B7FE4C
)
GO

Always Encrypted

Security

CREATE TABLE [dbo].[Patients](
	[PatientId] [int] IDENTITY(1,1) NOT NULL,
	[SSN] [nvarchar](11) COLLATE Latin1_General_BIN2
                ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK], 
                                ENCRYPTION_TYPE = Deterministic, 
                                ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL,
	[FirstName] [nvarchar](50) NULL,
	[LastName] [nvarchar](50) NULL,
	[MiddleName] [nvarchar](50) NULL,
	[StreetAddress] [nvarchar](50) NULL,
	[City] [nvarchar](50) NULL,
	[ZipCode] [int] NULL,
	[State] [nvarchar](50) NULL,
	[BirthDate] [datetime2](7)
                ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK], 
                                ENCRYPTION_TYPE = Randomized, 
                                ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL,
 CONSTRAINT [PK_Patients] PRIMARY KEY CLUSTERED 
(
	[PatientId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, 
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

Always Encrypted

Security

"Data Source=server63; Integrated Security=true; Column Encryption Setting=enabled";
cmd.CommandText = @"INSERT INTO [dbo].[Patients] 
([SSN], [FirstName], [LastName], [BirthDate]) 
VALUES (@SSN, @FirstName, @LastName, @BirthDate);";
 
 SqlParameter paramSSN = cmd.CreateParameter();
 paramSSN.ParameterName = @"@SSN";
 paramSSN.DbType = DbType.String;
 paramSSN.Direction = ParameterDirection.Input;
 paramSSN.Value = ssn;
 paramSSN.Size = 11;
 cmd.Parameters.Add(paramSSN);
 SqlParameter paramBirthdate = cmd.CreateParameter();
 paramBirthdate.ParameterName = @"@BirthDate";
 paramBirthdate.DbType = DbType.DateTime2;
 paramBirthdate.Direction = ParameterDirection.Input;
 paramBirthdate.Value = birthdate;
 cmd.Parameters.Add(paramBirthdate);
 
 cmd.ExecuteNonQuery();

Always Encrypted

Security

-------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------
|             SSN |        FirstName |         LastName |                 BirthDate |
-------------------------------------------------------------------------------------
|     123-45-6789 |             John |              Doe |      Friday, May 21, 1971 |
-------------------------------------------------------------------------------------
|     111-22-3333 |           Joanne |              Doe | Sunday, December 01, 1974 |
-------------------------------------------------------------------------------------
|     562-00-6354 |          Michael |             Park | Sunday, November 18, 1928 |
-------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------

Always Encrypted

Security

Stretch Database

Archive/Performance

Analytics

Real-Time

PolyBase/R

Data Warehousing

Dynamic Data Masking

Security

CREATE TABLE [dbo].[Employees]
([EmployeeId] [int] NOT NULL IDENTITY(1,1),
[FirstName] [varchar] (25) NOT NULL,
[LastName] [varchar] (25) NOT NULL,
[CreditCard] [varchar](16) NULL,
[Salary] [int] NULL,
[EmailAddress] [varchar] (50) NULL,
[PhoneNumber] [varchar](12) NULL)
GO
ALTER Table [dbo].[Employees] ALTER COLUMN [Salary] 
ADD MASKED WITH (FUNCTION='default()')
GO
ALTER Table [dbo].[Employees] ALTER COLUMN [CreditCard] 
ADD MASKED WITH (FUNCTION='partial(2,"XXXX",2)')
GO
ALTER Table [dbo].[Employees] ALTER COLUMN [EmailAddress] 
ADD MASKED WITH (FUNCTION='email()')
GO
ALTER Table [dbo].[Employees] ALTER COLUMN [PhoneNumber] 
ADD MASKED WITH (FUNCTION = 'partial(5,"XXXXXXX",0)')
GO

Dynamic Data Masking

Security

INSERT INTO [dbo].[Employees] 
([FirstName],[LastName],[CreditCard],[Salary],[EmailAddress],[PhoneNumber])
VALUES 
('Jeff','Taylor','1234123412341234','12000','jtaylor@mydomain.com','123-123-1234')
GO
USE [master]
GO
CREATE LOGIN [TestUser] WITH PASSWORD=N'testuser', DEFAULT_DATABASE=[master],
CHECK_EXPIRATION=OFF, CHECK_POLICY=OFF
GO
USE [Demo16]
GO
CREATE USER [TestUser] FOR LOGIN [TestUser]
GO
ALTER ROLE [db_datareader] ADD MEMBER [TestUser]
GO
EXECUTE AS USER='TestUser';
SELECT * FROM dbo.Employees;
REVERT;

Dynamic Data Masking

Security

GRANT UNMASK TO TestUser;
EXECUTE AS USER = 'TestUser';
SELECT * FROM dbo.Employees;
REVERT; 
-- Removing the UNMASK permission
REVOKE UNMASK TO TestUser;

EXECUTE AS USER='TestUser';
SELECT * FROM dbo.Employees;
REVERT;

Row Level Security

Security

Row Level Security

Security

  • Alter Security Policy to use different function based on time of day or other criteria without disabling Security Policy.
  • Handled on 'Creation' of record, not during execution of query.
  • Works for a simple ID or username, or as much complicated criteria you want.
  • Currently it's 'FILTER' based - You can create any custom filter combination or schedule

Query Data Store

Performance

  • Captures Queries, Query Plans, Runtime Statistics. - Think of it as a flight recorder or black box for your database.
     
  • Policies - Option to 'Force Plans'
     
  • Access full history of query execution
     
  • Quickly pinpoint the most expensive queries.
     
  • Get all queries whose performance regressed over time.
     
  • Identify issues with upgrades

Query Data Store

Performance

Temporal Database

Change Tracking

  • Data is rarely static.
  • Query data as of a particular point in time.
  • Each temporal table consists of two tables actually - on for current data, one for historical data. - Complexity hidden from user.
  • SQL Server maintains validity for periods for current and old records.
  • Provide regulatory compliance and performance auditing.
  • Reverting table to a 'last good known state' without downtime.
  • Perform time-based data analysis.

Temporal Database

Change Tracking

IF OBJECT_ID('dbo.MyTemporalTable', 'U') IS NOT NULL
BEGIN
       IF ((SELECT temporal_type FROM SYS.TABLES WHERE object_id = 
            OBJECT_ID('dbo.MyTemporalTable', 'U')) = 2)
       BEGIN
            ALTER TABLE [dbo].[MyTemporalTable] SET (SYSTEM_VERSIONING = OFF)
       END
       DROP TABLE [dbo].[MyTemporalTable]
       --Had to add this in order to reset, otherwise it brings history table in as 
       --actual table.
       DROP TABLE dbo.MyTemporalTable_history
END
GO

CREATE TABLE [dbo].[MyTemporalTable]
(
       MyTemporalTableId int NOT NULL IDENTITY(1,1),
       UserName varchar(25) NOT NULL,
       UserGroupId int NOT NULL,
       CreatedDatetime datetime2(7) not null CONSTRAINT DF_MyTemporalTable_CreatedDatetime default (getdate()),
       ModifiedDatetime datetime2(7) null,
       SysStartTime datetime2(7) GENERATED ALWAYS AS ROW START NOT NULL,
       SysEndTime datetime2(7) GENERATED ALWAYS AS ROW END NOT NULL,
       PERIOD FOR SYSTEM_TIME(SysStartTime,SysEndTime),
       CONSTRAINT PK_MyTemporalTable PRIMARY KEY (MyTemporalTableId)
)WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [dbo].[MyTemporalTable_history]))
GO

Temporal Database

Change Tracking

--Showing First Insert
SELECT * from dbo.MyTemporalTable FOR SYSTEM_TIME AS OF '2015-06-19 20:32:54.0004984'
--Showing First and Second Insert
SELECT * from dbo.MyTemporalTable FOR SYSTEM_TIME AS OF '2015-06-19 20:33:00.0004984'
--Showing after all three inserts
SELECT * from dbo.MyTemporalTable FOR SYSTEM_TIME AS OF '2015-06-19 20:34:00.0004984'

Temporal Database

Change Tracking

--As Of for Group 1 - Showing Before/After updates/deletes
SELECT * FROM dbo.MyTemporalTable FOR SYSTEM_TIME AS OF '2015-06-19 20:34:23.3944162' 
WHERE [UserGroupId] = 1
SELECT * FROM dbo.MyTemporalTable FOR SYSTEM_TIME AS OF '2015-06-19 20:34:25.3944162' 
WHERE [UserGroupId] = 1
--As Of for Group 2 - Showing Before/After updates/deletes
SELECT * FROM dbo.MyTemporalTable FOR SYSTEM_TIME AS OF '2015-06-19 20:34:23.3944162' 
WHERE [UserGroupId] = 2
SELECT * FROM dbo.MyTemporalTable FOR SYSTEM_TIME AS OF '2015-06-19 20:34:25.3944162' 
WHERE [UserGroupId] = 2

Temporal Database

Change Tracking

--Current State
SELECT * FROM dbo.MyTemporalTable
--Showing Between Start and End
DECLARE @Start datetime2 = '2015-06-19 20:32:54.0004984', 
        @End datetime2 = '2015-06-19 20:32:55.0004984'
SELECT * FROM dbo.MyTemporalTable FOR SYSTEM_TIME BETWEEN @Start and @End 
WHERE [UserGroupId] = 1
SELECT * FROM dbo.MyTemporalTable FOR SYSTEM_TIME BETWEEN @Start and @End 
WHERE [UserGroupId] = 2

In-Memory OLTP

Updates

  • 256GB Limit changed to 2TB
  • Indexes can use any collation. (Performance still best using BIN2, however)
  • ALTER TABLE can be used on memory-optimized tables to add, drop or alter columns, or to add, drop or rebuild indexes. (Rebuilding indexes will require 2x memory)
  • Parallel Plans - Certain operations that use hash indexes can be performed in parallel, if they’re not used in a natively compiled procedure. 
  • TDE now supported for In-Memory OLTP.
  • Multiple Log Readers for system for both recovery and checkpoint.

In-Memory OLTP

Updates

  • Improved scaling with a 4-socket machine.
  • AlwaysOn - Delay of data visibility of in-memory OLTP on the secondary replica limitation is now removed.
  • The garbage collection algorithms have been improved to provide greater scalability. With sufficient processing power, the garbage collection for your DML operations will happen almost instantaneously
  • Unsupported Data Types - datetimeoffset, geography, geometry, hierarchyid, rowversion, xml, sql_variant, User-Defined Types​. (See supported data types https://msdn.microsoft.com/en-us/library/dn133179.aspx)

In-Memory OLTP

Native Compilation

  • LEFT and RIGHT OUTER JOIN
  • SELECT DISTINCT
  • OR and NOT operators
  • Subqueries in all clauses of a SELECT statement
  • Nested stored procedure calls
  • UNION and UNION ALL
  • All built-in math functions

Natively compiled procedures will support a wider range of features, including the following constructs:

SQL Server 2017

OS

Unix

Platform Supported version(s)
SUSE Linux Enterprise Server v12 SP2
Ubuntu 16.04
Docker Engine 1.8+

Contact_WS

Function

CREATE TABLE dbo.Addresses
(
    AddressId INT NOT NULL IDENTITY(1, 1) PRIMARY KEY CLUSTERED,
    UserId INT NOT NULL,
    AddressLine1 VARCHAR(100) NULL,
    AddressLine2 VARCHAR(100) NULL,
    AddressLine3 VARCHAR(100) NULL,
    City VARCHAR(50) NULL,
    [State] VARCHAR(2) NULL,
    ZipCode VARCHAR(10) NULL
) ON FGData;
INSERT INTO dbo.Addresses
	(UserId,AddressLine1,AddressLine2,AddressLine3,City,State,ZipCode)
VALUES
	(1, '123 Main Street', NULL, NULL, 'Jacksonville',  'FL', '32225' ),
	(2, '5th Ave South', 'Suite 500', NULL, 'New York', 'NY','01032'),
	(3, 'Attn: Returns Department', '1 Google Way','Building 25',  'San Fancisco', 'CA','66435');
SELECT
    CONCAT_WS(', ', AddressLine1, AddressLine2, AddressLine3, City, [State], ZipCode) AS MailingAddress
FROM
    dbo.Addresses;

Trim

Function

SELECT TRIM( '     test    ');

SELECT TRIM( '.,! ' FROM  '#     test    .');

SELECT trim('-' FROM CAST(NEWID() AS VARCHAR(36)));

SELECT trim('-, ' FROM '-5E905525-CE5B-');

SELECT trim('.' FROM '.5E905525.CE5B.');

SELECT REPLACE(CAST(NEWID() AS VARCHAR(36)),'-','');

SELECT TRIM( '#.' FROM  '#     test    .');

Translate

Function

SELECT 
    REPLACE(
        REPLACE(
            REPLACE(
                REPLACE('2*[3+4]/{7-2}','[','('), ']', ')'), '{', '('), '}', ')');
SELECT TRANSLATE('2*[3+4]/{7-2}', '[]{}', '()()');
  • Equivalent to multiple replace functions.
     
  • TRANSLATE is always SC collation aware.

Translate

Function

SELECT 
    TRANSLATE('[137.4, 72.3]' , '[,]', '( )') AS Point, 
    TRANSLATE('(137.4 72.3)' , '( )', '[,]') AS Coordinates;
  • GeoJSON points to WKT format and vice versa

Within Group

String_AGG Function

SELECT 
	City, 
    STRING_AGG (EmailAddress, ';') WITHIN GROUP (ORDER BY EmailAddress ASC)
FROM 
	dbo.Users
GROUP BY 
	City;

SQL Server 2019

CTP 2.4

OS

Unix

Platform Supported version(s)
Red Hat Enterprise Linux 7.3, 7.4, 7.5, 7.6
   

Developer Features

  • Use derived table or view aliases in graph match query
     
  • Match support in MERGE DML
     
  • Edge Constraints - Now supports the ability to define cascaded delete actions on an Edge Constraint

SQL Graph Enhancements

Developer Features

  • UTF-8 Support
     
  • Machine Learning Services
     
  • Java extension - Resource Governor tied in
     
  • New Polybase external connectors - SQL Server, Oracle, Teradata and MongoDB
     
  • String or binary data would be truncated in table '%.*ls', column '%.*ls'. Truncated value: '%.*ls'.

SQL Server On Linux

  • MSDTC
     
  • Machine Learning on Linux
     
  • Open LDAP Provider Support
     
  • Microsoft Container Registry

Tuning

  • Columnstore stats in DBCC CLONEDATABASE
     
  • Estimate compression for Columnstore indexes
EXEC sp_estimate_data_compression_savings 'dbo','Votes',NULL,NULL,'COLUMNSTORE'
EXEC sp_estimate_data_compression_savings 'dbo','Votes',NULL,NULL,'COLUMNSTORE_ARCHIVE'

Tuning

  • Troubleshoot page resource waits - DBCC Page not needed - Entire dbcc page header now available
use tempdb
go
SELECT object_name(page_info.object_id), page_info.* 
FROM sys.dm_exec_requests AS d 
  CROSS APPLY sys.fn_PageResCracker(d.page_resource) AS r
  CROSS APPLY sys.dm_db_page_info(r.db_id, r.file_id, r.page_id,'DETAILED')
    AS page_info
GO

Performance and Scale

  • Approximate COUNT DISTINCT
  • Access data sets of millions of rows
  • Aggregates column(s) that have large number of distinct values
  • Responsiveness is more critical than absolute precision.
  • Results are typically within 2% of actual answer.
  • Returns the approx in fraction of time.

Static Data

  • Demo

Questions?

Resources

Contact

What's New in SQL Server for the Developer - OCC

By reviewmydb

What's New in SQL Server for the Developer - OCC

What's New in SQL Server 2012-2019 for the Developer

  • 630