《MSSQL2008技術內幕:T-SQL語言基礎》讀書筆記(上)

索引:html

1、SQL Server的體系結構數據庫

2、查詢編程

3、表表達式安全

4、集合運算架構

5、透視、逆透視及分組併發

6、數據修改模塊化

7、事務和併發函數

8、可編程對象佈局

1、SQL Server體系結構

1.1 數據庫的物理佈局

  數據庫在物理上由數據文件和事務日誌文件組成,每一個數據庫必須至少有一個數據文件和一個日誌文件。post

  (1)數據文件用於保存數據庫對象數據。數據庫必須至少有一個主文件組(Primary),而用戶定義的文件組則是可選的。Primary文件組包括 主數據文件(.mdf),以及數據庫的系統目錄(catalog)。能夠選擇性地爲Primary增長多個輔助數據文件(.ndf)。用戶定義的文件組只能包含輔助數據文件。

  (2)日誌文件則用於保存SQL Server爲了維護事務而須要的信息。雖然SQL Server能夠同時寫多個數據文件,但同一時刻只能以順序方式寫一個日誌文件。

.mdf、.ldf和.ndf

.mdf表明Master Data File,.ldf表明Log Data File,而.ndf表明Not Master Data File(非主數據文件)

1.2 架構(Schema)和對象

  一個數據庫包含多個架構,而每一個架構又包括多個對象。能夠將架構看做是各類對象的容器,這些對象能夠是表(table)、視圖(view)、存儲過程(stored procedure)等等。

  此外,架構也是一個命名空間,用做對象名稱的前綴。例如,架設在架構Sales中有一個Orders表,架構限定的對象名稱是Sales.Orders。若是在引用對象時省略架構名稱,SQL Server將採用必定的辦法來分析出架構名稱是什麼。若是不顯示指定架構,那麼在解析對象名稱時,就會要付出一些沒有意義的額外代價。所以,建議都加上架構名稱。

2、查詢

2.1 單表查詢

  (1)關於SELECT子句:使用*號是糟糕的習慣

SELECT * FROM Sales.Shippers;

  在絕大多數狀況下,使用星號是一種糟糕的編程習慣,在此仍是建議你們即便須要查詢表的全部列,也應該顯式地指定它們。

  (2)關於FROM子句:顯示指定架構名稱

  經過顯示指定架構名稱,能夠保證獲得的對象的確是你原來想要的,並且還沒必要付出任何額外的代價。

  (3)關於TOP子句:T-SQL獨有關鍵字

  ① 可使用PERCENT關鍵字按百分比計算知足條件的行數

SELECT TOP (1) PERCENT orderid, orderdate, custid, empid
FROM Sales.Orders
ORDER BY orderdate DESC;

  上面這條SQL就會請求最近更新過的前1%個訂單。

  ② 可使用WITH TIES選項請求返回全部具備相同結果的行

SELECT TOP (5) WITH TIES orderid, orderdate, custid, empid
FROM Sales.Orders
ORDER BY orderdate DESC;

  上面這條SQL請求返回與TOP n行中最後一行的排序值相同的其餘全部行。

  (4)關於OVER子句:爲行定義一個窗口以便進行特定的運算

  OVER子句的優勢在於可以在返回基本列的同時,在同一行對它們進行聚合也能夠在表達式中混合使用基本列和聚合值列

  例如,下面的查詢爲OrderValues的每一行計算當前價格佔總價格的百分比,以及當前價格佔客戶總價格的百分比 。

SELECT orderid, custid, val,
100.0 * val / SUM(val) OVER() AS pctall,
100.0 * val / SUM(val) OVER(PARTITION BY custid) AS pctcust
FROM Sales.OrderValues;

  (5)子句的邏輯處理順序

  (6)運算符的優先級

  (7)CASE表達式

  ① 簡單表達式:將一個值與一組可能的取值進行比較,並返回知足第一個匹配的結果;

SELECT productid,productname,categoryid,categoryname=(
    CASE categoryid
        WHEN 1 THEN 'Beverages'
        WHEN 2 THEN 'Condiments'
        WHEN 3 THEN 'Confections'
        WHEN 4 THEN 'Dairy Products'
        ELSE 'Unkonw Category'
    END)
FROM Production.Products;

  ② 搜索表達式:將返回結果爲TRUE的第一個WHEN邏輯表達式所關聯的THEN子句中指定的值。若是沒有任何WHEN表達式結果爲TRUE,CASE表達式則返回ELSE子句中出現的值。(若是沒有指定ELSE,則默認返回NULL);

