2008技术内幕:T-SQL语言基础 单表查询摘记
这里的摘抄来自《Microsoft SQL Server 2008技术内幕:T-SQL语言基础》,书中用到的案例数据库是这个 TSQLFundamentals2008 ,官网给出的连接是这个(貌似有的要穿墙), 冠军也有一份。
第一章 T-SQL查询和编程基础
SQL (Structured Query Language)
为查询和管理关系型数据库管理系统(RDBMS--Relational Database Management System)中的数据而专门设计的一种标准语言。
RDBMS是一种基于关系模型的数据库管理系统,而关系模型则是一种用于表示数据的语义模型。该模型基于两种数学理论:集合论和谓词逻辑。
关系模型是独立于语言的,除了SQL外,还可以用其他的语言来实现关系模型,例如,C#的类模型。
SQL是基于关系模型的ANSI和ISO标准语言。
SQL有几种不同类型的语句:
数据定义语言(DDL--Data Definition Language):用于处理数据对象的定义,包括Create、Alter、Drop语句。
数据处理语言(DML--Data Manipulation Language):用于查询和修改数据,包括Select、Insert、Update、Delete、Merge语句。
数据控制语言(DCL--Data Control Language):用于处理权限管理,包括Grant,Revoke语句。
此书重点介绍的是DML数据处理语言。
集合
所谓集合是把我们直观或思维中确定的、不同的哪些对象作为一个整体来考虑的结果。这些对象就是集合的元素或成员。
谓词逻辑:True or False
约束:通过规则或约束来确保关系模型数据的完整性。
范式:
第一范式,表中的行必须是唯一的。属性应该是原子的(即列不可再拆分)。
第二范式,有两条,第一:首先必须满足第一范式;
第二要求非键属性和候选键属性之间必须满足一定的条件,对于每个候选键,每个非键属性都必须完全函数依赖于整个候选键。
第三范式,有两条,第一:首先必须满足第二范式;
第二所有非键属性必须非传递依赖于候选键。 第二、第三范式概括起来讲:每个非键属性都依赖与键。
自己的话概括,第一范式,表中的各个列不可拆分,有明确的属性;
第二范式,在满足第一范式的同时,每个表还要有一个唯一的主键标识,通过主键可以查到这个实例的表中所有信息;
第三范式:在满足第二范式的同时,两表联合,不能通过两表的主键与主键联接,因存在表1的主键与表2的非主键联合。(救命实在是不知道怎么表达了...)
例子,员工表(employe),部门表(department),员工表中有员工编号,员工姓名,员工性别,员工住址,所属部门编号等等;部门表中有部门编号,部门名称,部门信息等等。
数据生命周期
联机事务处理(OLTP--OnLine Transctional Processing):重点是数据输入,主要处理的事务包括插入、更新 删除数据。
数据仓库(DW--Data Warehouse):专门针对数据检索和生成报表而设计的环境。
当这样的环境服务于整个企业时,就称之为数据仓库;而只服务于企业一部分时,如一个特定的部门,就称之为数据集市(data mart)。
数据集市主要是为了支持数据检索,而对数据库仓库中的数据模型进行设计和优化。
联机分析处理(OLAP--OnLine Analytical Processing)系统,支持对聚合后的数据进行动态的在线分析。
数据挖掘(DM--Data Mining)由数据挖掘算法梳理数据,从中筛选出有用的信息。
文件扩展名
.mdf(Master Data File主数据文件)
.ndf(Not Master Data File非主数据文件)
.ldf(Log Data File日志数据文件)
架构(Schema)和对象:
一个数据库包含多个架构,而每个架构又包含多个对象(对象可以是表,视图,存储过程等)。
可以在架构级别上控制对象的访问权限。表是属于架构,而架构又是属于数据库的。
CREATE SCHEMA xxx AUTHORIZATION dbo;
建表:
DB_ID(databaseName) return int,函数接受一个数据库名称作为输入,返回它的内部数据库ID
如果输入的数据库不存在,那么返回的将是NULL,这是检查数据库是否存在的简单方法。
--如果不存在testdb的数据库,那么重新创建一个叫testdb的数据库 IF DB_ID('testdb') IS NULL CREATE
DATABASE testdb
更多详细的建库建表,另看:SQL_DDL_建库建表
第二章 单表查询
select
select语句的目的是对表进行查询、应用一定的逻辑处理,并返回结果。
查询处理顺序
1. from
2. where
3. group by
4. having
5. select
6. 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
--按照查询逻辑顺序
--1. from
--2. where
--3. group by
--4. having
--5. select
--6. order by
--上面的select查询语句可解释成
FROM Sales.Orders--从Sales.Orders表中查询记录
WHERE custid=71--数据筛选,只留下custid=71的数据
GROUP BY empid,YEAR(orderdate)--按照雇员id和订单年份进行分组
HAVING COUNT(*) > 1--分组后筛选,一年中下单超过1份的留下,不包括1
SELECT empid,YEAR(orderdate) AS orderyear,COUNT(*) AS numorders--select查询出来
ORDER BY empid,orderyear--按照雇员id和年份排序
From 子句
from子句是在逻辑阶段第一个要处理的查询子句。这个子句用于指定要查询的表名,以及对这些表进行操作的表运算符。
为了避免SQL Server自己主动隐式去解析一个对象的架构,我们有必要通过显示指定架构名称,这样既能保证得到的对象确实是你原来想要的又减少了一些额外的代价(隐式推算架构),还减少了有可能产生的错误。
Where 子句
在where子句中,可以指定一个谓词或者逻辑表达式,从而过滤由from阶段返回的行,where阶段只返回让逻辑表达式为True的哪些行。
这里需要强调记忆的是:T-SQL使用的是三值谓词逻辑,True,False,Unknow
Group By 子句
Group by 阶段可以将前面逻辑查询处理阶段返回的行按“组”进行组合。 因为聚合函数只为每个组返回一个值,所以一个元素如果不在Group by列表中出现。就只能作为聚合函数(count、sum、avg、min、max)的输入。
Having 子句
Having子句用户指定对组装进行过滤的谓词或逻辑表达式。Having子句只会留下逻辑表达式为True组的数据,False和Unknow将会被过滤。
Select 子句
select子句用于指定需要在查询返回的结果集中包含的属性(列),select列中的表达式可以直接基于正在查询的表的各个列上做进一步的处理。
关于别名
在select查询中,T-SQL允许查询返回没有名称的结果集列(但会没有列名),但关系模型不允许这样,所以最好是加上别名。
取别名的三种方式
除了<表达式>as<别名> 这种格式,T-SQL还支持<别名>=<表达式>(别名 等于 表达式) 和 <表达式><别名>(表达式 空格 别名)三种方式,
不过最好还是用 <...>as<...>这种方式,直观,方便阅读。 关于取别名还有一个要注意的是,如:
把select col1,col2 from table_1 写成了 select col1 col2 from table_1它还是成立的,只是本来查两列的,成了一列而且列名为col2了,这样的bug很不好被发现,所以要小心编写。
另一个易犯的错
select子句是在from、where、group by、having子句之后处理的。这意味着对select子句之前处理的那些子句,在select子句中为表达式分配的别名不存在。如:
SELECT orderid,YEAR(orderdate) AS orderyear
FROM Sales.Orders
WHERE orderyear > 2006
这样做,是会报错的!因为逻辑顺序from,where...select,处理where阶段的时候,并没有别名orderyear,所以会报错:Invalid column name 'orderyear'.
Order by 子句(ASC 升序<默认> DESC降序)
Order by子句用于展示数据对输出结果中的进行排序。 SQL最重要的一点:表不保证是有序的,因为表是为了代表一个集合,而集合是无序的。
Top 选项
Top选项是T-SQL特有的,用于限制查询返回的行数或百分比。依靠了Order by就会起到双重作用。在select阶段。
在Top选项中,还可以使用Percent关键字,如: select top(1) percent ,id,name,age from tabName
在top选项中,我们还可以加入 with ties 选项
1.
SELECT TOP 5 * FROM Sales.Orders ORDER BY custid DESC
SELECT TOP 5 WITH TIES * FROM Sales.Orders ORDER BY custid DESC
2.
DECLARE @table TABLE
(
id INT NULL,
Aid INT NULL
)
INSERT INTO @table ( id, Aid )VALUES (1,1)
INSERT INTO @table ( id, Aid )VALUES (2,1)
INSERT INTO @table ( id, Aid )VALUES (3,1)
INSERT INTO @table ( id, Aid )VALUES (4,2)
INSERT INTO @table ( id, Aid )VALUES (5,2)
INSERT INTO @table ( id, Aid )VALUES (6,2)
INSERT INTO @table ( id, Aid )VALUES (7,3)
INSERT INTO @table ( id, Aid )VALUES (8,3)
INSERT INTO @table ( id, Aid )VALUES (9,4)
INSERT INTO @table ( id, Aid )VALUES (10,4)
INSERT INTO @table ( id, Aid )VALUES (11,4)
INSERT INTO @table ( id, Aid )VALUES (12,5)
INSERT INTO @table ( id, Aid )VALUES (13,5)
INSERT INTO @table ( id, Aid )VALUES (14,6)
INSERT INTO @table ( id, Aid )VALUES (15,6)
INSERT INTO @table ( id, Aid )VALUES (16,6)
INSERT INTO @table ( id, Aid )VALUES (17,6)
SELECT TOP 5 * FROM @table ORDER BY Aid desc
SELECT TOP 5 WITH TIES* FROM @table ORDER BY Aid DESC
加入with ties选项之后,可以筛选出更多的与最后一行相同值的其他行,更多更详细可看参考二。
Over 子句 开窗函数(window function)
Over子句用于为行(row)一个窗口,以便进行特定的运算。可以把行的窗口简单的认为是运算将要操作的一个行的集合。
如果想对行(row)进行限制或者分区,这可以使用Partition by 子句。
如下,第一个查询的是所有行,二个查询的是所有价格的总和,三个则是查询当前客户(和当前行具有相同custid的所有行)的总价格,
Sum(val) Over (Partition by custid)
--1
SELECT orderid ,custid ,val
FROM Sales.OrderValues
ORDER BY custid ASC
--2
SELECT orderid ,custid ,val ,
SUM(val) OVER ( ) AS totalValue
FROM Sales.OrderValues
ORDER BY custid ASC
--3
SELECT orderid ,custid ,val ,
SUM(val) OVER ( PARTITION BY custid ) AS CurrTotalValue
FROM Sales.OrderValues
Over子句的一个有点就是能够在返回基本列的同时,在同一行对它们进行聚合;也可以在表达式中混合使用基本列和聚合值列。
SELECT orderid,custid,val,
100*val/SUM(val) OVER() AS alltotal, --每行val占总val的百分比
100*val/SUM(val) OVER(PARTITION BY custid) AS totalbyaustid--每行val占当行custid的总val的百分比
FROM Sales.OrderValues
Over子句也支持四种排名函数:ROW_NUMBER(行号)、 RANK(排名)、 DENSE_RANK(密集排名) NTILE。
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
--图片 1
row_number 函数用于为查询的结果集中的各行分配递增的序列号,其逻辑顺序通过over子句中的order by 语句进行指定。
rank 和 dense_rank 函数与row_number类似,但它们为具有相同逻辑排序值的所以行生产相同的排名。
rank 和 dense_rank 函数的区别是 如图 1,如果rank有相同的val值,两个都是 7 ,那么下一个 rank 的值是 9,跳过了 8; 而dense_rank却不同,上两个是7,下面接上还是8;
ntile 函数可以把结果中的行关联到组(tile,相当于由行组成的指定数目的组) ,并为每一行分配一个所属的组的编号。
比如,现在有一个集合是20条数据,利用ntile(5) 函数,我将1-4 条分为一组,组号为 1 ,5-8 一组,组号 2 这样以此类推。
ntile 函数在逻辑上需要依赖与row_number函数。
整个过程是先根据对val的排序结果,为每一行分配行号,再等量分组,如果除不尽,如有22条数据要分10组, ntile(10),那么一二组将会有3条记录。
SELECT * ,NTILE(10) OVER ( ORDER BY val ) AS [ntile]
FROM (
SELECT orderid ,custid , val ,
ROW_NUMBER() OVER ( ORDER BY val ) AS [rownum]
FROM Sales.OrderValues
) v WHERE rownum BETWEEN 1 AND 22
图二
和聚合开窗函数一样,排名函数也支持在over子句中使用partition by 语句。rank 、dense_rank与 row_number差不多
SELECT orderid ,custid ,val ,
rank() OVER ( PARTITION BY custid ORDER BY val ) AS [rownum]
FROM Sales.OrderValues
SELECT orderid ,custid ,val ,
NTILE(2) OVER ( PARTITION BY custid ORDER BY val ) AS [rownum]
FROM Sales.OrderValues
图三
加入了over distinct top 之后的SQL逻辑处理顺序
1. from
2. where
3. group by
4. having
5. select
5.1 over
5.2 distinct
5.3 top
6. order by
如果想利用不同的val生成行号,不用使 row_number 和 distinct 同级使用,因为row_number是在distinct之前处理的,而且row_number是唯一的 后面的distinct无效。
但我们可以采用下面两中方案。
--一
SELECT val ,ROW_NUMBER() OVER ( ORDER BY val ) AS [rownum]
FROM Sales.OrderValues
GROUP BY val
--二
SELECT * ,ROW_NUMBER() OVER ( ORDER BY val ) AS [rownum]
FROM ( SELECT DISTINCT val
FROM Sales.OrderValues
) v
图四
谓词和运算符
谓词:T-SQL支持的谓词包括IN、Between、以及Like。
in 用于检查一个值(或标量表达式)是否与一组元素中的至少一个相等。
如:select c1,c2,c3 from tab_1 where id in (1001,1101)
between用于检查一个值是否在一定的范围内,包括两个指定的边界值。
如:select c1,c2,c3 from tab_1 where id between 1 and 5 --包括1和5
like用于检查一个字符串值是否与指定的模式匹配。
如:select c1,c2,c3 from tab_1 where name like '%D%'
运算符:T-SQL支持的比较运算符 =、>、<、>=、<=、<>、!=、!>、!<,最后三个运算符不是标准运算符
小数运算 5/2 整数等于2 而不是2.5,如果想得到小数的话,可以这样
SELECT CAST(5 AS NUMERIC(12,2))/CAST(2 AS NUMERIC(12,2))
NUMERIC(12,2)这个数据类型的精确度为12,带有2位小数
SQL运算符的优先级
1.()
2.*,/,%
3.+,-
4.=,>,<,>=,<=,<>,!=,!>,!<
5.not
6.and
7.between,in,like,or
8.=
如下句子
SELECT * FROM Sales.Orders WHERE
custid =1 AND empid IN(1,3,5)
OR
custid = 85 AND empid IN(2,4,6)
因为and比or的优先级高,所以是先出现 由客户1下的并由雇员1,3,5处理的订单,在返回由客户85下的并由雇员2,4,6处理的订单。
SELECT * FROM Sales.Orders WHERE
(custid =1 AND empid IN(1,3,5))
OR
(custid = 85 AND empid IN(2,4,6))
圆括号的优先级最高,虽然我们在这里加上了,并没有改变他的逻辑运算,但明显的提高了代码的可读性,逻辑清晰了很多,这个一个很好的编程习惯!
Case表达式
case表达式是一个标量表达式,它基于条件逻辑来返回一个值。
在这里值得注意的是,case是一个表达式,而不是一条语句,也就是说,不能用它来控制活动的流程,也不能根据条件逻辑来做某些处理。相反,它只是根据条件逻辑来返回某个值。
因为case是一个标量表达式,所以他可以支持任何标量表达式(如:select、where、having、order by子句)check约束等等。
case表达式有两种格式:简单表达式 和 搜索表达式。
简单格式:将一个值(或者一个标量表达式)与一组可能的取值进行比较,并返回第一个匹配的结果。
如果列表没有值等于测试值,case表达式就返回其else子句中列出的值。如果case表达式中没有else子句,这默认将其视为else null。
SELECT c1 ,c2 ,c3 ,c4 ,
CASE empid
WHEN 1 THEN 'Monday'
WHEN 2 THEN 'Tuesday'
WHEN 3 THEN 'Wednesday'
WHEN 4 THEN 'Thursday'
WHEN 5 THEN 'Friday'
WHEN 6 THEN 'Saturday'
WHEN 7 THEN 'Sunday'
ELSE 'Unknow'
END AS 'a week'
FROM table_1
下面一个例子利用case与开窗函数(over),将集合中的数据根据val生成三个层次Low、Medium、High
SELECT orderid,custid,val,
NTILE(3) OVER(ORDER BY val) AS Tile,
CASE NTILE(3) OVER(ORDER BY val)
WHEN 1 THEN 'Low'
WHEN 2 THEN 'Medium'
WHEN 3 THEN 'High'
ELSE 'Unknow'
END AS 'TitleDesc'
FROM Sales.OrderValues
图 2 部分截图
Case搜索表达式
case简单表达式只有一个测试值(或表达式),它紧跟在case关键字后面,与where子句中的一组可能值进行比较。
而case搜索表达式要更灵活些,他可以在when子句中指定谓词或逻辑表达式,而不是只进行相等比较。
case搜索表达式返回结果为true的第一个when逻辑表达式所关联的then子句中指定的值。
如果没有任何when表达式结果为true,case表达式就返回else子句中出现的值(如果else子句也没指定,就返回null)。
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 300' WHEN val > 3000.00 THEN 'More then 3000' ELSE 'Unkown' END AS 'ValueCategory' FROM Sales.OrderValues
NULL值
SQL用NULL符号来表示缺少的值。
null 与三值谓词逻辑,
表达式 salary > 0 ,当salary 等于 1 时 ,表达式的结果为 true
当salary 等于-1 时 ,表达式的结果为 false
当salary 等于null时 ,表达式的结果为 unknow
表达式 null=null ,其结果是unknow ,不管什么值与null比较得到的逻辑值都是unknow
在三值谓词逻辑中,接受true 则会拒绝unknow,而拒绝false 则会接受unknow。
where条件后面只接受为true的逻辑结果,如果想得到指定的值和列为NULL的集合,可以这样
SELECT c1 ,c2 ,c3 ,c4 ,c5 FROM tableName WHERE c1 = '' OR c1 IS NULL
在SQL中,有的时候又会认为两个NULL彼此相等,当进行分组和排序时,两个NULL值是相等的。
对于多个NULL的排序,ANSI SQL把它留给了具体的产品实现。T-SQL是把NULL值排在了有效值之前。
ANSI SQL有两种unique约束,一种是将多个null值视为相等的(值允许有一个null值),另一个则将多个null值视为不同的(允许有多个null值)。SQL Server只实现了前者。
同时操作(All-At-Once-Operation)这一章不是很理解,第二的例子完全在打P
概念:在同一逻辑查询处理阶段中出现的所有表达式都是同时进行计算的。
--来看
SELECT orderid ,
orderdate ,
YEAR(orderdate) AS yearDate,
yearDate + 1 AS nextyeat
FROM Sales.Orders
--这样是不行的,看起来可行,但其实是会报错的
Invalid column name 'yearDate'.
--再来看
SELECT *
FROM Sales.OrderDetails
WHERE col1 <> 0
AND col2 / col1 > 2
因为SQL存在同时操作的概念,上面的语句,我们担心出问题,so将条件改动,因为如果是 col2 / col1 > 2 ,遇到col=0就sb了。
--1
SELECT *
FROM Sales.OrderDetails
WHERE CASE WHEN col1 = 0 THEN 'no'
WHEN col2 / col1 > 2 THEN 'yes'
ELSE 'no'
END = 'yes'
--2
SELECT *
FROM Sales.OrderDetails
WHERE col1 <> 0
AND col2 > 2 * col1
--3
SELECT *
FROM Sales.OrderDetails
WHERE col1 <> 0
AND col2 / 2 > col1
数据类型
SQL Server支持两种字符数据类型:普通字符和Unicode字符
普通的字符数据类型包括 char、varchar、 变量表示 'XXX'
Unicode字符数据类型包括Nchar、Nvarchar 变量表示 N'XXX'
二者的区别
普通字符使用一个字节(byte)来保存每个字符,而Unicode字符则需要两个字节。普通字符只能代表256(2^8)个不同的字符,Unicode可以代表65536(2^16)个不同的字符。
char、Nchar都是固定长度的。比例 char(25) 长度就是25个字符,无法扩展。
Nchar、Nvarchar是可变长的。SQL会按照实际长度来保存数据,外加两个额外的自己以保存数据的偏移值。比例varchar(25)最多只能保存25个字符。
排序规则
排序规则是字符数据的一个属性,封装了几个方面的特征,包括多语言支持(和Unicode类型有关,因为它支持所有语言)、排序规则、区分大小写、区分重音,等等。
要得到系统中目前支持的所有排序规则及其描述,可以查询表函数sys.fn_helpcollations()
SELECT [name] , [description] FROM sys.fn_helpcollations()
例如,排序规则 Latin1_General_CI_AS
Latin1_General 支持的语言是英语
字典排序 基于字段顺序对字符数据进行排序和比较('A'和'a'<'B'和'b')。
CI 数据不区分大小写
AS 数据区分重音
SQL Server实例的排序规则是在安装时设置的,他决定了所有系统数据库的排序规则,同时也是用户数据库默认使用的排序规则。
当创建用户数据库时,可以使用COLLATE子句指定数据库的排序规则。如果不指定,则默认采用SQL Server实例的排序规则。
如果想在列的排序规则是不区分大小写的前提下,让过滤条件区分大小写的,则可以按照如下所示的方法来修改表达式的排序规则:
--默认排序规则 SELECT empid , firstname , lastname FROM HR.Employees WHERE lastname = N'davis' --利用表达式修改排序规则 SELECT empid , firstname , lastname FROM HR.Employees WHERE lastname COLLATE Latin1_General_CS_AS = N'davis'
分隔符
在标准SQL中,单引号用于分割文字字符串,而双引号用于分割不规则的标识符(表名或列名包含空格或以数据作为开始)。除了双引号分割符外,还有[]作为分隔符。
运算符和函数
--1
SELECT region ,country ,
region + N',' + country AS 'Address'
FROM Sales.Customers
--如果 region 列的值为null的话,Address 拼接的值也为NULL。
--通过SET CONCAT_NULL_YIELDS_NULL OFF会话就可以改变SQL Server处理串联的方式
--2
SET CONCAT_NULL_YIELDS_NULL OFF
SELECT region ,country ,
region + N',' + country AS 'Address'
FROM Sales.Customers
SET CONCAT_NULL_YIELDS_NULL ON
--但是!强烈建议避免修改SQL Server的标准行为,大多数程序员(或接口)都默认代码会以标准行为来进行处理。
--如果只是将NULL值用''替换,我们能用coalesce函数。
--3
SELECT region ,country ,
COALESCE(region,'') + N',' + country AS 'Address'
FROM Sales.Customers
函数
T-SQL提供了一套字符串处理函数,其中包括substring、left、right、len、charindex、patindex、replace、replicate、stuff、upper、lower、rtrim、ltrim等等。
substring 函数用于从字符串中提取子串。
语法
substring(string,start,length)
start :从1开始,不能超过输入字符串的长度
length:从1开始,可超过输入字符串的长度,但只能取得输入长度的最长值。
该函数对输入的字符串进行处理,提取从指定位置开始,遇有特定长度的子字符串。
如:PRINT SUBSTRING('jolinson',1,10) --jolinson
left 和 right函数
left 和 right函数是substring函数的简略形式,他们分别返回输入字符串从左边或右边开始指定个数的字符。
left(string,n),right(string,n)
string 要处理的字符串
n 从字符的左边或右边提取的字符个数
PRINT LEFT('jolinson',8)--jolinson
PRINT LEFT('jolinson',8)--jolinson
len 和 datalength函数
len函数返回输入字符串中的字符数。
语法
len(string)
datalength函数返回字符串字节数的长度。
语法
datalength(string)
PRINT LEN('jolinson')--8
PRINT DATALENGTH('jolinson')--8
PRINT LEN(N'jolinson')--8
PRINT DATALENGTH(N'jolinson')--16
还有一个区别:len和datalength函数,前者不包含尾随空格,而后者会包含尾随的空格。
PRINT LEN(' jolinson ')--9
PRINT DATALENGTH(' jolinson ')--10
PRINT LEN(N' jolinson ')--9
PRINT DATALENGTH(N' jolinson ')--20
charindex函数
charindex函数返回字符串中某个字符串第一次出现的起始位置。
语法
charindex(substring,string,[,start_pos])
substring 要搜索的字符
string 输入的字符串,从这里找
start_pos 开始位置,可选,如果没有从字符串第一个字符开始搜索
返回 找到则出现第一次出现的位置,没找到则返回0
PRINT CHARINDEX('on','jolinson',6)--7
patindex函数
patindex函数返回字符串中某个模式第一次出现的起始位置。
语法
patindex(pattern,string)
pattern 通配符
string 输入的string
PRINT PATINDEX('%[0-9]%','sdfas1321asdf') --6
replace函数
replace函数将字符串中出现的所有某个子字符串替换为另一个字符串。
replace(string,substring1,substring2)
将string中的substring1 替换为 substring2
PRINT REPLACE('1-2 , 2-3','-',':') --1:2 , 2:3
还可以利用replace函数计算出某个substring在string中出现的次数
SELECT custid ,
contactname ,
LEN(contactname) - LEN(REPLACE(contactname, 'a', '')) AS 'count'
FROM Sales.Customers
replicate 函数
replicate函数以指定的次数复制字符串值
语法
replicate(string,n)
string 要被赋值的字符串
n 复制的次数
PRINT REPLICATE('jolinson ', 3)--jolinson jolinson jolinson
利用replicate、right函数,成生列以0开头10位数字的数据
SELECT RIGHT(REPLICATE('0', 9) + CAST(supplierid AS VARCHAR(10)), 10) AS strsupplierid
FROM Production.Suppliers
0000000029
0000000028
0000000004
0000000021
0000000002
0000000022
0000000014
0000000011
首先利用replicate函数将0复制了9个,然后拼接supplierid,再利用right函数从右边开始截取10个字符.
stuff函数
stuff函数对输入参数string,从输入参数pos指定的位置开始删除delete_length参数指定长度的字符,然后将insertstring 参数指定的字符串插入到pos指定的位置
语法
stuff(string,pos,delete_length,insertstring)
string 被输入的string pos 开始删除位置,位置从1开始。从0开始的话会得到一个null
delete_length 删除长度,是0,是0的话,将不删除任何字符将字符插在string最前面
insertstring 重新插入的字符串
print STUFF('jolinson',1,1,'J')--Jolinson
upper 和 lower 函数
upper和lower函数将输入字符串中的所有字符都转为大写或小写字符。
语法
upper(string) lower(string)
PRINT UPPER('jolinson')--JOLINSON
PRINT LOWER('JOLINSON')--jolinson
rtrim 和 ltrim函数
用于删除输入字符串中的尾部空格或开头处空格。
语法
rtrim(string)、ltrim(string)
PRINT RTRIM(' jolinson ')-- jolinson
如果既想删除开头空格又想删除末尾空格可以使用两个函数嵌套
SELECT LTRIM(RTRIM(' jolinson '))-- jolinson
模式匹配
Like谓词 用于检查字符串是否能够匹配指定的模式。
%(百分号) 通配符代表任意长度的字符串,包括空字符串。'A%' ----匹配任意A字符开头的字符串。
_(下划线) 代表任意单个字符。'A_C' ----匹配A开头C结尾中间一个为任意字符的字符串
[<字符列>]通配符,方括号中包含一列字符(例如'[ABC]'),表示必须匹配列指定字符中的一个字符。'[ABC]%' ----匹配A、B或者C开头的字符串。
[<字符>-<字符>]通配符方括号中包含一个字符范围(例如['A-Z']),表示必须匹配指定范围内的一个字符。'[A-G]'匹配的只能是ABCDEFG其中的一个字符。
[^<字符列或范围>]通配符表示不属于指定字符列或范围内的任意单个字符串。 '[^A-G]'匹配的只能是除ABCDEFG以外的任一个字符
转移字符,当搜索的通配符中出现了特定的通配符如'%','_','[',']'的时候,我们需要把他们转义。 LIKE '%!_%' ESCAPE '!' 或者 LIKE '%[!]_%'
处理日期和时间数据
数据类型 存储大小(字节) 日期范围 准确度 推荐格式及示例
datetime 8 1753-01-01到9999-12-31 3.33毫秒 'YYYYMMDD hh:mm:ss:nnn' '20090212 12:30:15:123'
smalldatetime 4 1900-01-01到2079-06-06 1分钟 'YYYYMMDD hh:mm' '20090212 12:30'
date 3 0001-01-01到9999-12-31 1天 'YYYYMMDD'
time 3-5 00:00:00.0000000到23:59:59.9999999 100纳秒 'hh:mm:ss.nnnnnn' '12:30:15.1234567'
datetime2 6-8 0001-01-01 00:00:00.0000000到 100纳秒 'YYYYMMDD hh:mm:ss.nnnnnn'
9999-12-31 23:59:59.9999999 '20090212 12:30:15.1234567'
Datetimeoffset 8-10 0001-01-01 00:00:00.0000000到 100纳秒 'YYYYMMDD hh:mm:ss.nnnnnn[+|-]' hh:mm
9999-12-31 23:59:59.9999999 '20090212 12:30:15.1234567+02.00'
最后三种数据类型(time、datetime2、datetimeoffset)的存储空间大小要依赖于所选择的精度,可以通过0-7之间的整数来指定其精度
DECLARE @time DATETIME2=GETDATE()
DECLARE @time0 DATETIME2(0)=GETDATE()
DECLARE @time1 DATETIME2(1)=GETDATE()
DECLARE @time2 DATETIME2(2)=GETDATE()
DECLARE @time3 DATETIME2(3)=GETDATE()
DECLARE @time4 DATETIME2(4)=GETDATE()
DECLARE @time5 DATETIME2(5)=GETDATE()
DECLARE @time6 DATETIME2(6)=GETDATE()
DECLARE @time7 DATETIME2(7)=GETDATE()
SELECT @time,@time0,@time1,@time2,@time3,@time4,@time5,@time6,@time7
--2014-03-03 16:09:30.3030000
--2014-03-03 16:09:30
--2014-03-03 16:09:30.3
--2014-03-03 16:09:30.30
--2014-03-03 16:09:30.303
--2014-03-03 16:09:30.3030
--2014-03-03 16:09:30.30300
--2014-03-03 16:09:30.303000
--2014-03-03 16:09:30.3030000
set language 命令改写会话中默认语言(不推荐,因为代码中的某些地方可能会依赖与用户的默认语言)
set dateformat 命令可以设置当把字符串类型转换成日期和时间类型时,SQL Server如何解释输入的字符串常量。
dateformat 设置是由字符d、m、y的组合表示的。例如 us_english语言设计会把dateformat设置为mdy,而british语言设置则将dateformat设置为dmy。
当一个表达式涉及了两种不同类型的操作数,会将其中类型隐式转换成另外一种类型在进行比对,总是将数据类型优先级低的向高的转换。
数据类型优先级 当两个不同数据类型的表达式用运算符组合后,数据类型优先级规则指定将优先级较低的数据类型转换为优先级较高的数据类型。如果此转换不是所支持的隐式转换,则返回错误。
当两个操作数表达式具有相同的数据类型时,运算的结果便为该数据类型。
SQL Server 对数据类型使用以下优先级顺序:
1.用户定义数据类型(最高)
2.sql_variant
3.xml
4.datetimeoffset
5.datetime2
6.datetime
7.smalldatetime
8.date
9.time
10.float
11.real
12.decimal
13.money
14.smallmoney
15.bigint
16.int
17.smallint
18.tinyint
19.bit
20.ntext
21.text
22.image
23.timestamp
24.uniqueidentifier
25.nvarchar(包括 nvarchar(max))
26.nchar
27.varchar(包括 varchar(max))
28.char
29.varbinary(包括 varbinary(max))
30.binary(最低)
为了潜在地有效利用索引,就需要对谓词进行调整,一边对过滤条件中的列不进行处理----在谓词条件中不做过多的处理
cast和convert函数
cast 和 convert函数用于转换值的数据类型。
语法
cast(value as datatype)
convert(datatype , value , [style_number])
这两个函数都可以将输入的值转换为指定的数据类型。在一些情况下,还能用convert提供的第三个参数来指定转换的样式。
注意 cast是ANSI标准SQL,而convert不是,所以除非需要使用样式值,否则推荐优先使用cast函数。
DateAdd函数
DateAdd函数 可以将指定日期的部分作为单位,为输入的日期和时间值增加指定的数量。
语法
dateadd(part,n,dt_val)
日期部分的有效值包括year、quarter、month、dayofyear、day、week、weekday、hour、minite、second、millisecond、microsecond、nanosecond,最后两个是SQL 2008新增加的。
也可以用缩写代替如 year(yy).
SELECT DATEADD(YEAR,2,'20120228')--2014-02-28 00:00:00.000
datadiff函数
datediff返回两个日期和时间值之间相差的指定部分的计数
语法
datediff(part,dt_val,dt_val2)
返回两个值之间的相差的天数 SELECT DATEDIFF(DAY,'20130228','20140228')--365
--返回两个值之间的相差的天数
SELECT DATEDIFF(DAY,'20130228','20140228')--365
--当天午夜
SELECT DATEADD(DAY,DATEDIFF(DAY,0,CURRENT_TIMESTAMP),0);--2014-02-26 00:00:00.000
--当月第一天
SELECT DATEADD(month,DATEDIFF(month,0,CURRENT_TIMESTAMP),0);--2014-02-01 00:00:00.000
--当月的最后一天
SELECT DATEADD(month,DATEDIFF(month,'19911231',CURRENT_TIMESTAMP),'19911231');--2014-02-28 00:00:00.000
datepart函数
datepart函数返回一个表示给定日期和时间值的指定部分的总数。
语法
patepart(part,dt_val)
part参数的有效值包括year、quater、month、dayofyear、day、week、weekday、hour、minute、second、milliseconde、milliseconde、microsecond,manosecond,
TZoffset、ISO_WEEK
year、month、day函数
year、month、day函数是datepart函数的简略版本,他们分别返回一个代表输入日期和时间值中年月日部分的整数。
语法
year(dt_val)
month(dt_val)
day(dt_val)
datename 函数
datename函数返回一个表示给定日期和时间值的指定部分的字符串。
语法
datename(part,dt_val)
SELECT DATENAME(year,'20240225')--2024
SELECT DATENAME(month,'20240225')--February
SELECT DATENAME(day,'20240225')--25
datename返回的月份名称是依赖语言的。
isdate函数
isdate函数接受一个字符串作为输入,如果能把这个字符串转换为日期和时间数据类型的值,则返回1,如果不能则返回0。
语法 isdate(string)
select ISDATE('20240225')--1
查看更多时间与转型的例子 日期函数与转型
查询元数据
如果想列出数据库中的各个表,以及他们的框架名称,查询sys.tables
SELECT SCHEMA_NAME([schema_id]) AS table_schema_name ,
name AS table_name
FROM sys.tables
上面利用了SCHEMA_NAME()函数把表示框架Id的整数转换成它的名称。
如果你想得到有关某个表的列的信息,你可以查询sys.columns表。
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')
更多资料更多内容可在SQL Server联机丛书的“查询SQL Server系统目录(queryint the SQL Server System Catalog)”章节中查找
练习题
--练习题
--1.返回2007年六月生成的订单。
--1.1
SELECT orderid,orderdate,custid,empid
FROM Sales.Orders
WHERE orderdate >= '20070601' AND orderdate < '20070701'
ORDER BY orderid
--1.2
SELECT orderid,orderdate,custid,empid
FROM Sales.Orders
WHERE orderdate BETWEEN '20070601' AND '20070630'
ORDER BY orderid
--2.返回每个月最后一天生成的订单
--每个月的最后一天
SELECT DATEADD(MONTH, DATEDIFF(MONTH, '20140131', GETDATE()), '20140131') AS '当月最后一天'
SELECT orderid ,orderdate ,custid ,empid
FROM Sales.Orders
WHERE CAST(orderdate AS DATE) = CAST(DATEADD(MONTH,DATEDIFF(MONTH, '20140131',orderdate), '20140131') AS DATE)
--3.返回姓氏(last name)中包含字母‘a’两次或者更多次的雇员。
SELECT empid ,firstname ,lastname
FROM HR.Employees
WHERE lastname LIKE '%a%a%'
--4.返回总价格(数量*单价)大于10000的所有订单,并按照总价格排序。
SELECT orderid ,
SUM(unitprice * qty) AS totalvalue
FROM Sales.OrderDetails
GROUP BY orderid
HAVING SUM(unitprice * qty) > 10000
--5.返回2007年平均运费最高的三个发货国家
--5.1
SELECT TOP 3 shipcountry ,
AVG(freight) AS avgfreight
FROM Sales.Orders
WHERE YEAR(orderdate) = 2007
GROUP BY shipcountry
ORDER BY avgfreight DESC
--5.2
SELECT TOP 3 shipcountry ,
AVG(freight) AS avgfreight
FROM Sales.Orders
WHERE orderdate >= '20070101' AND orderdate < '20080101'
GROUP BY shipcountry
ORDER BY avgfreight DESC
--6.为每个顾客单独根据订单日期的顺序(用order ID作为附加属性)来计算其订单的行号。
SELECT custid ,orderdate ,orderid ,
ROW_NUMBER() OVER ( PARTITION BY custid ORDER BY orderid ) AS rownum
FROM Sales.Orders
--7.构造一个select语句,让他根据每个雇员的友好称呼,而返回其性别。
--对于‘Ms.’和‘Mrs.’,则返回‘Female’,对于‘Mr.’ 则返回‘Male’;对于其他情况(例如,‘Dr.’),则返回‘Unknow’。
--7.1
SELECT empid ,lastname ,firstname ,titleofcourtesy ,
CASE titleofcourtesy
WHEN 'Ms.' THEN 'Female'
WHEN 'Mr.' THEN 'Male'
ELSE 'Unknow'
END AS gender
FROM hr.Employees
--7.2
SELECT empid ,lastname ,firstname ,titleofcourtesy ,
CASE WHEN titleofcourtesy = 'Ms.' THEN 'Female'
WHEN titleofcourtesy = 'Mr.' THEN 'Male'
ELSE 'Unknow'
END AS gender
FROM hr.Employees
--8.返回每个客户的客户ID和所在区域。对输出中的行按区域排序,NULL值排在最后面。
--8.1
SELECT custid ,region
FROM Sales.Customers
ORDER BY CASE WHEN region IS NULL THEN 1 ELSE 0 END ,region
--8.2
SELECT custid ,region
FROM ( SELECT custid ,region ,
CASE WHEN region IS NULL THEN 1 ELSE 0 END AS Orderregion
FROM Sales.Customers
) v
ORDER BY Orderregion
--8.3
SELECT custid ,region
FROM Sales.Customers
WHERE region IS NOT NULL
UNION ALL
SELECT custid ,region
FROM Sales.Customers
WHERE region IS NULL
参考一:SQL_DDL_建库建表
参考二:SQL with ties的理解与_huanghai223
参考三:日期函数与转型
参考四 :SQL_DDL_建库建表