T-SQL学习笔记1

时间:2022-03-02 10:19:55

一个月之前就注册了这个博客,今天才开始动笔写,真是有点。。。。

最近开始看面试题发现一个月之前看的sql那点东西都忘光了,所以决定通过上传当时学习时记录的笔记回忆当时的内容,希望能有所帮助。

==============================================分割线============================

本博客内容没有任何技术含量,仅供个人复习使用,慎重。

=============================================分割线==============================

 

use TSQLFundamentals2008

-- 基本执行顺序 from->where->group by->having->select->order by
select
empid
,
YEAR(orderdate) as orderyear
,
COUNT(*) as numorders
from
sales.orders
where
custid
= 71
group by
empid
,
year(orderdate)
having
count(*) > 1
order by
empid
, orderyear;
-- distinct 排除重复
select
empid
,
year(orderdate)
,
count(distinct custid) as numcusts
from
Sales.Orders
group by
empid
,
year(orderdate);
-- order by 对结果进行排序,默认从小到大,desc倒序
— top (5)取出排序后的前五个结果
select top (5) orderid, orderdate, custid, empid
from Sales.Orders
order by orderdate desc;
-- top (5) percent 取出排序后前百分之五的结果
select top (5) percent orderid, orderdate, custid, empid
from Sales.Orders
order by orderdate desc;

use TSQLFundamentals2008
-- with ties 能够请求返回与top n行中最后一行的排序值相同的其他所有行
select top (5) with ties orderid, orderdate, custid, empid
from Sales.Orders
order by orderdate desc;


-- 关键字partition by可以对指定项进行聚合运算
select
orderid
, custid
, val
,
SUM(val) over() as totalvalue
,
SUM(val) over(partition by custid) as custtotalvalue
from Sales.OrderValues;


select
orderid
, custid
, val
,
100. * val / SUM(val) over() pctall
,
100. * val / sum(val) over(partition by custid) as pctcust
from
Sales.OrderValues;

-- over子句也支持四种排名函数:row_number(行号)、rank(排名)、
--
dense_rank(密集排名)、以及ntile。

-- row_number函数用于为查询的结果集中的各行递增的序列号,
--
其逻辑顺序通过over子句中的order by语句进行指定。在我们的查询例子中,
--
逻辑顺序基于的是val列:因此,从输出中可以看到,随着订单价格的增加,行号也随之增加。
--
不过,即使订单价格没有增加,行号也会依然增加。
--
所以,如果row_number函数的order by不能唯一确定行的顺序,
--
查询结果就是不确定的。也就是说, 查询可能返回多个正确的结果。
--
如果想却取得确定结果,需要在order by子句中添加元素。

-- rank与dense_rank的作用和row_number作用类似。区别是rank表示之前有多少行具有更低的排序值,
--
而dense_rank则表示之前有多少个更低的排序值。

-- ntile函数可以把结果中的行关联到组(tile,相当于由行组成的指定数目的组),并为每一行分配
--
一个所属的组的编号。ntile接受一个表示组的数量的输入参数,并要在over子句中指定逻辑顺序。
select
orderid
, custid
, val
, ROW_NUMBER()
over(order by val) as rownum
, rank()
over(order by val) as "rank"
, DENSE_RANK()
over(order by val) as "dense_rank"
, ntile(
10) over(order by val) as "ntile"
from
Sales.OrderValues
order by
val;
-- 排名函数也支持在over子句中使用partition by语句
select
orderid
, custid
, val
, ROW_NUMBER()
over(partition by custid order by val) as rownum
from sales.ordervalues
order by
custid
, val;

-- 注意,over子句中指定的order by逻辑与数据展示没什么关系,并不会改变查询结果
--
表中的任何内容。如果在查询中不指定order by,和前面介绍的一样,就不能保证输出中行的任何顺序。
--
如果需要确保查询结果的排名顺序,就不需再order by子句增加相应的排序条件,
--
就像前面排名函数的最后两个查询例子演示的那样。
--
如果在select处理接单制定了开窗函数,开窗计算会在distinct子句之前进行处理

--总结逻辑处理顺序如下
--
from
--
where
--
group by
--
having
--
select
--
over
--
distinct
--
top
--
order by

-- 由于在row_number会为不同行分配顺序号,造成所有行都不会相同,使distinct失去作用,
--
所以在同一select中不同是指定distinct和row_number是一条最佳实践原则。


-- in谓词
select
orderid
, empid
, orderdate
from
Sales.Orders
where
orderid
in(10248, 12049, 10250);