SELECT orderid, custid, val, valuecategory=(
  CASE 
    WHEN val < 1000.00    THEN 'Less than 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
)
FROM Sales.OrderValues

  (8)三值謂詞邏輯:TRUE、FALSE與UNKNOWN

  SQL支持使用NULL表示缺乏的值,它使用的是三值謂詞邏輯,表明計算結果可使TRUE、FALSE與UNKNOWN。在SQL中,對於UNKNOWN和NULL的處理不一致,這就須要咱們在編寫每一條查詢語句時應該明確地注意到正在使用的是三值謂詞邏輯。

  例如,咱們要請求返回region列不等於WA的全部行,則須要在查詢過濾條件中顯式地增長一個隊NULL值得測試:

SELECT custid, country, region, city
FROM Sales.Customers
WHERE region <> N'WA'
  OR region IS NULL;

  另外,T-SQL對於NULL值得處理是先輸出NULL值再輸出非NULL值得順序,若是想要先輸出非NULL值,則須要改變一下排序條件,例以下面的請求:

select custid, region
from sales.Customers
order by (case 
when region is null then 1 else 0
end), region;

  當region列爲NULL時返回1,不然返回0。非NULL值得表達式返回值爲0,所以,它們會排在NULL值(表達式返回1)的前面。如上所示的將CASE表達式做爲第一個拍序列,並把region列指定爲第二個拍序列。這樣,非NULL值也能夠正確地參與排序,是一個完整解決方案的查詢。

  (9)LIKE謂詞的花式用法

  ① %(百分號)通配符

SELECT empid, lastname
FROM HR.Employees
WHERE lastname LIKE N'D%';

  ② _(下劃線)通配符:下劃線表明任意單個字符

  下面請求返回lastname第二個字符爲e的全部員工

SELECT empid, lastname
FROM HR.Employees
WHERE lastname LIKE N'_e%';

  ③ [<字符列>]通配符:必須匹配指定字符中的一個字符

  下面請求返回lastname以字符A、B、C開頭的全部員工

SELECT empid, lastname
FROM HR.Employees
WHERE lastname LIKE N'[ABC]%';

  ④  [<字符-字符>]通配符:必須匹配指定範圍內中的一個字符

  下面請求返回lastname以字符A到E開頭的全部員工:

SELECT empid, lastname
FROM HR.Employees
WHERE lastname LIKE N'[A-E]%';

  ⑤ [^<字符-字符>]通配符:不屬於特定字符序列或範圍內的任意單個字符

  下面請求返回lastname不以A到E開頭的全部員工:

SELECT empid, lastname
FROM HR.Employees
WHERE lastname LIKE N'[^A-E]%';

  ⑥ ESCAPE轉義字符

  若是搜索包含特殊通配符的字符串(例如'%','_','['、']'等),則必須使用轉移字符。下面檢查lastname列是否包含下劃線:

SELECT empid, lastname
FROM HR.Employees
WHERE lastname LIKE N'%!_%' ESCAPE '!';

  (10)兩種轉換值的函數:CAST和CONVERT

  CAST和CONVERT都用於轉換值的數據類型。

SELECT CAST(SYSDATETIME() AS DATE);
SELECT CONVERT(CHAR(8),CURRENT_TIMESTAMP,112);

  須要注意的是,CAST是ANSI標準的SQL,而CONVERT不是。因此,除非須要使用樣式值,不然推薦優先使用CAST函數,以保證代碼儘量與標準兼容

2.2 聯接查詢

  (1)交叉聯接:返回笛卡爾積,即m*n行的結果集

-- CROSS JOIN
select c.custid, e.empid
from sales.Customers as c
    cross join HR.Employees as e;
-- INNER CROSS JOIN    
select e1.empid,e1.firstname,e1.lastname,
    e2.empid,e2.firstname,e2.lastname
from hr.Employees as e1
    cross join hr.Employees as e2;

  (2)內聯接:先笛卡爾積,而後根據指定的謂詞對結果進行過濾

select e.empid,e.firstname,e.lastname,o.orderid
from hr.Employees as e
    join sales.Orders as o
    on e.empid=o.empid;

雖然不使用JOIN這種ANSI SQL-92標準語法也能夠實現聯接,但強烈推薦使用ANSI SQL-92標準,由於它用起來更加安全。好比,假如你要寫一條內聯接查詢,若是不當心忘記了指定聯接條件,若是這時候用的是ANSI SQL-92語法,那麼語法分析器將會報錯。

  (3)外聯結:笛卡爾積→對結果過濾→添加外部行

  經過例子來理解外聯結:根據客戶的客戶ID和訂單的客戶ID來對Customers表和Orders表進行聯接,並返回客戶和他們的訂單信息。該查詢語句使用的聯接類型是左外鏈接,因此查詢結果也包括那些沒有發出任何訂單的客戶;

