Problem
A zero-knowledge proof is a special way to you, the prover, to show someone else, the verifier, that you know a secret without ever telling them what the secret is. In this article, we look at how you can implement a zero-knowledge proof in SQL Server without using external tools.
Solution
Using a zero-knowledge proof is very useful to preserve information and to retrieve only what you want. The main use case is for private transactions or blockchain.
Examples:
- Finance: a customer can prove they have enough balance to make a transaction without revealing the full account balance.
- Healthcare: a patient can prove they are eligible for a medical trial without exposing their medical history.
- Compliance: a company can prove to a regulator that they processed data according to the rules without sharing any sensitive data.
How It Works
The phase of trusted setup to generate the secret parameters requires security measures to avoid breaches.
The proof is succinct and the verification speed is very fast but recursive proofs are hard and complex.
I will use the Schnorr-style ZKP where everything started with the math-dance between prover and verifier:
- p: is a large prime number that defines the group size
- g: is a generator number that can reach all values in the group when raised to powers
- x: is the prover’s secret private key
- y: is the prover’s public value, computed as

Now, in the challenge-response part, the proof:
- r: is a random number the prover picks
- t: is a commitment mask computed as

- c: is the challenge sent by the verifier
- s: is the prover´s response where

This is how it does the work:
- Prover: computes t and sends it
- Verifier: sends random challenge c
- Prover: replies with s
- Verifier: check if

