程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 數據庫知識 >> SqlServer數據庫 >> 關於SqlServer >> SQL如何構建多條件組合查詢,而且不降低效率

SQL如何構建多條件組合查詢,而且不降低效率

編輯:關於SqlServer

我們知道,在一般的信息系統中,特別是主要信息表,如客戶基本信息,工單受理主界面,用戶會用到多條件組合查詢。

我看過一些系統,有的人是采用將一個表的欄位給用戶選擇,然後用戶可以針對某一欄位來下條件,但是這違背多條件組合查詢的需求初衷。多條件組合的需求是:用戶在不同的視覺情況下,可以使用某個條件或者忽略某個條件,而且前面講到的基於單表的按字段查詢也不適用於復雜的數據結構,比如設計的系統結合的表很多,則將用戶限制在單表的多字段是不行的。

我的做法是,作一個通用查詢條件窗口出來作為模板,其它界面使用時通過另存或者繼承,再修改為符合某個特定界面的條件查詢窗。而某些條件如果某窗口用不到,可以讓它enabled = false來解決。

我的查詢條件公用窗是類似這樣的。來自我的[鐵通客服自受理系統]:PB11

構建這個公用窗口的目的在於一勞永逸。但是要注意系統條件,常用的需要全部考慮在上面。

小提示:1. 對於不用的條件,enabled=false這樣可以讓用戶不能填寫,不發生雜亂難用的感覺。
                2. 對於常用的下拉列表框內容條目,比如上面的工單類型和業務類型,顯然不要用"游標"從數據庫裡去fetch然後添加,因為這樣會造成一些掛起,嚴重時鎖表。(我最近看了一套自來水水管理系統,破解後看了看程序的event裡很多程序代碼,而且內嵌SQL很多。對於dropdownlist這樣的填充用的是游標fetch。我分析就是個新手寫的程序。客戶反映說經常鎖表。)其實這個例子應該是用一些辦法在程序啟動時,將一些FK表檢索到本地的全局緩沖裡,用的時候直接用,不需要每次都檢索。對數據庫應用來說,我遵循一個原則:盡量少地讀數據庫,充分考慮簡單高效,並杜絕重復動作。如果你使用pb那就可以將一些表整個檢索到datastore裡,然後對填充dropdownlist這樣的重復勞動,必然要寫好全局函數來處理,不需要傻傻地作重復編碼工作,或者將下拉框封裝成自定義控件,傳入dastore,用內部函數完成填充。

 注: 如果多選鈕勾上,表明這個條件參加SQL查詢,否則不參加,那怎麼作到呢?技巧就是在SQL的條件裡構建用and連接的多組條件,通過邏輯表達式的一個控制參數,如果某個多選框沒選擇,則直接忽略它。