-- where 谓词
select
orderid
, empid
, orderdate
from
Sales.Orders
where
orderid
between 10300 and 10310;

-- like谓词
select
empid
, firstname
, lastname
from
hr.Employees
where
lastname
like N'D%';

-- 运算符:=, >, <, >=, <>, <=
--
非标准运算符:!=, !>, !<
--
逻辑运算符:or, and
--
算数运算符:+,-,*,/,%

-- case表达式:简单表达式和搜索表达式
--
case简单格式将一个值(或一个标量表达式)与一组可能的取值进行比较,并返回第一个匹配的结果。
--
如果列表中没有值等于测试值,case表达式就返回其else子句中列出的值。
--
如果case表达式中没有else子句,则默认将其视为else null。

select
productid
, productname
, categoryid
,
case categoryid
when 1 then 'Beverages'
when 2 then 'condiments'
when 3 then 'confections'
when 4 then 'dairy products'
else 'unkown category'
end as categoryname
from
production.products;

select
orderid
, custid
, val
,
case ntile(3) over(order by val)
when 1 then 'low'
when 2 then 'medium'
when 3 then 'high'
else 'unkown'
end as titledesc
from
Sales.OrderValues
order by val;

-- case搜索表达式
select
orderid
, custid
, val
,
case
when val < 1000.00 then 'less then 1000'
when val between 1000.00 and 3000.00 then 'between 1000 and 3000'
when val > 3000.00 then 'more than 3000'
else 'unknown'
end as valuecategory
from Sales.OrderValues;

-- 关于null
select
custid
, country
, region
, city
from
Sales.Customers
where
region
= N'WA';

select
custid
, country
, region
, city
from
Sales.Customers
where
region
is null;
-- 在用于比较和排序目的的不同语言元素中,sql处理null的方式也有所不同。一些元素
--
认为两个null值彼此相等,而另一些则认为他们不相等。
--
例如,当进行分组和排序时,认为两个null值是相等的。也就是说,group by子句会在每个组中重新组织所有的null值,
--
就像有具体值得列一样;order by子句也会对所有null值进行排序。至于null值应该排在有效值之前还是之后,
--
ansi sql把它留给了具体的产品实现。t-sql是把null值排在了有效值之前。
--
ansi sql有两种unique约束:一种将多个null值视为相等的(只允许有一个null值),另一种则将多个null值视为不同的
--
(允许有多个null值)。sql server只实现了前者。
--
记住这些sql在处理unknown和null值方面不一致的敌法个,以及发生逻辑错误的潜在可能,在编写每一条查询语句时应该
--
明确地意识到正在使用的是三值谓词逻辑。如果默认的处理并不是你想要的效果,就必须显示地进行干预;否则,只要
--
确保sql的默认行为是你实际上需要的就可以了。

-- 同时操作的概念和意义
--
错误的例子,没有短路求值,可能会出现除零错误
--
select col1, col2
--
from dob.t1
--
where col1 <> 0 and col2 / col1 > 2;
--
可以用case判断解决,但是代码麻烦
--
可以用以下方法解决
--
select col1, col2
--
from dbo.t1
--
where col1 <> 0 and col2 > 2*col1;







-- 字符串相关
--
系统中目前支持的所有排序规则及其描述
select
name
, description
from
sys.fn_helpcollations();

-- 让过滤条件区分大小写
select
empid
, firstname
, lastname
from
hr.Employees
where
lastname collate latin1_general_cs_as
= N'davis';

-- 双引号"用于分隔不规则的标志符。在sql server中,有一个名为QUOTED_IDENTIFIER的设置选项,用于控制双引号
--
的含义。可以在数据库级应用这个设置选项(用alter databse命令),也可以在会话级应用这个设置选项(用set命令)。
--
当打开这个设置时,其行为符合标准sql的规定,双引号仅用于分割标志符。当关闭这个设置时,其行为就不是标准的了,
--
双引号这时也可以用于分隔文字字符串(单引号的作用)。

-- 字符串连接符“+”。通过将一个名为CONCAT_NULL_YIELDS_NULL的会话选项设置为off,就可以改变sql server处理串联的方式。
--
这时,sql server将把null值作为空字符串进行串联。
set concat_null_yields_null off;
select custid, country, region, city,
country
+ N',' + region + N',' + city as location
from Sales.Customers;
set concat_null_yields_null on;

