一个月之前就注册了这个博客,今天才开始动笔写,真是有点。。。。
最近开始看面试题发现一个月之前看的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;