数据库设计(6/9):存储过程主体
對(duì)于設(shè)計(jì)和創(chuàng)建數(shù)據(jù)庫(kù)完全是個(gè)新手?沒(méi)關(guān)系,Joe Celko, 世界上讀者數(shù)量最多的SQL作者之一,會(huì)告訴你這些基礎(chǔ)。和往常一樣,即使是最專業(yè)的數(shù)據(jù)庫(kù)老手,也會(huì)給他們帶來(lái)驚喜。Joe是DMBS雜志是多年來(lái)最受 讀者喜愛(ài)的作者。他在美國(guó)、英國(guó),北歐,南美及非洲傳授SQL知識(shí)。他在ANSI / ISO SQL標(biāo)準(zhǔn)委員會(huì)工作了10年,為SQL-89和SQL-92標(biāo)準(zhǔn)做出了杰出貢獻(xiàn)。
在上一篇文章里已經(jīng)介紹了SQL Server里的存儲(chǔ)過(guò)程標(biāo)題,Joe會(huì)繼續(xù)談下存儲(chǔ)過(guò)程內(nèi)容的話題。在這篇文章里,他會(huì)概況談下作為過(guò)程化語(yǔ)言的T-SQL的局限性,當(dāng)決定如何使用它們時(shí)要記住那些。
在第一篇到第四篇,我們創(chuàng)建了表,架構(gòu)的基礎(chǔ)和可視化。但我們還沒(méi)結(jié)束,因?yàn)榧軜?gòu)不止這些。在一個(gè)真正的數(shù)據(jù)庫(kù)里,有更多的結(jié)構(gòu)需要考慮。在這些其它架構(gòu)層級(jí)外的東西是:游標(biāo),觸發(fā)器和存儲(chǔ)過(guò)程。還有其它像核對(duì),翻譯,特權(quán)(collations, translations, privileges)和像這樣的東西。我只談這三個(gè)東西——游標(biāo),觸發(fā)器和存儲(chǔ)過(guò)程——我只用最常規(guī)的方式命名。5-SQL和其它產(chǎn)品可以有更高的專利,不管ANSI/ISO標(biāo)準(zhǔn)。理由很簡(jiǎn)單:這些東西是建立在早期SQL產(chǎn)品使用的現(xiàn)有文件系統(tǒng)之上。這些過(guò)程化的結(jié)構(gòu)是用來(lái)彌補(bǔ)早期產(chǎn)品聲明式代碼的缺陷。供應(yīng)商有鎖在“代碼博物館”里的用戶,不會(huì)放棄他們的客戶基礎(chǔ)。
在第5篇,對(duì)于存儲(chǔ)過(guò)程,我們討論存儲(chǔ)過(guò)程標(biāo)題有什么和它是如何工作的,就像一個(gè)黑盒子。在第6篇,我們到黑盒子里面看看。
過(guò)程化SQL
SQL允許存儲(chǔ)過(guò)程代碼模塊在架構(gòu)里保存。同時(shí)在標(biāo)準(zhǔn)SQL里有SQL/PSM語(yǔ)言,你會(huì)使用像T-SQL的專門(mén)語(yǔ)言。這些語(yǔ)言通常是Algol家族的成員;那就是說(shuō)他它們有IF-THEN-ELSE,WHILE循環(huán)和有BEGIN-END作用域的代碼塊。
這些專用語(yǔ)言的大多數(shù)從未想用做程序開(kāi)發(fā)。對(duì)于T- SQL的首要規(guī)則(The rules of thumb)是不寫(xiě)超過(guò)50行的的過(guò)程,且不使用PRINT。但事實(shí)上,你可以避免所有的面向過(guò)程,每個(gè)表像文件和代碼一樣對(duì)待,好像數(shù)據(jù)庫(kù)是個(gè)過(guò)程化的 文件系統(tǒng)。如果你喜歡疼痛,大可敲個(gè)釘子到你身體,所以不用糾結(jié)。
T-SQL是個(gè)一次通過(guò)的編譯器。這是你必須前置本地變量,使用@(@標(biāo)志,“蝸牛(snail)”或“小蝸牛(petite escargot)”)的參數(shù),@@是系統(tǒng)級(jí)的變量,#(井號(hào))和##是臨時(shí)表。多通道編譯器創(chuàng)建符號(hào)表,然后用每個(gè)通道探索程序?qū)ο蟮臇|西。當(dāng)它第一次找到它時(shí),一次通過(guò)編譯器需要用新的符號(hào)來(lái)告知做什么。一旦它們傳過(guò)來(lái),因此@表示“為我分配本地存儲(chǔ)”,@@表示“在程序外找我,對(duì)它我是全局的”,#表示“對(duì)于當(dāng)前會(huì)話在tempdb里創(chuàng)建我”,##表示“在臨時(shí)表里創(chuàng)建并保持我”。其它的一起是假定在DDL里定義。
SQL不計(jì)算
你不能期望T-SQL來(lái)做過(guò)程化代碼的優(yōu)化。那需要多通道。例如,大部分FORTRAN編譯器使用代數(shù)學(xué)寫(xiě)入來(lái)進(jìn)行計(jì)算上的優(yōu)化。來(lái)自IBM的F和G系列可以給學(xué)生完整錯(cuò)誤信息的快速編譯器,慢但可以為產(chǎn)成品優(yōu)化性能。
1960年期間一個(gè)經(jīng)典的IT故事是,IBM的國(guó)防部(DoD (Department of Defense) )測(cè)試和通用計(jì)算機(jī)的FORTRAN編譯器。IBM編譯器運(yùn)行了很長(zhǎng)時(shí)間,生成一個(gè)壓縮的可執(zhí)行模塊,當(dāng)運(yùn)行的時(shí)候,很快得出正確答案。通用編譯器運(yùn)行了很長(zhǎng)時(shí)間,生成了很小的可執(zhí)行模塊,包含一個(gè)WRITE語(yǔ)句,立即打印出正確的答案。啥問(wèn)題?問(wèn)題是涉及函數(shù)和它們的取消退出逆轉(zhuǎn)。關(guān)鍵是浮點(diǎn)取整錯(cuò)誤。通用FORTRAN編譯器成功配對(duì)函數(shù)和它們的逆向,完成代數(shù)并并以常量生成答案。
在T-SQL里避免浮點(diǎn)數(shù)和實(shí)數(shù)。在T-SQL里有同樣的數(shù)據(jù)類(lèi)型,但在標(biāo)準(zhǔn)SQL里沒(méi)有。問(wèn)題是浮點(diǎn)數(shù)需要特定來(lái)處理避免取整錯(cuò)誤和比較。有個(gè)它們需要調(diào)用近似數(shù)字?jǐn)?shù)據(jù)類(lèi)型的特定原因。這個(gè)特定處理需要要么是軟件內(nèi)建的,要么是硬件的一部分,這就說(shuō)你需要浮點(diǎn)處理器。同樣,你的老板不會(huì)為你的桌面安裝游戲顯卡。商業(yè)應(yīng)用服務(wù)器通常不需要這些昂貴的功能,不管你在工作的時(shí)候花多少時(shí)間在玩Halo或Doom游戲上。
即使芯片便宜,你也不能合理期望期望T-SQL來(lái)做數(shù)學(xué)上優(yōu)化的事。SQL是個(gè)數(shù)據(jù)檢索和管理語(yǔ)言,不是用來(lái)計(jì)算的。你想要的是寫(xiě)出傳給統(tǒng)計(jì)軟件包來(lái)獲得數(shù)據(jù)的好查詢,一個(gè)報(bào)表或其他特定工具。
如果需要十進(jìn)制的地方,那么就使用DECIMAL數(shù)據(jù)類(lèi)型。它們可以很好處理。竅門(mén)就是給你自己足夠的十進(jìn)制控件來(lái)獲得正確的整數(shù)。那意味著你需要知道你行業(yè)的標(biāo)準(zhǔn)。尤其是,如果你用歐元,你需要知道“歐元三角(euro triangulation)”,貨幣轉(zhuǎn)換和記賬規(guī)則。
最好親自做下代數(shù),讓算法盡可能簡(jiǎn)單。這樣建議也適用于字符和時(shí)間數(shù)據(jù)。
T-SQL有基于C的函數(shù)庫(kù)。這是為什么可以使用%來(lái)代替標(biāo)準(zhǔn)mod函數(shù)的原因。
SQL不用來(lái)顯示
再次強(qiáng)調(diào),SQL是數(shù)據(jù)檢索和管理語(yǔ)言,不是用來(lái)做前端顯示的。在SQL數(shù)據(jù)類(lèi)型里一起拿到數(shù)據(jù),把它們“FQ(over the wall)”傳給前端程序,例如報(bào)表編輯器和圖形包,這樣看起來(lái)會(huì)更好。
但是因?yàn)檫^(guò)程語(yǔ)言是焊接到它們的文件,程序員寫(xiě)單片程序成長(zhǎng)起來(lái)。COBOL只是字符串和顯示模板。FORTRAN有它自己的格式化語(yǔ)句。BASIC版本有使用#和其它符號(hào)的圖片選項(xiàng)。即使像C的低級(jí)語(yǔ)言,在它的printf函數(shù)里有精確的格式化選項(xiàng)!
長(zhǎng)期的過(guò)程化語(yǔ)言編程后,對(duì)于很多程序員,分層的概念非常困難。事實(shí)上,在現(xiàn)在,你還是可以聽(tīng)到抗議:“在數(shù)據(jù)庫(kù)我就可以完整這個(gè)并節(jié)約時(shí)間”。
有時(shí)候拿是對(duì)的。但大多時(shí)候,這不會(huì)節(jié)約。顯示格式化會(huì)從在基本列上使用索引阻止優(yōu)化器。前端然后會(huì)拆回格式化列到它們的源數(shù)據(jù)或另一個(gè)格式。比起在它們的列里有基本數(shù)據(jù)類(lèi)型的簡(jiǎn)單列,真正的損失是這更難維護(hù)。
讓我給你2個(gè)常見(jiàn)的例子。使用專門(mén)的CONVERT()函數(shù)把時(shí)間數(shù)據(jù)轉(zhuǎn)為字符來(lái)顯示。讓程序?yàn)槟阕鲞@個(gè);它們有函數(shù)庫(kù)來(lái)做這個(gè)。你不用擔(dān)心國(guó)家設(shè)置或正確的取整(可以是通過(guò)程序設(shè)計(jì)決定的程序)。當(dāng)你有DATEPART()和CAST()時(shí),CONVERT()的最壞使用是對(duì)字符處理。可以看下兩個(gè)日期轉(zhuǎn)為字符串,然后比較字符串。
第2個(gè)常見(jiàn)例子是從姓和名組合為姓名。這會(huì)阻止在姓列上的索引使用,會(huì)給前端帶來(lái)可用空間和規(guī)則的重格式化問(wèn)題。你會(huì)看到翻轉(zhuǎn)名字順序(名,姓)的前端代碼
基本聲明式編程啟發(fā)法(Basic Declarative Programming Heuristics)
結(jié)構(gòu)化編程實(shí)際上有修正性的數(shù)學(xué)證據(jù)。你可以且應(yīng)該看下Dijkstra, Wirth and Manna。這實(shí)際上是會(huì)幫你編程的理論。聲明式編程還沒(méi)到那個(gè)點(diǎn)。但可以給你寫(xiě)啟發(fā)。當(dāng)你看到一個(gè)特定情形時(shí)可以嘗試些事情;它們不是宇宙法則,就像精明投資者的押注。
關(guān)于這個(gè)話題有2個(gè)系列(看下下面參考文章)可以給你過(guò)程化的例子,半過(guò)程化和聲明式編程風(fēng)格。但現(xiàn)在,讓我給你有幫助的“高水平提示”的快速清單。
傾向一句頂多句
在一個(gè)沒(méi)有使用T-SQL流程控制的一個(gè)SQL語(yǔ)句里,你可以做的更多工作,代碼越好,工作越順。因此,如果你的存儲(chǔ)過(guò)程主體有兩個(gè)或更多引用到同個(gè)表,你大可以組合它們并一次訪問(wèn)那個(gè)表。
你可以使用CASE表達(dá)式來(lái)避免很多的IF-THEN-ELSE控制邏輯。在CASE表達(dá)式前,是應(yīng)用邏輯到SQL的表達(dá)式。經(jīng)典的例子是多年來(lái)Sybase/SQL服務(wù)器類(lèi)一部分的UPDATE語(yǔ)句。你有個(gè)書(shū)店,想修改書(shū)的價(jià)格。超過(guò)25美元的書(shū)上漲10%(這個(gè)會(huì)做廣告),低于25美元打85折(這個(gè)不會(huì)做廣告)。
經(jīng)典的在偽代碼里,結(jié)構(gòu)化編程的答案如下:
BEGIN OPEN FILE (Bookstore); READ (price) FROM Bookstore; WHILE NOT EOF (bookstore) DO BEGINIF price < 25.00THEN UPDATE Books SET price = price * 1.10ELSE UPDATE Books SET price = price * 0.85;FETCH NEXT Bookstore;END IF; END WHILE; CLOSE FILE (Bookstore); END:很容易把偽代碼準(zhǔn)換為游標(biāo)。純粹的SQL語(yǔ)句會(huì)如下:
BEGIN UPDATE BooksSET price = price * 1.10WHERE price < 25.00; UPDATE BooksSET price = price * 0.85WHERE price >= 25.00; END;但這不對(duì)!如果一本書(shū)現(xiàn)在售價(jià)是24.95美元。當(dāng)?shù)谝粋€(gè)UPDATE語(yǔ)句執(zhí)行后,會(huì)是27.45美元。但當(dāng)我們執(zhí)行第2個(gè)UPDATE時(shí),最后的價(jià)格會(huì)是23.33美元。這不是我們想要的。交換下UPDATE語(yǔ)句也沒(méi)用;在頂部的書(shū)會(huì)更新2次。
這是對(duì)游標(biāo)的經(jīng)典異議。在那些日子里,對(duì)于這類(lèi)問(wèn)題,我們有各類(lèi)可怕的多個(gè)表掃描存儲(chǔ)過(guò)程。現(xiàn)在,我們有了CASE表達(dá)式,它是聲明式,做一次表掃描。
UPDATE BooksSET price= CASEWHEN price < 25.00THEN price * 1.10ELSE price * 0.85END;這個(gè)啟發(fā)式有個(gè)部分:
同樣的啟發(fā)式適用于INSERT INTO語(yǔ)句。這個(gè)的一個(gè)格式是插入初始的一些行,隨后是選擇的一些行。結(jié)構(gòu)如下:
1 INSERT INTO Foobar (..) VALUES(..); 2 INSERT INTO Foobar SELECT .. FROM.. WHERE..;也可以寫(xiě)成這樣:
1 INSERT INTO Foobar (..) 2 (SELECT X.* FROM (VALUES (..)) AS X) 3 UNION ALL 4 SELECT .. FROM.. WHERE..;當(dāng)然CASE表達(dá)式也可以用在SELECT語(yǔ)句里。
或許這個(gè)啟發(fā)式的最佳例子是MERGE語(yǔ)句,可以讓你把INSERT和UPDATE組合為一個(gè)語(yǔ)句。這里我不會(huì)討論它,但強(qiáng)烈建議你看下它。
避免本地變量
T-SQL必須分配本地本地變量,它們經(jīng)常是不需要的。一個(gè)常見(jiàn)的模式:
1 CREATE FUNCTION Foobar (..) 2 RETURNS <data type> 3 AS 4 BEGIN 5 DECLARE @local_return_holder <data type>; 6 SET @local_return_holder 7 = <scalar query>: 8 END;可以更簡(jiǎn)單:
1 CREATE FUNCTION Foobar (..) 2 RETURNS <data type> 3 AS 4 BEGIN 5 RETURN (<scalar query>); 6 END;本地變量的其他缺點(diǎn)它們會(huì)從優(yōu)化器隱藏表達(dá)式。
BEGIN SET @local_x = (<scalar query>); -- has to load local variable .. <statement using @local_x>; END;可以是:
BEGIN .. <statement using (<scalar query>)>; --optimizes whole expression END;你也可以嵌套調(diào)用函數(shù),不用在本地變量里的直接值逐步處理。這個(gè)的最好例子是REPLACE()的如下系列調(diào)用:
SET @x = REPLACE (@x, 'a', 'A'); SET @x = REPLACE (@x, 'B', 'b'); ETC使用REPLACE (REPLACE..(REPLACE (@x, 'z', 'Z') ..))最多你可以32層。
對(duì)此概念有問(wèn)題,你可以和LISP程序員談下。這個(gè)語(yǔ)言只有嵌套函數(shù)調(diào)用。
傾向JOIN非Loop
有很多其他技巧可以避免逐行處理。例如,不用說(shuō)太多,for循環(huán)通常可以用join到系列表(Series table)來(lái)代替。系列表(Series table)是來(lái)一個(gè)到上限的一系列整數(shù)。
尋找應(yīng)該在DDL里的東西
在存儲(chǔ)過(guò)程里IF-THEN邏輯的使用在運(yùn)行時(shí)清理數(shù)據(jù),這是你真的在DDL里需要CHECK(),在第一時(shí)間就阻止出錯(cuò)。例如:
1 SET T.x = COALESCE (T.x, 0); 2 IF (x > 12)..;這是你需要你在一些列上有默認(rèn)值和約束的標(biāo)志。在表里修改“x INTEGER”如下:
CREATE TABLE T (..x INTEGER DEFAULT 0 NOT NULLCHECK (x BETWEEN 0 AND 12), ..);避免CLR和XML混用
保持外部語(yǔ)言在架構(gòu)之外。不添加其他語(yǔ)言來(lái)混合的SQL已經(jīng)很難維護(hù)。當(dāng)你在語(yǔ)句里找到一個(gè)CLR模塊你不知道,你會(huì)怎么辦?它們不會(huì)遵循例如MOD(),SUBSTRING()和算術(shù)取整等同樣的定義。最好的例子是C#和VB之間的區(qū)別,2個(gè)微軟專屬語(yǔ)言在布爾值表達(dá)上卻是一致的。
參考文章:
https://www.simple-talk.com/sql/t-sql-programming/procedural,-semi-procedural-and-declarative-programming-in-sql/
https://www.simple-talk.com/sql/t-sql-programming/procedural,-semi-procedural-and-declarative-programing-part-ii/
原文鏈接:
http://www.sqlservercentral.com/articles/Stairway+Series/70950/
總結(jié)
以上是生活随笔為你收集整理的数据库设计(6/9):存储过程主体的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: linux设备驱动归纳总结(六):2.分
- 下一篇: win7 绿色版MySQL安装与配置