Jeff Taylor
Principal Data Consultant
Database Consulting, LLC
What's New In SQL Server For The Developer


Jeff Taylor
Principal Data Consultant
Database Consulting, LLC





Super Hero Suit


JaxData.org
Every 3rd Wednesday 6-8pm
Except for May, November and December


Jeff Taylor
Principal Data Consultant
Database Consulting, LLC
What's New In SQL Server For The Developer


Questions
- What is your role?
- Which version of SQL Server do you currently use?
- What Edition 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 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
--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
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
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
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 tables
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 tables
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 = [History].[MyTemporalTable]))
GO
Temporal tables
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 tables
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 tables
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
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+ |



ConCAt_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}', '[]{}', '()()');
-
TRANSLATE ( inputString, characters, translations)
- 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
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.
SQL Server 2022
String_Split
Ordinal
SELECT
a.Authors,
TRIM(a1.value) AS Author,
TRIM(a2.value) AS Author
FROM
stg.Authors AS a
CROSS APPLY STRING_SPLIT(a.Authors, ',', 1) AS a1
CROSS APPLY STRING_SPLIT(a.Authors, ',', 2) AS a2
WHERE
a1.ordinal = 1
AND a2.ordinal = 2
GROUP BY
a.Authors,
a1.value,
a2.value;

Generate_Series
Start/Stop
SELECT
value
FROM
GENERATE_SERIES(1, 10);

Generate_Series
Start/Stop/Step
SELECT
value
FROM
GENERATE_SERIES(1, 50, 5);

Generate_Series
Start/Stop/Step/Decimal
DECLARE @start decimal(2, 1) = 0.0;
DECLARE @stop decimal(2, 1) = 1.0;
DECLARE @step decimal(2, 1) = 0.1;
SELECT
value
FROM
GENERATE_SERIES(@start, @stop, @step);

Logical Function
Least/Greatest SELECT
SELECT
LEAST('5.44', 7.434, N'9') AS LeastVal;
GO
SELECT
GREATEST('5.44', 7.434, N'9') AS GreatestVal;
GO

Logical Function
Greatest WHERE
SELECT
a.PublishedDate,
a.JournalDate
FROM
dbo.Articles AS a
WHERE
GREATEST(a.PublishedDate, a.JournalDate) >= '2019-07-01'
AND a.JournalDate IS NOT NULL;