我的SQL語句是這樣的:

 SELECT serv_100001.docno,  
         serv_100001.docdate,  
         serv_100001.createuser,  
         serv_100001.status   
  FROM serv_100001
    WHERE (:control0 = 0 or
   (:control0 = 1 and  not serv_100001.status in(110,120,

130))) and
   (:control1=0 or
   (:control1=1 and serv_100001.docdate >= :date_begin and serv_100001.docdate <=:date_end) or
   (:control1=2 and serv_100001.docno in(select docno100001 from serv_101001 where docdate >= :date_begin and docdate <=:date_end and status=20)) or
   (:control1=3 and serv_100001.docno in(select docno100001 from serv_102001 where recdate >= :date_begin and recdate <=:date_end and status=20)) or
   (:control1=4 and serv_100001.docno in(select docno100001 from serv_103001 where recdate >= :date_begin and recdate <=:date_end and status=20))) and
                (:control3 = 0 or                --單據類型
   (:control3=1 and doctype = :doctype)) and
   (:control4 = 0 or                --業務種類
   (:control4 = 1 and (servtype = :servtype or servtype2 = :servtype or servtype3 = :servtype or servtype4 = :servtype))) and
   (0 in (:control5) or
   (1 in (:control5) and serv_100001.status in(20)) or      --新錄入
   (2 in (:control5) and serv_100001.status in(40,60)) or     --派單
   (3 in (:control5) and serv_100001.status in(80)) or      --簽收
   (4 in (:control5) and serv_100001.status in(90,100,110)) or   --繼續跟進
   (5 in (:control5) and serv_100001.status in(70)) or      --處理OK
   (6 in (:control5) and serv_100001.status in(120,130,140))) and  --回復客戶結案
   (:control6 = 0 or
                (:control6 = 1 and serv_100001.docno between :docno_begin and :docno_end)) and
   (:control7 = 0  or
   (:control7 = 1 and  serv_100001.callnbr = :callnbr))  and
   (:control8 = 0 or
   (:control8 = 1 and serv_100001.duty = :dutydept)) and
   (:control9 = 0 or
   (:control9 = 1 and serv_100001.failnbr = :failnbraccount) or
   (:control9 = 2 and serv_100001.adaccout = :failnbraccount)) and
   (:control10 = 0 or
   (:control10 = 1 and serv_100001.attn = :attn)) and
   (:control11 = 0 or
   (:control11=1 and (serv_100001.content like ''%''+ :content + ''%'' or serv_100001.remark like ''%''+ :content + ''%'')) or
   (:control11=2 and serv_100001.docno in(select docno100001 from serv_101001 where advice like ''%''+ :content + ''%'' or designate like ''%''+ :content + ''%'' or reamrk like ''%''+ :content + ''%'')) or
   (:control11=3 and serv_100001.docno in(select docno100001 from serv_102001 where content like ''%''+ :content + ''%'' or serv_100001.remark like ''%''+ :content + ''%'')) or
   (:control11=4 and serv_100001.docno in(select docno100001 from serv_103001 where content like ''%''+ :content + ''%'' or serv_100001.remark like ''%''+ :content + ''%''))) and
   (:control12 = 0 or
   (:control12=1 and serv_100001.userid = :userid) or
   (:control12=2 and serv_100001.docno in(select docno100001 from serv_101001 where userid = :userid)) or
   (:control12=3 and serv_100001.docno in(select docno100001 from serv_102001 where userid = :userid) ) or
   (:control12=4 and serv_100001.docno in(select docno100001 from serv_103001 where userid = :userid)))

這是我自己琢磨很久而想到的辦法。並且在兩套軟件中使用。效果還可以。

執行的最後SQL帶入條件參數後就是這樣的:

SELECT serv_100001.docno,  
         serv_100001.docdate,  
         serv_100001.createuser,  
         serv_100001.status   