-- substring(string, start, length), 从1开始!!!!!
select substring('abcde', 1, 3);

-- left 和 right函数 left(string, n) right(string, n)

-- len和datalength函数
--
len返回字符串中的字符数,不包含尾随的空格
--
datalength返回字节数,包含尾随的空格

-- charindex函数返回字符串中某个子串第一次出现的起始位置
--
charindex(substring, sring[, start_pos])

-- patindex函数返回字符串中某个模式第一次出现的起始位置
--
patindex(pattern, string)
--
参数pattern使用的模式与t-sql中like谓词的使用模式类似。

-- replace函数将字符串中出现的所有某个子串替换为另一个字符串
--
replace(string, substring1, substring2);
--
可以使用replace函数来计算字符串中某个字符出现的次数。为此,先将字符串中所有的那个字符替换为空字符串,
--
再计算字符串的原始长度和新长度的差值。
select
empid
, lastname
,
LEN(lastname) - LEN(replace(lastname, 'e', '')) as numoccur
from
hr.Employees;

-- replicate函数以指定的次数复制字符串
--
replicate(string, n);
--
对production.suppliers的查询为每个供应商的整数id生成一个10位数的字符串表示
select
supplierid
,
right(replicate('0', 9) + cast(supplierid as varchar(10)), 10) as strsupplierid
from
Production.Suppliers;

-- stuff函数可以先删除字符串中的一个子串,再插入一个新的子字符串作为替换
--
stuff(string, pos, delete_length, insertstring);
select stuff('xyz', 2, 1, 'abc');

-- upper和lower函数

-- rtrim和ltrim函数

-- like谓词
--
%通配符代表任意长度的字符串,包括空字符串
--
_通配符代表单个字符
--
[<字符列>]通配符表示必须匹配匹配列指定字符中的一个字符(类似正则表达式)
--
escape(转义)字符:指定一个确保不会在数据中出现的字符作为转义字符,把它放在待查找的字符串前面,并紧接着
--
模式字符串,在escape关键字后面制定该转义字符。例如,要检查一个名为col1的列中是否包含下划线,可以使用
--
col1 like '%!_%' escape'!'
--
也可以使用 col1 like '%[_]%'





-- 日期时间类型
--
datetime 'YYYYMMDD hh:mm:ss.nnn'
--
smalldatetime 'YYYYMMDD hh:mm'
--
date 'YYYY-MM-DD'
--
time(0-7) 'hh:mm:ss.nnnnnnn'
--
datetime2(0-7) 'YYYY-MM-DD hh:mm:ss.nnnnnnn'
--
datetimeoffset(0-7) 'YYYY-MM-DD hh:mm:ss.nnnnnnn [+|-]hh:mm'
--
最后三种可选精度,默认为7
--
为了有效的利用潜在索引,推荐使用(需要索引和性能的背景知识)
select
orderid
, custid
, empid
, orderdate
from
Sales.Orders
where
orderdate
>= '20070201' and orderdate < '20080301';
-- 而不是
select
orderid
, custid
, empid
, orderdate
from
Sales.Orders
where
YEAR(orderdate) = 2007 and MONTH(orderdate) = 2;

-- getdate, current_timestamp, getutcdate,
--
sysdatetime(2008+), sysutcdatetime(2008+), sysdatetimeoffset(2008+)
--
返回系统日期和时间。 除了current_timestamp,都需要多加一对圆括号。
select
GETDATE() as [getdate]
,
current_timestamp as [current_timestamp]
, GETUTCDATE()
as [getutcdate]
, SYSDATETIME()
as [sysdatetime]
, SYSUTCDATETIME()
as [sysutcdatetime]
, SYSDATETIMEOFFSET()
as [sysdatetimewoffset];

-- cast和convert函数
--
cast(value as datatype)
--
convert(datatype, value, [, style_number])
--
cast是ansi标准的sql,而convert不是,推荐优先使用cast
select cast('20090212' as date);
select cast(sysdatetime() as date);
select cast(sysdatetime() as time);
select convert(char(8), CURRENT_TIMESTAMP, 112);
select cast(convert(char(8), current_timestamp, 112) as datetime);
select CONVERT(char(12), CURRENT_TIMESTAMP, 114);
select cast(convert(char(12), CURRENT_TIMESTAMP, 114) as datetime);

-- switchoffset函数可以按指定的时区对输入的datetimeoffset值进行调整
--
switchoffset(datetimeoffset_value, time_zone)
select SWITCHOFFSET(sysdatetimeoffset(), '-05:00');
select SWITCHOFFSET(sysdatetimeoffset(), '+00:00');