ISJSON
Type Constraint
SELECT
id,
JSONValue
FROM
dbo.Table
WHERE
ISJSON(JSONValue, SCALAR) = 1
Value | Description |
---|---|
VALUE | Tests for a valid JSON value. This can be a JSON object, array, number, string or one of the three literal values (false, true, null) |
ARRAY | Tests for a valid JSON array |
OBJECT | Tests for a valid JSON object |
SCALAR | Tests for a valid JSON scalar – number or string |
JSON_Path_Exists
DECLARE @jsonInfo NVARCHAR(MAX)
SET @jsonInfo=N'{"info":{"address":[{"city":"Jacksonville"},{"City":"Miami"}]}}';
SELECT JSON_PATH_EXISTS(@jsonInfo,'$.info.address'); -- 1
DECLARE @jsonInfo NVARCHAR(MAX)
SET @jsonInfo=N'{"info":{"address":[{"City":"Jacksonville"},{"City":"Orlando"}]}}';
SELECT JSON_PATH_EXISTS(@jsonInfo,'$.info.addresses'); -- 0
JSON_ARRAY
SELECT
s.session_id,
JSON_ARRAY(s.host_name, s.program_name, s.client_interface_name)
FROM
sys.dm_exec_sessions AS s
WHERE
s.is_user_process = 1;
session_id | info |
---|---|
52 | ["WIN16-VM","Microsoft SQL Server Management Studio - Query",".Net SqlClient Data Provider"] |
55 | ["WIN16-VM","Microsoft SQL Server Management Studio - Query",".Net SqlClient Data Provider"] |
56 | ["WIN19-VM","SQLServerCEIP",".Net SqlClient Data Provider"] |
JSON_OBJECT
SELECT
s.session_id,
JSON_OBJECT('security_id':s.security_id, 'login':s.login_name, 'status':s.status) as info
FROM
sys.dm_exec_sessions AS s
WHERE
s.is_user_process = 1;
session_id | info |
---|---|
51 | {"security_id":"AQYAAAAAAAVQAAAAY/0dmFnai5oioQHh9eNArBIkYd4=","login":"NT SERVICE\\SQLTELEMETRY$SQL22","status":"sleeping"} |
52 | {"security_id":"AQUAAAAAAAUVAAAAoGXPfnhLm1/nfIdwAMgbAA==","login":WORKGROUP\\sqluser","status":"running"} |
ISJSON, ARRAY?
DECLARE @JSONValue VARCHAR(MAX), @IsArray BIT;
SELECT @IsArray = ISJSON(d.Value, ARRAY) FROM OPENJSON(@JSONValue) AS d WHERE d.[Key] = 'users';
IF (@IsArray = 1)
BEGIN
UPDATE
u
SET
u.UserName = JSON_VALUE(d.Value, '$.UserName'),
u.UpdatedDateTime = SYSDATETIMEOFFSET()
FROM
OPENJSON(@JSONValue, '$.users') AS d
INNER JOIN Dim.Users AS u
ON a.UserId = CAST(JSON_VALUE(u.Value, '$.UserId') AS INT)
WHERE
ISJSON(@JSONValue) = 1;
END;
ELSE
BEGIN
UPDATE
u
SET
u.UserName = JSON_VALUE(d.Value, '$.UserName'),
u.UpdatedDateTime = SYSDATETIMEOFFSET()
FROM
OPENJSON(@JSONValue) AS d
INNER JOIN Dim.Users AS u
ON a.UserId = CAST(JSON_VALUE(u.Value, '$.UserId') AS INT)
WHERE
ISJSON(@JSONValue) = 1
AND d.[Key] = 'users';
END;
Real World
Window
SELECT
SalesOrderID, ProductID, OrderQty
,SUM(OrderQty) OVER win AS Total
,AVG(OrderQty) OVER win AS [Avg]
,COUNT(OrderQty) OVER win AS [Count]
,MIN(OrderQty) OVER win AS [Min]
,MAX(OrderQty) OVER win AS [Max]
FROM
Sales.SalesOrderDetail
WHERE
SalesOrderID IN(43659, 43664)
WINDOW win AS (PARTITION BY SalesOrderID);
GO
SELECT
SalesOrderID,
ProductID,
OrderQty
,SUM(OrderQty) OVER (PARTITION BY SalesOrderID) AS Total
,AVG(OrderQty) OVER (PARTITION BY SalesOrderID) AS [Avg]
,COUNT(OrderQty) OVER (PARTITION BY SalesOrderID) AS [Count]
,MIN(OrderQty) OVER (PARTITION BY SalesOrderID) AS [Min]
,MAX(OrderQty) OVER (PARTITION BY SalesOrderID) AS [Max]
FROM Sales.SalesOrderDetail
WHERE
SalesOrderID IN(43659, 43664);
GO
Partition vs. Window
Window
;WITH totalOrders AS
(
SELECT
st.StoreId,
st.ReportDate,
ISNULL(st.SalesAmount, 0.00) AS DailyTotal,
SUM(ISNULL(st.SalesAmount, 0.00)) OVER wmtd AS TotalMTD,
SUM(ISNULL(st.SalesAmount, 0.00)) OVER wqtd AS TotalQTD,
SUM(ISNULL(st.SalesAmount, 0.00)) OVER wytd AS TotalYTD,
SUM(ISNULL(st.SalesAmount, 0.00)) OVER r365td AS Rolling365
FROM
dbst.Stores AS st
INNER JOIN Dim.Date AS d
ON d.Date = st.ReportDate
WINDOW
wmtd AS(PARTITION BY st.StoreId, d.YEAR, d.MONTH, d.Quarter ORDER BY st.StoreId, d.Date),
wqtd AS(PARTITION BY st.StoreId, d.YEAR, d.QUARTER ORDER BY st.StoreId, d.Date),
wytd AS(PARTITION BY st.StoreId, d.YEAR ORDER BY st.StoreId, d.Date),
r365td AS(PARTITION BY st.StoreId ORDER BY st.StoreId, d.Date ROWS BETWEEN 364 PRECEDING AND CURRENT ROW)
)
UPDATE
s
SET
s.TotalMTD = totalOrders.TotalMTD,
s.TotalQTD = totalOrders.TotalQTD,
s.TotalYTD = totalOrders.TotalYTD,
s.Rolling365 = totalOrders.Rolling365
FROM
totalOrders
INNER JOIN Fact.Sales AS s
ON totalOrders.StoreId = s.StoreId
Real World
SQL Server 2025 (Preview)
Change Event Streaming
DML
Streams data into Azure Event Hubs
- Build an event-driven system on top of your relational databases
- Synchronize data across systems
- Implement real-time analytics on top of your relational data
- Audit and monitor. Track changes of sensitive data or logging specific events.
- All data changes made by INSERT, UPDATE, and DELETE commands
fuzzy string matching
Fuzzy functions
Fuzzy or approximate string match to check if two strings are similar and calculate difference
-
EDIT_DISTANCE - Calculates the number of insertions, deletions, substitutions, and transpositions needed to transform one string to another.
-
EDIT_DISTANCE_SIMILARITY - Calculates a similarity value ranging from 0 (indicating no match) to 100 (indicating full match).
-
JARO_WINKLER_DISTANCE - Calculates the edit distance between two strings, giving preference to strings that match from the beginning for a set prefix length.
- JARO_WINKLER_SIMILARITY - Calculates a similarity value ranging from 0 (indicating no match) to 1 (indicating full match).
Regular expressions
Functions
Match and manipulate data with regular expressions
- REGEXP_LIKE - Returns a Boolean value that indicates whether the text input matches the regex pattern.
- REGEXP_REPLACE - Returns a modified source string replaced by a replacement string, where occurrence of the regex pattern found
-
REGEXP_SUBSTR - Extracts parts of a string based on a regular expression pattern.
Returns Nth occurrence of a substring that matches the regex pattern. - REGEXP_INSTR - Returns the starting or ending position of the matched substring, depending on the option supplied.
- REGEXP_COUNT - Returns a count of the number of times that regex pattern occurs in a string.
- REGEXP_MATCHES - Returns a table of captured substring(s) that match a regular expression pattern to a string. If no match is found, the function returns no row.
- REGEXP_SPLIT_TO_TABLE - Returns a table of strings split, delimited by the regex pattern. If there's no match to the pattern, the function returns the string.
REST API Support
API Stored Procedure
Call REST APIs directly from SQL Server
EXECUTE @returnValue = sp_invoke_external_rest_endpoint
[ @url = ] N'url'
[ , [ @payload = ] N'request_payload' ]
[ , [ @headers = ] N'http_headers_as_json_array' ]
[ , [ @method = ] 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' ]
[ , [ @timeout = ] seconds ]
[ , [ @credential = ] credential ]
[ , @response OUTPUT ]
[ , [ @retry_count = ] # of retries if there are errors ]
JSON
Data Type
The new JSON data type stores JSON documents in a native binary format that provides benefits over storing in varchar/nvarchar
- More efficient reads, as the document is already parsed
- More efficient writes, as the query can update individual values without accessing the entire document
- More efficient storage, optimized for compression
- No change in compatibility with existing code
JSON
Functions
- ISJSON (Transact-SQL) tests whether a string contains valid JSON.
- JSON_VALUE (Transact-SQL) extracts a scalar value from a JSON string.
- JSON_QUERY (Transact-SQL) extracts an object or an array from a JSON string.
- JSON_MODIFY (Transact-SQL) changes a value in a JSON string.
SELECT Name,
Surname,
JSON_VALUE(jsonCol, '$.info.address.PostCode') AS PostCode,
JSON_VALUE(jsonCol, '$.info.address."Address Line 1"')
+ ' ' + JSON_VALUE(jsonCol, '$.info.address."Address Line 2"') AS Address,
JSON_QUERY(jsonCol, '$.info.skills') AS Skills
FROM People
WHERE ISJSON(jsonCol) > 0
AND JSON_VALUE(jsonCol, '$.info.address.Town') = 'Belgrade'
AND STATUS = 'Active'
ORDER BY JSON_VALUE(jsonCol, '$.info.address.PostCode');
Mathematical
Functions
DATETRUNC
Functions
The DATETRUNC function returns an input date truncated to a specified datepart.
DECLARE @d datetime2 = '2021-12-08 11:30:15.1234567';
SELECT 'Year', DATETRUNC(year, @d);
SELECT 'Quarter', DATETRUNC(quarter, @d);
SELECT 'Month', DATETRUNC(month, @d);
SELECT 'Week', DATETRUNC(week, @d); -- Using the default DATEFIRST setting value of 7
SELECT 'Iso_week', DATETRUNC(iso_week, @d);
SELECT 'DayOfYear', DATETRUNC(dayofyear, @d);
SELECT 'Day', DATETRUNC(day, @d);
SELECT 'Hour', DATETRUNC(hour, @d);
SELECT 'Minute', DATETRUNC(minute, @d);
SELECT 'Second', DATETRUNC(second, @d);
SELECT 'Millisecond', DATETRUNC(millisecond, @d);
SELECT 'Microsecond', DATETRUNC(microsecond, @d);
Year 2021-01-01 00:00:00.0000000
Quarter 2021-10-01 00:00:00.0000000
Month 2021-12-01 00:00:00.0000000
Week 2021-12-05 00:00:00.0000000
Iso_week 2021-12-06 00:00:00.0000000
DayOfYear 2021-12-08 00:00:00.0000000
Day 2021-12-08 00:00:00.0000000
Hour 2021-12-08 11:00:00.0000000
Minute 2021-12-08 11:30:00.0000000
Second 2021-12-08 11:30:15.0000000
Millisecond 2021-12-08 11:30:15.1230000
Microsecond 2021-12-08 11:30:15.1234560
Questions?
- https://msdn.microsoft.com/en-us/library/bb510411(v=sql.110).aspx
- https://msdn.microsoft.com/en-us/library/bb510411(v=sql.120).aspx
- https://msdn.microsoft.com/en-us/library/bb510411.aspx
- https://msdn.microsoft.com/en-us/library/ff848799.aspx
- https://www.microsoft.com/en-us/sql-server/sql-server-editions
- https://docs.microsoft.com/en-us/sql/sql-server/what-s-new-in-sql-server-2022?view=sql-server-ver16
- https://learn.microsoft.com/en-us/sql/sql-server/?view=sql-server-ver17
- https://learn.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-invoke-external-rest-endpoint-transact-sql?view=sql-server-ver17&tabs=request-headers
- https://learn.microsoft.com/en-us/sql/relational-databases/json/json-data-sql-server?view=sql-server-ver17#sql-server-2025-changes
- https://learn.microsoft.com/en-us/sql/t-sql/functions/mathematical-functions-transact-sql?view=sql-server-ver17
- https://learn.microsoft.com/en-us/sql/t-sql/functions/datetrunc-transact-sql?view=sql-server-ver17
Resources
Thank you!
Jeff Taylor
Thank you for attending my session today.
If you have any additional questions, please don't hesitate to reach out.




Session Evaluation
What's New in SQL Server for the Developer
By reviewmydb
What's New in SQL Server for the Developer
What's New in SQL Server for the Developer
- 1