2010-10-21 138 views
2

假设你有如下表(注:这是一个人为/简体为例):在SQL where子句中需要参数?

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,页)对是唯一的,在同一页面数量将书籍之间重复(很多书会有一页1)。因此,如果SQL查询未指定book_id,则可能会选择/更新/删除错误的页面。我们所有的查询一次只能处理一本书,但是我发现了一些错误,其中book_id参数被忽略。

是否有一种编程方式来强制每个select,insert,update等查询在where子句中指定book_id?

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

+0

什么数据库?您可能可以通过PostgreSQL规则系统实现这些目标。 – 2010-10-21 21:22:01

+0

感谢您的建议。刚刚补充说“Oracle”是原始问题的DB。 – 2010-10-21 21:23:34

+0

如果有人想运行不需要book_id的查询,例如“找到页数最多的书” - 这样的查询在book_id上不会有谓词。 – 2010-10-22 02:24:26

回答

2

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

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

+0

使用存储过程而不是查询将需要更改很多代码,但是我想我可以做到这一点,如果没有其他解决方案是可能的。 – 2010-10-21 21:37:50

+0

噢,我们正在使用参数化查询,问题在于它们中有很多,新增加了所有时间,偶尔会有人忘记where子句中的book_id参数。换句话说,我正在寻找一种解决方案,以帮助程序员无法正确编写查询。 – 2010-10-21 21:39:34

3

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

检查你的proc是否比ad hoc查询更合适。

0

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

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

1

首先,这确实是一个测试问题 - 不是用户会犯这个错误,它是开发人员,他们的错误应该在之前被抓到

说了这么多,你可以通过触发器的组合捕获这样的更新:

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

一个简单的例子:

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 
+0

他希望这个用于'SELECT',以及... – egrunin 2010-10-22 13:37:18

+0

没错,这只能解决更新和删除问题,另一种方法将需要选择。但我看不出如何将选择一次限制在一本书中 - 用户如何从列表中选择一本书?或者统计某个主题上有多少本书?或者......呃,你明白了。 – 2010-10-22 13:45:45

+0

有趣的方法,谢谢。我无法想象有一种基于触发器的方法可以用于'SELECT'吗?正如我在上面的评论中所说的,实际的域不涉及书籍(为便于讨论我修改了表/列名),而在真实域中,跨多个“书籍”的查询没有意义。 – 2010-10-22 17:31:48