1、視圖html
假設天氣記錄和城市爲止的組合列表對咱們的應用有用,但咱們又不想每次須要使用它時都敲入整個查詢。咱們能夠在該查詢上建立一個視圖,這會給該查詢一個名字,咱們能夠像使用一個普通表同樣來使用它:java
CREATE VIEW myview AS SELECT city, temp_lo, temp_hi, prcp, date, location FROM weather, cities WHERE city = name; SELECT * FROM myview;
對視圖的使用是成就一個好的SQL數據庫設計的關鍵方面。視圖容許用戶經過始終如一的接口封裝表的結構細節,這樣能夠避免表結構隨着應用的進化而改變。數據庫
視圖幾乎能夠用在任何可使用表的地方。在其餘視圖基礎上建立視圖也並很多見。api
2、外鍵併發
新的表定義以下:數據庫設計
CREATE TABLE cities ( city varchar(80) primary key, location point ); CREATE TABLE weather ( city varchar(80) references cities(city), temp_lo int, temp_hi int, prcp real, date date );
如今嘗試插入一個非法的記錄:函數
INSERT INTO weather VALUES ('Berkeley', 45, 53, 0.0, '1994-11-28'); ERROR: insert or update on table "weather" violates foreign key constraint "weather_city_fkey" DETAIL: Key (city)=(Berkeley) is not present in table "cities".
3、事務oop
事務是全部數據庫系統的基礎概念。事務最重要的一點是它將多個步驟捆綁成了一個單一的、要麼全完成要麼全不完成的操做。步驟之間的中間狀態對於其餘併發事務是不可見的,而且若是有某些錯誤發生致使事務不能完成,則其中任何一個步驟都不會對數據庫形成影響。post
例如,考慮一個保存着多個客戶帳戶餘額和支行總存款額的銀行數據庫。假設咱們但願記錄一筆從Alice的帳戶到Bob的帳戶的額度爲100.00美圓的轉帳。在最大程度地簡化後,涉及到的SQL命令是:spa
UPDATE accounts SET balance = balance - 100.00 WHERE name = 'Alice'; UPDATE branches SET balance = balance - 100.00 WHERE name = (SELECT branch_name FROM accounts WHERE name = 'Alice'); UPDATE accounts SET balance = balance + 100.00 WHERE name = 'Bob'; UPDATE branches SET balance = balance + 100.00 WHERE name = (SELECT branch_name FROM accounts WHERE name = 'Bob');
這些命令的細節在這裏並不重要,關鍵點是爲了完成這個至關簡單的操做涉及到多個獨立的更新。咱們的銀行職員但願確保這些更新要麼所有發生,或者所有不發生。固然不能發生由於系統錯誤致使Bob收到100美圓而Alice並未被扣款的狀況。Alice固然也不但願本身被扣款而Bob沒有收到錢。咱們須要一種保障,當操做中途某些錯誤發生時已經執行的步驟不會產生效果。將這些更新組織成一個事務就能夠給咱們這種保障。一個事務被稱爲是原子的:從其餘事務的角度來看,它要麼整個發生要麼徹底不發生。
咱們一樣但願能保證一旦一個事務被數據庫系統完成並承認,它就被永久地記錄下來且即使其後發生崩潰也不會被丟失。例如,若是咱們正在記錄Bob的一次現金提款,咱們固然不但願他剛走出銀行大門,對他帳戶的扣款就消失。一個事務型數據庫保證一個事務在被報告爲完成以前它所作的全部更新都被記錄在持久存儲(即磁盤)。
事務型數據庫的另外一個重要性質與原子更新的概念緊密相關:當多個事務併發運行時,每個都不能看到其餘事務未完成的修改。例如,若是一個事務正忙着總計全部支行的餘額,它不會只包括Alice的支行的扣款而不包括Bob的支行的存款,或者反之。因此事務的全作或全不作並不僅體如今它們對數據庫的持久影響,也體如今它們發生時的可見性。一個事務所作的更新在它完成以前對於其餘事務是不可見的,而以後全部的更新將同時變得可見。
在PostgreSQL中,開啓一個事務須要將SQL命令用BEGIN和COMMIT命令包圍起來。所以咱們的銀行事務看起來會是這樣:
BEGIN; UPDATE accounts SET balance = balance - 100.00 WHERE name = 'Alice'; -- etc etc COMMIT;
若是,在事務執行中咱們並不想提交(或許是咱們注意到Alice的餘額不足),咱們能夠發出ROLLBACK命令而不是COMMIT命令,這樣全部目前的更新將會被取消。
PostgreSQL實際上將每個SQL語句都做爲一個事務來執行。若是咱們沒有發出BEGIN命令,則每一個獨立的語句都會被加上一個隱式的BEGIN以及(若是成功)COMMIT來包圍它。一組被BEGIN和COMMIT包圍的語句也被稱爲一個事務塊。
也能夠利用保存點來以更細的粒度來控制一個事務中的語句。保存點容許咱們有選擇性地放棄事務的一部分而提交剩下的部分。在使用SAVEPOINT定義一個保存點後,咱們能夠在必要時利用ROLLBACK TO回滾到該保存點。該事務中位於保存點和回滾點之間的數據庫修改都會被放棄,可是早於該保存點的修改則會被保存。
在回滾到保存點以後,它的定義依然存在,所以咱們能夠屢次回滾到它。反過來,若是肯定再也不須要回滾到特定的保存點,它能夠被釋放以便系統釋放一些資源。記住無論是釋放保存點仍是回滾到保存點都會釋放定義在該保存點以前的全部其餘保存點。
全部這些都發生在一個事務塊內,所以這些對於其餘數據庫會話都不可見。當提交整個事務塊時,被提交的動做將做爲一個單元變得對其餘會話可見,而被回滾的動做則永遠不會變得可見。
記住那個銀行數據庫,假設咱們從Alice的帳戶扣款100美圓,而後存款到Bob的帳戶,結果直到最後才發現咱們應該存到Wally的帳戶。咱們能夠經過使用保存點來作這件事:
BEGIN; UPDATE accounts SET balance = balance - 100.00 WHERE name = 'Alice'; SAVEPOINT my_savepoint; UPDATE accounts SET balance = balance + 100.00 WHERE name = 'Bob'; -- oops ... forget that and use Wally's account ROLLBACK TO my_savepoint; UPDATE accounts SET balance = balance + 100.00 WHERE name = 'Wally'; COMMIT;
固然,這個例子是被過分簡化的,可是在一個事務塊中使用保存點存在不少種控制可能性。此外,ROLLBACK TO是惟一的途徑來從新控制一個因爲錯誤被系統置爲中斷狀態的事務塊,而不是徹底回滾它並從新啓動。
4、窗口函數
一個窗口函數在一系列與當前行有某種關聯的錶行上執行一種計算。這與一個彙集函數所完成的計算有可比之處。可是與一般的彙集函數不一樣的是,使用窗口函數並不會致使行被分組成爲一個單獨的輸出行--行保留它們獨立的標識。在這些現象背後,窗口函數能夠訪問的不只僅是查詢結果的當前行。
下面是一個例子用於展現如何將每個員工的薪水與他/她所在部門的平均薪水進行比較:
SELECT depname, empno, salary, avg(salary) OVER (PARTITION BY depname) FROM empsalary; depname | empno | salary | avg -----------+-------+--------+----------------------- develop | 11 | 5200 | 5020.0000000000000000 develop | 7 | 4200 | 5020.0000000000000000 develop | 9 | 4500 | 5020.0000000000000000 develop | 8 | 6000 | 5020.0000000000000000 develop | 10 | 5200 | 5020.0000000000000000 personnel | 5 | 3500 | 3700.0000000000000000 personnel | 2 | 3900 | 3700.0000000000000000 sales | 3 | 4800 | 4866.6666666666666667 sales | 1 | 5000 | 4866.6666666666666667 sales | 4 | 4800 | 4866.6666666666666667 (10 rows)
最開始的三個輸出列直接來自於表empsalary,而且表中每一行都有一個輸出行。第四列表示對與當前行具備相同depname值的全部錶行取得平均值(這實際和通常的avg
彙集函數是相同的函數,可是OVER子句使得它被當作一個窗口函數處理並在一個合適的行集合上計算。)。
一個窗口函數調用老是包含一個直接跟在窗口函數名及其參數以後的OVER子句。這使得它從句法上和一個普通函數或彙集函數區分開來。OVER子句決定究竟查詢中的哪些行被分離出來由窗口函數處理。OVER子句中的PARTITION BY列表指定了將具備相同PARTITION BY表達式值的行分到組或者分區。對於每一行,窗口函數都會在當前行同一分區的行上進行計算。
咱們能夠經過OVER上的ORDER BY控制窗口函數處理行的順序(窗口的ORDER BY並不必定要符合行輸出的順序。)。下面是一個例子:
SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary DESC) FROM empsalary; depname | empno | salary | rank -----------+-------+--------+------ develop | 8 | 6000 | 1 develop | 10 | 5200 | 2 develop | 11 | 5200 | 2 develop | 9 | 4500 | 4 develop | 7 | 4200 | 5 personnel | 2 | 3900 | 1 personnel | 5 | 3500 | 2 sales | 1 | 5000 | 1 sales | 4 | 4800 | 2 sales | 3 | 4800 | 2 (10 rows)
如上所示,rank
函數在當前行的分區內按照ORDER BY子句的順序爲每個可區分的ORDER BY值產生了一個數字等級。rank
不須要顯式的參數,由於它的行爲徹底決定於OVER子句。
一個窗口函數所考慮的行屬於那些經過查詢的FROM子句產生並經過WHERE、GROUP BY、HAVING過濾的"虛擬表"。例如,一個因爲不知足WHERE條件被刪除的行是不會被任何窗口函數所見的。在一個查詢中能夠包含多個窗口函數,每一個窗口函數均可以用不一樣的OVER子句來按不一樣方式劃分數據,可是它們都做用在由虛擬表定義的同一個行集上。
咱們已經看到若是行的順序不重要時ORDER BY能夠忽略。PARTITION BY一樣也能夠被忽略,在這種狀況下只會產生一個包含全部行的分區。
這裏有一個與窗口函數相關的重要概念:對於每一行,在它的分區中的行集被稱爲它的窗口幀。 不少(但不是所有)窗口函數只做用在窗口幀中的行上,而不是整個分區。默認狀況下,若是使用ORDER BY,則幀包括從分區開始到當前行的全部行,以及後續任何與當前行在ORDER BY子句上相等的行。若是ORDER BY被忽略,則默認幀包含整個分區中全部的行。 [1] 下面是使用sum
的例子:
SELECT salary, sum(salary) OVER () FROM empsalary; salary | sum --------+------- 5200 | 47100 5000 | 47100 3500 | 47100 4800 | 47100 3900 | 47100 4200 | 47100 4500 | 47100 4800 | 47100 6000 | 47100 5200 | 47100 (10 rows)
如上所示,因爲在OVER子句中沒有ORDER BY,窗口幀和分區同樣,而若是缺乏PARTITION BY則和整個表同樣。換句話說,每一個合計都會在整個表上進行,這樣咱們爲每個輸出行獲得的都是相同的結果。可是若是咱們加上一個ORDER BY子句,咱們會獲得很是不一樣的結果:
SELECT salary, sum(salary) OVER (ORDER BY salary) FROM empsalary; salary | sum --------+------- 3500 | 3500 3900 | 7400 4200 | 11600 4500 | 16100 4800 | 25700 4800 | 25700 5000 | 30700 5200 | 41100 5200 | 41100 6000 | 47100 (10 rows)
這裏的合計是從第一個(最低的)薪水一直到當前行,包括任何與當前行相同的行(注意相同薪水行的結果)。
窗口函數只容許出如今查詢的SELECT列表和ORDER BY子句中。它們不容許出如今其餘地方,例如GROUP BY、HAVING和WHERE子句中。這是由於窗口函數的執行邏輯是在處理完這些子句以後。另外,窗口函數在普通匯集函數以後執行。這意味着能夠在窗口函數的參數中包括一個彙集函數,但反過來不行。
若是須要在窗口計算執行後進行過濾或者分組,咱們可使用子查詢。例如:
SELECT depname, empno, salary, enroll_date FROM (SELECT depname, empno, salary, enroll_date, rank() OVER (PARTITION BY depname ORDER BY salary DESC, empno) AS pos FROM empsalary ) AS ss WHERE pos < 3;
上述查詢僅僅顯示了內層查詢中rank低於3的結果。
當一個查詢涉及到多個窗口函數時,能夠將每個分別寫在一個獨立的OVER子句中。但若是多個函數要求同一個窗口行爲時,這種作法是冗餘的並且容易出錯的。替代方案是,每個窗口行爲能夠被放在一個命名的WINDOW子句中,而後在OVER中引用它。例如:
SELECT sum(salary) OVER w, avg(salary) OVER w FROM empsalary WINDOW w AS (PARTITION BY depname ORDER BY salary DESC);
5、繼承
繼承是面向對象數據庫中的概念。它展現了數據庫設計的新的可能性。
讓咱們建立兩個表:表cities
和表capitals
。天然地,首都也是城市,因此咱們須要有某種方式可以在列舉全部城市的時候也隱式地包含首都。若是真的聰明,咱們會設計以下的模式:
CREATE TABLE capitals ( name text, population real, altitude int, -- (in ft) state char(2) ); CREATE TABLE non_capitals ( name text, population real, altitude int -- (in ft) ); CREATE VIEW cities AS SELECT name, population, altitude FROM capitals UNION SELECT name, population, altitude FROM non_capitals;
這個模式對於查詢而言工做正常,可是當咱們須要更新一些行時它就變得很差用了。
更好的方案是:
CREATE TABLE cities ( name text, population real, altitude int -- (in ft) ); CREATE TABLE capitals ( state char(2) ) INHERITS (cities);
在這種狀況下,一個capitals
的行從它的父親cities
繼承了全部列(name、population和altitude)。列name的類型是text,一種用於變長字符串的本地PostgreSQL類型。州首都有一個附加列state用於顯示它們的州。在PostgreSQL中,一個表能夠從0個或者多個表繼承。
例如,以下查詢能夠尋找全部海拔500尺以上的城市名稱,包括州首都:
SELECT name, altitude FROM cities WHERE altitude > 500;
name | altitude -----------+---------- Las Vegas | 2174 Mariposa | 1953 Madison | 845 (3 rows)
在另外一方面,下面的查詢能夠查找全部海拔高於500尺且不是州首府的城市:
SELECT name, altitude FROM ONLY cities WHERE altitude > 500; name | altitude -----------+---------- Las Vegas | 2174 Mariposa | 1953 (2 rows)
其中cities
以前的ONLY用於指示查詢只在cities
表上進行而不會涉及到繼承層次中位於cities
之下的其餘表。