SQL 可選的 where 子句,沒有指數級的大量只是略有不同的查詢
我正在使用 python 中的 postgres,我想編寫一個清晰的查詢,可以選擇允許 WHERE 子句子集,但不需要我編寫子集 where 子句是否存在的指數爆炸。具體來說,我有以下內容:
query = """ select * -- in reality, something more complicated from myTable t WHERE t.first_id in :args1 AND t.second_id in :args2 """
我想盡可能少地重複,並讓這個查詢明智地返回:
- 當 args1 為空,而 args2 為空
- 當 args1 為空且 args2 有 1 個參數時
- 當 args1 為空且 args2 有 n 個參數時
- 當 args1 有 1 個參數,並且 args2 為 null
- 當 args1 有 1 個參數,而 args2 有 1 個參數時
- 當 args1 有 1 個參數,而 args2 有 n 個參數時
- 當 args1 有 n 個參數,並且 args2 為空時
- 當 args1 有 n 個參數,而 args2 有 1 個參數時
- 當 args1 有 n 個參數,而 args2 有 n 個參數時
其中 args1 可以是:
args1 = [] args1 = ["a"] args1 = ["a", "b"]
…例如,對於 args2 類似。
正如你在上面看到的,
(1) is effectively no WHERE clause (2) is effectively remove the first argument and the AND (2) Would also likely want an equality operator = 'a', rather than an in ('a')
…ETC。
對於 2 個子句和 3 個選項,我們有 3^2 = 9 個不同的查詢要編寫。如果我們再添加 1-2 個 where 子句,這很快就會變得不堪重負,並且會為實際上相同的問題重複大量程式碼。雖然我可以寫出這些查詢中的每一個並在它們上切換大小寫,但我懷疑這是 SQL 可以為我做的語法,並且可能已經在 stackoverflow 上回答了一千次,但我似乎找不到。
如果有任何改變,我將在 python 中使用 sqlalchemy。
鑑於這句話
column IN ()
不是有效的 SQL 語法,我不相信有辦法用 SQL 做到這一點。如果有任何 SQL 語法來執行此操作,它將異常複雜,可能導致次優查詢計劃,並通過要求您多次指定相同的參數列表來使您的查詢膨脹——一次檢查參數列表在 SQL 中,並且一次實際使用它。在 Postgres 中,數據庫不應將單項
IN
子句與相等子句區別對待。將單項IN
子句重寫為等式子句不會給您帶來任何好處。由於您已經在使用 SQLAlchemy,因此您可以根據參數列表中的內容有條件地將這些條件添加到查詢中:
# your values need to be tuples for Postgres to properly # convert them to lists for IN clauses; Python lists will convert # to Postgres ARRAY types args1 = ("value",) args2 = () # the "1=1" is an always-true check so that you can # specify a WHERE clause, even with no provided arguments clauses = ["1=1"] if len(args1) > 0: clauses.append("t.first_id IN :args1") if len(args2) > 0: clauses.append("t.second_id IN :args2") ... query = """ SELECT * -- in reality, something more complicated FROM myTable t WHERE """ + " AND ".join(clauses) result = conn.execute(text(query), args1=args1, args2=args2, ...).fetchall()
SQLAlchemy 不關心您是否提供未使用的參數。換句話說,如果 args2 為空,則將其傳遞給
execute()
將在這裡起作用,即使查詢沒有佔位符。如果你使用 SQLAlchemy Core API 生成查詢,你可以
where()
用類似的方式連結你的子句:s = select([myTable]) if len(args1) > 0: s = s.where(myTable.c.first_id.in_(args1)) if len(args2) > 0: s = s.where(myTable.c.second_id.in_(args2)) ... result = conn.execute(s).fetchall()