WHERE 多條件 AND
這段查詢在做什麼?
SELECT product_id
FROM public.products
WHERE low_fats = 'Y'
AND recyclable = 'Y';
語意可以拆成兩層:
- 篩選:
low_fats為'Y'(低脂)而且recyclable為'Y'(可回收) - 投影:只回傳符合條件的
product_id
AND 代表兩個條件必須同時成立,任一個不成立該列就會被排除。
資料表結構(PostgreSQL 版)
PostgreSQL 沒有 MySQL 那種直接寫在欄位上的 ENUM('Y', 'N'),需要先用 CREATE TYPE 建立列舉型別,再拿來當欄位型別:
CREATE TYPE public.yes_no AS ENUM ('Y', 'N');
CREATE TABLE public.products (
product_id integer PRIMARY KEY,
low_fats public.yes_no NOT NULL,
recyclable public.yes_no NOT NULL
);
| 欄位 | 型別 | 說明 |
|---|---|---|
product_id | integer | 主鍵 |
low_fats | yes_no('Y'/'N') | 'Y' 表示低脂 |
recyclable | yes_no('Y'/'N') | 'Y' 表示可回收 |
建立測試資料:
INSERT INTO public.products (product_id, low_fats, recyclable)
VALUES (0, 'Y', 'N'),
(1, 'Y', 'Y'),
(2, 'N', 'Y'),
(3, 'Y', 'Y'),
(4, 'N', 'N');
用表格 看差異
原始資料:
| product_id | low_fats | recyclable |
|---|---|---|
| 0 | Y | N |
| 1 | Y | Y |
| 2 | N | Y |
| 3 | Y | Y |
| 4 | N | N |
逐列判斷 low_fats = 'Y' AND recyclable = 'Y':
| product_id | low_fats = 'Y' | recyclable = 'Y' | AND 結果 |
|---|---|---|---|
| 0 | true | false | false |
| 1 | true | true | true |
| 2 | false | true | false |
| 3 | true | true | true |
| 4 | false | false | false |
查詢結果:
| product_id |
|---|
| 1 |
| 3 |
只有 1、3 兩個產品同時符合「低脂」與「可回收」。
PostgreSQL 注意事項
1. AND 遇到 NULL:三值邏輯
SQL 的布林運算有 true、false、NULL(unknown)三種結果,WHERE 只保留結果為 true 的列。
| A | B | A AND B |
|---|---|---|
| true | true | true |
| true | false | false |
| true | NULL | NULL(被排除) |
| false | NULL | false |
| NULL | NULL | NULL(被排除) |
本例欄位加了 NOT NULL,不會有這個問題;若欄位允許 NULL,recyclable 為 NULL 的列一律不會出現在結果中。
2. 參數化查詢
在應用程式中,條件值應該用佔位符傳入,避免 SQL injection:
SELECT product_id
FROM public.products
WHERE low_fats = $1
AND recyclable = $2
ORDER BY product_id;
題目說「順序不限」,但實務上建議加 ORDER BY,讓 API 回傳結果穩定、可預期。
3. 用 boolean 取代 'Y'/'N'
只有「是/否」兩種狀態時,PostgreSQL 更推薦直接用 boolean,語意更清楚,條件也更簡潔:
CREATE TABLE public.products (
product_id integer PRIMARY KEY,
is_low_fat boolean NOT NULL DEFAULT false,
is_recyclable boolean NOT NULL DEFAULT false
);
SELECT product_id
FROM public.products
WHERE is_low_fat
AND is_recyclable;
| 方案 | 優點 | 缺點 |
|---|---|---|
ENUM ('Y', 'N') | 與既有 MySQL schema 對齊 | 需額外建型別,新增值要 ALTER TYPE |
CHECK (col IN ('Y', 'N')) | 不用建型別,搬遷簡單 | 本質仍是字串比較 |
boolean | 語意最清楚、可直接當條件 | 需調整既有資料與程式 |
4. 常用查詢可建 Partial Index
如果「低脂且可回收」是高頻查詢,可以建立只涵蓋符合條件列的部分索引(Partial Index),索引體積小、命中快:
CREATE INDEX idx_products_low_fat_recyclable
ON public.products (product_id)
WHERE low_fats = 'Y'
AND recyclable = 'Y';
查詢的 WHERE 條件要與索引條件一致,planner 才會使用它。
程式呼叫範例
Python(asyncpg)
"""查詢同時符合低脂與可回收的產品 ID。"""
import asyncio
from typing import Literal
import asyncpg
type YesNo = Literal["Y", "N"]
FIND_PRODUCTS_SQL = """
SELECT product_id
FROM public.products
WHERE low_fats = $1
AND recyclable = $2
ORDER BY product_id
"""
async def find_product_ids(
conn: asyncpg.Connection,
low_fats: YesNo = "Y",
recyclable: YesNo = "Y",
) -> list[int]:
"""依低脂、可回收條件查詢產品 ID 清單。"""
rows = await conn.fetch(FIND_PRODUCTS_SQL, low_fats, recyclable)
return [row["product_id"] for row in rows]
async def main() -> None:
conn = await asyncpg.connect("postgresql://user:pass@localhost:5432/shop")
try:
product_ids = await find_product_ids(conn)
print(product_ids) # [1, 3]
except asyncpg.PostgresError as exc:
print(f"query failed: {exc}")
finally:
await conn.close()
if __name__ == "__main__":
asyncio.run(main())
小結
AND:所有條件都為true,該列才會被選出。- PostgreSQL 的 ENUM 要先
CREATE TYPE,不能像 MySQL 直接寫在欄位上。 WHERE只保留true,條件結果為NULL的列會被排除,必要時加NOT NULL。- 是/否欄位在 PostgreSQL 優先考慮
boolean。 - 高頻固定條件可搭配 Partial Index 加速。