在 SQL 中动态构建 WHERE 子句,无需硬解析即可执行

Posted

技术标签:

【中文标题】在 SQL 中动态构建 WHERE 子句,无需硬解析即可执行【英文标题】:Build WHERE clause in SQL dynamically and execute without hard parsing 【发布时间】:2016-04-15 15:59:47 【问题描述】:

我想动态构建 SQL,添加/更改/删除 WHERE 条件,直到找到记录。 添加/更改/删除 WHERE 条件基于业务规则设置的优先级。

为了更好地描述问题陈述,这是我的 2 个结构表(示例键列)

表 1:--> 此表包含每个提交的索赔的索赔相关信息。

CLAIM_DOCUMENT   
--------------
(
HDR_SID NUMBER,
PRVDR_NPI   NUMBER,
INV_TYPE    VARCHAR2(4),
SPLTY       VARCHAR2(100),
PRCDR_CODE  VARCHAR2(100),
MDFR_CODE   VARCHAR2(10),
DIAG_CODE   VARCHAR2(100) )

表 2:--> 这是保存提供商费率的配置表。

PROVIDER_RATE  
-------------
( PRVDR_SID     NUMBER,
PRVDR_NPI       NUMBER,
SPLTY           VARCHAR2(100),
SUB_SPLTY       VARCHAR2(100),
PRCDR_CODE      VARCHAR2(100),
MDFR_CODE   VARCHAR2(10) 
RATE_VALUE      NUMBER(20,6)
RATE_TYPE       VARCHAR2(10)
);

目标是从 PROVIDER_RATE 表中确定适用的 RATE_VALUE。 为了确定提供者费率,我们将在声明中提交的属性(即 PRVDR_NPI、PRCDR_CODE、MDFR_CODE)与 PROVIDER_RATE 匹配以找到 RATE_VALUE。

声明中的所有属性可能与 PROVIDER_RATE 不匹配,因此我们添加/更改/删除“WHERE”条件,直到从 PROVIDER_RATE 表中找到 RATE_VALUE。

例如在第一遍中,业务规则说匹配所有可能的值,所以我编写 SQL 语句如下:

SELECT RATE_VALUE, RATE_TYPE into v_rate_value, v_rate_type 
FROM CLAIM_DOCUMENT cd, PROVIDER_RATE pr
WHERE cd.hdr_sid = p_hdr_sid      -- p_hdr_sid is passed as parameter to procedure for 1 claim
AND cd.PRVDR_NPI = pr.PRVDR_NPI    -- PRVDR_NPI is key column to match between 2 tables
----all optional where clause to form here based on busienss rules priority --
AND cd.PRCDR_CODE = pr.PRCDR_CODE          -- optional WHERE clause 1
AND cd.MDFR_CODE = pr.MDFR_CODE          -- optional WHERE clause 2
AND cd.SPLTY = pr.SPLTY               -- optional WHERE clause 3

--IF rate value is NOT found using all WHERE clause above, the next rule priority 
--is to REMOVE SPLTY and check
    IF v_rate_value IS NULL THEN
        SELECT RATE_VALUE, RATE_TYPE into v_rate_value, v_rate_type 
        FROM CLAIM_DOCUMENT cd, PROVIDER_RATE pr
        WHERE cd.hdr_sid = p_hdr_sid      -- p_hdr_sid is passed as parameter to procedure for 1 claim
        AND cd.PRVDR_NPI = pr.PRVDR_NPI    -- PRVDR_NPI is key column to match between 2 tables
        ----all optional where clause to form here based on busienss rules priority --
        AND cd.PRCDR_CODE = pr.PRCDR_CODE          -- optional WHERE clause 1
        AND cd.MDFR_CODE = pr.MDFR_CODE          -- optional WHERE clause 2
    END IF;
--IF rate value is NOT found using WHERE clause above, 
--the next rule priority is to REMOVE MDFR_CODE and check
        IF v_rate_value IS NULL THEN
            SELECT RATE_VALUE, RATE_TYPE into v_rate_value, v_rate_type 
            FROM CLAIM_DOCUMENT cd, PROVIDER_RATE pr
            WHERE cd.hdr_sid = p_hdr_sid      -- p_hdr_sid is passed as parameter to procedure for 1 claim
            AND cd.PRVDR_NPI = pr.PRVDR_NPI    -- PRVDR_NPI is key column to match between 2 tables
            ----all optional where clause to form here based on busienss rules priority --
            AND cd.PRCDR_CODE = pr.PRCDR_CODE          -- optional WHERE clause 1
        END IF;