--LEFT OUTER JOIN
select c.custid,c.companyname,o.orderid
from sales.Customers as c
  left outer join sales.Orders as o
  on c.custid=o.custid;

  另外,須要注意的是在對外聯結中非保留值得列值進行過濾時,不要再WHERE子句中指定錯誤的查詢條件。

  例如,下面請求返回在2007年2月12日下過訂單的客戶,以及他們的訂單。同時也返回在2007年2月12日沒有下過訂單的客戶。這是一個典型的左外鏈接的案例,可是咱們常常會犯這樣的錯誤:

select c.custid,c.companyname,o.orderid,o.orderdate
from sales.Customers as c
    left outer join sales.Orders as o
    on c.custid=o.custid 
where o.orderdate='20070212';

  執行結果以下:

      

  這是由於對於全部的外部行,由於它們在o.orderdate列上的取值都爲NULL,因此WHERE子句中條件o.orderdate='20070212'的計算結果爲UNKNOWN,所以WHERE子句會過濾掉全部的外部行。

  咱們應該將這個條件搬到on後邊:

select c.custid,c.companyname,o.orderid,o.orderdate
from sales.Customers as c
    left outer join sales.Orders as o
    on c.custid=o.custid 
        and o.orderdate='20070212';

  這下的執行結果以下:

      

2.3 子查詢

  (1)獨立子查詢:不依賴於它所屬的外部查詢

  例以下面要查詢Orders表中訂單ID最大的訂單信息,這種叫作獨立標量子查詢,即返回值不能超過一個。

select orderid, orderdate, empid, custid
from sales.Orders
where empid=(select MAX(o.orderid) from sales.Orders as o);

  西面請求查詢返回姓氏以字符D開頭的員工處理過的訂單的ID,這種叫作獨立多值子查詢,即返回值可能有多個。

select orderid
from sales.Orders
where empid in (select e.empid 
    from hr.Employees as e
    where e.lastname like N'D%');

  (2)相關子查詢:必須依賴於它所屬的外部查詢,不能獨立地調用它

  例以下面的查詢會返回每一個客戶的訂單記錄中訂單ID最大的記錄:

select custid, orderid, orderdate, empid
from sales.Orders as o1
where orderid=(select MAX(o2.orderid) 
    from sales.Orders as o2
    where o2.custid=o1.custid);

  簡單地說,對於o1表中的每一行,子查詢負責返回當前客戶的最大訂單ID。若是o1表中某行的訂單ID和子查詢返回的訂單ID匹配,那麼o1中的這個訂單ID就是當前客戶的最大訂單ID,在這種狀況下,查詢便會返回o1表中的這個行。

  (3)EXISTS謂詞:它的輸入是一個查詢,若是子查詢可以返回任何行,則返回True,不然返回False

  例以下面的查詢會返回下過訂單的西班牙客戶:

select custid, companyname
from sales.customers as c
where c.country=N'Spain' and exists (
    select * from sales.Orders as o
    where o.custid=c.custid);

  一樣,要查詢沒有下過訂單的西班牙客戶只須要加上NOT便可:

select custid, companyname
from sales.customers as c
where c.country=N'Spain' and not exists (
    select * from sales.Orders as o
    where o.custid=c.custid);

對於EXISTS,它採用的是二值邏輯(TRUE和FALSE),它只關心是否存在匹配行,而不考慮SELECT列表中指定的列,而且無須處理全部知足條件的行。能夠將這種處理方式看作是一種「短路」,它可以提升處理效率。 

另外,因爲EXISTS採用的是二值邏輯,所以相較於IN要更加安全,能夠避免對NULL值得處理。 

  (4)高級子查詢

  ① 如何表示前一個或後一個記錄?邏輯等式:上一個->小於當前值的最大值;下一個->大於當前值的最小值;

-- 上一個訂單ID
select orderid, orderdate, empid, custid,
(
select MAX(o2.orderid) 
from sales.Orders as o2
where o2.orderid<o1.orderid
) as prevorderid 
from sales.Orders as o1;

  ② 如何實現連續聚合函數?在子查詢中連續計算

