在WHERE子句中使用可选条件的正确方法

时间:2014-07-13 17:29:05

标签: sql django postgresql django-queryset

我在SQL查询的WHERE子句中有一个可选条件。如果用户提供参数opt_y,则会检查条件cond2 <> opt_y,否则会跳过该条件。另一个条件(cond1 = x)仍然是固定的。

以下方法中,效率&amp; 安全(避免SQL注入)。我使用Django原始查询管理器来运行我的SQL(我理解性能取决于具体情况,但如果有一些明显的陷阱,比如数据库引擎无法优化特定技术或者可以&# 39;有效使用缓存,请突出显示)。当然,如果有更好的方法,请分享。

预期查询

MyTable.objects.raw("""
  SELECT id, RANK() OVER (PARTITION BY name ORDER BY age)
  FROM mytable
  WHERE cond1 = %s AND cond2 <> %s """, [x, opt_y])
)

方法1: CASE声明

MyTable.objects.raw("""
  SELECT id, RANK() OVER (PARTITION BY name ORDER BY age)
  FROM mytable
  WHERE cond1 = %s AND 
    CASE WHEN %s IS NULL THEN true ELSE cond2 <> %s END""", [x, opt_y, opt_y])
)

方法2:组合字符串

query  = """SELECT id, RANK() OVER (PARTITION BY name ORDER BY age)
            FROM mytable
            WHERE cond1 = %s """
params = [x]
if opt_y:
    q += """AND cond2 <> %s"""
    params.append(opt_y)
MyTable.objects.raw(query, params)

方法3:逻辑操作

MyTable.objects.raw("""
  SELECT id, RANK() OVER (PARTITION BY name ORDER BY age)
  FROM mytable
  WHERE cond1 = %s AND (%s IS NULL OR cond2 <> %s)""", [x, opt_y, opt_y])
)

更新,再添加1个方法:

方法4:这更像是黑客攻击

if not opt_y:
    opt_y = -1 #Or some value that will NEVER appear in a column 

MyTable.objects.raw("""
  SELECT id, RANK() OVER (PARTITION BY name ORDER BY age)
  FROM mytable
  WHERE cond1 = %s AND cond2 <> %s""", [x, opt_y])
)

使用Django 1.6&amp; PostgreSQL 9.3

1 个答案:

答案 0 :(得分:3)

CASE或逻辑操作如果您不介意详细程度,然而,如果您正在使用它们,它们将无法得到优化服务器端预处理语句,因此它们可能导致非最佳计划选择。

如果你正在使用客户端参数化查询,客户端驱动程序替换参数,他们就可以了。您可以通过查看PostgreSQL查询日志来判断您正在使用的内容 - 如果它记录您的语句如下:

... WHERE $1 = 'fred' AND ...

然后您使用服务器端参数绑定。

正因为如此,遗憾的是,将谓词添加到SQL字符串并向查询参数列表添加额外参数可能是许多应用程序最有效的方法。这是关于SQL IMO更可怕的事情之一。


如果您对PostgreSQL的内容不感兴趣,请立即停止阅读

如果您对如何判断某个特定构造是否已经过优化感到好奇,可以检查PostgreSQL的低级查询分析树,重写和查询计划结构。如果您对优化器效果感兴趣,那么查询计划结构就是您想要的。

说我对表:

是否感兴趣
                                 Table "public.manufacturers"
 Column  |         Type          |                         Modifiers                          
---------+-----------------------+------------------------------------------------------------
 id      | integer               | not null default nextval('manufacturers_id_seq'::regclass)
 name    | character varying(30) | not null
 country | character varying(40) | 
Indexes:
    "manufacturers_pkey" PRIMARY KEY, btree (id)
    "manufacturers_name_key" UNIQUE CONSTRAINT, btree (name)

一个(相当愚蠢的)查询,如:

select * from manufacturers 
where case when null is null then true else id = id end
order by id;

是否已优化CASE。我会:

SET debug_print_plan = on;
SET client_min_messages = debug1;
select * from manufacturers 
    where case when null is null then true else id = id end
    order by id;

psql会打印出来:

LOG:  statement: select * from manufacturers where case when null is null then true else id = id end order by id;
LOG:  plan:
DETAIL:     {PLANNEDSTMT 
   :commandType 1 
   :queryId 0 
   :hasReturning false 
   :hasModifyingCTE false 
   :canSetTag true 
   :transientPlan false 
   :planTree 
      {INDEXSCAN 
      :startup_cost 0.15 
      :total_cost 53.85 
      :plan_rows 380 
      :plan_width 180 
      :targetlist (
         {TARGETENTRY 
         :expr 
            {VAR 
            :varno 1 
            :varattno 1 
            :vartype 23 
            :vartypmod -1 
            :varcollid 0 
            :varlevelsup 0 
            :varnoold 1 
            :varoattno 1 
            :location 7
            }
         :resno 1 
         :resname id 
         :ressortgroupref 1 
         :resorigtbl 104875 
         :resorigcol 1 
         :resjunk false
         }
         {TARGETENTRY 
         :expr 
            {VAR 
            :varno 1 
            :varattno 2 
            :vartype 1043 
            :vartypmod 34 
            :varcollid 100 
            :varlevelsup 0 
            :varnoold 1 
            :varoattno 2 
            :location 7
            }
         :resno 2 
         :resname name 
         :ressortgroupref 0 
         :resorigtbl 104875 
         :resorigcol 2 
         :resjunk false
         }
         {TARGETENTRY 
         :expr 
            {VAR 
            :varno 1 
            :varattno 3 
            :vartype 1043 
            :vartypmod 44 
            :varcollid 100 
            :varlevelsup 0 
            :varnoold 1 
            :varoattno 3 
            :location 7
            }
         :resno 3 
         :resname country 
         :ressortgroupref 0 
         :resorigtbl 104875 
         :resorigcol 3 
         :resjunk false
         }
      )
      :qual <> 
      :lefttree <> 
      :righttree <> 
      :initPlan <> 
      :extParam (b)
      :allParam (b)
      :scanrelid 1 
      :indexid 104879 
      :indexqual <> 
      :indexqualorig <> 
      :indexorderby <> 
      :indexorderbyorig <> 
      :indexorderdir 1
      }
   :rtable (
      {RTE 
      :alias <> 
      :eref 
         {ALIAS 
         :aliasname manufacturers 
         :colnames ("id" "name" "country")
         }
      :rtekind 0 
      :relid 104875 
      :relkind r 
      :lateral false 
      :inh false 
      :inFromCl true 
      :requiredPerms 2 
      :checkAsUser 0 
      :selectedCols (b 9 10 11)
      :modifiedCols (b)
      }
   )
   :resultRelations <> 
   :utilityStmt <> 
   :subplans <> 
   :rewindPlanIDs (b)
   :rowMarks <> 
   :relationOids (o 104875)
   :invalItems <> 
   :nParamExec 0
   }

阅读计划树需要一些练习和理解PostgreSQL的内部结构。如果大多数根本没有任何意义,请不要强调。这里感兴趣的主要是用于读取表的索引扫描的qual(限定符或where子句)是空的。 PostgreSQL还没有优化CASE,它注意到id = id总是true并完全优化了where子句。