所以我遇到了一个问题,因为我们一直在让复杂的SQL查询出错。
实际上,这会导致向不正确的客户发送邮件以及其他“问题”。每个人创建类似SQL查询的经验是什么,基本上我们每隔一周创建一组新的数据。
所以这里有一些我的想法和对它们的限制。
创建测试数据 - 虽然这可以证明我们拥有所有正确的数据,但并未强制排除生产中的异常。这是今天被认为是错误的数据,但10年前可能是正确的,没有记录,因此我们只在提取数据后知道它。
创建维恩图和数据图 - 这似乎是测试查询设计的可靠方法,但它并不能保证实现是正确的。它让开发人员向前挖掘并思考他们写作时发生的事情。
感谢您提供给我的问题的任何意见。
答案 0 :(得分:134)
您不会编写功能为200行的应用程序。您将这些长函数分解为较小的函数,每个函数都有一个明确定义的职责。
为什么要这样编写SQL?
分解您的查询,就像分解您的功能一样。这使它们更短,更简单,更容易理解,更容易测试,更容易重构。它允许您在它们之间添加“垫片”,并在它们周围添加“包装”,就像在过程代码中一样。
你是怎么做到的?通过使查询进入视图中的每个重要事物。然后,您可以从这些更简单的视图中撰写更复杂的查询,就像从更原始的函数中组合更复杂的函数一样。
最棒的是,对于大多数视图组合,您将获得与RDBMS完全相同的性能。 (对于某些人你不会;那么什么?过早的优化是所有邪恶的根源。首先正确编码,然后优化,如果你需要。)
Here's an example of using several view to decompose a complicated query.
在示例中,因为每个视图只添加一个转换,每个转换都可以独立测试以查找错误,并且测试很简单。
以下是示例中的基表:
create table month_value(
eid int not null, month int, year int, value int );
此表存在缺陷,因为它使用两列(月和年)来表示一个数据,即绝对月份。这是我们对新计算列的规范:
我们将其作为线性变换进行排序,以便对(年,月)进行排序,并且对于任何(年,月)元组,只有一个值,并且所有值都是连续的:
create view cm_absolute_month as
select *, year * 12 + month as absolute_month from month_value;
现在我们要测试的是我们的规范中固有的,即对于任何元组(年,月),只有一个(absolute_month),并且(absolute_month)s是连续的。我们来写一些测试。
我们的测试将是一个SQL select
查询,具有以下结构:测试名称和一起连接的case语句。测试名称只是一个任意字符串。 case语句只是case when
测试语句then 'passed' else 'failed' end
。
测试语句只是SQL选择(子查询)必须为true才能传递测试。
这是我们的第一次测试:
--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 );
现在让我们将我们的测试(只是查询)放入一个文件中,然后针对数据库运行该脚本。实际上,如果我们将视图定义存储在脚本(或脚本中,我建议每个相关视图使用一个文件)对数据库运行,我们可以将每个视图的测试添加到相同的脚本中,这样(重新)创建视图的行为也会运行视图的测试。这样,当我们重新创建视图时,我们都会得到回归测试,当视图创建针对生产运行时,视图也将在生产中进行测试。
答案 1 :(得分:6)
创建一个可以根据需要重新加载的测试系统数据库。加载数据或创建数据并将其保存。生成一种简单的方法来重新加载它。在开始生产之前,将开发系统附加到该数据库并验证代码。每当你设法让问题投入生产时就自己踢。创建一套测试来验证已知问题并随着时间的推移扩展您的测试套件。
答案 2 :(得分:4)
您可能需要检查DbUnit,因此您可以尝试使用一组固定数据为您的程序编写单元测试。这样你就可以编写具有或多或少可预测结果的查询。
您可能想要做的另一件事是分析您的SQL Server执行堆栈并查明所有查询是否确实是正确的,例如,如果您只使用一个返回正确和不正确结果的查询,那么正在使用的查询有问题,但是如果您的应用程序在代码中的不同位置发送不同的查询呢?
任何修复查询的尝试都是徒劳的......无论如何,流氓查询可能仍然会引发错误的结果。
答案 3 :(得分:2)
Re: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
请注意,这仅检查连续月份的值是否连续,而不是连续数据存在(这可能是您最初的预期)。如果您的所有源数据都不是连续的(例如,您只有偶数月份),即使您的计算完全关闭,也会一直通过。
我也错过了什么,或者那个ON子句的后半部分是否碰到错误的月份值? (即在2010年1月之后检查12/2011)
更糟糕的是,如果我没记错的话,SQL Server至少会在优化程序将其虚拟手抛向空中并开始对每个请求进行全表扫描之前允许少于10个级别的视图,因此请勿过度执行这种方法。
请记住测试你的测试用例!
否则创建一个非常广泛的数据集以包含大多数或所有可能的输入形式,使用SqlUnit或DbUnit或任何其他*单元自动检查针对该数据的预期结果,并审查,维护和更新它必要时通常似乎是要走的路。