patternsqlModerate
What is the most efficient method of performing the FIZZBUZZ test in SQL Server?
Viewed 0 times
performingthemethodwhatsqlefficientfizzbuzztestservermost
Problem
Is there a more efficient method of obtaining a list of numbers from 1 to 49 with a column containing the words
My attempts are (CAUTION, this will empty your procedure cache, so DON'T RUN ON a PRODUCTION BOX):
I've modified my attempts to run each set of statements 100 times each, then s
FIZZ when the number can be evenly divided by 3, BUZZ when the number can be evenly divided by 5, and FIZZBUZZ when the number can be evenly divided by both 3 and 5?My attempts are (CAUTION, this will empty your procedure cache, so DON'T RUN ON a PRODUCTION BOX):
DBCC FREEPROCCACHE
GO
/*VARIANT1*/
;WITH t AS (
SELECT RowNum = ROW_NUMBER() OVER (ORDER BY o.object_id)
FROM sys.objects o
)
SELECT t.RowNum
, CASE WHEN ((t.RowNum % 3) + (t.RowNum % 5)) = 0 THEN 'FIZZBUZZ'
ELSE
CASE WHEN t.RowNum % 3 = 0 THEN 'FIZZ'
ELSE
CASE WHEN t.RowNum % 5 = 0 THEN 'BUZZ'
ELSE ''
END
END
END
FROM t
WHERE t.RowNum < 50;
GO 100
/*VARIANT2*/
DECLARE @t TABLE
(
Num INT NOT NULL PRIMARY KEY CLUSTERED
);
INSERT INTO @t (Num)
SELECT ROW_NUMBER() OVER (ORDER BY o.object_id)
FROM sys.objects o;
SELECT t.Num
, CASE WHEN ((t.Num % 3) + (t.Num % 5)) = 0 THEN 'FIZZBUZZ'
ELSE
CASE WHEN t.Num % 3 = 0 THEN 'FIZZ'
ELSE
CASE WHEN t.Num % 5 = 0 THEN 'BUZZ'
ELSE ''
END
END
END
FROM @t t
WHERE t.Num < 50;
GO 100
SELECT CASE WHEN dest.text LIKE '%/*VARIANT1*/%' THEN 'VARIANT1' ELSE 'VARIANT2' END
, MAX(deqs.execution_count)
, SUM(deqs.total_worker_time)
, AvgWorkerTime = SUM(deqs.total_worker_time) / MAX(deqs.execution_count)
FROM sys.dm_exec_query_stats deqs
CROSS APPLY sys.dm_exec_sql_text(deqs.sql_handle) dest
WHERE (dest.text LIKE '%/*VARIANT1*/%'
OR dest.text LIKE '%/*VARIANT2*/%')
AND dest.text NOT LIKE '%/*NOT_ME!*/%'
GROUP BY CASE WHEN dest.text LIKE '%/*VARIANT1*/%' THEN 'VARIANT1' ELSE 'VARIANT2' END
ORDER BY CASE WHEN dest.text LIKE '%/*VARIANT1*/%' THEN 'VARIANT1' ELSE 'VARIANT2' END
/*NOT_ME!*/;I've modified my attempts to run each set of statements 100 times each, then s
Solution
Using a SQL Server 2014 memory optimized table and a natively compiled procedure:
Native procedure:
Test:
Typical results:
This writes the procedure output to an in-memory table variable, because otherwise we're just testing the speed of displaying results in SSMS.
One million rows
The above native procedure takes about 12 seconds to run on 1,000,000 numbers. There are all sorts of faster ways to do the same thing in T-SQL. One I have written before follows. It runs in about 500ms on my laptop on a million rows when the intended parallel plan is achieved:
-- Setup
CREATE DATABASE InMem;
GO
ALTER DATABASE InMem
ADD FILEGROUP FG1
CONTAINS MEMORY_OPTIMIZED_DATA;
GO
ALTER DATABASE InMem
ADD FILE
(
NAME = 'FN1',
-- Change to suit your system
FILENAME = 'C:\Program Files\Microsoft SQL Server\MSSQL12.SQL2014\MSSQL\DATA\FN1.mod'
)
TO FILEGROUP FG1;
GO
USE InMem;
GO
CREATE TYPE dbo.FizzBuzzTableType AS TABLE
(
n integer NOT NULL INDEX i,
FizzBuzz varchar(8) NOT NULL
) WITH (MEMORY_OPTIMIZED = ON);
GONative procedure:
CREATE PROCEDURE dbo.FizzBuzz
WITH
NATIVE_COMPILATION,
SCHEMABINDING,
EXECUTE AS OWNER
AS
BEGIN ATOMIC
WITH
(
TRANSACTION ISOLATION LEVEL = SNAPSHOT,
LANGUAGE = N'english'
)
DECLARE @n AS dbo.FizzBuzzTableType;
DECLARE @i integer = 1;
WHILE @i < 50
BEGIN
IF @i % 15 = 0
BEGIN
INSERT @n (n, FizzBuzz)
VALUES (@i, 'FizzBuzz')
END
ELSE
BEGIN
IF @i % 3 = 0
BEGIN
INSERT @n (n, FizzBuzz)
VALUES (@i, 'Fizz')
END
ELSE
BEGIN
IF @i % 5 = 0
BEGIN
INSERT @n (n, FizzBuzz)
VALUES (@i, 'Buzz')
END
ELSE
BEGIN
INSERT @n (n, FizzBuzz)
VALUES (@i, CONVERT(varchar(8), @i));
END;
END;
END;
SET @i += 1;
END;
SELECT
N.n,
N.FizzBuzz
FROM @n AS N
ORDER BY
N.n;
END;Test:
SET NOCOUNT ON;
PRINT SYSUTCDATETIME();
GO
DECLARE @T AS dbo.FizzBuzzTableType;
INSERT @T (n, FizzBuzz)
EXECUTE dbo.FizzBuzz;
GO 100
PRINT SYSUTCDATETIME();Typical results:
-- 95ms for 100 iterations, < 1ms each
2014-12-31 10:07:13.7993355
Beginning execution loop
Batch execution completed 100 times.
2014-12-31 10:07:13.8943409This writes the procedure output to an in-memory table variable, because otherwise we're just testing the speed of displaying results in SSMS.
One million rows
The above native procedure takes about 12 seconds to run on 1,000,000 numbers. There are all sorts of faster ways to do the same thing in T-SQL. One I have written before follows. It runs in about 500ms on my laptop on a million rows when the intended parallel plan is achieved:
IF OBJECT_ID(N'tempdb..#Result', N'U') IS NOT NULL
DROP TABLE #Result;
IF OBJECT_ID(N'tempdb..#Thousand', N'U') IS NOT NULL
DROP TABLE #Thousand;
SET NOCOUNT ON;
DECLARE @start datetime2(7) = SYSUTCDATETIME();
CREATE TABLE #Thousand
(
n integer NOT NULL,
CONSTRAINT PK_#Thousand
PRIMARY KEY CLUSTERED (n)
);
-- Add 1,000 rows numbered 0-999 to #Thousand
WITH
L1 (n) AS
(
SELECT V.n
FROM
(
VALUES (0), (1), (2), (3), (4),
(5), (6), (7), (8), (9)
) AS V (n)
),
Thousand AS
(
SELECT n =
CONVERT
(
integer,
ROW_NUMBER() OVER (
ORDER BY (SELECT NULL))
- 1
)
FROM L1
CROSS JOIN L1 AS L2
CROSS JOIN L1 AS L3
)
INSERT #Thousand (n)
SELECT n
FROM Thousand;
-- To hold the Fizz Buzz output
CREATE TABLE #Result
(
n integer NOT NULL,
result varchar(8) NOT NULL
);
INSERT #Result
SELECT
Million.n,
Million.result
FROM
(
-- Modulo operation to encourage few outer rows parallelism
SELECT n
FROM #Thousand
WHERE n % 1 = 0
) AS T1
-- Outer Apply to keep the Compute Scalar parallel
OUTER APPLY
(
SELECT
F2.n,
F2.result
FROM #Thousand AS T2
CROSS APPLY
(
-- Row numbers 1 to 1,000,000
SELECT (T1.n * 1000) + T2.n + 1
) AS F1 (n)
CROSS APPLY
(
-- The Fizz Buzz bit
SELECT
F1.n,
result =
CASE
WHEN F1.n % 15 = 0 THEN 'FizzBuzz'
WHEN F1.n % 3 = 0 THEN 'Buzz'
WHEN F1.n % 5 = 0 THEN 'Fizz'
ELSE CONVERT(varchar(8), F1.n)
END
) AS F2
) AS Million
OPTION (MAXDOP 4, QUERYTRACEON 9481);
PRINT DATEDIFF(MILLISECOND, @start, SYSUTCDATETIME());Code Snippets
-- Setup
CREATE DATABASE InMem;
GO
ALTER DATABASE InMem
ADD FILEGROUP FG1
CONTAINS MEMORY_OPTIMIZED_DATA;
GO
ALTER DATABASE InMem
ADD FILE
(
NAME = 'FN1',
-- Change to suit your system
FILENAME = 'C:\Program Files\Microsoft SQL Server\MSSQL12.SQL2014\MSSQL\DATA\FN1.mod'
)
TO FILEGROUP FG1;
GO
USE InMem;
GO
CREATE TYPE dbo.FizzBuzzTableType AS TABLE
(
n integer NOT NULL INDEX i,
FizzBuzz varchar(8) NOT NULL
) WITH (MEMORY_OPTIMIZED = ON);
GOCREATE PROCEDURE dbo.FizzBuzz
WITH
NATIVE_COMPILATION,
SCHEMABINDING,
EXECUTE AS OWNER
AS
BEGIN ATOMIC
WITH
(
TRANSACTION ISOLATION LEVEL = SNAPSHOT,
LANGUAGE = N'english'
)
DECLARE @n AS dbo.FizzBuzzTableType;
DECLARE @i integer = 1;
WHILE @i < 50
BEGIN
IF @i % 15 = 0
BEGIN
INSERT @n (n, FizzBuzz)
VALUES (@i, 'FizzBuzz')
END
ELSE
BEGIN
IF @i % 3 = 0
BEGIN
INSERT @n (n, FizzBuzz)
VALUES (@i, 'Fizz')
END
ELSE
BEGIN
IF @i % 5 = 0
BEGIN
INSERT @n (n, FizzBuzz)
VALUES (@i, 'Buzz')
END
ELSE
BEGIN
INSERT @n (n, FizzBuzz)
VALUES (@i, CONVERT(varchar(8), @i));
END;
END;
END;
SET @i += 1;
END;
SELECT
N.n,
N.FizzBuzz
FROM @n AS N
ORDER BY
N.n;
END;SET NOCOUNT ON;
PRINT SYSUTCDATETIME();
GO
DECLARE @T AS dbo.FizzBuzzTableType;
INSERT @T (n, FizzBuzz)
EXECUTE dbo.FizzBuzz;
GO 100
PRINT SYSUTCDATETIME();-- 95ms for 100 iterations, < 1ms each
2014-12-31 10:07:13.7993355
Beginning execution loop
Batch execution completed 100 times.
2014-12-31 10:07:13.8943409IF OBJECT_ID(N'tempdb..#Result', N'U') IS NOT NULL
DROP TABLE #Result;
IF OBJECT_ID(N'tempdb..#Thousand', N'U') IS NOT NULL
DROP TABLE #Thousand;
SET NOCOUNT ON;
DECLARE @start datetime2(7) = SYSUTCDATETIME();
CREATE TABLE #Thousand
(
n integer NOT NULL,
CONSTRAINT PK_#Thousand
PRIMARY KEY CLUSTERED (n)
);
-- Add 1,000 rows numbered 0-999 to #Thousand
WITH
L1 (n) AS
(
SELECT V.n
FROM
(
VALUES (0), (1), (2), (3), (4),
(5), (6), (7), (8), (9)
) AS V (n)
),
Thousand AS
(
SELECT n =
CONVERT
(
integer,
ROW_NUMBER() OVER (
ORDER BY (SELECT NULL))
- 1
)
FROM L1
CROSS JOIN L1 AS L2
CROSS JOIN L1 AS L3
)
INSERT #Thousand (n)
SELECT n
FROM Thousand;
-- To hold the Fizz Buzz output
CREATE TABLE #Result
(
n integer NOT NULL,
result varchar(8) NOT NULL
);
INSERT #Result
SELECT
Million.n,
Million.result
FROM
(
-- Modulo operation to encourage few outer rows parallelism
SELECT n
FROM #Thousand
WHERE n % 1 = 0
) AS T1
-- Outer Apply to keep the Compute Scalar parallel
OUTER APPLY
(
SELECT
F2.n,
F2.result
FROM #Thousand AS T2
CROSS APPLY
(
-- Row numbers 1 to 1,000,000
SELECT (T1.n * 1000) + T2.n + 1
) AS F1 (n)
CROSS APPLY
(
-- The Fizz Buzz bit
SELECT
F1.n,
result =
CASE
WHEN F1.n % 15 = 0 THEN 'FizzBuzz'
WHEN F1.n % 3 = 0 THEN 'Buzz'
WHEN F1.n % 5 = 0 THEN 'Fizz'
ELSE CONVERT(varchar(8), F1.n)
END
) AS F2
) AS Million
OPTION (MAXDOP 4, QUERYTRACEON 9481);
PRINT DATEDIFF(MILLISECOND, @start, SYSUTCDATETIME());Context
StackExchange Database Administrators Q#87288, answer score: 14
Revisions (0)
No revisions yet.