-- todatetimeoffset可以为输入的日期和时间值设置时区偏移量
--
todatetimeoffset(date_and_time_value, timezone)
--
这个函数与swichoffset函数的区别有两点。首先,它可以接受的输入不限于datetimeoffset值,
--
而是支持任何日期和时间数据类型。其次,它不是根据输入的原始值和指定的时区之间的差值来调整时间,
--
而只是简单的利用指定的时区和时间值作为datetimeoffset值返回。
select todatetimeoffset(SYSDATETIMEOFFSET(), '-05:00');
select TODATETIMEOFFSET(sysdatetime(), '-05:00');

-- dateadd函数可以将指定日期的部分作为单位,为输入的日期和时间值增加指定的数量
--
dateadd(part, n, dt_val);
--
日期部分的有效值包括year, quarter, month, dayofyear, day, week, weekday, hour, minute, sencond,
--
millisecond, nanosecond, 最后两个是2008新加的。
select dateadd(year, 1, '20090212');

-- datediff函数返回两个日期和时间值之间相差的指定部分的计数
--
datediff(part, dt_val, dt_val2)
select DATEDIFF(day, '20080212', '20090212');
-- 将当前系统日期和时间中值得时间部分设置为午夜
select
DATEADD(
day
,
datediff(day, '20010101', CURRENT_TIMESTAMP), '20010101');
-- 本月第一天
select
DATEADD(
month
,
DATEDIFF(month, '20010101', current_timestamp), '20010101');
-- 本月最后一天
select
DATEADD(
month
,
datediff(month, '20010131', current_timestamp), '20010131');

-- DATEPART函数返回一个给定的日期和时间值的指定的整数
--
datepart(part, dt_val)
--
part部分包括year, quarter, month, dayofyear, day, week, weekday, hour, minute, sencond,
--
millisecond, microsecond, nanosecond, TZoffset, ISO_WEEK。最后四个是2008种增加的。
select DATEPART(month, '20090212');

-- year, month, day函数

-- datename函数返回一个给定日期和时间值部分的字符串
--
datename(part, dt_val)
select DATENAME(month, '20090212');

-- isdate函数接受一个字符串作为输入,如果能把这个字符串转换为日期和时间数据类型的值,则返回1,否则返回0。
--
isdate(string)
select ISDATE('20090212');
select ISDATE('20090230');






-- 查询元数据
--
目录视图提供了关于数据库中各对象的非常详细的信息,包括sql server特定的信息。
--
如果想列出数据库中的各个表,以及它们的架构名称,只要按一下所示的方法去查询sys.tables视图
use tsqlfundamentals2008;
select
SCHEMA_NAME(schema_id)
as table_schema_name
, name
as table_name
from
sys.tables;

-- 要得到有关某个表的列信息,可以查询sys.columns表。
--
以下代码返回sales.orders表中的列信息,包括列名、数据类型(用type_name函数把系统类型id转换成类型名称)、
--
最大长度、排序规则名称, 以及是否允许为null。
select
name
as column_name
, type_name(system_type_id)
as column_type
, max_length
, collation_name
, is_nullable
from
sys.columns
where
object_id = object_id(N'sales.Orders');

-- 信息构架视图是位于information_schema架构内的一组视图,它们以一种标准化的方式来提供元数据信息。
--
也就是说,这些视图是基于ansi sql标准而定义的,因此它们自然不会包含sql server特有的元数据。
--
以下对information_schema.tables视图的查询可以列出当前数据库中的用户表,以及它们的架构名称
select table_schema, table_name
from information_schema.TABLES
where table_type = N'base table';

-- 以下对information_schema.columns视图的查询提供了有关sales.orders表中各个列的绝大多数的可用信息
select
column_name
, data_type
, character_maximum_length
from INFORMATION_SCHEMA.COLUMNS
where
table_schema
= N'sales'
and table_name = N'orders';


-- sp_tables存储过程返回可以在当前数据库中查询的对象列表:
exec sys.sp_tables;

-- sp_help存储过程接受一个对象名称作为输入,返回与之相关的多个结果集,包含了有关对象的一般信息,以及关于列、
--
索引、约束等对象的信息。以下代码返回关于orders表的详细信息:
exec sys.sp_help
@objname = N'sales.orders';

-- sp_columns存储过程返回对象中有关列的信息。以下代码返回orders表中关于列的详细信息:
exec sys.sp_columns
@table_name = N'orders',
@table_owner = N'Sales';

