条件连接 (Oracle)

conditional join (Oracle)

我们有前端应用程序,用户可以在其中输入客户 and/or PO 来检索数据。 例如,如果用户想要检索客户的所有 PO,他将在 PO 字段中输入“%”。 如果用户想要检索所有数据,他将在每个字段中输入“%”。 我正在尝试这个,但它不起作用

SELECT *
FROM PO_INFO
WHERE customer_id = case when '%' then customer_id else 'Macys' end
  AND purchase_order = case when '%' then purchase_order else '79124' end

我错过了什么?

你不应该(有些人会说 必须 不)只是将 user-entered 搜索值作为文字插入到你的 SQL 查询中。也就是说,

AND purchase_order = case when '%' then purchase_order else '79124' end  

... 不会很好地执行或扩展,因为在 Oracle 看来,每个搜索都像一个全新的 SQL 查询,必须进行解析和优化(即“硬解析”)。这是一个昂贵的过程,还需要很多锁存器,这意味着多个用户试图同时 运行 搜索将不得不互相等待。

相反,您应该使用绑定变量构建 SQL。所以,

AND purchase_order = :1 -- or something.  We'll discuss the exact logic later...

:1 是一个绑定变量,它是您的代码在提交查询时将提供的值的占位符。现在,每个进行搜索的人都在使用相同的查询(只是每个人都为绑定变量提供不同的值)。不再避免过多的硬解析和性能灾难。

但是,这是下一个问题。所有人的一个查询意味着它只会被硬解析一次(很好),但它也意味着每个人 运行s 使用相同的执行计划。但在这样的搜索中,一种尺寸并不适合所有人。假设 Oracle 提出的执行计划使用列 'ABC' 上的索引。如果用户没有为列 'ABC' 提供绑定变量值,该执行计划仍将遵循,但结果很糟糕。

因此,我们真正想要的是一个 SQL 用于每组有值或无值的绑定变量,而不是一个 SQL 用于每组不同的文字搜索值。

从以下字符串开始在代码中构建您的 SQL:

SELECT * FROM PO_INFO WHERE 1=1

然后,为每个搜索条件添加这个(如果值为 %)

AND (:1 IS NULL)    -- and pass `NULL`, not "%" as the value for :1

(旁白:这种情况的原因,本质上是 NULL IS NULL 是为了使必须传入的绑定变量的数量和顺序始终相同,无论最终用户做什么或不给你一个价值。这使得提交某些语言的 SQL 变得容易得多,例如 PL/SQL).

如果搜索条件不是%,添加:

AND (customer_id /* or whatever column */ = :1)  -- and pass the user-entered value

因此,例如,如果用户指定了 customer_idpo_date 的值而不是 purchase_order,您的最终 SQL 可能如下所示:

SELECT *
FROM PO_INFO
WHERE 1=1
  AND  customer_id = :1
  AND  po_date := 2
  AND :3 IS NULL  -- this is purchase order and :3 you will pass as null

如果你做到了这一切,你将获得最少的 hard-parsing 和每次搜索的最佳执行计划。