想當初我自己想出來用where 1=1的時候還高興了一小會,畢竟把代碼簡化了許多。今天看到的書里面說會影響性能。摘要如下:
低效的“WHERE 1=1”
網上有不少人提出過類似的問題:“看到有人寫了WHERE 1=1這樣的SQL,到底是什么意 思?”。
其實使用這種用法的開發人員一般都是在使用動態組裝的SQL。
讓我們想像如下的場景:用戶要求提供一個靈活的查詢界面來根據各種復雜的條件來查詢 員工信息,界面如下圖:
界面中列出了四個查詢條件,包括按工號查詢、按姓名查詢、按年齡查詢以及按工資查詢,
每個查詢條件前都有一個復選框,如果復選框被選中,則表示將其做為一個過濾條件
。比如上圖 就表示“檢索工號介於DEV001和DEV008之間、姓名中含有J並且工資介於3000元到6000元的 員工信息”。
如果不選中姓名前的復選框,比如下圖表示“檢索工號介於DEV001和DEV008之 間並且工資介於3000元到6000元的員工信息”:
如果將所有的復選框都不選中,則表示表示“檢索所有員工信息”,比如下圖:
這里的數據檢索與前面的數據檢索都不一樣,因為前邊例子中的數據檢索的過濾條件都是 確定的,而這里的過濾條件則隨着用戶設置的不同而有變化,這時就要根據用戶的設置來動態組 裝SQL了。當不選中年齡前的復選框的時候要使用下面的SQL語句: SELECT * FROM T_Employee WHERE FNumber BETWEEN 'DEV001' AND 'DEV008' AND FName LIKE '%J%' AND FSalary BETWEEN 3000 AND 6000 而如果不選中姓名和年齡前的復選框的時候就要使用下面的SQL語句: SELECT * FROM T_Employee WHERE FNumber BETWEEN 'DEV001' AND 'DEV008' AND FSalary BETWEEN 3000 AND 6000 而如果將所有的復選框都不選中的時候就要使用下面的SQL語句: SELECT * FROM T_Employee
要實現這種動態的SQL語句拼裝,我們可以在宿主語言中建立一個字符串,然后逐個判斷各 個復選框是否選中來向這個字符串中添加SQL語句片段。這 里 有 一 個問題就是當有復選框被選中 的時候SQL語句是含有WHERE子句的,而當所有的復選框都沒有被選中的時候就沒有WHERE子句 了,因此在添加每一個過濾條件判斷的時候都要判斷是否已經存在WHERE語句了,如果沒有 WHERE語句則添加WHERE語句。在判斷每一個復選框的時候都要去判斷,這使得用起來非常麻煩, “聰明的程序員是會偷懶的程序員”,因此開發人員想到了一個捷徑:為SQL語句指定一個永遠 為真的條件語句(比如“1=1”),這樣就不用考慮WHERE語句是否存在的問題了。
偽代碼如下
1 String sql = " SELECT * FROM T_Employee WHERE 1=1"; 2 3 if(工號復選框選中) { 4 5 sql.appendLine("AND FNumber BETWEEN '"+工號文本框1內容+"' AND '"+工號 文本框2內容+"'"); 6 7 } 8 if(姓名復選框選中) { 9 sql.appendLine("AND FName LIKE '%"+姓名文本框內容+"%'"); 10 } 11 12 if(年齡復選框選中) { 13 sql.appendLine("AND FAge BETWEEN "+年齡文本框1內容+" AND "+年齡文本框2 內容); 14 } 15 executeSQL(sql);
這樣如果不選中姓名和年齡前的復選框的時候就會執行下面的SQL語句: SELECT * FROM T_Employee WHERE 1=1 AND FNumber BETWEEN 'DEV001' AND 'DEV008' AND FSalary BETWEEN 3000 AND 6000 而如果將所有的復選框都不選中的時候就會執行下面的SQL語句: SELECT * FROM T_Employee WHERE 1=1 這看似非常優美的解決了問題,殊不知這樣很可能會造成非常大的性能損失,因為使用添 加了“1=1”的過濾條件以后數據庫系統就無法使用索引等查詢優化策略,數據庫系統將會被迫 對每行數據進行掃描(也就是全表掃描)以比較此行是否滿足過濾條件,當表中數據量比較大的 時候查詢速度會非常慢。因此如果數據檢索對性能有比較高的要求就不要使用這種“簡便”的方 式。下面給出一種參考實現,偽代碼如下:
private void doQuery() { Bool hasWhere = false; StringBuilder sql = new StringBuilder(" SELECT * FROM T_Employee"); if(工號復選框選中) { hasWhere = appendWhereIfNeed(sql, hasWhere); sql.appendLine("FNumber BETWEEN '"+工號文本框1內容+"' AND '"+工號 文本框2內容+"'"); } if(姓名復選框選中) { hasWhere = appendWhereIfNeed(sql, hasWhere); sql.appendLine("FName LIKE '%"+姓名文本框內容+"%'"); } if(年齡復選框選中) { hasWhere = appendWhereIfNeed(sql, hasWhere); sql.appendLine("FAge BETWEEN "+年齡文本框1內容+" AND "+年齡文本框2 內容); } executeSQL(sql); } private Bool appendWhereIfNeed(StringBuilder sql,Bool hasWhere) { if(hasWhere==false) { sql. appendLine("WHERE"); } else { sql. appendLine("AND"); } }
這里演示的將檢索參數值直接拼接到 SQL中的做法是有一定的問題的,會造成性能問題以及注入漏洞攻 擊。為了降低問題的復雜度,這里規避了這個問題,在本書的后續章節將會詳細講解。
HAVING 語句 有的時候需要對部分分組進行過濾,比如只檢索人數多余1個的年齡段,
有的開發人員會使 用下面的SQL語句: SELECT FAge,COUNT(*) AS CountOfThisAge FROM T_Employee GROUP BY FAge WHERE COUNT(*)>1 可以在數據庫系統中執行下面的SQL的時候,數據庫系統會提示語法錯誤,
這是因為聚合函數不能在WHERE語句中使用,必須使用HAVING子句來代替,
比如: SELECT FAge,COUNT(*) AS CountOfThisAge FROM T_Employee GROUP BY FAge HAVING COUNT(*)>1