If it holds, the proof is valid once the prover must know the value of x, which is never revealed.
In summary, the prover hides their secret x behind math with p and g, uses r and t to disguise it, gets a test c, replies with s, and the verifier checks the equation with only public values.
We need to talk about a 256-bit safe prime which is a prime p that is approximately
, about
in size, and is called a safe prime due to
, where q is also prime. The importance of use this is to make it secure because they ensure the math group is large and has no weak shortcuts for attackers. If p value is small make it trivial to use brute-force attack to guess x.
Visual Studio Code
Once I found that SQL Server bigint has a limitation to deal with numbers greater than
, I decided to use as a solution the CLR integration using BigInteger in VB .NET in Visual Studio. You will notice that to deal with big integers I will retrieve them as strings for the purpose to visually follow-up what is going on, but the best format should be varbinary.
Open visual studio:
- Get started choosing Create a new project
- Choose the template of Class Library (.Net framework) – Visual Basic
- I will call the Project SqlMathClr and click Create
- Rename the Class1.vb file to SqlMath.vb
- Replace the content of SqlMath.vb with the respective code below
- Insert a new module called GenSafePrime.vb and replace with the respective code below
- Build the solution
- Record the path of the created SqlMathClr.dll, in my case “E:\VS Projects\SqlMathClr\SqlMathClr\bin\Release”
SqlMath.vb code:
Imports System.Data.SqlTypes
Imports System.Numerics
Imports System.Security.Cryptography
Imports Microsoft.SqlServer.Server
Public Class SqlMath
Private Shared ReadOnly rnd As New Random()
<SqlFunction(DataAccess:=DataAccessKind.None, IsDeterministic:=True, IsPrecise:=True)>
Public Shared Function BigAdd(num1 As SqlString, num2 As SqlString) As SqlString
Dim a As BigInteger = BigInteger.Parse(num1.Value)
Dim b As BigInteger = BigInteger.Parse(num2.Value)
Return New SqlString(BigInteger.Add(a, b).ToString())
End Function
<SqlFunction(DataAccess:=DataAccessKind.None, IsDeterministic:=True, IsPrecise:=True)>
Public Shared Function BigMod(num As SqlString, modNum As SqlString) As SqlString
Dim n As BigInteger = BigInteger.Parse(num.Value)
Dim m As BigInteger = BigInteger.Parse(modNum.Value)
Return New SqlString(BigInteger.Remainder(n, m).ToString())
End Function
<SqlFunction(DataAccess:=DataAccessKind.None, IsDeterministic:=True, IsPrecise:=True)>
Public Shared Function BigModPow(baseNum As SqlString, exponent As SqlString, modNum As SqlString) As SqlString
Dim b As BigInteger = BigInteger.Parse(baseNum.Value)
Dim e As BigInteger = BigInteger.Parse(exponent.Value)
Dim m As BigInteger = BigInteger.Parse(modNum.Value)
Return New SqlString(BigInteger.ModPow(b, e, m).ToString())
End Function
<SqlFunction(DataAccess:=DataAccessKind.None, IsDeterministic:=True, IsPrecise:=True)>
Public Shared Function BigMultiply(num1 As SqlString, num2 As SqlString) As SqlString
Dim a As BigInteger = BigInteger.Parse(num1.Value)
Dim b As BigInteger = BigInteger.Parse(num2.Value)
Return New SqlString(BigInteger.Multiply(a, b).ToString())
End Function
<SqlFunction(DataAccess:=DataAccessKind.None, IsDeterministic:=True, IsPrecise:=True)>
Public Shared Function BigSubtract(num1 As SqlString, num2 As SqlString) As SqlString
Dim a As BigInteger = BigInteger.Parse(num1.Value)
Dim b As BigInteger = BigInteger.Parse(num2.Value)
Return New SqlString(BigInteger.Subtract(a, b).ToString())
End Function
<SqlFunction(DataAccess:=DataAccessKind.None, IsDeterministic:=True, IsPrecise:=True)>
Public Shared Function GuidToBigInteger(g As Guid) As SqlString
Dim guidBytes As Byte() = g.ToByteArray()
Dim bigInt As New BigInteger(guidBytes)
Return New SqlString(bigInt.ToString)
End Function
<SqlFunction(DataAccess:=DataAccessKind.None, IsDeterministic:=True, IsPrecise:=True)>
Public Shared Function HashToBigInteger(t As SqlBytes, mStr As SqlString) As SqlString
Dim maxValue As BigInteger = BigInteger.Parse(mStr.Value)
Dim hb() As Byte = t.Value
Dim hashInt As New BigInteger(hb)
If hashInt.Sign < 0 Then
hashInt = BigInteger.Negate(hashInt)
End If
Dim result As BigInteger = hashInt Mod maxValue
Return New SqlString(result.ToString())
End Function
<SqlFunction(DataAccess:=DataAccessKind.None, IsDeterministic:=False, IsPrecise:=True)>
Public Shared Function CryptoRandomBigInteger(maxStr As SqlString) As SqlString
Dim max As BigInteger = BigInteger.Parse(maxStr.Value)
Dim bytes(max.ToByteArray().Length - 1) As Byte
Using rng As RandomNumberGenerator = RandomNumberGenerator.Create()
rng.GetBytes(bytes)
End Using
Dim value As New BigInteger(bytes)
value = BigInteger.Abs(value) Mod (max - 1) + 1
Return value.ToString()
End Function
<SqlFunction(DataAccess:=DataAccessKind.None, IsDeterministic:=False)>
Public Shared Function GenerateSafePrime(bits As SqlInt32) As SqlString
Dim p As BigInteger = SafePrimeGen(bits.Value)
Return New SqlString(p.ToString())
End Function
End ClassIf you observe the code I am returning for precaution, in all functions, the values as string due to overflow problems with bigint in SQL.
GenSafePrime.vb code:
Imports System.Numerics
Imports System.Security.Cryptography
Module GenSafePrime
Private ReadOnly SmallPrimes As Integer() = {
3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47
}
<System.Runtime.CompilerServices.Extension>
Public Function GetBitLength(value As BigInteger) As Integer
Dim bytes() As Byte = value.ToByteArray()
Dim msb As Byte = bytes(bytes.Length - 1)
Dim bitLen As Integer = (bytes.Length - 1) * 8
While msb <> 0
bitLen += 1
msb >>= 1
End While
Return bitLen
End Function
<System.Runtime.CompilerServices.Extension()>
Public Function GetByteCount(value As BigInteger) As Integer
Return value.ToByteArray().Length
End Function
Public Function IsProbablePrime(n As BigInteger, Optional k As Integer = 40) As Boolean
If n < 2 Then Return False
If n = 2 OrElse n = 3 Then Return True
If n Mod 2 = 0 Then Return False
Dim d As BigInteger = n - 1
Dim r As Integer = 0
While d Mod 2 = 0
d = BigInteger.Divide(d, 2)
r += 1
End While
For i As Integer = 1 To k
Dim a As BigInteger
Do
a = RandomBigInteger(n.GetBitLength())
Loop While a < 2 OrElse a >= n - 2
Dim x As BigInteger = BigInteger.ModPow(a, d, n)
If x = 1 OrElse x = n - 1 Then Continue For
Dim passed As Boolean = False
For j As Integer = 1 To r - 1
x = BigInteger.ModPow(x, 2, n)
If x = n - 1 Then
passed = True
Exit For
End If
Next
If Not passed Then Return False
Next
Return True
End Function
Public Function IsPrime(n As BigInteger) As Boolean
For Each p In SmallPrimes
If n Mod p = 0 Then
Return False
End If
Next
Return True
End Function
Public Function SafePrimeGen(bits As Integer) As BigInteger
Do
Dim q As BigInteger
Do
q = RandomBigInteger(bits - 1) Or 1
Loop Until IsProbablePrime(q)
Dim p As BigInteger = 2 * q + 1
If IsProbablePrime(p) AndAlso p.GetBitLength() = bits Then
Return p
End If
Loop
End Function
Private Function RandomBigInteger(bits As Integer) As BigInteger
Dim byteLen As Integer = (bits + 7) \ 8
Dim bytes(byteLen - 1) As Byte
Using rng As RandomNumberGenerator = RandomNumberGenerator.Create()
rng.GetBytes(bytes)
End Using
bytes(bytes.Length - 1) = bytes(bytes.Length - 1) Or &H80
Return BigInteger.Abs(New BigInteger(bytes))
End Function
End ModuleSQL Server CLR
The first thing that you need to do it to enable CLR if it is not enabled.
-- MSSQLTips (TSQL)
EXEC sp_configure 'clr enabled', 1;
RECONFIGURE;
GOI will create a schema to group in all functions of the CLR.
-- MSSQLTips (TSQL)
IF NOT EXISTS
(SELECT 1
FROM sys.schemas
WHERE [name] = N'AsmMath')
EXEC('CREATE SCHEMA [AsmMath]')
GOIt is necessary for security reasons to add the dll as trusted assembly. Change the path to your destination.
-- MSSQLTips (TSQL)
DECLARE @hash varbinary(64) =
(SELECT HASHBYTES('SHA2_512', BulkColumn)
FROM OPENROWSET(BULK 'E:\VS Projects\SqlMathClr\SqlMathClr\bin\Release\SqlMathClr.dll', SINGLE_BLOB) AS AssemblyBlob);
EXEC sp_add_trusted_assembly
@assembly_hash = @hash,
@description = N'SQL Math CLR assembly';
GONow it is necessary to register the assembly, creating or altering it. Change the path to your destination.
-- MSSQLTips (TSQL)
-- Register assembly
IF NOT EXISTS
(SELECT 1
FROM sys.assemblies
WHERE [name] = N'SqlMathClr')
CREATE ASSEMBLY [AsmMath]
FROM 'E:\VS Projects\SqlMathClr\SqlMathClr\bin\Release\SqlMathClr.dll'
WITH PERMISSION_SET = SAFE;
ELSE
ALTER ASSEMBLY [AsmMath]
FROM 'E:\VS Projects\SqlMathClr\SqlMathClr\bin\Release\SqlMathClr.dll'
WITH PERMISSION_SET = SAFE;
GONow we are going to add all functions to the schema created.
-- MSSQLTips (TSQL)
CREATE FUNCTION AsmMath.BigAdd(@a nvarchar(MAX), @b nvarchar(MAX))
RETURNS nvarchar(MAX)
AS EXTERNAL NAME [AsmMath].[SqlMathClr.SqlMath].BigAdd;
GO
CREATE FUNCTION AsmMath.BigMod(@a nvarchar(MAX), @m nvarchar(MAX))
RETURNS nvarchar(MAX)
AS EXTERNAL NAME [AsmMath].[SqlMathClr.SqlMath].BigMod;
GO
CREATE FUNCTION AsmMath.BigModPow(@a nvarchar(MAX), @exp nvarchar(MAX), @m nvarchar(MAX))
RETURNS nvarchar(MAX)
AS EXTERNAL NAME [AsmMath].[SqlMathClr.SqlMath].BigModPow;
GO
CREATE FUNCTION AsmMath.BigMultiply(@a nvarchar(MAX), @b nvarchar(MAX))
RETURNS nvarchar(MAX)
AS EXTERNAL NAME [AsmMath].[SqlMathClr.SqlMath].BigMultiply;
GO
CREATE FUNCTION AsmMath.BigSubtract(@a nvarchar(MAX), @b nvarchar(MAX))
RETURNS nvarchar(MAX)
AS EXTERNAL NAME [AsmMath].[SqlMathClr.SqlMath].BigSubtract;
GO
CREATE FUNCTION AsmMath.GuidToBigInteger(@g uniqueidentifier)
RETURNS nvarchar(MAX)
AS EXTERNAL NAME [AsmMath].[SqlMathClr.SqlMath].GuidToBigInteger;
GO
CREATE FUNCTION AsmMath.zkCryptoRandomBigInteger(@max nvarchar(MAX))
RETURNS nvarchar(MAX)
AS EXTERNAL NAME [AsmMath].[SqlMathClr.SqlMath].CryptoRandomBigInteger;
GO
CREATE FUNCTION AsmMath.zkGenerateSafePrime(@bits int)
RETURNS nvarchar(MAX)
AS EXTERNAL NAME [AsmMath].[SqlMathClr.SqlMath].GenerateSafePrime;
GO
CREATE FUNCTION [AsmMath].[zkHashToBigInteger] (
@t VARBINARY(MAX), -- must match SqlBytes
@mStr NVARCHAR(MAX) -- must match SqlString
)
RETURNS NVARCHAR(MAX)
AS EXTERNAL NAME [AsmMath].[SqlMathClr.SqlMath].HashToBigInteger;
GOUse this, if you want to verify the assembler details.
-- MSSQLTips (TSQL)
SELECT [name]
,[clr_name]
,[create_date]
,[modify_date]
FROM sys.assemblies
WHERE [name] = 'AsmMath';
GOUse this, in case you need to remove the CLR code.
-- MSSQLTips (TSQL)
DROP FUNCTION IF EXISTS AsmMath.BigAdd;
DROP FUNCTION IF EXISTS AsmMath.BigMod;
DROP FUNCTION IF EXISTS AsmMath.BigModPow;
DROP FUNCTION IF EXISTS AsmMath.BigMultiply;
DROP FUNCTION IF EXISTS AsmMath.BigSubtract;
DROP FUNCTION IF EXISTS AsmMath.GuidToBigInteger;
DROP FUNCTION IF EXISTS AsmMath.zkCryptoRandomBigInteger;
DROP FUNCTION IF EXISTS AsmMath.zkGenerateSafePrime;
DROP FUNCTION IF EXISTS AsmMath.zkHashToBigInteger;
DROP ASSEMBLY IF EXISTS AsmMath;
GOSQL Server Testing
Here is our first test.
-- MSSQLTips (TSQL)
-- Prover: computes t and sends it =======================================================
DECLARE @p nvarchar(MAX) = AsmMath.zkGenerateSafePrime(256)
,@g nvarchar(MAX) = FLOOR(RAND(CHECKSUM(NEWID())) * 8 + 2);
DECLARE @p1 nvarchar(MAX) = [AsmMath].[BigSubtract] (@p,1);
-- keys private (x) and public (y)
DECLARE @x nvarchar(MAX) = [AsmMath].[zkCryptoRandomBigInteger] (@p1);
DECLARE @y nvarchar(MAX) = [AsmMath].[BigModPow] (@g,@x,@p);
DECLARE @r nvarchar(MAX) = [AsmMath].[zkCryptoRandomBigInteger] (@p1);
DECLARE @t nvarchar(MAX) = [AsmMath].[BigModPow] (@g,@r,@p);
-- Verifier: sends random challenge c ====================================================
DECLARE @c nvarchar(MAX) = [AsmMath].[zkHashToBigInteger](HASHBYTES('SHA2_256', @t), @p1);
-- Prover: replies with s ================================================================
DECLARE @n1 nvarchar(MAX) = [AsmMath].[BigMultiply] (@c ,@x);
DECLARE @n2 nvarchar(MAX) = [AsmMath].[BigAdd] (@r ,@n1);
DECLARE @s nvarchar(MAX) = [AsmMath].[BigMod] (@n2 ,@p1);
-- Checking ==============================================================================
DECLARE @left nvarchar(MAX) = [AsmMath].[BigModPow] (@g,@s,@p);
SET @n1 = [AsmMath].[BigModPow] (@y,@c,@p);
SET @n2 = [AsmMath].[BigMultiply] (@t,@n1);
DECLARE @right nvarchar(MAX) = [AsmMath].[BigMod] (@n2 ,@p);
IF @left = @right
SELECT N'✅ Proof is valid!' AS Chk
ELSE
SELECT N'✖ Proof is invalid!' AS Chk
GOResulting in

