SQL 視圖簡介


學習重點

  • 從 SQL 的角度來看,視圖和表是相同的,兩者的區別在於表中保存的是實際的數據,而視圖中保存的是 SELECT 語句(視圖本身並不存儲數據)。

  • 使用視圖,可以輕松完成跨多表查詢數據等復雜操作。

  • 可以將常用的 SELECT 語句做成視圖來使用。

  • 創建視圖需要使用 CREATE VIEW 語句。

  • 視圖包含“不能使用 ORDER BY ”和“可對其進行有限制的更新”兩項限制。

  • 刪除視圖需要使用 DROP VIEW 語句。

一、視圖和表

我們首先要學習的是一個新的工具——視圖

KEYWORD

  • 視圖

視圖究竟是什么呢?如果用一句話概述的話,就是“從 SQL 的角度來看視圖就是一張表”。實際上,在 SQL 語句中並不需要區分哪些是表,哪些是視圖,只需要知道在更新時它們之間存在一些不同就可以了,這一點之后會為大家進行介紹。至少在編寫 SELECT 語句時並不需要特別在意表和視圖有什么不同。

那么視圖和表到底有什么不同呢?區別只有一個,那就是“是否保存了實際的數據”。

通常,我們在創建表時,會通過 INSERT 語句將數據保存到數據庫之中,而數據庫中的數據實際上會被保存到計算機的存儲設備(通常是硬盤)中。因此,我們通過 SELECT 語句查詢數據時,實際上就是從存儲設備(硬盤)中讀取數據,進行各種計算之后,再將結果返回給用戶這樣一個過程。

但是使用視圖時並不會將數據保存到存儲設備之中,而且也不會將數據保存到其他任何地方。實際上視圖保存的是 SELECT 語句(圖 1)。我們從視圖中讀取數據時,視圖會在內部執行該 SELECT 語句並創建出一張臨時表。

視圖和表

圖 1 視圖和表

  • 視圖的優點

    視圖的優點大體有兩點。第一點是由於視圖無需保存數據,因此可以節省存儲設備的容量。例如,我們在 數據的插入 中創建了用來匯總商品種類(product_type)的表。由於該表中的數據最終都會保存到存儲設備之中,因此會占用存儲設備的數據空間。但是,如果把同樣的數據作為視圖保存起來的話,就只需要代碼清單 1 那樣的 SELECT 語句就可以了,這樣就節省了存儲設備的數據空間。

    代碼清單 1 通過視圖等 SELECT 語句保存數據

    SELECT product_type, SUM(sale_price), SUM(purchase_price)
    FROM Product
    GROUP BY product_type;
    

    由於本示例中表的數據量充其量只有幾行,所以使用視圖並不會大幅縮小數據的大小。但是在實際的業務中數據量往往非常大,這時使用視圖所節省的容量就會非常可觀了。

    法則 1

    表中存儲的是實際數據,而視圖中保存的是從表中取出數據所使用的 SELECT 語句。

    第二個優點就是可以將頻繁使用的 SELECT 語句保存成視圖,這樣就不用每次都重新書寫了。創建好視圖之后,只需在 SELECT 語句中進行調用,就可以方便地得到想要的結果了。特別是在進行匯總以及復雜的查詢條件導致 SELECT 語句非常龐大時,使用視圖可以大大提高效率。

    而且,視圖中的數據會隨着原表的變化自動更新。視圖歸根到底就是 SELECT 語句,所謂“參照視圖”也就是“執行 SELECT 語句”的意思,因此可以保證數據的最新狀態。這也是將數據保存在表中所不具備的優勢 [1]

    法則 2

    應該將經常使用的 SELECT 語句做成視圖。

二、創建視圖的方法

創建視圖需要使用 CREATE VIEW 語句,其語法如下所示。

KEYWORD

  • CREATE VIEW 語句

語法 1 創建視圖的 CREATE VIEW 語句

CREATE VIEW 視圖名稱(<視圖列名1>, <視圖列名2>, ……)
AS
<select語句>

SELECT 語句需要書寫在 AS 關鍵字之后。SELECT 語句中列的排列順序和視圖中列的排列順序相同,SELECT 語句中的第 1 列就是視圖中的第 1 列,SELECT 語句中的第 2 列就是視圖中的第 2 列,以此類推。視圖的列名在視圖名稱之后的列表中定義。

備忘

接下來,我們將會以此前使用的 Product(商品)表為基礎來創建視圖。如果大家已經根據之前章節的內容更新了 Product 表中的數據,請在創建視圖之前將數據恢復到初始狀態。操作步驟如下>所示。

① 刪除 Product 表中的數據,將表清空

DELETE FROM Product;

② 執行代碼清單 6(表的刪除和更新)中的 SQL 語句,將數據插入到空表 Product

下面就讓我們試着來創建視圖吧。和此前一樣,這次我們還是將 Product 表(代碼清單 2)作為基本表。

