• T-SQL学习笔记1


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

    最近开始看面试题发现一个月之前看的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;
  • 相关阅读:
    Python-常用的高级函数
    Excel
    业务思维
    数据分析思维
    Netbeans 12无法打开项目(project的)的问题
    C++ tuple元组
    如何保障一场千万级大型直播?
    回声消除的昨天、今天和明天
    无参考评估在云信的视频测试实践
    一文读懂Python 高阶函数
  • 原文地址:https://www.cnblogs.com/panshu/p/3271708.html
Copyright © 2020-2023  润新知