FORALL語句
FORALL語句的一個關鍵性改進,它可大大簡化代碼,并且對于那些要在PL/SQL程序中更新很多行數據的程序來說,它可顯著提高其性能。
1:
用FORALL來增強DML的處理能力
Oracle為Oracle8i中的PL/SQL引入了兩個新的數據操縱語言(DML)語句:BULK COLLECT和FORALL。這兩個語句在PL/SQL內部進行一種數組處理
;BULK COLLECT提供對數據的高速檢索,FORALL可大大改進INSERT、UPDATE和DELETE操作的性能。Oracle數據庫使用這些語句大大減少了
PL/SQL與SQL語句執行引擎的環境切換次數,從而使其性能有了顯著提高。
使用BULK COLLECT,你可以將多個行引入一個或多個集合中,而不是單獨變量或記錄中。下面這個BULK COLLECT的實例是將標題中包含
有"PL/SQL"的所有書籍檢索出來并置于記錄的一個關聯數組中,它們都位于通向該數據庫的單一通道中。
DECLARE
TYPE books_aat
IS TABLE OF book%ROWTYPE
INDEX BY PLS_INTEGER;
books books_aat;
BEGIN
SELECT *
BULK COLLECT INTO book
FROM books
WHERE title LIKE '%PL/SQL%';
...
END;
類似地,FORALL將數據從一個PL/SQL集合傳送給指定的使用集合的表。下面的代碼實例給出一個過程,即接收書籍信息的一個嵌套表,并將該
集合(綁定數組)的全部內容插入該書籍表中。注意,這個例子還利用了Oracle9i的FORALL的增強功能,可以將一條記錄直接插入到表中。
BULK COLLECT和FORALL都非常有用,它們不僅提高了性能,而且還簡化了為PL/SQL中的SQL操作所編寫的代碼。下面的多行FORALL INSERT相當
清楚地說明了為什么PL/SQL被認為是Oracle數據庫的最佳編程語言。
CREATE TYPE books_nt
IS TABLE OF book%ROWTYPE;
/
CREATE OR REPLACE PROCEDURE add_books (
books_in IN books_nt)
IS
BEGIN
FORALL book_index
IN books_in.FIRST .. books_in.LAST
INSERT INTO book
VALUES books_in(book_index);
...
END;
不過在Oracle數據庫10g之前,以FORAll方式使用集合有一個重要的限制:該數據庫從IN范圍子句中的第一行到最后一行,依次讀取集合的內容
。如果在該范圍內遇到一個未定義的行,Oracle數據庫將引發ORA-22160異常事件:
ORA-22160: element at index [N] does not exist
對于FORALL的簡單應用,這一規則不會引起任何麻煩。但是,如果想盡可能地充分利用FORALL,那么要求任意FORALL驅動數組都要依次填充可
能會增加程序的復雜性并降低性能。
在Oracle數據庫10g中,PL/SQL現在在FORALL語句中提供了兩個新子句:INDICES OF與VALUES OF,它們使你能夠仔細選擇驅動數組中該由擴展
DML語句來處理的行。
當綁定數組為稀疏數組或者包含有間隙時,INDICES OF會非常有用。該語句的語法結構為:
FORALL indx IN INDICES
OF sparse_collection
INSERT INTO my_table
VALUES sparse_collection (indx);
VALUES OF用于一種不同的情況:綁定數組可以是稀疏數組,也可以不是,但我只想使用該數組中元素的一個子集。那么我就可以使用VALUES
OF來指向我希望在DML操作中使用的值。該語句的語法結構為:
FORALL indx IN VALUES OF pointer_array
INSERT INTO my_table
VALUES binding_array (indx);
不用FOR循環而改用FORALL
假定我需要編寫一個程序,對合格員工(由comp_analysis.is_eligible函數確定)加薪,編寫關于不符合加薪條件的員工的報告并寫入
employee_history表。我在一個非常大的公司工作;我們的員工非常非常多。
對于一位PL/SQL開發人員來說,這并不是一項十分困難的工作。我甚至不需要使用BULK COLLECT或FORALL就可以完成這項工作,如清單 1所示
,我使用一個CURSOR FOR循環和單獨的INSERT及UPDATE語句。這樣的代碼簡潔明了;不幸地是,我花了10分鐘來運行此代碼,我的"老式"方法
要運行30分鐘或更長時間。
清單 1:
CREATE OR REPLACE PROCEDURE give_raises_in_department (
dept_in IN employee.department_id%TYPE
, newsal IN employee.salary%TYPE
)
IS
CURSOR emp_cur
IS
SELECT employee_id, salary, hire_date
FROM employee
WHERE department_id = dept_in;
BEGIN
FOR emp_rec IN emp_cur
LOOP
IF comp_analysis.is_eligible (emp_rec.employee_id)
THEN
UPDATE employee
SET salary = newsal
WHERE employee_id = emp_rec.employee_id;
ELSE
INSERT INTO employee_history
(employee_id, salary
, hire_date, activity
)
VALUES (emp_rec.employee_id, emp_rec.salary
, emp_rec.hire_date, 'RAISE DENIED'
);
END IF;
END LOOP;
END give_raises_in_department;
好在我公司的數據庫升級到了Oracle9i,而且更幸運的是,在最近的Oracle研討會上(以及Oracle技術網站提供的非常不錯的演示中)我了解
到了批量處理方法。所以我決定使用集合與批量處理方法重新編寫程序。寫好的程序如清單 2所示。
清單 2:
1 CREATE OR REPLACE PROCEDURE give_raises_in_department (
2 dept_in IN employee.department_id%TYPE
3 , newsal IN employee.salary%TYPE
4 )
5 IS
6 TYPE employee_aat IS TABLE OF employee.employee_id%TYPE
7 INDEX BY PLS_INTEGER;
8 TYPE salary_aat IS TABLE OF employee.salary%TYPE
9 INDEX BY PLS_INTEGER;
10 TYPE hire_date_aat IS TABLE OF employee.hire_date%TYPE
11 INDEX BY PLS_INTEGER;
12
13 employee_ids employee_aat;
14 salaries salary_aat;
15 hire_dates hire_date_aat;
16
17 approved_employee_ids employee_aat;
18
19 denied_employee_ids employee_aat;
20 denied_salaries salary_aat;
21 denied_hire_dates hire_date_aat;
22
23 PROCEDURE retrieve_employee_info
24 IS
25 BEGIN
26 SELECT employee_id, salary, hire_date
27 BULK COLLECT INTO employee_ids, salaries, hire_dates
28 FROM employee
29 WHERE department_id = dept_in;
30 END;
31
32 PROCEDURE partition_by_eligibility
33 IS
34 BEGIN
35 FOR indx IN employee_ids.FIRST .. employee_ids.LAST
36 LOOP
37 IF comp_analysis.is_eligible (employee_ids (indx))
38 THEN
39 approved_employee_ids (indx) := employee_ids (indx);
40 ELSE
41 denied_employee_ids (indx) := employee_ids (indx);
42 denied_salaries (indx) := salaries (indx);
43 denied_hire_dates (indx) := hire_dates (indx);
44 END IF;
45 END LOOP;
46 END;
47
48 PROCEDURE add_to_history
49 IS
50 BEGIN
51 FORALL indx IN denied_employee_ids.FIRST .. denied_employee_ids.LAST
52 INSERT INTO employee_history
53 (employee_id
54 , salary
55 , hire_date, activity
56 )
57 VALUES (denied_employee_ids (indx)
58 , denied_salaries (indx)
59 , denied_hire_dates (indx), 'RAISE DENIED'
60 );
61 END;
62
63 PROCEDURE give_the_raise
64 IS
65 BEGIN
66 FORALL indx IN approved_employee_ids.FIRST .. approved_employee_ids.LAST
67 UPDATE employee
68 SET salary = newsal
69 WHERE employee_id = approved_employee_ids (indx);
70 END;
71 BEGIN
72 retrieve_employee_info;
73 partition_by_eligibility;
74 add_to_history;
75 give_the_raise;
76 END give_raises_in_department;
掃一眼清單1 和清單2 就會清楚地認識到:改用集合和批量處理方法將增加代碼量和復雜性。但是,如果你需要大幅度提升性能,這還是值得
的。下面,我們不看這些代碼,我們來看一看當使用FORALL時,用什么來處理CURSOR FOR循環內的條件邏輯。
定義集合類型與集合
在清單 2中,聲明段的第一部分(第6行至第11行)定義了幾種不同的集合類型,與我將從員工表檢索出的列相對應。我更喜歡基于employee%
ROWTYPE來聲明一個集合類型,但是FORALL還不支持對某些記錄集合的操作,在這樣的記錄中,我將引用個別字段。所以,我還必須為員工ID、
薪金和雇用日期分別聲明其各自的集合。
接下來為每一列聲明所需的集合(第13行至第21行)。首先定義與所查詢列相對應的集合(第13行至第15行):
employee_ids employee_aat;
salaries salary_aat;
hire_dates hire_date_aat;
然后我需要一個新的集合,用于存放已被批準加薪的員工的ID(第17行):
approved_employee_ids employee_aat;
最后,我再為每一列聲明一個集合(第19行至第21行),用于記錄沒有加薪資格的員工:
denied_employee_ids employee_aat;
denied_salaries salary_aat;
denied_hire_dates hire_date_aat;
深入了解代碼
數據結構確定后,我們現在跳過該程序的執行部分(第72行至第75行),了解如何使用這些集合來加速進程。
retrieve_employee_info;
partition_by_eligibility;
add_to_history;
give_the_raise;
我編寫此程序使用了逐步細化法(也被稱為"自頂向下設計")。所以執行部分不是很長,也不難理解,只有四行,按名稱對過程中的每一步進
行了描述。首先檢索員工信息(指定部門的所有員工)。然后進行劃分,將要加薪和不予加薪的員工區分出來。完成之后,我就可以將那些不
予加薪的員工添加至員工歷史表中,對其他員工進行加薪。
以這種方式編寫代碼使最終結果的可讀性大大增強。因而我可以深入到該程序中對我有意義的任何部分。
有了已聲明的集合,我現在就可以使用BULK COLLECT來檢索員工信息(第23行至第30行)。這一部分有效地替代了CURSOR FOR循環。至此,數
據被加載到集合中。
劃分邏輯(第32行至第46行)要求對剛剛填充的集合中的每一行進行檢查,看其是否符合加薪條件。如果符合,我就將該員工ID從查詢填充的
集合復制到符合條件的員工的集合。如果不符合,則復制該員工ID、薪金和雇用日期,因為這些都需要插入到employee_history表中。
初始數據現在已被分為兩個集合,可以將其分別用作兩個不同的FORALL語句(分別從第51行和第66行開始)的驅動器。我將不合格員工的集合
中的數據批量插入到employee_history(add_to_history)表中,并通過give_the_raise過程,在employee表中批量更新合格員工的信息。
最后再仔細地看一看add_to_history(第48行至第61行),以此來結束對這個重新編寫的程序的分析。FORALL語句(第51行)包含一個IN子句
,它指定了要用于批量INSERT的行號范圍。在對程序進行第二次重寫的說明中,我將把用于定義范圍的集合稱為"驅動集合"。但在
add_to_history的這一版本中,我簡單地假定: 使用在denied_employee_ids中定義的所有行。在INSERT自身內部,關于不合格員工的三個集
合都會被用到;我將把這些集合稱為"數據集合"。可以看到,驅動集合與數據集合無需匹配。在學習Oracle數據庫10g的新特性時,這是一個關
鍵點。
結果,清單 2 的行數大約是清單 1行數的2倍,但是清單 2 中的代碼會在要求的時間內運行。在使用Oracle數據庫10g之前,在這種情況下,
我只會對能夠在這一時間內運行代碼并開始下一個任務這一點感到高興。
不過,有了Oracle數據庫10g中最新版的PL/SQL,現在我就可以在性能、可讀性和代碼量方面作出更多的改進。
將VALUES OF用于此過程
在Oracle數據庫10g中,可以指定FORALL語句使用的驅動集合中的行的子集。可以使用以下兩種方法之一來定義該子集:
將數據集合中的行號與驅動集合中的行號進行匹配。你需要使用INDICES OF子句。
將數據集合中的行號與驅動集合中所定義行中找到的值進行匹配。這需要使用VALUES OF子句。
在對give_raises_in_department進行第二次和最后一次改寫中我將使用VALUES OF子句。清單 3 包含這個版本的全部代碼。我將略過這一程序
中與前一版本相同的部分。
從聲明集合開始,請注意我不再另外定義集合來存放合格的和不合格的員工信息,而是在清單 3 (第17行至第21行)中聲明兩個"引導"集合:
一個用于符合加薪要求的員工,另一個用于不符合加薪要求的員工。這兩個集合的數據類型都是布爾型;不久將會看到,這些集合的數據類型
與FORALL語句毫無關系。FORALL語句只關心定義了哪些行。 在員工表中擁有50 000行信息的give_raises_in_department的三種執行方法的占
用時間 執行方法 用時
CURSOR FOR循環 00:00:38.01
Oracle數據庫10g之前的批量處理 00:00:06.09
Oracle數據庫10g的批量處理 00:00:02.06
在員工表中擁有100,000行數據的give_raises_in_department的三種執行方法的占用時間 執行方法 用時
CURSOR FOR循環 00:00:58.01
Oracle數據庫10g之前的批量處理 00:00:12.00
Oracle數據庫10g的批量處理 00:00:05.05
表1:處理50,000行和100,000行數據的用時測試結果
retrieve_employee_info子程序與前面的相同,但是對數據進行劃分的方式完全不同(第32行至第44行)。我沒有將記錄從一個集合復制到另
一個集合(這個操作相對較慢),而只是確定與員工ID集合中的行號相匹配的相應引導集合中的行(通過為其指定一個TRUE值)。
現在可以在兩個不同FORALL語句(由第49行和第65行開始)中,將approved_list和denied_list集合用作驅動集合。
為了插入到employee_history表中,我使用了如下語句:
FORALL indx IN VALUES OF denied_list
為了進行更新(給員工進行加薪),我使用這一格式:
FORALL indx IN VALUES OF approved_list
在這兩個DML語句中,數據集合是在BULK COLLECT 檢索步驟中填充的最初的集合;沒有進行過復制。利用VALUES OF,Oracle數據庫在這些數據
集合的行中進行篩選,僅使用行號與驅動集合中行號相匹配的行
利用本程序中的VALUES OF,可以避免復制對全部記錄進行復制,而是用行號的一個簡單列表來替換它們。對于大型數組,進行這些復制的開銷
是非常可觀的。為了測試Oracle數據庫10g的優越性,我裝入employee表并對50,000行和100,000行的數據運行測試。為了模擬更多的現實情況
,我將Oracle數據庫10g之前的批量處理的執行方法作了修改以進行集合內容的多次復制。然后我使用SQL*Plus SET TIMING ON來顯示運行各個
不同的執行方法所用的時間。表 1 給出了結果。
從這些時間測定得到的結論非常清楚:由單個DML語句變為批量處理將大幅縮短耗用時間,數據為50,000行時的用時由38秒減為6秒,數據為
100,000行時的用時由58秒減為12秒。而且,通過使用VALUES OF來避免復制數據,我可以將用時縮短一半左右。
即使沒有性能上的改進,VALUES OF及其同類子句--INDICES OF也提高了PL/SQL語言的靈活性,使開發人員能夠更輕松地編寫出更直觀和更容易
維護的代碼。
在產品壽命這一點上,PL/SQL是一種成熟且功能強大的語言。因而,其很多新特性都是逐漸增加和改進而成的。不過,這些新特性還是使應用
程序的性能和開發人員的開發效率有了重大改變。VALUES OF就是這種特性的一個很好的例子。
dba_開頭.....
dba_users 數據庫用戶信息
dba_segments 表段信息
dba_extents 數據區信息
dba_objects 數據庫對象信息
dba_tablespaces 數據庫表空間信息
dba_data_files 數據文件設置信息
dba_temp_files 臨時數據文件信息
dba_rollback_segs 回滾段信息
dba_ts_quotas 用戶表空間配額信息
dba_free_space 數據庫空閑空間信息
dba_profiles 數據庫用戶資源限制信息
dba_sys_privs 用戶的系統權限信息
dba_tab_privs 用戶具有的對象權限信息
dba_col_privs 用戶具有的列對象權限信息
dba_role_privs 用戶具有的角色信息
dba_audit_trail 審計跟蹤記錄信息
dba_stmt_audit_opts 審計設置信息
dba_audit_object 對象審計結果信息
dba_audit_session 會話審計結果信息
dba_indexes 用戶模式的索引信息
user_開頭
user_objects 用戶對象信息
user_source 數據庫用戶的所有資源對象信息
user_segments 用戶的表段信息
user_tables 用戶的表對象信息
user_tab_columns 用戶的表列信息
user_constraints 用戶的對象約束信息
user_sys_privs 當前用戶的系統權限信息
user_tab_privs 當前用戶的對象權限信息
user_col_privs 當前用戶的表列權限信息
user_role_privs 當前用戶的角色權限信息
user_indexes 用戶的索引信息
user_ind_columns 用戶的索引對應的表列信息
user_cons_columns 用戶的約束對應的表列信息
user_clusters 用戶的所有簇信息
user_clu_columns 用戶的簇所包含的內容信息
user_cluster_hash_expressions 散列簇的信息
v$開頭
v$database 數據庫信息
v$datafile 數據文件信息
v$controlfile 控制文件信息
v$logfile 重做日志信息
v$instance 數據庫實例信息
v$log 日志組信息
v$loghist 日志歷史信息
v$sga 數據庫SGA信息
v$parameter 初始化參數信息
v$process 數據庫服務器進程信息
v$bgprocess 數據庫后臺進程信息
v$controlfile_record_section 控制文件記載的各部分信息
v$thread 線程信息
v$datafile_header 數據文件頭所記載的信息
v$archived_log 歸檔日志信息
v$archive_dest 歸檔日志的設置信息
v$logmnr_contents 歸檔日志分析的DML DDL結果信息
v$logmnr_dictionary 日志分析的字典文件信息
v$logmnr_logs 日志分析的日志列表信息
v$tablespace 表空間信息
v$tempfile 臨時文件信息
v$filestat 數據文件的I/O統計信息
v$undostat Undo數據信息
v$rollname 在線回滾段信息
v$session 會話信息
v$transaction 事務信息
v$rollstat 回滾段統計信息
v$pwfile_users 特權用戶信息
v$sqlarea 當前查詢過的sql語句訪問過的資源及相關的信息
v$sql 與v$sqlarea基本相同的相關信息
v$sysstat 數據庫系統狀態信息
all_開頭
all_users 數據庫所有用戶的信息
all_objects 數據庫所有的對象的信息
all_def_audit_opts 所有默認的審計設置信息
all_tables 所有的表對象信息
all_indexes 所有的數據庫對象索引的信息
session_開頭
session_roles 會話的角色信息
session_privs 會話的權限信息
index_開頭
index_stats 索引的設置和存儲信息
偽表
dual 系統偽列表信息
select col.COLUMN_NAME ,com.comments,col.DATA_TYPE
from user_tab_columns col
inner join user_col_comments com
on col.TABLE_NAME='B_ZA_CZRKXX' and
col.TABLE_NAME=com.TABLE_NAME and
col.COLUMN_NAME=com.COLUMN_NAME
order by col.COLUMN_ID
SELECT * FROM EMP WHERE EMPNO > 0 AND EXISTS (SELECT ‘X' FROM DEPT WHERE DEPT.DEPTNO = EMP.DEPTNO AND LOC = ‘MELB')
SELECT * FROM EMP WHERE EMPNO > 0 AND DEPTNO IN(SELECT DEPTNO FROM DEPT WHERE LOC = ‘MELB')
推薦方案:在業務密集的SQL當中盡量不采用IN操作符。
不等于操作符是永遠不會用到索引的,因此對它的處理只會產生全表掃描。
推薦方案:用其它相同功能的操作運算代替,如:
1)a<>0 改為 a>0 or a<0
2)a<>’’ 改為 a>’’
IS NULL 或IS NOT NULL操作(判斷字段是否為空)
判斷字段是否為空一般是不會應用索引的,因為B樹索引是不索引空值的。
推薦方案:
用其它相同功能的操作運算代替,如:
1)a is not null 改為 a>0 或a>’’等。
2)不允許字段為空,而用一個缺省值代替空值,如業擴申請中狀態字段不允許為空,缺省為申請。
3) 建立位圖索引(有分區的表不能建,位圖索引比較難控制,如字段值太多索引會使性能下降,多人更新操作會增加數據塊鎖的現象)
當通配符“%”或者“_”作為查詢字符串的第一個字符時,索引不會被使用
對于有連接的列“||”,最后一個連接列索引會無效。盡量避免連接,可以分開連接或者使用不作用在列上的函數替代。
如果索引不是基于函數的,那么當在Where子句中對索引列使用函數時,索引不再起作用。
Where子句中避免在索引列上使用計算,否則將導致索引失效而進行全表掃描。
對數據類型不同的列進行比較時,會使索引失效。
大于或小于操作符一般情況下是不用調整的,因為它有索引就會采用索引查找,但有的情況下可以對它進行優化,如一個表有100萬記錄,一個數值型字段A, 30萬記錄的A=0,30萬記錄的A=1,39萬記錄的A=2,1萬記錄的A=3。那么執行A>2與A>=3的效果就有很大的區別了,因為 A>2時ORACLE會先找出為2的記錄索引再進行比較,而A>=3時ORACLE則直接找到=3的記錄索引。
推薦方案:用“>=”替代“>”。
例: A>2 改為 A>=3
A<2 改為 A<=1
UNION在進行表鏈接后會篩選掉重復的記錄,所以在表鏈接后會對所產生的結果集進行排序運算,刪除重復的記錄再返回結果。實際大部分應用中是不會產生重復的記錄,最常見的是過程表與歷史表UNION。如:
select * from gc_dfys
union
select * from ls_jg_dfys
這個SQL在運行時先取出兩個表的結果,再用排序空間進行排序刪除重復的記錄,最后返回結果集,如果表數據量大的話可能會導致用磁盤進行排序。
推薦方案:采用UNION ALL操作符替代UNION,因為UNION ALL操作只是簡單的將兩個結果合并后就返回。
select * from gc_dfys
union all
select * from ls_jg_dfys
LIKE 操作符可以應用通配符查詢,里面的通配符組合可能達到幾乎是任意的查詢,但是如果用得不好則會產生性能上的問題,如LIKE ‘%5400%’ 這種查詢不會引用索引,而LIKE ‘X5400%’則會引用范圍索引。一個實際例子:用YW_YHJBQK表中營業編號后面的戶標識號可來查詢營業編號 YY_BH LIKE ‘%5400%’ 這個條件會產生全表掃描,如果改成YY_BH LIKE ’X5400%’ OR YY_BH LIKE ’B5400%’ 則會利用YY_BH的索引進行兩個范圍的查詢,性能肯定大大提高。
同一功能同一性能不同寫法SQL的影響
如一個SQL在A程序員寫的為
Select * from zl_yhjbqk
B程序員寫的為
Select * from dlyx.zl_yhjbqk(帶表所有者的前綴)
C程序員寫的為
Select * from DLYX.ZLYHJBQK(大寫表名)
D程序員寫的為
Select * from DLYX.ZLYHJBQK(中間多了空格)
以上四個SQL在ORACLE分析整理之后產生的結果及執行的時間是一樣的,但是從ORACLE共享內存SGA的原理,可以得出ORACLE對每個SQL 都會對其進行一次分析,并且占用共享內存,如果將SQL的字符串及格式寫得完全相同則ORACLE只會分析一次,共享內存也只會留下一次的分析結果,這不僅可以減少分析SQL的時間,而且可以減少共享內存重復的信息,ORACLE也可以準確統計SQL的執行頻率。
推薦方案:不同區域出現的相同的Sql語句,要保證查詢字符完全相同,以利用SGA共享池,防止相同的Sql語句被多次分析。
Oracle從下到上處理Where子句中多個查詢條件,所以表連接語句應寫在其他Where條件前,可以過濾掉最大數量記錄的條件必須寫在Where子句的末尾。
WHERE子句后面的條件順序對大數據量表的查詢會產生直接的影響,如
Select * from zl_yhjbqk where dy_dj = '1KV以下' and xh_bz=1
Select * from zl_yhjbqk where xh_bz=1 and dy_dj = '1KV以下'
以上兩個SQL中dy_dj(電壓等級)及xh_bz(銷戶標志)兩個字段都沒進行索引,所以執行的時候都是全表掃描,第一條SQL的dy_dj = '1KV以下'條件在記錄集內比率為99%,而xh_bz=1的比率只為0.5%,在進行第一條SQL的時候99%條記錄都進行dy_dj及xh_bz的比較,而在進行第二條SQL的時候0.5%條記錄都進行dy_dj及xh_bz的比較,以此可以得出第二條SQL的CPU占用率明顯比第一條低。
Oracle從右到左處理From子句中的表名,所以在From子句中包含多個表的情況下,將記錄最少的表放在最后。(只在采用RBO優化時有效)
在FROM后面的表中的列表順序會對SQL執行性能影響,在沒有索引及ORACLE沒有對表進行統計分析的情況下ORACLE會按表出現的順序進行鏈接,由此因為表的順序不對會產生十分耗服務器資源的數據交叉。(注:如果對表進行了統計分析, ORACLE會自動先進小表的鏈接,再進行大表的鏈接)。
可能引起全表掃描的操作
ORACLE在SQL執行分析方面已經比較成熟,如果分析執行的路徑不對首先應在數據庫結構(主要是索引)、服務器當前性能(共享內存、磁盤文件碎片)、數據庫對象(表、索引)統計信息是否正確這幾方面分析。
--備注:多表查詢時,記錄最少的表放到最后
以我做過的一個項目中的表為例,表結構如下:
CREATE TABLE FLFL ( ID NUMBER NOT NULL, MC NVARCHAR2(20), FLJB NUMBER, SJFLID NUMBER )
FLJB是作為樹的級別,在很多查詢中可以加快SQL的查詢效率。在下面演示的功能基本上不使用這個關鍵字。
SJFLID存儲的是上級ID,如果是頂級父節點,該SJFLID為null(得補充一句,當初的確是這樣設計的,不過現在知道,表中最好別有null記錄,這會引起全文掃描,建議改成0代替)。
我們從最基本的操作,逐步列出樹查詢中常見的操作,所以查詢出來的節點以家族中的輩份作比方。
1. 查找樹中的所有頂級父節點(輩份最長的人)。 假設這個樹是個目錄結構,那么第一個操作總是找出所有的頂級節點,再根據該節點找到其下屬節點。
SELECT * FROM flfl WHERE sjflid IS NULL;
這是個引子,沒用到樹型查詢。
2.查找一個節點的直屬子節點(所有兒子)。 如果查找的是直屬子類節點,也是不用用到樹型查詢的。
SELECT * FROM flfl WHERE sjflid = 819459;
這個可以找到ID為819459的直屬子類節點。
3.查找一個節點的所有 直屬子節點(所有后代)。
SELECT * FROM flfl START WITH ID = 819459 CONNECT BY sjflid = PRIOR ID;
這個查找的是ID為819459的節點下的所有直屬子類節點,包括子輩的和孫子輩的所有直屬節點。
4.查找一個節點的直屬父節點(父親)。 如果查找的是節點的直屬父節點,也是不用用到樹型查詢的。
SELECT b.* FROM flfl a JOIN flfl b ON a.sjflid = b.ID WHERE a.ID = 6758;
這個找到的是ID為6758的節點的直屬父節點,要用到同一張表的關聯了。
5.查找一個節點的所有直屬父節點(祖宗)。
SELECT * FROM flfl START WITH ID = 6758 CONNECT BY PRIOR sjflid = ID;
這里查找的就是ID為6758的所有直屬父節點,打個比方就是找到一個人的父親、祖父等。但是值得注意的是這個查詢出來的結果的順序是先列出子類節點再列出父類節點,姑且認為是個倒序吧。
上面列出兩個樹型查詢方式,第3條語句和第5條語句,這兩條語句之間的區別在于prior關鍵字的位置不同,所以決定了查詢的方式不同。 當sjflid = PRIOR ID時,數據庫會根據當前的ID迭代出sjflid與該ID相同的記錄,所以查詢的結果是迭代出了所有的子類記錄;而PRIOR ID = sjflid時,數據庫會跟據當前的sjflid來迭代出與當前的sjflid相同的id的記錄,所以查詢出來的結果就是所有的父類結果。
以下是一系列針對樹結構的更深層次的查詢,這里的查詢不一定是最優的查詢方式,或許只是其中的一種實現而已。
6.查詢一個節點的兄弟節點(親兄弟)。
SELECT a.* FROM flfl a WHERE EXISTS (SELECT * FROM flfl b WHERE a.sjflid = b.sjflid AND b.ID = 6757);
這里查詢的就是與ID為6757的節點同屬一個父節點的節點了,就好比親兄弟了。
7.查詢與一個節點同級的節點(族兄弟)。 如果在表中設置了級別的字段,上表中的FLJB,那么在做這類查詢時會很輕松,同一級別的就是與那個節點同級的,在這里列出不使用該字段時的實現!
WITH tmp AS (SELECT a.*, LEVEL lev FROM flfl a START WITH a.sjflid IS NULL CONNECT BY a.sjflid = PRIOR a.ID) SELECT * FROM tmp WHERE lev = (SELECT lev FROM tmp WHERE ID = 819394)
這里使用兩個技巧,一個是使用了LEVEL來標識每個節點在表中的級別,還有就是使用with語法模擬出了一張帶有級別的臨時表。
8.查詢一個節點的父節點的的兄弟節點(伯父與叔父)。
WITH tmp AS (SELECT flfl.*, LEVEL lev FROM flfl START WITH sjflid IS NULL CONNECT BY sjflid = PRIOR ID) SELECT b.* FROM tmp b, (SELECT * FROM tmp WHERE ID = 7004 AND lev = 2) a WHERE b.lev = 1 UNION ALL SELECT * FROM tmp WHERE sjflid = (SELECT DISTINCT x.ID FROM tmp x, tmp y, (SELECT * FROM tmp WHERE ID = 7004 AND lev > 2) z WHERE y.ID = z.sjflid AND x.ID = y.sjflid);
這里查詢分成以下幾步。首先,將第7個一樣,將全表都使用臨時表加上級別;其次,根據級別來判斷有幾種類型,以上文中舉的例子來說,有三種情況:(1)當前節點為頂級節點,即查詢出來的lev值為1,那么它沒有上級節點,不予考慮。(2)當前節點為2級節點,查詢出來的lev值為2,那么就只要保證lev級別為1的就是其上級節點的兄弟節點。(3)其它情況就是3以及以上級別,那么就要選查詢出來其上級的上級節點(祖父),再來判斷祖父的下級節點都是屬于該節點的上級節點的兄弟節點。 最后,就是使用UNION將查詢出來的結果進行結合起來,形成結果集。
9.查詢一個節點的父節點的同級節點(族叔)。
這個其實跟第7種情況是相同的。
WITH tmp AS (SELECT a.*, LEVEL lev FROM flfl a START WITH a.sjflid IS NULL CONNECT BY a.sjflid = PRIOR a.ID) SELECT * FROM tmp WHERE lev = (SELECT lev FROM tmp WHERE ID = 819394) - 1
只需要做個級別判斷就成了。
基本上,常見的查詢在里面了,不常見的也有部分了。其中,查詢的內容都是節點的基本信息,都是數據表中的基本字段,但是在樹查詢中還有些特殊需求,是對查詢數據進行了處理的,常見的包括列出樹路徑等。
補充一個概念,對于數據庫來說,根節點并不一定是在數據庫中設計的頂級節點,對于數據庫來說,根節點就是start with開始的地方。
下面列出的是一些與樹相關的特殊需求。
10.名稱要列出名稱全部路徑。
這里常見的有兩種情況,一種是是從頂級列出,直到當前節點的名稱(或者其它屬性);一種是從當前節點列出,直到頂級節點的名稱(或其它屬性)。舉地址為例:國內的習慣是從省開始、到市、到縣、到居委會的,而國外的習慣正好相反(老師說的,還沒接過國外的郵件,誰能寄個瞅瞅 )。
從頂部開始:
SELECT SYS_CONNECT_BY_PATH (mc, '/') FROM flfl WHERE ID = 6498 START WITH sjflid IS NULL CONNECT BY sjflid = PRIOR ID;
從當前節點開始:
SELECT SYS_CONNECT_BY_PATH (mc, '/') FROM flfl START WITH ID = 6498 CONNECT BY PRIOR sjflid = ID;
在這里我又不得不放個牢騷了。oracle只提供了一個sys_connect_by_path函數,卻忘了字符串的連接的順序。在上面的例子中,第一個SQL是從根節點開始遍歷,而第二個SQL是直接找到當前節點,從效率上來說已經是千差萬別,更關鍵的是第一個SQL只能選擇一個節點,而第二個SQL卻是遍歷出了一顆樹來。再次PS一下。
sys_connect_by_path函數就是從start with開始的地方開始遍歷,并記下其遍歷到的節點,start with開始的地方被視為根節點,將遍歷到的路徑根據函數中的分隔符,組成一個新的字符串,這個功能還是很強大的。
11.列出當前節點的根節點。
在前面說過,根節點就是start with開始的地方。
SELECT CONNECT_BY_ROOT mc, flfl.* FROM flfl START WITH ID = 6498 CONNECT BY PRIOR sjflid = ID;
connect_by_root函數用來列的前面,記錄的是當前節點的根節點的內容。
12.列出當前節點是否為葉子。
這個比較常見,尤其在動態目錄中,在查出的內容是否還有下級節點時,這個函數是很適用的。
SELECT CONNECT_BY_ISLEAF, flfl.* FROM flfl START WITH sjflid IS NULL CONNECT BY sjflid = PRIOR ID;
connect_by_isleaf函數用來判斷當前節點是否包含下級節點,如果包含的話,說明不是葉子節點,這里返回0;反之,如果不包含下級節點,這里返回1。
至此,oracle樹型查詢基本上講完了,以上的例子中的數據是使用到做過的項目中的數據,因為里面的內容可能不好理解,所以就全部用一些新的例子來進行闡述。以上所有SQL都在本機上測試通過,也都能實現相應的功能,但是并不能保證是解決這類問題的最優方案(如第8條明顯寫成存儲過程會更好),如果誰有更好的解決方案、或者有關oracle樹查詢的任何問題,歡迎留言討論,以上的SQL有什么問題也歡迎大家留言批評。
聯接條件可在FROM或WHERE子句中指定,建議在FROM子句中指定聯接條件。WHERE和HAVING子句也可以包含搜索條件,以進一步篩選聯接條件所選的行。
聯接可分為以下幾類:
1、內聯接(典型的聯接運算,使用像 = 或 <> 之類的比較運算符)。包括相等聯接和自然聯接。
內聯接使用比較運算符根據每個表共有的列的值匹配兩個表中的行。例如,檢索 students和courses表中學生標識號相同的所有行。
2、外聯接。外聯接可以是左向外聯接、右向外聯接或完整外部聯接。
在 FROM子句中指定外聯接時,可以由下列幾組關鍵字中的一組指定:
1)LEFT JOIN或LEFT OUTER JOIN
左向外聯接的結果集包括 LEFT OUTER子句中指定的左表的所有行,而不僅僅是聯接列所匹配的行。如果左表的某行在右表中沒有匹配行,則在相關聯的結果集行中右表的所有選擇列表列均為空值。
2)RIGHT JOIN 或 RIGHT OUTER JOIN
右向外聯接是左向外聯接的反向聯接。將返回右表的所有行。如果右表的某行在左表中沒有匹配行,則將為左表返回空值。
3)FULL JOIN 或 FULL OUTER JOIN
完整外部聯接返回左表和右表中的所有行。當某行在另一個表中沒有匹配行時,則另一個表的選擇列表列包含空值。如果表之間有匹配行,則整個結果集行包含基表的數據值。
3、交叉聯接
交叉聯接返回左表中的所有行,左表中的每一行與右表中的所有行組合。交叉聯接也稱作笛卡爾積。
FROM 子句中的表或視圖可通過內聯接或完整外部聯接按任意順序指定;但是,用左或右向外聯接指定表或視圖時,表或視圖的順序很重要。有關使用左或右向外聯接排列表的更多信息,請參見使用外聯接。
例子:
-------------------------------------------------
a表 id name b表 id job parent_id
1 張3 1 23 1
2 李四 2 34 2
3 王武 3 34 4
a.id同parent_id 存在關系
--------------------------------------------------
1) 內連接
select a.*,b.* from a inner join b on a.id=b.parent_id
結果是
1 張3 1 23 1
2 李四 2 34 2
2)左連接
select a.*,b.* from a left join b on a.id=b.parent_id
結果是
1 張3 1 23 1
2 李四 2 34 2
3 王武 null
3) 右連接
select a.*,b.* from a right join b on a.id=b.parent_id
結果是
1 張3 1 23 1
2 李四 2 34 2
null 3 34 4
4) 完全連接
select a.*,b.* from a full join b on a.id=b.parent_id
結果是
1 張3 1 23 1
2 李四 2 34 2
null 3 34 4
3 王武 null
--函數:
create or replace FUNCTION za_concat(P1 VARCHAR2)
RETURN VARCHAR2 AGGREGATE USING za_concat_im ;
/
5、insert into 會將查詢結果保存到已經存在的表中
insert into t2(column1, column2, ....) select column1, column2, .... from t1
每天遇到問題記錄
1.#變量名# 會轉化為 jdbc 的 ?, 比如 select * from user where name=#name# 會轉化為jdbc的 select * from user where name=?,把?參數設置為name的值,而$變量名$就直接把 $name$替換為 name的內容, 也就是由可能 select * from user where name=$name$ 如果name為 "' ' or 1 = 1", 那么這樣就有可能導致sql注入,所以ibatis用#比$好,不會造成sql注入。
2.ibatis中的參數傳入的值參數比較多,最好用bean方式傳入,也就是通過set/get取值的方式給sql map注入參數,不要用hashmap結構傳入,每次用hashmap傳入會占用比較多的內容。如果參數少,用hashmap也比較方便簡單。但是對傳入參數的判斷,用bean方式比較容易檢測發現,配置也能夠統一配置。
(一)、配置和移植工具
1.Oracle Administration Assistant for window
作用:管理ORACEL本地賬戶
2.Database Configuration Assistant
作用:數據庫創建、配置、刪除、集群配置等
每個數據庫都會有三個服務:OracleService[XXX]、OracleDBConsole[XXX]、OracleJobScheduler[XXX]
3.Database Upgrade Assistant
作用:將oracle8、9、10等版本數據庫升級到10g2版本。
4.Locale Builder
作用:管理地域、語言、字符集等本地化內容。
5.Microsoft ODBC 管理員
作用:不用解釋了吧,創建連接字符串的。
6.Net Configuration Assistant
作用:網絡服務,包括監聽程序配置、命名方法配置、本地NET服務名配置和目錄使用配置。
默認監聽:Oracle[XXX]10g_home1TNSListener
添加1522端口監聽:Oracle[XXX]10g_home1TNSListener[新監聽名稱]
7.Net Manager
作用:管理服務命名、監聽程序等。
(二)、應用程序開發
1.SQL Plus
作用:這個我就不多說了。
(三)、集成管理工具
1.Oracle Directory Manager
作用:ORACLE目錄管理
2.Wallet Manager
作用:ORACLE錢包管理,我也沒用過。
(四)、Oracle Installation Products
1.Universal Installer
1.分頁方案一:(利用Not In和SELECT TOP分頁)
語句形式:
SELECT TOP 頁記錄數量 *
FROM 表名
WHERE (ID NOT IN
(SELECT TOP (每頁行數*(頁數-1)) ID
FROM 表名
ORDER BY ID))
ORDER BY ID
//自己還可以加上一些查詢條件
例:
select top 2 *
from Sys_Material_Type
where (MT_ID not in
(select top (2*(3-1)) MT_ID from Sys_Material_Type order by MT_ID))
order by MT_ID
2.分頁方案二:(利用ID大于多少和SELECT TOP分頁)
語句形式:
SELECT TOP 每頁記錄數量 *
FROM 表名
WHERE (ID >
(SELECT MAX(id)
FROM (SELECT TOP 每頁行數*頁數 id FROM 表
ORDER BY id) AS T)
)
ORDER BY ID
例:
SELECT TOP 2 *
FROM Sys_Material_Type
WHERE (MT_ID >
(SELECT MAX(MT_ID)
FROM (SELECT TOP (2*(3-1)) MT_ID
FROM Sys_Material_Type
ORDER BY MT_ID) AS T))
ORDER BY MT_ID
3.分頁方案三:(利用SQL的游標存儲過程分頁)
create procedure SqlPager
@sqlstr nvarchar(4000), --查詢字符串
@currentpage int, --第N頁
@pagesize int --每頁行數
as
set nocount on
declare @P1 int, --P1是游標的id
@rowcount int
exec sp_cursoropen @P1 output,@sqlstr,@scrollopt=1,@ccopt=1, @rowcount=@rowcount output
select ceiling(1.0*@rowcount/@pagesize) as 總頁數--,@rowcount as 總行數,@currentpage as 當前頁
set @currentpage=(@currentpage-1)*@pagesize+1
exec sp_cursorfetch @P1,16,@currentpage,@pagesize
exec sp_cursorclose @P1
set nocount off
4.總結:
其它的方案:如果沒有主鍵,可以用臨時表,也可以用方案三做,但是效率會低。
建議優化的時候,加上主鍵和索引,查詢效率會提高。
通過SQL 查詢分析器,顯示比較:我的結論是:
分頁方案二:(利用ID大于多少和SELECT TOP分頁)效率最高,需要拼接SQL語句
分頁方案一:(利用Not In和SELECT TOP分頁) 效率次之,需要拼接SQL語句
分頁方案三:(利用SQL的游標存儲過程分頁) 效率最差,但是最為通用
本文轉摘自『IT學習者』http://www.itlearner.com/article/2007/3740.shtml
<1>.Microsoft SQL server 2005 Express Edition
下載地址:http://download.microsoft.com/download/0/9/0/09020fab-d2c3-4a8c-b9e0-db53a7a30ae8/SQLEXPR_CHS.EXE
<2>.SQL Server Management Studio
<3>.SQL Server 2005 driver for JDBC
2.都下載完之后開始進行安裝 ,前兩個是屬于數據庫軟件,正常安裝即可(注意數據庫登陸不要使用windows驗證)
<1> 將JDBC解壓縮到任意位置,比如解壓到C盤program files下面,并在安裝目錄里找到sqljdbc.jar文件,得到其路徑開始配置環境變量
在環境變量classpath 后面追加 C:\Program Files\Microsoft SQL Server2005 JDBC Driver\sqljdbc_1.2\enu\sqljdbc.jar
<2> 設置SQLEXPRESS服務器:
a.打開SQL Server Configuration Manager -> SQLEXPRESS的協議 -> TCP/IP
b.右鍵單擊啟動TCP/IP
c.雙擊進入屬性,把IP地址中的IP all中的TCP端口設置為1433
d.重新啟動SQL Server 2005服務中的SQLEXPRESS服務器
e.關閉SQL Server Configuration Manager
<3> 打開剛剛安裝好的 SQL Server Management Studio,連接SQLEXPRESS服務器, 新建數據庫,起名字為sample
<4> 打開Eclipse
a.新建工程-> java -> java project,起名為Test
b.選擇eclipse->窗口->首選項->java->installed JRE 編輯已經安裝好的jdk,查找目錄添加sqljdbc.jar
c.右鍵單擊目錄窗口中的Test, 選擇Build Path ->Configure Build Path..., 添加擴展jar文件,即把sqljdbc.jar添加到其中
<5> 編寫java代碼來測試連接數據庫
程序代碼:
public class Test {
public static void main(String[] srg) {
String driverName = "com.microsoft.sqlserver.jdbc.SQLServerDriver"; //加載JDBC驅動
String dbURL = "jdbc:sqlserver://localhost:1433; DatabaseName=sample"; //連接服務器和數據庫sample
String userName = "sa"; //默認用戶名
String userPwd = "123456"; //密碼
Connection dbConn;
try {
Class.forName(driverName);
dbConn = DriverManager.getConnection(dbURL, userName, userPwd);
System.out.println("Connection Successful!"); //如果連接成功 控制臺輸出Connection Successful!
} catch (Exception e) {
e.printStackTrace();
}
}
}
注:
1.因為SQLexpress服務器默認是禁用的并且端口號沒有配置,所以要進行重新設置
2.如果你以前用java連接sql server 2000的話就要注意了:
在sql server 2000 中加載驅動和URL路徑的語句是
String driverName = "com.microsoft.jdbc.sqlserver.SQLServerDriver";
String dbURL = "jdbc:microsoft:sqlserver://localhost:1433; DatabaseName=sample";
而sql server 2005 中加載驅動和url的語句則為
String driverName = "com.microsoft.sqlserver.jdbc.SQLServerDriver";
String dbURL = "jdbc:sqlserver://localhost:1433; DatabaseName=sample";
如果寫法錯誤將會找不到驅動.
好了,應該沒什么疑問了吧...偶是有連接失敗經歷才分享給大家的...
ORACLE 中ROWNUM用法總結!
對于 Oracle 的 rownum 問題,很多資料都說不支持>,>=,=,between...and,只能用以上符號(<、<=、!=),并非說用>,>=,=,between..and 時會提示SQL語法錯誤,而是經常是查不出一條記錄來,還會出現似乎是莫名其妙的結果來,其實您只要理解好了這個 rownum 偽列的意義就不應該感到驚奇,同樣是偽列,rownum 與 rowid 可有些不一樣,下面以例子說明
假設某個表 t1(c1) 有 20 條記錄
如果用 select rownum,c1 from t1 where rownum < 10, 只要是用小于號,查出來的結果很容易地與一般理解在概念上能達成一致,應該不會有任何疑問的。
可如果用 select rownum,c1 from t1 where rownum > 10 (如果寫下這樣的查詢語句,這時候在您的頭腦中應該是想得到表中后面10條記錄),你就會發現,顯示出來的結果要讓您失望了,也許您還會懷疑是不誰刪了一些記錄,然后查看記錄數,仍然是 20 條啊?那問題是出在哪呢?
先好好理解 rownum 的意義吧。ROWNUM是一個虛假的列,它將被分配為 1,2,3,4,...N,N 是行的數量。即查到結果集之后,任何排序和聚合之前,加上去的一個列 (強調:先要有結果集)。簡單的說 rownum 是對符合條件結果的序列號。它總是從1開始排起的。所以你選出的結果不可能沒有1,而有其他大于1的值。所以您沒辦法期望得到下面的結果集:
11 aaaaaaaa
12 bbbbbbb
13 ccccccc
.................
ROWNUM是一個序列,是oracle數據庫從數據文件或緩沖區中讀取數據的順序。它取得第一條記錄則rownum值為1,第二條為2,依次類推。如果你用>,>=,=,between...and這些條件,因為從緩沖區或數據文件中得到的第一條記錄的rownum為1,不滿足條件,則被刪除,接著取下條,可是它的rownum還是1,又被刪除,依次類推,便沒有了數據。
有了以上從不同方面建立起來的對 rownum 的概念,那我們可以來認識使用 rownum 的幾種現像:
1. select rownum,c1 from t1 where rownum != 10 為何是返回前9條數據呢?它與 select rownum,c1 from tablename where rownum < 10 返回的結果集是一樣的呢?
因為是在查詢到結果集后,顯示完第 9 條記錄后,之后的記錄也都是 != 10,或者 >=10,所以只顯示前面9條記錄。也可以這樣理解,rownum 為9后的記錄的 rownum為10,因條件為 !=10,所以去掉,其后記錄補上,rownum又是10,也去掉,如果下去也就只會顯示前面9條記錄了
2. 為什么 rownum >1 時查不到一條記錄,而 rownum >0 或 rownum >=1 卻總顯示所以的記錄?
因為 rownum 是在查詢到的結果集后加上去的,它總是從1開始,如果rownum>1的話,查詢第一數據的rownum=1,不滿足條件,刪除,第二條的rownum 又等于1,還是不滿足,刪除,所以到最后一天數據也沒有。
3. 為什么 between 1 and 10 或者 between 0 and 10 能查到結果,而用 between 2 and 10 卻得不到結果
原因同上一樣,因為 rownum 總是從 1 開始
從上可以看出,任何時候想把 rownum = 1 這條記錄拋棄是不對的,它在結果集中是不可或缺的,少了rownum=1 就像空中樓閣一般不能存在,所以你的 rownum 條件要包含到 1
但如果就是想要用 rownum > 10 這種條件的話話就要用嵌套語句,把 rownum 先生成,然后對他進行查詢。
select * from (selet rownum as rn,t1.* from a where ...) where rn >10
一般代碼中對結果集進行分頁就是這么干的。
另外:rowid 與 rownum 雖都被稱為偽列,但它們的存在方式是不一樣的,rowid 可以說是物理存在的,表示記錄在表空間中的唯一位置ID,在DB中唯一。只要記錄沒被搬動過,rowid是不變的。rowid 相對于表來說又像表中的一般列,所以以 rowid 為條件就不會有 rownum那些情況發生。
另外還要注意:rownum不能以任何基表的名稱作為前綴。