-- sp_helpconstraint存储过程返回对象中关于约束的信息。例如,以下代码返回orders表中关于约束的信息
exec sys.sp_helpconstraint
@objname = N'sales.orders';


-- 还有一组函数可以返回关于数据库实体的各属性信息。
--
serverproperty函数返回当前数据库实例的指定属性信息。
--
以下代码返回当前数据库实例的版本级别
select serverproperty('productlevel');
-- databasepropertyex函数返回最后定数据库的特定属性的信息。
--
下列代码返回tsqlfundamentals2008的排序规则的当前设置
select DATABASEPROPERTYEX(N'tsqlfundamentals2008', 'collation');
-- objectproperty函数返回指定对象的特定属性的信息。
--
以下代码的输出可以表明orders表是否具有主键
select objectproperty(object_id(N'sales_orders'), 'TableHasPrimaryKey');
-- columnproperty函数返回指定列上的特定属性的信息。
--
以下代码的输出可以表明orders表中的shipcountry列是否可以为null
select
COLUMNPROPERTY(object_id(N'sales.orders'), N'shipcountry', 'allownull');



-- 练习
--
第一题 返回2007年6月的订单
select
orderid
, orderdate
, custid
, empid
from
sales.Orders
where
orderdate
>= '20070601' and orderdate < '20070701';

-- 第二题 返回每个月最后一天的订单
select
orderid
, orderdate
, custid
, empid
from
sales.Orders
where
month(dateadd(day, 1, orderdate)) = month(DATEADD(month, 1, orderdate));

-- 答案
select orderid, orderdate, custid, empid
from Sales.Orders
where orderdate = DATEADD(month, datediff(month, '19991231', orderdate), '19991231');

-- 第三题 返回姓氏(last name)中包含字母‘a'两次或更多次的雇员
select
empid
, firstname
, lastname
from
hr.Employees
where
lastname
like '%a%a%';

-- 第四题 返回总价格(数量 * 单价)大于10000的所有订单,并按总价格排序
--
错误,总价,先分组
--
select
--
orderid
--
, (unitprice * qty) as totalvalue
--
from
--
sales.OrderDetails
--
where
--
(unitprice * qty) > 10000
--
order by
--
totalvalue desc;

select
orderid
,
sum((unitprice * qty)) as totalvalue
from
Sales.OrderDetails
group by
orderid
having
sum(unitprice * qty) > 10000
order by
totalvalue
desc;

-- 第五题 返回2007年平均运费最高的三个发货国家(结果与答案不同)(没写年份!!!!)
select top (3)
shipcountry
,
avg(freight) as avgfreight
from
sales.Orders
where
year(orderdate) = 2007
group by
shipcountry
order by
avgfreight
desc;

-- 第六题 为每个顾客单独根据订单日期的顺序(用orderid作为附加属性)里计算其订单的行号。
select
custid
, orderdate
, orderid
, ROW_NUMBER()
over(order by custid, orderid) as rownumber
from
Sales.orders
order by
custid, orderid;

-- 第七题 构造一个select语句,让它根据每个雇员的友好称谓,而返回性别。对于'Ms.'和
--
'Mrs.',返回'Female';对于'Mr.'则返回'Male'; 对于其他情况(例如,'Dr.'),则返回'unknown'。
select
empid
, firstname
, lastname
, titleofcourtesy
,
case titleofcourtesy
when 'Mr.' then 'Male'
when 'Mrs.' then 'Female'
when 'Ms' then 'Female'
else 'Unknown'
end as gender
from
hr.Employees;

--
select empid, firstname, lastname, titleofcourtesy,
case
when titleofcourtesy in ('Ms.', 'mrs.') then 'Female'
when titleofcourtesy = 'mr.' then 'Male'
else 'Unknown'
end as gender
from hr.Employees;

-- 第八题 返回每个客户的客户id和所在区域。对输出中的行按区域排序,null值排在最后面。
--
(注意,tsql中默认是把null排在前面的)
--
为了把null值排在最后,可以用一个case表达式,当region列为null时就返回1,当region列不为null是就返回0.
--
非null值得表达式返回值为0,因此,他们会排在null值的前面。把case表达式作为第一个排序列,并把region列
--
指定为第二个排序列。这样,非null值也可以正确的参与排序。
select
custid
, region
from
sales.Customers
order by
case when region is null then 1 else 0 end
, region;