-- 連續聚合
select orderyear, qty, 
(select SUM(o2.qty) 
 from sales.OrderTotalsByYear as o2
 where o2.orderyear<=o1.orderyear) as runqty 
from sales.OrderTotalsByYear as o1
order by orderyear;

   執行結果以下圖所示:

        

  ③ 使用NOT EXISTS謂詞取代NOT IN隱式排除NULL值:當對至少返回一個NULL值的子查詢使用NOT IN謂詞時,外部查詢總會返回一個空集。(前面提到,EXISTS謂詞采用的是二詞邏輯而不是三詞邏輯)

-- 隱式排除NULL值
select custid,companyname from sales.Customers as c
where not exists
(select * 
 from sales.Orders as o
 where o.custid=c.custid);

  又如如下查詢請求返回每一個客戶在2007年下過訂單而在2008年沒有下過訂單的客戶:

select custid, companyname
from sales.Customers as c
where exists 
(select * from sales.Orders as o1
 where c.custid=o1.custid 
 and o1.orderdate>='20070101' and o1.orderdate<'20080101')
and not exists 
(select * from sales.Orders as o2
 where c.custid=o2.custid
 and o2.orderdate>='20080101' and o2.orderdate<'20090101');

3、表表達式

  表表達式是一種命名的查詢表達式,表明一個有效地關係表。能夠像其餘表同樣,在數據處理中使用表表達式。MSSQL中支持4種類型的表表達式:

3.1 派生表

  派生表(也稱爲表子查詢)是在外部查詢的FROM子句中定義的,只要外部查詢一結束,派生表也就不存在了。

  例以下面代碼定義了一個名爲USACusts的派生表,它是一個返回全部美國客戶的查詢。外部查詢則選擇了派生表的全部行。

select *
from (select custid, companyname 
      from sales.Customers 
      where country='USA') as USACusts;

3.2 公用表表達式

  公用表達式(簡稱CTE,Common Table Expression)是和派生表很類似的另外一種形式的表表達式,是ANSI SQL(1999及之後版本)標準的一部分。

  舉個栗子,下面的代碼定義了一個名爲USACusts的CTE,它的內部查詢返回全部來自美國的客戶,外部查詢則選擇了CTE中的全部行:

WITH USACusts AS
(
    select custid, companyname
    from sales.Customers 
    where country=N'USA'
)
select * from USACusts;

  和派生表同樣,一旦外部查詢完成,CTE的生命週期也就結束了。

3.3 視圖

  派生表和CTE都是不可重用的,而視圖和內聯表值函數倒是可重用,它們的定義存儲在一個數據庫對象中,一旦建立,這些對象就是數據庫的永久部分。只有用刪除語句顯式地刪除,它們纔會從數據庫中移除。

  下面仍然繼續上面的例子,建立一個視圖:

IF OBJECT_ID('Sales.USACusts') IS NOT NULL
   DROP VIEW Sales.USACusts;
GO
CREATE VIEW Sales.USACusts
AS
SELECT 
    custid, companyname, contactname, contacttitle, address,
    city, region, postalcode, country, phone, fax
FROM Sales.Customers
WHERE country=N'USA';
GO

  使用該視圖:

SELECT * FROM Sales.USACusts;

  執行結果以下:

      

3.4 內聯表值函數

  內聯表值函數可以支持輸入參數,其餘方面就與視圖相似了。

  下面演示如何建立函數:

IF OBJECT_ID('dbo.fn_GetCustOrders') IS NOT NULL
   DROP FUNCTION dbo.fn_GetCustOrders;
GO
CREATE FUNCTION dbo.fn_GetCustOrders
    (@cid AS INT) RETURNS TABLE
AS
RETURN 
    SELECT
        orderid, custid, empid, orderdate, requireddate,
        shippeddate, shipperid, freight, shipname, shipaddress, shipcity,
        shipregion, shippostalcode, shipcountry
    FROM Sales.Orders
    WHERE custid=@cid;
GO

  如何使用函數:

SELECT orderid, custid
FROM dbo.fn_GetCustOrders(1) AS CO;

  執行結果以下:

       

總結:

藉助表表達式能夠簡化代碼,提升代碼地可維護性,還能夠封裝查詢邏輯。

當須要使用表表達式,並且不計劃重用它們的定義時,可使用派生表或CTE,與派生表相比,CTE更加模塊化,更容易維護。

當須要定義可重用的表表達式時,可使用視圖或內聯表值函數。若是不須要支持輸入,則使用視圖;反之,則使用內聯表值函數。

4、集合運算