If you observe the code, in some cases, to find the safe prime is the part of the code that takes the most time to process. You could change as follows to make it process faster.
--DECLARE @p nvarchar(MAX) = AsmMath.zkGenerateSafePrime(256)
-- ,@g nvarchar(MAX) = FLOOR(RAND(CHECKSUM(NEWID())) * 8 + 2);
DECLARE @p nvarchar(MAX) = '61472017860777475416208240371337801680959896334481278354353551701346589915127'
,@g nvarchar(MAX) = '2';
-- the number 614…15127 was obtained running
-- SELECT AsmMath.zkGenerateSafePrime(256);
GOExample Use
The premise is to prove a statement is true without revealing the underlying secret.
I will use as example from the ZKProof Community Reference, at page 77, which describes “Mainly, there is an identity, Alice, who wants to prove to some company, Bob Inc. that she is an accredited investor, under the SEC rules, in order to acquire some company shares. Alice is the prover; the IRS, the AML entity and The Bank are all issuers; and Bob Inc. is the verifier.”
Each issuer verified all the constraints before saving its credential.
I intentionally simplified for educational purposes omitting many of the security measures and protocol rules that would be necessary in a real-world zero-knowledge proof implementation. I also fixed the p value to speed up the result since sometimes to find the safe prime value takes a bit of time.
-- MSSQLTips (TSQL)
-- ZKProof Community Reference (pg. 77)
-- Credential Aggregation
DECLARE @Credentials
TABLE (Issuer nvarchar(10)
,CredentialHolder uniqueidentifier
,CredentialIssuer nvarchar(MAX));
DECLARE @CredentialsChk
TABLE (Issuer nvarchar(10)
,CredentialStatus nvarchar(MAX));
DECLARE @c nvarchar(MAX)
,@p nvarchar(MAX)
,@p1 nvarchar(MAX)
,@r nvarchar(MAX)
,@t nvarchar(MAX)
,@y nvarchar(MAX)
,@chk nvarchar(80)
,@issuer nvarchar(10);
DECLARE @CredentialHolder uniqueidentifier = 'CCBB64E5-62C4-4BFE-B296-FE4AFFD8B57B'; -- SELECT NEWID();
DECLARE @Credential nvarchar(MAX) = AsmMath.GuidToBigInteger (@CredentialHolder);
-- private info
DECLARE @x nvarchar(MAX) = @Credential
,@g nvarchar(MAX) = '5';
-- public info
-- SET @p = AsmMath.zkGenerateSafePrime(256);
/*
-- IRS =====================================================================================
The IRS issues a tax credential, C0, that testifies to the claim “from 1/1/2017 until
1/1/2018, Alice, with identifier X0, owes 0$ to the IRS, with identifier Y ” and holds two
attributes: the net income of Alice, $income, and a bit b such that b = 1 if Alice has
paid her taxes
*/
SET @p = '61472017860777475416208240371337801680959896334481278354353551701346589915127';
SET @y = [AsmMath].[BigModPow] (@g,@x,@p);
SET @p1 = [AsmMath].[BigSubtract] (@p,1);
SET @r = [AsmMath].[zkCryptoRandomBigInteger] (@p1);
SET @t = [AsmMath].[BigModPow] (@g,@r,@p);
INSERT INTO @Credentials
VALUES ('IRS'
,@CredentialHolder
,[AsmMath].[zkHashToBigInteger] (HASHBYTES('SHA2_256', CONCAT(@t, @y)), @p1));
/*
-- AML =====================================================================================
The AML entity issues a KYC credential, C1, that testifies to claim T1:= “Alice, with
identifier X1, has NO relation to a (set of) blacklisted organization(s)”
*/
SET @p = '61472017860777475416208240371337801680959896334481278354353551701346589915127';
SET @y = [AsmMath].[BigModPow] (@g,@x,@p);
SET @p1 = [AsmMath].[BigSubtract] (@p,1);
SET @r = [AsmMath].[zkCryptoRandomBigInteger] (@p1);
SET @t = [AsmMath].[BigModPow] (@g,@r,@p);
INSERT INTO @Credentials
VALUES ('AML'
,@CredentialHolder
,[AsmMath].[zkHashToBigInteger] (HASHBYTES('SHA2_256', CONCAT(@t, @y)), @p1));
/*
-- The bank ================================================================================
The Bank issues a net-worth credential, C2, that testifies to claim T2:= “Alice has a net
worth of V Alice”
*/
SET @p = '61472017860777475416208240371337801680959896334481278354353551701346589915127';
SET @y = [AsmMath].[BigModPow] (@g,@x,@p);
SET @p1 = [AsmMath].[BigSubtract] (@p,1);
SET @r = [AsmMath].[zkCryptoRandomBigInteger] (@p1);
SET @t = [AsmMath].[BigModPow] (@g,@r,@p);
INSERT INTO @Credentials
VALUES ('The Bank'
,@CredentialHolder
,[AsmMath].[zkHashToBigInteger] (HASHBYTES('SHA2_256', CONCAT(@t, @y)), @p1));
SELECT * FROM @Credentials;
-- Bob Inc. ================================================================================
-- Alice as Prover: replies with s =========================================================
DECLARE crsZkProofChk CURSOR FAST_FORWARD READ_ONLY FOR
SELECT Issuer
,CredentialIssuer
FROM @Credentials;
OPEN crsZkProofChk
FETCH NEXT FROM crsZkProofChk INTO @issuer,@c;
WHILE @@FETCH_STATUS = 0 BEGIN
SET @p = '61472017860777475416208240371337801680959896334481278354353551701346589915127';
SET @y = [AsmMath].[BigModPow] (@g,@x,@p);
SET @p1 = [AsmMath].[BigSubtract] (@p,1);
SET @r = [AsmMath].[zkCryptoRandomBigInteger] (@p1);
SET @t = [AsmMath].[BigModPow] (@g,@r,@p);
-- Tampering =============
--IF @issuer = 'The Bank'
-- SET @g = '3';
DECLARE @n1 nvarchar(MAX) = [AsmMath].[BigMultiply] (@c ,@x);
DECLARE @n2 nvarchar(MAX) = [AsmMath].[BigAdd] (@r ,@n1);
DECLARE @s nvarchar(MAX) = [AsmMath].[BigMod] (@n2 ,@p1);
DECLARE @left nvarchar(MAX) = [AsmMath].[BigModPow] (@g,@s,@p);
SET @n1 = [AsmMath].[BigModPow] (@y,@c,@p);
SET @n2 = [AsmMath].[BigMultiply] (@t,@n1);
DECLARE @right nvarchar(MAX) = [AsmMath].[BigMod] (@n2 ,@p);
IF @left = @right
SET @chk = N'✅ Proof is valid!';
ELSE
SET @chk = N'✖ Proof is invalid!';
INSERT INTO @CredentialsChk
VALUES (@issuer
,@chk);
FETCH NEXT FROM crsZkProofChk INTO @issuer,@c;
END
CLOSE crsZkProofChk
DEALLOCATE crsZkProofChk
-- Checking ==============================================================================
SELECT * FROM @CredentialsChk;
GOResulting in

