SQL判斷數(shù)據(jù)存不存在的正確做法(99%的人還在寫錯!)
還在用 COUNT(*) 判斷數(shù)據(jù)存不存在?學會這招,性能提升 10 倍!
今天咱們聊一個超實用的話題。
相信很多剛接觸數(shù)據(jù)庫的朋友,想要判斷某條數(shù)據(jù)是否存在時,第一反應就是會寫出類似下面的 SQL:
SELECT COUNT(*) FROM users WHERE email = 'test@example.com';
然后再在代碼里判斷,返回的數(shù)據(jù)結(jié)果是不是大于 0。
這樣寫雖然沒有什么錯誤,可以實現(xiàn)功能,但是,其實并不是最好的方式。
今天,就跟大家聊一聊,一個更優(yōu)雅、性能更好的方法!
先說說 COUNT(*) 哪里不好
假設你的用戶表中有 100 萬條數(shù)據(jù),你想看看郵箱 zhang@example.com 有沒有已經(jīng)被注冊過。
如果你使用 COUNT(*) 的話:
SELECT COUNT(*) FROM users WHERE email = 'zhang@example.com';
數(shù)據(jù)庫就會這樣工作:
- 找到第 1 條匹配的記錄:"找到了!"
- 繼續(xù)找第 2 條:"還有嗎?"
- 繼續(xù)找第 3 條:"再找找..."
- 一直找到最后:"總共找到了 1 條"
那么,現(xiàn)在問題就來了:我們只是想知道"有沒有",但數(shù)據(jù)庫卻要告訴我們"有多少"。然而,我們壓根兒就不關(guān)心具體的數(shù)量有多少,這純粹就是妥妥的浪費了數(shù)據(jù)庫資源,并且查詢的性能極差。
那么,我想知道數(shù)據(jù)庫中有沒有這條數(shù)據(jù)存在,又應該如何操作呢?
正確做法:使用 EXISTS
EXISTS 就是來解決這個痛點的!只要有數(shù)據(jù)符合查詢條件,那么就立即返回,不會進一步查找了。
exists 的基礎(chǔ)用法
-- ? 推薦寫法
SELECT EXISTS (
SELECT 1 FROM users WHERE email = 'test@example.com'
) AS user_exists;這個查詢會返回:
1(或true):表示存在0(或false):表示不存在
一般情況下,只會返回
1或者0能不能返回 boolean 值,取決于你使用的 orm 的封裝。
為什么寫SELECT 1
有的童鞋看到了上面的 SQL,就比較好奇了:為什么是 SELECT 1 而不是 SELECT * 呢?
其實在這個場景中,下面的這些寫法,效果都是一樣的,但 SELECT 1 最簡潔。
SELECT EXISTS (SELECT 1 FROM users WHERE email = 'test@example.com'); SELECT EXISTS (SELECT * FROM users WHERE email = 'test@example.com'); SELECT EXISTS (SELECT email FROM users WHERE email = 'test@example.com');
因為 EXISTS 只關(guān)心"有沒有結(jié)果",不關(guān)心"具體是什么結(jié)果"。所以寫 SELECT 1 也可以在代碼層面上看起來簡潔又高效。
實際應用
場景一:用戶注冊時檢查郵箱
-- 檢查郵箱是否已被注冊
SELECT EXISTS (
SELECT 1 FROM users
WHERE email = 'newuser@example.com'
) AS email_taken;
-- 返回 1 表示已被占用,0 表示可以使用場景二:查詢有訂單的用戶
-- 找出所有有過訂單的用戶
SELECT u.id, u.name, u.email
FROM users u
WHERE EXISTS (
SELECT 1 FROM orders o
WHERE o.user_id = u.id
);這個查詢的意思是:對于每個用戶,檢查訂單表里是否存在該用戶的訂單記錄。
場景三:查詢沒有訂單的用戶
-- 找出從來沒下過單的用戶
SELECT u.id, u.name, u.email
FROM users u
WHERE NOT EXISTS (
SELECT 1 FROM orders o
WHERE o.user_id = u.id
);NOT EXISTS 就是"不存在"的意思。
性能對比
我們用一個真實例子來看看性能差異:
-- 假設用戶表中有 50 萬條記錄
-- 使用 COUNT(*) 的方式
SELECT COUNT(*) FROM users WHERE city = '上海';
-- 執(zhí)行時間:150ms(需要統(tǒng)計所有上海的用戶)
-- 使用 EXISTS 方式
SELECT EXISTS (
SELECT 1 FROM users WHERE city = '上海'
) AS has_sh_users;
-- 執(zhí)行時間:3ms(找到第一個就直接停止了)性能直接提升了 50 倍! 不過具體的執(zhí)行時間,也取決于硬件設備的情況。
為什么這么快?因為 EXISTS 找到第一條符合條件的記錄就立刻返回 true,不會繼續(xù)往下找了。
在 Go 中怎么用?
假設我們用 Go + MySQL 開發(fā)一個用戶系統(tǒng):
基礎(chǔ)用法
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/go-sql-driver/mysql"
)
// 檢查郵箱是否已存在
func CheckEmailExists(db *sql.DB, email string) (bool, error) {
var exists bool
query := `
SELECT EXISTS (
SELECT 1 FROM users
WHERE email = ?
)`
err := db.QueryRow(query, email).Scan(&exists)
if err != nil {
return false, err
}
return exists, nil
}
func main() {
// 連接數(shù)據(jù)庫
db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/mydb")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// 檢查郵箱是否存在
email := "test@example.com"
exists, err := CheckEmailExists(db, email)
if err != nil {
log.Fatal(err)
}
if exists {
fmt.Printf("郵箱 %s 已被注冊\n", email)
} else {
fmt.Printf("郵箱 %s 可以使用\n", email)
}
}實際業(yè)務場景
// 用戶注冊邏輯
func RegisterUser(db *sql.DB, email, password string) error {
// 1. 先檢查郵箱是否已存在
exists, err := CheckEmailExists(db, email)
if err != nil {
return fmt.Errorf("檢查郵箱失敗: %v", err)
}
if exists {
return fmt.Errorf("郵箱 %s 已被注冊", email)
}
// 2. 郵箱可用,執(zhí)行注冊邏輯
_, err = db.Exec(`
INSERT INTO users (email, password, created_at)
VALUES (?, ?, NOW())
`, email, password)
if err != nil {
return fmt.Errorf("注冊失敗: %v", err)
}
fmt.Printf("用戶 %s 注冊成功!\n", email)
return nil
}
// 檢查用戶是否有訂單
func UserHasOrders(db *sql.DB, userID int) (bool, error) {
var hasOrders bool
query := `
SELECT EXISTS (
SELECT 1 FROM orders
WHERE user_id = ? AND status != 'cancelled'
)`
err := db.QueryRow(query, userID).Scan(&hasOrders)
return hasOrders, err
}
// 獲取用戶信息,同時檢查是否為 VIP
func GetUserWithVIPStatus(db *sql.DB, userID int) error {
type UserInfo struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
IsVIP bool `json:"is_vip"`
}
var user UserInfo
query := `
SELECT
u.id,
u.name,
u.email,
EXISTS (
SELECT 1 FROM memberships m
WHERE m.user_id = u.id
AND m.status = 'active'
AND m.expired_at > NOW()
) AS is_vip
FROM users u
WHERE u.id = ?`
err := db.QueryRow(query, userID).Scan(
&user.ID, &user.Name, &user.Email, &user.IsVIP,
)
if err != nil {
return err
}
fmt.Printf("用戶信息: %+v\n", user)
return nil
}幾個建議點
1. 記得建索引
-- 為了讓 EXISTS 查詢更快,記得在經(jīng)常查詢的字段上建索引 CREATE INDEX idx_users_email ON users(email); CREATE INDEX idx_orders_user_id ON orders(user_id);
2. 處理 NULL 值
-- 如果字段可能為 NULL,記得特殊處理
SELECT EXISTS (
SELECT 1 FROM users
WHERE phone IS NOT NULL
AND phone = '13800138000'
) AS phone_exists;3. 不要在 EXISTS 里寫 ORDER BY
-- 沒必要排序
SELECT EXISTS (
SELECT 1 FROM users
WHERE city = '上海'
ORDER BY created_at -- 這個排序完全沒啥卵用
);
-- 直接開擼
SELECT EXISTS (
SELECT 1 FROM users
WHERE city = '上海'
);最后
現(xiàn)在,當你想要去查詢數(shù)據(jù)是否存在的時候,知道應該用哪個了吧?
- 數(shù)據(jù)有沒有,存不存在,直接用
exists - 要是必須知道數(shù)量有多少,那么才用
count
到此這篇關(guān)于SQL判斷數(shù)據(jù)存不存在正確做法的文章就介紹到這了,更多相關(guān)SQL判斷數(shù)據(jù)存不存在內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
ubuntu linux下使用Qt連接MySQL數(shù)據(jù)庫的方法
Linux下完整的MySQL開發(fā)需要安裝服務器端,如果安裝客戶端也沒什么不好。直接在軟件中心搜mysql,把client和server選上。2011-08-08
使用SQL語句統(tǒng)計數(shù)據(jù)時sum和count函數(shù)中使用if判斷條件的講解
今天小編就為大家分享一篇關(guān)于使用SQL語句統(tǒng)計數(shù)據(jù)時sum和count函數(shù)中使用if判斷條件的講解,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-02-02