4.1 UNION 並集運算

  在T-SQL中。UNION集合運算能夠將兩個輸入查詢的結果組合成一個結果集。須要注意的是:若是一個行在任何一個輸入集合衆出現,它也會在UNION運算的結果中出現。T-SQL支持如下兩種選項:

  (1)UNION ALL:不會刪除重複行

-- union all
select country, region, city from hr.Employees
union all
select country, region, city from sales.Customers; 

  結果獲得100行:

      

  (2)UNION:會刪除重複行

-- union
select country, region from hr.Employees
union
select country, region from sales.Customers; 

  結果獲得34行:

      

4.2 INTERSECT 交集運算

  在T-SQL中,INTERSECT集合運算對兩個輸入查詢的結果取其交集,只返回在兩個查詢結果集中都出現的行。

  INTERSECT集合運算在邏輯上會首先刪除兩個輸入集中的重複行,而後返回只在兩個集合中中都出現的行。換句話說:若是一個行在兩個輸入集中都至少出現一次,那麼交集返回的結果中將包含這一行。

  例如,下面返回既是官員地址,又是客戶地址的不一樣地址:

-- intersect
select country, region, city from hr.Employees
intersect
select country, region, city from sales.Customers;

  執行結果以下圖所示:

      

這裏須要說的是,集合運算對行進行比較時,認爲兩個NULL值相等,因此就返回該行記錄。

4.3 EXCEPT 差集運算

  在T-SQL中,集合之差使用EXCEPT集合運算實現的。它對兩個輸入查詢的結果集進行操做,反會出如今第一個結果集中,但不出如今第二個結果集中的全部行。

  EXCEPT結合運算在邏輯上首先刪除兩個輸入集中的重複行,而後返回只在第一個集合中出現,在第二個結果集中不出現的全部行。換句話說:一個行可以被返回,僅當這個行在第一個輸入的集合中至少出現過一次,並且在第二個集合中一次也沒出現過。

  此外,相比UNION和INTERSECT,兩個輸入集合的順序是會影響到最後返回結果的。

  例如,藉助EXCEPT運算,咱們能夠方便地實現屬於A但不屬於B的場景,下面返回屬於員工抵制,但不屬於客戶地址的地址記錄:

-- except 
select country, region, city from hr.Employees
except
select country, region, city from sales.Customers;

  執行結果以下圖所示:

      

4.4 集合運算優先級

  SQL定義了集合運算之間的優先級:INTERSECT最高,UNION和EXCEPT相等。

  換句話說:首先會計算INTERSECT,而後按照從左至右的出現順序依次處理優先級相同的運算。

-- 集合運算的優先級
select country, region, city from Production.Suppliers
except
select country, region, city from hr.Employees
intersect
select country, region, city from sales.Customers;

  上面這段SQL代碼,由於INTERSECT優先級比EXCEPT高,因此首先進行INTERSECT交集運算。所以,這個查詢的含義是:返回沒有出如今員工地址和客戶地址交集中的供應商地址。

4.5 使用表表達式避開不支持的邏輯查詢處理

  集合運算查詢自己並不持之除ORDER BY意外的其餘邏輯查詢處理階段,但能夠經過表表達式來避開這一限制。

  解決方案就是:首先根據包含集合運算的查詢定義一個表表達式,而後在外部查詢中對錶表達式應用任何須要的邏輯查詢處理。

  (1)例如,下面的查詢返回每一個國家中不一樣的員工地址或客戶地址的數量:

select country, COUNT(*) as numlocations
from (select country, region, city from hr.Employees
      union
      select country, region, city from sales.Customers) as U
group by country;

  (2)例如,下面的查詢返回由員工地址爲3或5的員工最近處理過的兩個訂單:

select empid,orderid,orderdate 
from (select top (2) empid,orderid,orderdate 
    from sales.Orders
    where empid=3
    order by orderdate desc,orderid desc) as D1
union all
select empid,orderid,orderdate 
from (select top (2) empid,orderid,orderdate 
    from sales.Orders
    where empid=5
    order by orderdate desc,orderid desc) as D2;

參考資料

TSQLFundenmantals

[美] Itzik Ben-Gan 著,成保棟 譯,《Microsoft SQL Server 2008技術內幕:T-SQL語言基礎》

考慮到不少人買了這本書,卻下載不了這本書的配套源代碼和示例數據庫,特地上傳到了百度雲盤中,點此下載

強烈建議你們閱讀完每一章節後,練習一下課後習題,相信或多或少都會有一些收穫。

 

相關文章
相關標籤/搜索