【问题标题】:Requiring parameters in a SQL where clause?在 SQL where 子句中需要参数?
【发布时间】:2010-10-21 21:19:38
【问题描述】:

假设您有下表(注意:这是一个人为/简化的示例):

CREATE TABLE foo (    
  book_id number,
  page number,
  -- [a bunch of other columns describing a single page in a book]
);

ALTER TABLE foo
ADD (CONSTRAINT foo_pk PRIMARY KEY(book_id, page));

虽然 (book_id, page) 对是唯一的,但书之间会重复相同的页码(许多书的第 1 页)。因此,如果 SQL 查询未指定 book_id,则可能会选择/更新/删除错误的页面。我们所有的查询一次应该只针对一本书,但我看到了一些错误,其中 book_id 参数被意外省略了。

是否有一种编程方式来强制每个选择、插入、更新等查询在 where 子句中指定一个 book_id?

我们为查询动态生成 SQL 代码并使用 Spring 的 JdbcTemplate 执行它们。数据库是甲骨文。使用自动化测试来检查许多可能的查询(以及将来添加的新查询!)不会被重复的 page_ids 绊倒是很棘手的。我可以覆盖 JdbcTemplate 代码以确保 sql 查询始终包含 book_id 参数,但这涉及手动解析 SQL 代码(尤其是子查询时很棘手)并且看起来很麻烦。是否有更强大的解决方案来执行此操作?一些触发器、存储过程、约束?

【问题讨论】:

  • 什么数据库?使用 PostgreSQL 规则系统,您或许可以实现其中的一些目标。
  • 感谢您的建议。刚刚补充说“Oracle”是原始问题的数据库。
  • 如果有人想要运行不需要 book_id 的查询,例如“找到页数最多的书”——这样的查询不会有关于 book_id 的谓词。
  • 真实世界的案例并没有真正处理书籍和页面,我只是修改了表/列名称以便在这里讨论。在实际领域中,生产代码永远跨多个“书”运行查询是没有意义的。它可能在未来某个时候对分析有用,但无论如何这将在数据仓库(即单独的数据库)之外完成。

标签: sql oracle spring jdbc


【解决方案1】:

保护数据库免受程序员错误影响的常用方法是要求应用程序使用存储过程。 (有时这可以使用权限来完成。)

与临时查询相比,检查您的过程的合规性要容易得多。

【讨论】:

    【解决方案2】:

    您可以使用函数或存储过程,而不是直接使用 UPDATE。该过程接受 2 个参数,如果其中一个为 null,则会引发错误。

    另一个选项是确保您生成的查询始终具有 book_id 约束。我希望您没有将整个 SQL 语句创建为字符串,并且您正在使用参数化查询。如果不是,那么使用参数化查询是确保始终传递 book_id 的好方法(如果未设置参数,则查询将不会运行)。此外,如果您在使用参数化查询时不清理输入,您也不会面临风险。

    【讨论】:

    • 使用存储过程而不是查询将需要更改大量代码,但我想如果没有其他解决方案我可以做到。
    • 哦,我们使用的是参数化查询,问题是数量很多,一直在添加新的,偶尔有人忘记 where 子句中的 book_id 参数。换句话说,我正在寻找一种解决方案,以防程序员没有正确编写查询。
    【解决方案3】:

    首先,这确实是一个测试问题 - 犯错误的不是用户,而是开发人员,他们的错误应该在应用程序上线之前发现。

    话虽如此,您可以通过触发器组合捕获此类更新:

    • 将包变量 g_book_id 初始化为 null 的语句级 BEFORE 触发器
    • 行级触发器,用于 (a) 检查正在更新的 book_id 是否与包变量中的匹配(如果不为空),并且 (b) 如果包变量为空,则初始化包变量。

    一个简单的例子:

    SQL> create table t1 (id int, col2 int);
    
    Table created.
    
    SQL> insert into t1 values(1, null);
    
    1 row created.
    
    SQL> insert into t1 values(2, null);
    
    1 row created.
    
    SQL> create package p1 is g_id integer; end;
      2  /
    
    Package created.
    
    SQL> create trigger t1_bus
      2  before update on t1
      3  begin
      4    p1.g_id := null;
      5* end;
    SQL> /
    
    Trigger created.
    
    SQL> create trigger t1_bir
      2  before update on t1
      3  for each row
      4  begin
      5     if :new.id != p1.g_id then
      6       raise_application_error(-20000,'You can only update 1 ID at a time');
      7     end if;
      8     p1.g_id := :new.id;
      9  end;
     10  /
    
    Trigger created.
    
    SQL> update t1 set col2=1 where id=1;
    
    1 row updated.
    
    SQL> update t1 set col2=2 where id=2;
    
    1 row updated.
    
    SQL> update t1 set col2=3; -- ID not specified
    update t1 set col2=3
           *
    ERROR at line 1:
    ORA-20000: You can only update 1 ID at a time
    

    【讨论】:

    • 是的,这仅解决更新和删除问题,选择需要另一种方法。但我看不出一次将选择限制为一本书有什么意义——用户如何看到可供选择的书籍列表?或者数一数关于某个主题的书有多少?或者......好吧,你明白了。
    • 有趣的方法,谢谢。我无法想象有一种基于触发器的方法也适用于SELECT?正如我在上面的评论中所说,实际域不处理书籍(为了便于讨论,我修改了表/列名称),而在实际域中,跨多个“书籍”的查询只是没有意义。跨度>
    • 每个用户总是使用相同的 bookid 吗?如果是这样,VPD 可能是解决方案?
    【解决方案4】:

    我能想到的唯一方法是将表中的 book_id 和 page 列替换为存储两条信息的单个列 - 如果您想要整数列或 (book_id*10000 + page) 之类的东西字符串列的“book_id-page”。

    从正确性的角度来看,这是一个坏主意(两个属性存储在一个列中),但会迫使您的程序员使用这两个属性与表进行交互。如果这对你来说是一个足够大的问题,你可以考虑一下。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-06-20
      • 2015-11-10
      相关资源
      最近更新 更多