代碼清單 2 ProductSum 視圖

ProductSum 視圖

這樣我們就在數據庫中創建出了一幅名為 ProductSum(商品合計)的視圖。請大家一定不要省略第 2 行的關鍵字 AS。這里的 AS 與定義別名時使用的 AS 並不相同,如果省略就會發生錯誤。雖然很容易混淆,但是語法就是這么規定的,所以還是請大家牢記。

接下來,我們來學習視圖的使用方法。視圖和表一樣,可以書寫在 SELECT 語句的 FROM 子句之中(代碼清單 3)。

代碼清單 3 使用視圖

使用視圖

執行結果

 product_type | cnt_product
--------------+------------
 衣服         |           2
 辦公用品     |           2
 廚房用具     |           4

通過上述視圖 ProductSum 定義的主體(SELECT 語句)我們可以看出,該視圖將根據商品種類(product_type)匯總的商品數量(cnt_product)作為結果保存了起來。這樣如果大家在工作中需要頻繁進行匯總時,就不用每次都使用 GROUP BYCOUNT 函數寫 SELECT 語句來從 Product 表中取得數據了。創建出視圖之后,就可以通過非常簡單的 SELECT 語句,隨時得到想要的匯總結果。並且如前所述,Product 表中的數據更新之后,視圖也會自動更新,非常靈活方便。

之所以能夠實現上述功能,是因為視圖就是保存好的 SELECT 語句。定義視圖時可以使用任何 SELECT 語句,既可以使用 WHEREGROUP BYHAVING,也可以通過 SELECT * 來指定全部列。

  • 使用視圖的查詢

    FROM 子句中使用視圖的查詢,通常有如下兩個步驟:

    ① 首先執行定義視圖的 SELECT 語句

    ② 根據得到的結果,再執行在 FROM 子句中使用視圖的 SELECT 語句

    也就是說,使用視圖的查詢通常需要執行 2 條以上的 SELECT 語句 [2]

    這里沒有使用“2 條”而使用了“2 條以上”,是因為還可能出現以視圖為基礎創建視圖的多重視圖(圖 2)。例如,我們可以像代碼清單 4 那樣以 ProductSum 為基礎創建出視圖 ProductSumJim

    KEYWORD

    • 多重視圖
    可以在視圖的基礎上創建視圖

    圖 2 可以在視圖的基礎上創建視圖

    代碼清單 4 視圖 ProductSumJim

    視圖 ProductSumJim

    -- 確認創建好的視圖
    SELECT product_type, cnt_product
    FROM ProductSumJim;
    

    執行結果

    product_type | cnt_product
    --------------+------------
    辦公用品     |           2
    

    雖然語法上沒有錯誤,但是我們還是應該盡量避免在視圖的基礎上創建視圖。這是因為對多數 DBMS 來說,多重視圖會降低 SQL 的性能。因此,希望大家(特別是剛剛接觸視圖的讀者)能夠使用單一視圖。

    法則 3

    應該避免在視圖的基礎上創建視圖。

    除此之外,在使用時還要注意視圖有兩個限制,接下來會給大家詳細介紹。

三、視圖的限制 ①——定義視圖時不能使用 ORDER BY 子句

雖然之前我們說過在定義視圖時可以使用任何 SELECT 語句,但其實有一種情況例外,那就是不能使用 ORDER BY 子句,因此下述視圖定義語句是錯誤的。

定義視圖時不能使用 ORDER BY 子句

為什么不能使用 ORDER BY 子句呢?這是因為視圖和表一樣,數據行都是沒有順序的。實際上,有些 DBMS 在定義視圖的語句中是可以使用 ORDER BY 子句的 [3],但是這並不是通用的語法。因此,在定義視圖時請不要使用 ORDER BY 子句。

法則 4

定義視圖時不要使用 ORDER BY 子句。

四、視圖的限制 ② ——對視圖進行更新

之前我們說過,在 SELECT 語句中視圖可以和表一樣使用。那么,對於 INSERTDELETEUPDATE 這類更新語句(更新數據的 SQL)來說,會怎么樣呢?

實際上,雖然這其中有很嚴格的限制,但是某些時候也可以對視圖進行更新。標准 SQL 中有這樣的規定:如果定義視圖的 SELECT 語句能夠滿足某些條件,那么這個視圖就可以被更新。下面就給大家列舉一些比較具有代表性的條件。

SELECT 子句中未使用 DISTINCT

FROM 子句中只有一張表

③ 未使用 GROUP BY 子句

④ 未使用 HAVING 子句

在前幾章的例子中,FROM 子句里通常只有一張表。因此,大家可能會覺得 ② 中的條件有些奇怪,但其實 FROM 子句中也可以並列使用多張表。大家在學習完 SQL 聯結 的操作之后就明白了。

其他的條件大多數都與聚合有關。簡單來說,像這次的例子中使用的 ProductSum 那樣,使用視圖來保存原表的匯總結果時,是無法判斷如何將視圖的更改反映到原表中的。