我的问题: 有没有办法通过读取表中配置的业务规则来动态构建 WHERE 子句,更重要的是执行 DYNAMIC 形成的 SQL 语句没有硬解析?

【问题讨论】:

当然,只需阅读业务规则,将它们放在variable varchar2(100)EXECUTE IMMEDIATE variable 查询会有多少种变体?如果只有 3 个,那么硬解析并不重要。 我不想使用 EXECUTE IMMEDIATE 因为这个过程是最常用的,我相信这会产生性能问题。可能有很多变化,所以我想让它更具可配置性和动态性,但不以牺牲性能为代价 只有一个提示:如果查询返回零或多行,使用SELECT ... INTO ... 会引发异常,因此这种精确的查询语法对您不起作用。 【参考方案1】:

这样有条件地打开/关闭条件是否可以接受:

AND (cd.PRCDR_CODE = pr.PRCDR_CODE OR l_prcdr_code_flag='off')  -- optional WHERE clause 1
AND (cd.MDFR_CODE = pr.MDFR_CODE OR l_mdfr_code_flag='off')  -- optional WHERE clause 2
AND (cd.SPLTY = pr.SPLTY OR l_splty_flag='off')  -- optional WHERE clause 3

您在其余代码中所要做的就是处理“标志”,即。在开始时将所有l_*_flags 变量设置为'on',然后随时关闭它们(将它们设置为'off')。 这样您就不会更改 SQL,因此不需要硬解析。 为您的查询创建一个游标变量以避免重复代码也是一个好主意。

【讨论】:

首先,感谢 GoranM。不知道我是否做对了,所以我想问一下这种情况。如果我完全不必根据业务要求考虑 MDFR_CODE 和 SPLTY ,那么 flag 将如何提供帮助,例如假设我必须执行以下操作,您能否向我提供带有标志的查询: AND (cd.PRCDR_CODE = pr.PRCDR_CODE OR l_prcdr_code_flag='off') -- 可选 WHERE 子句 1【参考方案2】:

你可以试试这个:

SELECT RATE_VALUE, RATE_TYPE, 
       CASE
         WHEN
           cd.PRCDR_CODE = pr.PRCDR_CODE          -- optional WHERE clause 1
           AND cd.MDFR_CODE = pr.MDFR_CODE          -- optional WHERE clause 2
           AND cd.SPLTY = pr.SPLTY               -- optional WHERE clause 3
         THEN 1
         WHEN
           cd.PRCDR_CODE = pr.PRCDR_CODE          -- optional WHERE clause 1
           AND cd.MDFR_CODE = pr.MDFR_CODE          -- optional WHERE clause 2
         THEN 2
         WHEN
           cd.PRCDR_CODE = pr.PRCDR_CODE          -- optional WHERE clause 1
         THEN 3
       END matching_factor
  FROM CLAIM_DOCUMENT cd, PROVIDER_RATE pr
 WHERE cd.hdr_sid = p_hdr_sid      -- p_hdr_sid is passed as parameter to procedure for 1 claim
   AND cd.PRVDR_NPI = pr.PRVDR_NPI
 ORDER BY 3;

这样您将捕获所有满足连接条件 (cd.PRVDR_NPI = pr.PRVDR_NPI) 的记录,matching_factor 将告诉您还满足哪些附加条件 (1 => 满足所有 3 个可选子句,3 => 仅满足 PRCDR_CODE 子句)。 根据这个匹配因子对结果集进行排序,第一行应该是最佳匹配。 这次我做对了吗?

【讨论】:

感谢 GoranM 的支持。您的解决方案当然看起来更好。当我们必须在设计和实施过程中了解所有可能的排列时,您的解决方案会很有效。我更感兴趣的是检查是否有新的更改请求进来,其中说检查 PRVDR_NPI 和 SPLY,这应该是最高优先级。我们可以在不更改代码的情况下满足此更改请求吗?如果您需要更多详细信息,请告诉我。再次感谢 GoranM

以上是关于在 SQL 中动态构建 WHERE 子句,无需硬解析即可执行的主要内容,如果未能解决你的问题,请参考以下文章

有没有比在开头使用 1=1 更好的方法来动态构建 SQL WHERE 子句?

csharp 在Linq中动态构建Where子句

如何使用 JSON 输入构建动态 SQL“WHERE”语句

如何强制 SQL Server 在 WHERE 子句之前处理 CONTAINS 子句?

在 Where 子句中使用 case 构建动态查询

如何在 SQL 查询中动态跳过没有 If else 的 where 子句?