Uncomment the tampering part (shown below) in the above code and run again to check the results.
-- Tampering =============
--IF @issuer = 'The Bank'
-- SET @g = '3';Final Comments
There are some details to be aware of for a real ZK-Proof implementation:
- Use proper cryptographic primitives: secure groups, Pedersen commitments, strong hash functions, and cryptographically secure randomness.
- Ensure the core security properties really hold and that the protocol can be formally simulated without leaking information.
- Implementation details such as input encoding, proof aggregation, and error handling matter a lot, because real systems exploit these to gain efficiency and safety.
- Bulletproofs are an improvement to prove that a value is within a range without revealing it.
Next Steps

Sebastião Pereira has over 40 years of experience in database development including T-SQL, algorithm design, machine learning and bringing innovative mathematical formulas to SQL Server. He started his career at a transnational fast-moving consumer goods (FMCG) company as an employee then later transitioning into a consultant role. He eventually founded his own company to develop software solutions for the healthcare industry. Sebastião is a respected award-winning author on MSSQLTips.com extending SQL Server capabilities beyond traditional workloads.
- MSSQLTips Awards
- Author of the Year – 2025
- Trendsetter (25+ tips) – 2025
- Rookie of the Year – 2024



There are a lot of T-SQL anti-pattern scripts in this article. Please note that developers may use these scripts in production environments and experience poor performance. SQL Server is not a tool for implementing logic. It is a tool for storing and retrieving data.
Thanks!
Thanks for the tips. Did you examine the execution plans of the queries you published? Also, are you aware of the performance of CLR functions? Their performance is actually quite poor. Additionally, I don’t believe SQL Server is the right tool for writing logic code such as IF…Else.”
Hi David, I think the purpose of the article is to show how this could be done, not necessarily that it is the best option. There are lots of other tools that would perform better.