测试 SQL 查询的最佳方法

我遇到了一个问题,我们一直有复杂的 SQL 查询出错。从本质上讲,这会导致向不正确的客户发送邮件以及其他类似的“问题”。

每个人创建这样的 SQL 查询的经验是什么? 我们每隔一周创建一组新的数据。

以下是我的一些想法以及它们的局限性:

  • 创建测试数据 虽然这可以证明我们拥有所有正确的数据,但并不强制排除生产中的异常。这些数据在今天被认为是错误的,但在10年前可能是正确的; 它没有文档记录,因此我们只有在提取数据之后才能知道它。

  • Create Venn diagrams and data maps This seems to be a solid way to test the design of a query, however it doesn't guarantee that the implementation is correct. It gets the developers planning ahead and thinking of what is happening as they write.

谢谢你对我的问题提出的任何意见。

105195 次浏览

您可能希望检查 DbUnit,因此可以尝试使用固定的数据集为程序编写单元测试。这样,您应该能够编写具有或多或少可预测结果的查询。

另一件你可能想要做的事情是配置你的 SQL Server 执行堆栈,并找出所有的查询是否确实是正确的,例如,如果你只是使用一个查询,返回正确和不正确的结果,那么显然正在使用的查询是有问题的,但是如果你的应用程序发出不同的查询在代码的不同点?

任何尝试修复您的查询将是徒劳的... 流氓查询可能仍然是那些启动错误的结果反正。

您不会编写一个函数长达200行的应用程序。您可以将这些长函数分解为较小的函数,每个函数都有一个明确定义的职责。

为什么要这样编写 SQL?

Decompose your queries, just like you decompose your functions. This makes them shorter, simpler, easier to comprehend, 更容易测试, easier to refactor. And it allows you to add "shims" between them, and "wrappers" around them, just as you do in procedural code.

你是怎么做到的?通过将查询在视图中执行的每个重要操作。然后从这些更简单的视图中进行 作曲更复杂的查询,就像用更原始的函数组成更复杂的函数一样。

最棒的是,对于 大部分视图组合,您将从 RDBMS 中获得完全相同的性能。(对有些人来说你不会,那又怎样?过早优化是万恶之源。先正确编码,如果需要的话优化 那么。)

下面是使用多个视图分解复杂查询的示例

In the example, because each view adds only one transformation, each can be independently tested to find errors, and the tests are simple.

Here's the base table in the example:

create table month_value(
eid int not null, month int, year int,  value int );

This table is flawed, because it uses two columns, month and year, to represent one datum, an absolute month. Here's our specification for the new, calculated column:

我们将这样做作为一个线性转换,这样它的排序与(年、月)相同,并且对于任何(年、月)元组,只有一个值,并且所有值都是连续的:

create view cm_absolute_month as
select *, year * 12 + month as absolute_month from month_value;

现在我们要测试的是我们的规范中固有的东西,即对于任何元组(年、月) ,只有一个(绝对 _ 个月) ,而且(绝对 _ 个月)是连续的。让我们写一些测试。

我们的测试将是一个 SQLselect查询,具有以下结构: 一个测试名称和一个连接在一起的 case 语句。测试名称只是一个任意的字符串。Case 语句只是 case when测试语句 then 'passed' else 'failed' end

测试语句只是 SQL 选择(子查询) ,必须为真,测试才能通过。

这是我们的第一个测试:

--a select statement that catenates the test name and the case statement
select concat(
-- the test name
'For every (year, month) there is one and only one (absolute_month): ',
-- the case statement
case when
-- one or more subqueries
-- in this case, an expected value and an actual value
-- that must be equal for the test to pass
( select count(distinct year, month) from month_value)
--expected value,
= ( select count(distinct absolute_month) from cm_absolute_month)
-- actual value
-- the then and else branches of the case statement
then 'passed' else 'failed' end
-- close the concat function and terminate the query
);
-- test result.

运行该查询将产生以下结果: For every (year, month) there is one and only one (absolute_month): passed

只要 month _ value 中有足够的测试数据,这个测试就可以工作。

我们也可以添加一个测试来获得足够的测试数据:

select concat( 'Sufficient and sufficiently varied month_value test data: ',
case when
( select count(distinct year, month) from month_value) > 10
and ( select count(distinct year) from month_value) > 3
and ... more tests
then 'passed' else 'failed' end );

现在让我们测试它是连续的:

select concat( '(absolute_month)s are consecutive: ',
case when ( select count(*) from cm_absolute_month a join cm_absolute_month b
on (     (a.month + 1 = b.month and a.year = b.year)
or (a.month = 12 and b.month = 1 and a.year + 1 = b.year) )
where a.absolute_month + 1 <> b.absolute_month ) = 0
then 'passed' else 'failed' end );

现在,让我们将我们的测试(仅仅是查询)放到一个文件中,并在数据库中运行该脚本。实际上,如果我们将视图定义存储在一个脚本(或者脚本,我建议每个相关视图对应一个文件)中,以便针对数据库运行,我们可以将每个视图的测试添加到 一样脚本中,这样(重新)创建视图的操作也会运行视图的测试。这样,当我们重新创建视图时,我们都会得到回归测试,并且,当视图创建与生产运行时,视图也将在生产中进行测试。

创建一个测试系统数据库,您可以随时重新加载它。加载数据或创建数据并保存它。产生一个简单的方法来重新装载它。将开发系统附加到该数据库,并在投入生产之前验证代码。每当你设法让一个问题投入生产时,都要自责一番。创建一个测试套件来验证已知的问题,并随着时间的推移增长您的测试套件。

回复: tpdi

case when ( select count(*) from cm_abs_month a join cm_abs_month b
on (( a.m + 1 = b.m and a.y = b.y) or (a.m = 12 and b.m = 1 and a.y + 1 = b.y) )
where a.am + 1 <> b.am ) = 0

注意,这只检查连续月份的 am 值是否是连续的,而不检查是否存在连续的数据(这可能是您最初的打算)。如果没有源数据是连续的(例如,您只有偶数月份) ,即使您的 am 计算完全错误,这也会通过。

还有,是我漏掉了什么,还是 ON 条款的后半部分提高了错误的月值?(即检查2011年12月12日在2010年1月1日之后)

更糟糕的是,如果我没记错的话,SQL Server 至少允许您在优化器将其虚拟的双手抛向空中并开始对每个请求进行全表扫描之前访问不到10个级别的视图,所以不要过度使用这种方法。

Remember to test the heck out of your test cases!

否则,创建一个非常广泛的数据集,以包括大多数或所有可能的输入形式,使用 SqlUnit 或 DbUnit 或任何其他 * Unit 自动检查预期的结果对该数据,而 根据需要审查、维护和更新通常似乎是方法。