標籤:

關於 MySQL 你可能不知道的 SQL 使用技巧

近來處理了比較多的資料庫維護工作,對 SQL 的語法也算有了更深層次的認識,也學到了很多以前沒有用過的 SQL 語法技巧,這裡統一整理一下,希望對讀者也有所啟發。

本文將主要介紹一些我認為有用的 SQL 語法和技巧,並通過適當案例說明,但案例本身做了簡化處理,只希望通過案例讓讀者更好的理解。

使用 UNION | UNION ALL 語法

UNION 用於合併多個查詢的結果集,我目前遇到的主要有如下兩個場景用起來比較有效:

1. 同表的複雜查詢,很難通過一個 SELECT 語句搞定

2. 多表查詢,但返回的數據一致,常見一些聚合數據統計需求

UNION 也可以加 limit、order by 子句,用於對 UNION 後的結果集進行排序和過濾。

通過 UNION 的這些特性,我們可以把原本需要編寫代碼才能處理的一些工作交給資料庫,同時還減少 SQL 數,提高性能。

比如說,下面這個例子:

select product as type, count(*) as count from `products`

union

select comment as type, count(*) as count from `comments`

order by count;

我們通過 UNION 語法同時查詢了 products 表 和 comments 表的總記錄數,並且按照 count 排序。

結合 UPDATE | DELETE 與 JOIN

一直以來,我們得益於聯合查詢(SELECT + JOIN)給我們帶來的便利,但無形之中確形成了思維定勢(至少對於我而言是這樣的),殊不知 UPDATE DELETE 也能與 JOIN 聯合使用,從而簡化 SQL 編寫。

在使用 UPDATE | DELETE + JOIN 之前,我們可能做法要麼是先查詢出待刪除記錄的 ID 然後再根據 ID 進行刪除,要麼是使用 IN 子查詢。前者需要寫兩個 SQL 語句,在程序中處理邏輯,後者有時並不能正常工作。

就後者而言,應該有人遇到過這樣的錯誤:

ERROR 1093 (HY000): You cant specify target table xxx for update in FROM clause

這樣的錯誤產生的原因是:MySQL 不支持同一個 SQL 語句嘗試對同一個表進行查詢和修改兩個操作。

比如,刪除沒有評論的文章這條語句

delete from articles

where id in (

select a.id from articles as a left join comments as c on a.id=c.article_id

where c.is is NULL

)

articles 表既被查詢,也被更新,將出現上面的錯誤。

但是,如果 DELETE 結合 JOIN,則可以直接寫出這樣的 SQL 語句,簡潔許多:

delete s from articles as a

left join comments as c on a.id=c.article_id

where c.is is NULL

當然,UPDATE 也是同理:

update articles as a

left join comments as c on a.id=c.article_id

set a.deleted=1

where c.is is NULL

CASE 語法

CASE 語法可以在 SQL 內做簡單的分支判斷,根據不同的條件返回不同的值。比如考慮這樣的需求:

一個商品有多個訂單,訂單有已付款和未付款兩個狀態,現在給定一個商品列表,返回每個商品已付款和未付款訂單的數量。

這個時候我們可以通過 CASE 語句和 GROUP BY 通過一條 SQL 實現:

select

product_id,

count(

case is_paid

when 1 then 1

else null

end

) as total_paid,

count(

case is_paid

when 0 then 1

else null

end

) as total_not_paid

from orders

where product_id in (1, 2, 3, 4)

group by product_id;

配合 ORM 庫,這樣的寫法可以幫助我們實現 eager loading,避免 n + 1 查詢。

因為這個場景比較簡單,我們也可以使用 MySQL 提供的流程式控制制函數(Control Flow Functions) 使得該 SQL 更簡潔:

select

product_id,

count(if(is_paid = 1, 1, null)) as total_paid,

count(if(is_paid = 0, 1, null)) as total_not_paid

from orders

where product_id in (1, 2, 3, 4)

group by product_id;

使用 INSERT INTO ... SELECT 語法

通過 INSERT INTO ... SELECT 語法,我們可以把 SELECT 的結果集直接寫入另一張表中,而不需要程序處理。通過這個語法,外加一些變通,我們可以很方便的實現更多的需求場景。

比如說,我們要給所有購買了某一商品的用戶發放一張元價值10元的優惠券,我們可以這樣寫:

insert into tickets (user_id, price, expires_in)

select

user_id, 10 as price, 2017-09-09 as expires_in

from orders

where product_id=123 and is_paid=1;

又比如說,在選課的場景中,我們要給一批人分配一批課,假設要給1班的人分配體育課和美術課,我們可以通過該語法加 CROSS JOIN 實現:

insert into class_members (class_id, user_id, status)

select

c.id as class_id,

u.id as user_id,

1 as status

from classes as c cross join users as u

where c.name in (體育課, 美術課) and u.class_name=1班;

本文就這些了,你還知道有哪些技巧呢?

--------------------------------

本文首發於「代碼寫詩」微信公眾號: https://mp.weixin.qq.com/s?__biz=MzI4MjAwMTk0OA==&mid=2247483676&idx=1&sn=bc5c47f541f884e5686d2923430e5078

推薦閱讀:

MySQL多表關聯查詢效率高點還是多次單表查詢效率高,為什麼?
MySQL 對於千萬級的大表要怎麼優化?
pandas數據保存至Mysql資料庫
MySQL中的 MyISAM 讀的效率高,InnoDB 寫的效率高,原理是什麼?(只針對這兩種存儲引擎的對比)

TAG:MySQL | LAMP | PHP |