例如,對 ProductSum 視圖執行如下 INSERT 語句。

INSERT INTO ProductSum VALUES ('電器制品', 5);

但是,上述 INSERT 語句會發生錯誤。這是因為視圖 ProductSum 是通過 GROUP BY 子句對原表進行匯總而得到的。為什么通過匯總得到的視圖不能進行更新呢?

視圖歸根結底還是從表派生出來的,因此,如果原表可以更新,那么視圖中的數據也可以更新。反之亦然,如果視圖發生了改變,而原表沒有進行相應更新的話,就無法保證數據的一致性了。

使用前述 INSERT 語句,向視圖 ProductSum 中添加數據 (' 電器制品 ',5) 時,原表 Product 應該如何更新才好呢?按理說應該向表中添加商品種類為“電器制品”的 5 行數據,但是這些商品對應的商品編號、商品名稱和銷售單價等我們都不清楚(圖 3)。數據庫在這里就遇到了麻煩。

通過匯總得到的視圖無法更新

圖 3 通過匯總得到的視圖無法更新

法則 5

視圖和表需要同時進行更新,因此通過匯總得到的視圖無法進行更新。

  • 能夠更新視圖的情況

    像代碼清單 5 這樣,不是通過匯總得到的視圖就可以進行更新。

    代碼清單 5 可以更新的視圖

    可以更新的視圖

    對於上述只包含辦公用品類商品的視圖 ProductJim 來說,就可以執行類似代碼清單 6 這樣的 INSERT 語句。

    代碼清單 6 向視圖中添加數據行

    向視圖中添加數據行

注意事項

由於 PostgreSQL 中的視圖會被初始設定為只讀,所以執行代碼清單 6 中的 INSERT 語句時,會發生下面這樣的錯誤。

執行結果(使用 PostgreSQL)

ERROR:  不能向視圖中插入數據
HINT:  需要一個無條件的ON INSERT DO INSTEAD規則

這種情況下,在 INSERT 語句執行之前,需要使用代碼清單 A 中的指令來允許更新操作。在 DB2 和 MySQL 等其他 DBMS 中,並不需要執行這樣的指令。

代碼清單 A 允許 PostgreSQL 對視圖進行更新

PostgreSQL

CREATE OR REPLACE RULE insert_rule
AS ON INSERT
TO  ProductJim DO INSTEAD
INSERT INTO Product VALUES (
           new.product_id,
           new.product_name,
           new.product_type,
           new.sale_price,
           new.purchase_price,
           new.regist_date);

下面讓我們使用 SELECT 語句來確認數據行是否添加成功吧。

  • 視圖

    -- 確認數據是否已經添加到視圖中
    SELECT * FROM ProductJim;
    

    執行結果

    執行結果

  • 原表

    -- 確認數據是否已經添加到原表中
    SELECT * FROM Product;
    

    執行結果

    執行結果

    UPDATE 語句和 DELETE 語句當然也可以像操作表時那樣正常執行,但是對於原表來說卻需要設置各種各樣的約束(主鍵和 NOT NULL 等),需要特別注意。

五、刪除視圖

刪除視圖需要使用 DROP VIEW 語句,其語法如下所示。

KEYWORD

  • DROP VIEW 語句

語法 2 刪除視圖的 DROP VIEW 語句

DROP VIEW 視圖名稱(<視圖列名1>, <視圖列名2>, ……)

例如,想要刪除視圖 ProductSum 時,就可以使用代碼清單 7 中的 SQL 語句。

代碼清單 7 刪除視圖

DROP VIEW ProductSum;

特定的 SQL

在 PostgreSQL 中,如果刪除以視圖為基礎創建出來的多重視圖,由於存在關聯的視圖,因此會發生如下錯誤。

執行結果(使用 PostgreSQL)

ERROR:   由於存在關聯視圖,因此無法刪除視圖productsum
DETAIL:  視圖productsumjim與視圖productsum相關聯
HINT:    刪除關聯對象請使用DROP…CASCADE

這時可以像下面這樣,使用 CASCADE 選項來刪除關聯視圖。

PostgreSQL

DROP VIEW ProductSum CASCADE;

備忘

下面我們再次將 Product 表恢復到初始狀態(8 行)。請執行如下 DELETE 語句,刪除之前添加的 1 行數據。

代碼清單 B

-- 刪除商品編號為0009(印章)的數據
DELETE FROM Product WHERE product_id = '0009';

請參閱

(完)
</select語句>


  1. 數據保存在表中時,必須要顯式地執行 SQL 更新語句才能對數據進行更新。 ↩︎

  2. 但是根據實現方式的不同,也存在內部使用視圖的 SELECT 語句本身進行重組的 DBMS。 ↩︎

  3. 例如,在 PostgreSQL 中上述 SQL 語句就沒有問題,可以執行。 ↩︎


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM