T-SQL编程技巧汇总。碰到有意思的编程技巧都记录下,方便以后查找
1 日期时间:
1.1 datetime类型前面4 个字节存储的是距离1900-01-01的天数;后面4个字节存储的是距离午夜以来,所有的秒数。
select datediff(day,0,getutcdate()) as days select datediff(week,0,getutcdate()) as weeks
上面的sql查询出来的结果,第一个是距离1900-01-01相差的天数,第二个是距离1900-01-01以来相差的周数。0 在这里代表的意思就是1900-01-01,所以相差0天。
关于一周的定义,是从周天开始的,礼拜天算是一周的开始。一周的时间从上个礼拜天到这个礼拜六结束。
declare @loop int = 1
declare @begindate datetime = '1900-01-01'
while @loop <= 30
begin
select @begindate, datename(dw,@begindate) as day, datepart(week,@begindate)as weekNumber ,dateadd(week,1,@begindate) as aweekafter
set @begindate = @begindate + 1
set @loop = @loop +1
end
关于datename():
DATENAME ( datepart , date )
关于 datepart():
DATEPART(datepart,date)
这里区别在于weekday的描述,datename()以Monday,Tuesday等来表述,datepart()以1,2等来表述。
如何取每个月的第一天?
declare @begindate datetime = '2016-05-21'
select dateadd(dd,datepart(dd,@begindate)*-1 ,@begindate) + 1
时间刻度变量:
DECLARE @time time(0) = '16:30:23'
declare @date date
set @time = convert(time,getutcdate())
set @date = convert(date,getutcdate())
select @time ,@date
07:46:30 2016-05-23
时间差距,如何将当前的时间日期类型取出日期:
select convert(binary,getutcdate()) as binary_datetime
, convert(binary(8),getutcdate()) as binary_8_datetime
, getdate() as [current_date]
, datediff(day,0,getdate()) as days_after_19000101
, convert(bigint,convert(binary(8),getdate())) as x_wrong_format
, convert(int, substring(convert(varbinary(8),getdate()),1,4)) as days_after_19000101
最后一个表达式,取前面4个字节,也就是取了日期。因为时间日期型数据存储的是,前4个字节存储日期,后四位字节存储时间。
2 字符串:
Stuff(): 填充覆盖:根据填充字符长短,相应增加字符长度
STUFF ( character_expression , start , length , replaceWith_expression )
declare @stuffstring varchar(200) = 'hello world!'
select @stuffstring,stuff(@stuffstring,len(@stuffstring) , 10,', T-SQL')
结果:
hello world! hello world, T-SQL
Replace():替换覆盖
REPLACE ( string_expression , string_pattern , string_replacement )
declare @stuffstring varchar(200) = 'hello world!'
select @stuffstring,stuff(@stuffstring,len(@stuffstring) , 10,', T-SQL'),replace(@stuffstring,' ','T-SQL')
结果:
hello world! hello world, T-SQL helloT-SQLworld!
Stuff,按照字符位置来填充;Replace,按照指定字符匹配模式来替换
3 行列转换
–1 rows of records transformed to columns 行转列
if exists(select 1 from tempdb.sys.tables where upper(name) like upper('%tempScores%')) drop table #tempScores create table #tempScores ( studentName varchar(200), className varchar(200), classScore int ) insert into #tempScores (studentName,className,classScore) select 'Alex' as studentName, 'English' as className, 80 as classScore union select 'Alex' as studentName, 'Math' as className, 90 as classScore union select 'Ken' as studentName, 'English' as className, 60 as classScore union select 'Ken' as studentName, 'Math' as className, 80 as classScore union select 'Ken' as studentName, 'Sport' as className, 100 as classScore select * from #tempScores select studentName, ps.English, ps.Math, ps.Sport from #tempScores ts pivot ( max(ts.classScore) for className in(English, Math,Sport) ) ps
这里是行转列的例子 。 对于没有数值的那些行,转换过来之后显示null。Pivot在这里的作用,就是根据某一列的散列值做聚合。我们可以再进一步,如果不知道散列值有多少,那么怎么动态的区生成这个pivot的语句呢?
if exists(select 1 from tempdb.sys.tables where upper(name) like upper('%tempScores%')) drop table #tempScores declare @columnheader nvarchar(max) ;
declare @columnfilter nvarchar(max) ;
declare @sqlstatement nvarchar(max) ;
create table #tempScores ( studentName varchar(200), className varchar(200), classScore int ) insert into #tempScores (studentName,className,classScore) select 'Alex' as studentName, 'English' as className, 80 as classScore union select 'Alex' as studentName, 'Math' as className, 90 as classScore union select 'Ken' as studentName, 'English' as className, 60 as classScore union select 'Ken' as studentName, 'Math' as className, 80 as classScore union select 'Ken' as studentName, 'Sport' as className, 100 as classScore select * from #tempScores select @columnheader = ( select distinct className +',' from #tempScores for xml path('')) select @columnfilter = left(@columnheader ,len(@columnheader) - 1 ) select @columnheader = 'ps.'+replace(left(@columnheader,len(@columnheader)-1),',',',ps.') select @columnheader , @columnfilter select @sqlstatement = N' select studentName, ' + @columnheader + N' from #tempScores ts pivot ( max(ts.classScore) for className in(' + @columnfilter +N') ) ps ' exec sp_executesql @sqlstatement
–2 columns of records transformed to rows 列转行
if exists(select 1 from tempdb.sys.tables where upper(name) like upper('%tempScores%')) drop table #tempScores create table #tempScores (studentName varchar(200), English int, Math int, Sport int) insert into #tempScores (studentName, English, Math, Sport) select 'Alex' as studentName, 80 as English, 90 as Math, Null as Sport union select 'Ken' as studentName, 60 as English, 80 as Math, 100 as Sport select * from #tempScores select studentName, ps.ClassName, ps.Scores from #tempScores unpivot ( Scores for ClassName in ( English, Math, Sport) ) ps
这里unpivot的作用就是定义一个带值的列,并且将原来几个列的值映射到这个列上去,原来的几个列的列名就变为一个列的散列值。最终的两个列都是可以自定义的,带值的列放unpivot的for子句之前 。如果值是null的,在转换过程中就被过滤掉。所以要保留 null的列,必须将Null转变为一个有意思的值 。
同样,这里也有动态列的情况:
use lenistest3
go
if exists(select 1 from tempdb.sys.tables where upper(name) like upper('%tempScores%')) drop table #tempScores declare @columnheader nvarchar(max) ;
declare @columnfilter nvarchar(max) ;
declare @sqlstatement nvarchar(max) ;
create table #tempScores (studentName varchar(200), English int, Math int, Sport int) insert into #tempScores (studentName, English, Math, Sport) select 'Alex' as studentName, 80 as English, 90 as Math, isnull(Null ,0) as Sport union select 'Ken' as studentName, 60 as English, 80 as Math, 100 as Sport select * from #tempScores select @columnfilter = (select name + ',' from tempdb.sys.columns where object_id in (select object_id from tempdb.sys.tables where upper(name) like upper('%tempScores%')) and name<>'studentName' for xml path('')) select @columnfilter = left(@columnfilter,len(@columnfilter) - 1) select @columnheader = N'ps.ClassName,ps.Scores' select @columnfilter, @columnheader select @sqlstatement = N' select studentName, ' + @columnheader + ' from #tempScores unpivot ( Scores for ClassName in ( ' + @columnfilter +') ) ps ' exec sp_executesql @sqlstatement
4 加密与解密
举个简单的例子:
加密函数:
create function dbo.encryptbypassphrasepwd_1(
@password nvarchar(200)) returns varbinary(max) as begin declare @pwdencrypted varbinary(max) ;
set @pwdencrypted = ENCRYPTBYPASSPHRASE(N'123456',@password,1,CONVERT(varbinary,123)) ;
return @pwdencrypted; end
解密函数:
create function dbo.decryptbypassphrasepwd_1(
@encryptpwd varbinary(max)) returns nvarchar(max) as begin declare @clear_text nvarchar(200) ;
set @clear_text = convert(nvarchar, DECRYPTBYPASSPHRASE(N'123456',@encryptpwd,1,CONVERT(varbinary,123)));
return @clear_text ;
end
加密过程:
declare @pwd varbinary(max)
select @pwd = dbo.encryptbypassphrasepwd_1( N'lewis123。')
select @pwd
解密过程:
declare @clear_text nvarchar(200) ;
declare @pwdencrypted varbinary(max) = 0x01000000897C94116F62ABB73BE41BD1758616C43314B7965BA8D046C268E2BB39E80CC468C4BA4CFDA0E6EE9204181139FAAAA23E39CA382A379B4C10DE6ABFF71AA763 select @clear_text = dbo.decryptbypassphrasepwd_1( @pwdencrypted );
select @clear_text ;
这里要着重注意的是传入的varbinary(max)参数不要传入nvarchar(max)然后再转varbinary.
我们看看试着把函数改写成这样:
create function dbo.decryptbypassphrasepwd_2(
@encryptpwd nvarchar(max)) returns nvarchar(max) as begin declare @clear_text nvarchar(200) ;
declare @pwd varbinary(max) = convert(varbinary(max),@encryptpwd) ;
set @clear_text = convert(nvarchar, DECRYPTBYPASSPHRASE(N'123456',@pwd,1,CONVERT(varbinary,123)));
return @clear_text ;
end
然后解密:
declare @clear_text nvarchar(200) ;
declare @pwdencrypted nvarchar(max) = N'0x01000000897C94116F62ABB73BE41BD1758616C43314B7965BA8D046C268E2BB39E80CC468C4BA4CFDA0E6EE9204181139FAAAA23E39CA382A379B4C10DE6ABFF71AA763' select @clear_text = dbo.decryptbypassphrasepwd_2( @pwdencrypted );
select @clear_text ;
发现结果是NULL。但是如果我们就直接放入十六进制,比如
declare @clear_text nvarchar(200) ;
declare @pwdencrypted nvarchar(max) = 0x01000000897C94116F62ABB73BE41BD1758616C43314B7965BA8D046C268E2BB39E80CC468C4BA4CFDA0E6EE9204181139FAAAA23E39CA382A379B4C10DE6ABFF71AA763 select @clear_text = dbo.decryptbypassphrasepwd_2( @pwdencrypted );
select @clear_text ;
却又是可以解密完成的。
给表字段加密:
create table dbo.test(testid bigint, test_text varchar(200),test_text_pwd varbinary(max)) go insert into dbo.test(testid,test_text) values(1,'1-2X37') insert into dbo.test(testid,test_text) values(2,'2-2x37') update dbo.test set test_text_pwd = dbo.encryptbypassphrasepwd_1(test_text)
给表字段解密:
select testid,test_text,dbo.decryptbypassphrasepwd_1(test_text_pwd) as clear_text from dbo.test
另外这里的加密解密函数不能改成存储过程,这样加密解密的结果始终是在变化的,不是持久化的。
Create procedure dbo.encryptbypassphrasepwd @password nvarchar(200) as begin declare @pwdencrypted varbinary(max) ;
set @pwdencrypted = ENCRYPTBYPASSPHRASE(N'123456',@password,1,CONVERT(varbinary,123)) ;
return @pwdencrypted;
end
declare @pwd varbinary(max)
exec @pwd = dbo.encryptbypassphrasepwd N'lewis123.'
select @pwd
每次出来的加密值都是在变化的,也就是说不可持久化
Symmetric encryption 例子, 加密一列数据:
USE AdventureWorks2012;
–If there is no master key, create one now.
IF NOT EXISTS
(SELECT * FROM sys.symmetric_keys WHERE symmetric_key_id = 101)
CREATE MASTER KEY ENCRYPTION BY
PASSWORD = ‘23987hxJKL95QYV4369#ghf0%lekjg5k3fd117r$$#1946kcj$n44ncjhdlj’
GO
CREATE CERTIFICATE Sales09
WITH SUBJECT = ‘Customer Credit Card Numbers’;
GO
CREATE SYMMETRIC KEY CreditCards_Key11
WITH ALGORITHM = AES_256
ENCRYPTION BY CERTIFICATE Sales09;
GO
– Create a column in which to store the encrypted data.
ALTER TABLE Sales.CreditCard
ADD CardNumber_Encrypted varbinary(128);
GO
– Open the symmetric key with which to encrypt the data.
OPEN SYMMETRIC KEY CreditCards_Key11
DECRYPTION BY CERTIFICATE Sales09;
– Encrypt the value in column CardNumber using the
– symmetric key CreditCards_Key11.
– Save the result in column CardNumber_Encrypted.
UPDATE Sales.CreditCard
SET CardNumber_Encrypted = EncryptByKey(Key_GUID(‘CreditCards_Key11’)
, CardNumber, 1, HashBytes(‘SHA1’, CONVERT( varbinary
, CreditCardID)));
GO
– Verify the encryption.
– First, open the symmetric key with which to decrypt the data.
OPEN SYMMETRIC KEY CreditCards_Key11
DECRYPTION BY CERTIFICATE Sales09;
GO
– Now list the original card number, the encrypted card number,
– and the decrypted ciphertext. If the decryption worked,
– the original number will match the decrypted number.
SELECT CardNumber, CardNumber_Encrypted
AS ‘Encrypted card number’, CONVERT(nvarchar,
DecryptByKey(CardNumber_Encrypted, 1 ,
HashBytes(‘SHA1’, CONVERT(varbinary, CreditCardID))))
AS ‘Decrypted card number’ FROM Sales.CreditCard;
GO
来自microsoft : Encrypt a Column of Data
Column-level encryption (aka cell-level encryption) was introduced in SQL Server 2005 and is available in all editions of SQL Server, including the free SQL Server Express edition. To use cell-level encryption, the schema must be changed to varbinary, then reconverted to the desired data type. This means the application must be changed to support the encryption-decryption operation; in addition, it can affect performance. Encryption of the database occurs at the page level, but when those pages are read to buffer pool, they’re decrypted. Data can be encrypted using a passphrase, an asymmetric key, a symmetric key, or a certificate. The supported algorithms for column-level encryption are AES with 128,196,256 bit keys and 3DES. To learn more about column-level encryption, see the MSDN article “Encrypt a Column of Data.”
参考: http://sqlmag.com/database-security/sql-server-encryption-options
上面的这段引用来自网络,讲的大概意思是,加密一列数据,可以用AES(128,196,256位密钥)或者3DES(triple DES)算法.这里写代码片
我们从头过一遍这段加密列的脚本。
1 创建master key: master key采用的是symmetric encryption(对称加密). 作用是保护证书的私钥和当前数据库中的asymmetric key。 如果没有 service master key保护,则在使用master key的时候需要open master key. Master Key采用AES和口令加密,在SQL SERVER 2008及2008 R2 中也可以使用3DES(TRIPLE DES)来加密。
语法: CREATE MASTER KEY ENCRYPTION BY PASSWORD = ‘password’
CREATE MASTER KEY ENCRYPTION BY PASSWORD = ‘23987hxJ#KL95234nl0zBe’;
创建一个 MASTER KEY ENCRPTION, 这里要注意的是 password需要符合 windows密码要求。
创建完之后,可以查看创建的结果:
select * from sys.symmetric_keys
select * from sys.asymmetric_keys
select
name
, is_master_key_encrypted_by_server
from sys.databases
where name = ‘lenistest5’
这里 IS_MASTER_KEY_ENCRYPTED_BY_SERVER提示 MASTER KEY是不是被 service master key加密了。
打开一个 master key用来加密盒解密,这是一个必要步骤,每一次加密解密之前,都要先打开key.
语法:OPEN MASTER KEY DECRYPTION BY PASSWORD = ‘password’
OPEN MASTER KEY DECRYPTION BY PASSWORD = ‘23987hxJ#KL95234nl0zBe’;
这儿要注意的是,当一个数据库被恢复到一个新的instance上面的时候,这个数据库是不会自动拷贝一份Master Key(经过 service master key加密的)到本地数据库的,所以我们要打开这个master key. 当我们需要自动打开这个master key的时候,可以用alter master key设置。 当然我们也可以drop掉这个master key : DROP MASTER KEY. 如果有其他密钥,证书受它保护地时候,drop会失败。
看到一篇很有意思的文章 :
Can’t understand the difference between hashing and encryption?
https://blogs.msdn.microsoft.com/sqlserverfaq/2009/03/30/cant-understand-the-difference-between-hashing-and-encryption/
文中讲到了hashing与encryption的区别:
Encryption有两个特点:一是每次加密完之后,密文都是不一样的;二是密文都可以被解密。
Hashing有三个特点:一是每次加密完(hashing)之后,密文都是一样的;二是密文不可以被解密;三是不同的明文经过同一个hash算法,得到一个同一的值。
可以看看hashbytes的用法:
HASHBYTES ( ‘’, { @input | ‘input’ } )
::= MD2 | MD4 | MD5 | SHA | SHA1 | SHA2_256 | SHA2_512
DECLARE @HashThis nvarchar(4000);
SET @HashThis = CONVERT(nvarchar(4000),’dslfdkjLK85kldhnv$n000#knf’);
SELECT HASHBYTES(‘SHA1’, @HashThis);
输入可以是varchar,nvarchar,varbinary. 返回值一定是varbinary.
这里也有篇很有意思的文章,关于.net加密算法,总是针对一段相同的明文文本产生一段相同的密文。这也叫加密?
c# - How to obtain different cipher text for same plain text using AES
http://stackoverflow.com/questions/22126412/how-to-obtain-different-cipher-text-for-same-plain-text-using-aes
我们碰到一个有趣的需求,针对同一段明文,加密后需要输出同一段相同的密文,前提是在T-SQL里面实现。实现这种加密的时候,只能用RC4等加密算法,很不幸的是,这种加密算法在SQL Server 2008R2往上都将淘汰,为了使用这种算法,需要将数据库的competible level降为90或者110.
use lenistest5
go
–drop function dbo.EncryptByRC4
alter procedure dbo.EncryptByRC4(
@ID nvarchar(20)
,@ClearText nvarchar(max)
,@CipherText varbinary(max) output
)
as
begin
declare @symmetric_sql nvarchar(max) ;
declare @symmetric_header nvarchar(200) = ‘sym_key_’+replace(@ID,’-‘,”)
declare @symmetric_password nvarchar(max) = N’hoover’ + @ID +’.’ ;
declare @ciphertext varbinary(max) ;
if not exists(select 1 from sys.symmetric_keys where name = @symmetric_header )
begin
set @symmetric_sql = N’create symmetric key ‘+ @symmetric_header+ ’ with algorithm = RC4 encryption by password =N”’+
@symmetric_password + ”’
open symmetric key ‘+ @symmetric_header + ’ decryption by password =N”’
+ @symmetric_password + ”’
select @ciphertext = encryptbykey(key_guid(”’+@symmetric_header+”’),”’+convert(nvarchar(max),@ClearText)+”’)
close symmetric key ’ + @symmetric_header ;
–print @symmetric_sql
exec sp_executesql @stmt = @symmetric_sql ,@params =N’@ciphertext varbinary(max) output’,@ciphertext = @CipherText output ;
end
else
begin
set @symmetric_sql = N’
open symmetric key ‘+ @symmetric_header + ’ decryption by password =N”’
+ @symmetric_password + ”’
select @ciphertext = encryptbykey(key_guid(”’+@symmetric_header+”’),”’+convert(nvarchar(max),@ClearText)+”’)
close symmetric key ’ + @symmetric_header ;
–print @symmetric_sql
exec sp_executesql @stmt = @symmetric_sql ,@params =N’@ciphertext varbinary(max) output’,@ciphertext = @CipherText output ;
end
end
go
alter procedure dbo.DecryptByRC4(
@ID nvarchar(20)
,@ClearText nvarchar(max) output
,@CipherText varbinary(max)
)
as
begin
declare @symmetric_sql nvarchar(max) ;
declare @symmetric_header nvarchar(200) = ‘sym_key_’+replace(@ID,’-‘,”)
declare @symmetric_password nvarchar(max) = N’hoover’ + @ID +’.’ ;
declare @ciphertext varbinary(max) ;
if not exists(select 1 from sys.symmetric_keys where name = @symmetric_header )
begin
set @symmetric_sql = N’create symmetric key ‘+ @symmetric_header+ ’ with algorithm = RC4 encryption by password =N”’+
@symmetric_password + ”’
open symmetric key ‘+ @symmetric_header + ’ decryption by password =N”’
+ @symmetric_password + ”’
select @ClearText = convert(varchar,decryptbykey(@CipherText))
close symmetric key ’ + @symmetric_header ;
–print @symmetric_sql
exec sp_executesql @stmt = @symmetric_sql
,@params =N’@CipherText varbinary(max), @ClearText nvarchar(max) output’
,@CipherText = @CipherText
,@ClearText = @ClearText output ;
end
else
begin
set @symmetric_sql = N’
open symmetric key ‘+ @symmetric_header + ’ decryption by password =N”’
+ @symmetric_password + ”’
select @ClearText = convert(varchar,decryptbykey(@CipherText))
close symmetric key ’ + @symmetric_header ;
–print @symmetric_sql
exec sp_executesql @stmt = @symmetric_sql
,@params =N’@CipherText varbinary(max), @ClearText nvarchar(max) output’
,@CipherText = @CipherText
,@ClearText = @ClearText output ;
end
end
declare @ID nvarchar(20) = ‘2-x232’
declare @ClearText nvarchar(200) = N’lewis’
declare @ciphertext varbinary(max)
declare my_cur cursor for
select ‘2-x232’ as ID, N’lewis’ as ClearText
union all
select ‘2-x232’ as ID, N’lewis’ as ClearText
open my_cur
fetch next from my_cur into @ID,@ClearText
while @@FETCH_STATUS = 0
begin
exec dbo.EncryptByRC4 @ID, @ClearText,@ciphertext output
select @ID as ID, @ClearText as ClearText, @ciphertext as CipherText
fetch next from my_cur into @ID,@ClearText
set @ciphertext = null
select @ciphertext
end
close my_cur
deallocate my_cur
declare @ID nvarchar(20) = '20-x232'
declare @ClearText nvarchar(max) ;
declare @ciphertext varbinary(max) =0x003D25B2E76D8B44817B679A28441059010000001BDA96E0D14742E46062269ED3
exec dbo.DecryptByRC4 @ID = @ID, @ClearText = @ClearText output,@CipherText = @ciphertext
select @ClearText
这里面要注意的事情有很多,一是注意open key与close key的重要性。如果每一次加密解密都不close key,整个session都是处于解密状态,任何值都能解密出明文来。