FROM serv_100001
    WHERE (0 = 0 or
   (0 = 1 and  not serv_100001.status in(110,120,
130))) and
   (0=0 or
   (0=1 and serv_100001.docdate >= ''1900-01-01 00:00:00.000'' and serv_100001.docdate <=''1900-01-01 00:00:00.000'') or
   (0=2 and serv_100001.docno in(select docno100001 from serv_101001 where docdate >= ''1900-01-01 00:00:00.000'' and docdate <=''1900-01-01 00:00:00.000'' and status=20)) or
   (0=3 and serv_100001.docno in(select docno100001 from serv_102001 where recdate >= ''1900-01-01 00:00:00.000'' and recdate <=''1900-01-01 00:00:00.000'' and status=20)) or
   (0=4 and serv_100001.docno in(select docno100001 from serv_103001 where recdate >= ''1900-01-01 00:00:00.000'' and recdate <=''1900-01-01 00:00:00.000'' and status=20))) and
                (0 = 0 or                
   (0=1 and doctype = 0)) and
   (0 = 0 or                
   (0 = 1 and (servtype = 0 or servtype2 = 0 or servtype3 = 0 or servtype4 = 0))) and
   (0 in (1,1,1,1,1,1) or
   (1 in (1,1,1,1,1,1) and serv_100001.status in(20)) or      
   (2 in (1,1,1,1,1,1) and serv_100001.status in(40,60)) or     
   (3 in (1,1,1,1,1,1) and serv_100001.status in(80)) or      
   (4 in (1,1,1,1,1,1) and serv_100001.status in(90,100,110)) or   
   (5 in (1,1,1,1,1,1) and serv_100001.status in(70)) or      
   (6 in (1,1,1,1,1,1) and serv_100001.status in(120,130,140))) and  
   (0 = 0 or
                (0 = 1 and serv_100001.docno between '''' and '''')) and
   (0 = 0  or
   (0 = 1 and  serv_100001.callnbr = ''''))  and
 (0 = 0 or
   (0 = 1 and serv_100001.duty = 0)) and
   (0 = 0 or
   (0 = 1 and serv_100001.failnbr = '''') or
   (0 = 2 and serv_100001.adaccout = '''')) and
   (0 = 0 or
   (0 = 1 and serv_100001.attn = '''')) and
   (1 = 0 or
   (1=1 and (serv_100001.content like ''%''+ ''接通率'' + ''%'' or serv_100001.remark like ''%''+ ''接通率'' + ''%'')) or
   (1=2 and serv_100001.docno in(select docno100001 from serv_101001 where advice like ''%''+ ''接通率'' + ''%'' or designate like ''%''+ ''接通率'' + ''%'' or reamrk like ''%''+ ''接通率'' + ''%'')) or
   (1=3 and serv_100001.docno in(select docno100001 from serv_102001 where content like ''%''+ ''接通率'' + ''%'' or serv_100001.remark like ''%''+ ''接通率'' + ''%'')) or
   (1=4 and serv_100001.docno in(select docno100001 from serv_103001 where content like ''%''+ ''接通率'' + ''%'' or serv_100001.remark like ''%''+ ''接通率'' + ''%''))) and
   (0 = 0 or
   (0=1 and serv_100001.userid = '''') or
   (0=2 and serv_100001.docno in(select docno100001 from serv_101001 where userid = '''')) or
   (0=3 and serv_100001.docno in(select docno100001 from serv_102001 where userid = '''')) or
   (0=4 and serv_100001.docno in(select docno100001 from serv_103001 where userid = '''')))

我們很容易看明白。就是其中的controlx起到了很大作用,從而使得構建復雜的成組的條件容易實現,而且不影響到執行效率,因為邏輯關系式遵循短路原則。如果某組條件用戶沒勾選使用則control1=0就滿足,從而不會執行後面的語句,整個分組條件就滿足了,所以語句本身不影響速度。

*另外在網上有文章[使用Instr()與decode()進行多條件組合查詢]介紹Oracle下的函數,其實mssql用substring可以傳入一個字符串作為參數,從而解決上面我的代碼中的controlx太多的缺點。就是類似於control_string=“YYYYNNYY”,則substring(control_string,1,1)=''Y''代表某個條件進行了勾選。當然還有charindex()函數可以用來傳條件,比如多個條件傳入的參數是:control_string=“A_CD_FGH”則代表A,C,D,F,G,H組的條件被勾選。用charindex(''A'',control_string)>0則可以判斷A組條件被選擇了。

*當然,如果你在pb中的話,數據框檢索條件裡是可以用control[10]這樣的數組傳入的,則SQL的where條件可以這樣寫: (1 in (:control_array) and id = :id)。每組條件用不同的編號。比如control_arrar={1,0,0,0,5,9},id=5傳入後就是: (1 in (1,0,0,0,5,9) and id = 5),而且用這種方式傳遞比字符串傳入速度更快。

*以上所說方法有一個前提就是控制參數必須在前面。利用邏輯運算的短路原則。用反了可就問題大了。

*另外ASP的人說用sql_string="......." + "........."來拼湊代碼,也是一個方法。但傳遞到服務器端的代碼跟蹤出來,可讀性就非常差了。而且把檢索看作一個接口的話,這個接口是不固定的,造成代碼和數據邏輯的混雜不清。

如果大家有更好的辦法,歡迎賜教。


  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved