testingSQL查询的最佳方法
所以我遇到了一个问题,我们不断有复杂的SQL查询出错。
本质上这导致发送邮件给不正确的客户和其他“问题”。
什么是每个人创build这样的SQL查询的经验,实质上是我们每隔一周创build新的数据队列。
所以这里是我的一些想法和对他们的限制。
创buildtesting数据 – 虽然这将certificate我们拥有所有正确的数据,但并不强制排除生产中的exception情况。 那就是今天会被认为是错误的数据,但在10年前可能是正确的,没有logging,因此我们只有在提取数据后才知道。
创build维恩图和数据图 – 这似乎是testing查询devise的可靠方法,但它并不能保证实现是正确的。 它让开发人员在前方思考他们所写的内容。
感谢您为我的问题提供的任何input。
你不会写200行长的函数。 你会把这些长的函数分解成更小的函数,每个函数都有明确的责任。
为什么要这样写你的SQL?
分解你的查询,就像分解你的函数一样。 这使得他们更短,更简单,更容易理解, 更容易testing ,更容易重构。 它允许你在它们之间加上“垫片”,在它们周围加上“包装”,就像你在程序代码中一样。
你怎么做到这一点? 通过使每个重要的事情查询进入一个视图。 然后,从这些更简单的视图中编写更复杂的查询,就像从更原始的函数中编写更复杂的函数一样。
最重要的是,对于大多数视图的组合,您将获得与RDBMS完全相同的性能。 (对于一些你不会的,那么是什么?过早的优化是万恶的根源,先正确编码, 然后再优化,如果你需要的话)。
以下是使用多个视图分解复杂查询的示例。
在这个例子中,因为每个视图只添加一个转换,每个视图都可以独立testing以发现错误,并且testing很简单。
以下是示例中的基表:
create table month_value( eid int not null, m int, y int, v int );
这个表是有缺陷的,因为它使用两个月份和年份来表示一个数据,一个绝对月份。 以下是我们的新计算列的规格:
我们将以线性变换的forms来做到这一点,它的sorting方式与(y,m)相同,并且对于任何(y,m)元组都有唯一的值,并且所有值都是连续的:
create view cm_abs_month as select *, y * 12 + m as am from month_value;
现在我们要testing的是我们规范中固有的,也就是说对于任何元组(y,m),只有一个(am),而(am)是连续的。 我们来写一些testing。
我们的testing将是一个SQL select
查询,具有以下结构:一个testing名称和一个case语句拼接在一起。 testing名称只是一个任意的string。 case语句就是case when
testing语句then 'passed' else 'false' end
。
testing语句将只是SQLselect(子查询),testing必须通过。
这是我们的第一个testing:
--a select statement that catenates the test name and the case statement select concat( -- the test name 'For every (y,m) there is one and only one (am): ', -- 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 y, m) from month_value) --expected value, = ( select count(distinct am) from cm_abs_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 (y,m) there is one and only one (am): passed
只要month_value中有足够的testing数据,这个testing就可以工作。
我们也可以添加足够的testing数据的testing:
select concat( 'Sufficient and sufficiently varied month_value test data: ', case when ( select count(distinct y, m) from month_value) > 10 and ( select count(distinct y) from month_value) > 3 and ... more tests then 'passed' else 'failed' end );
现在让我们testing它是连续的:
select concat( '(am)s are consecutive: ', case when ( select count(*) from cm_abs_month a join cm_abs_month b on (( am + 1 = bm and ay = by) or (am = 12 and bm = 1 and ay + 1 = by) ) where a.am + 1 <> b.am ) = 0 then 'passed' else 'failed' end );
现在让我们把我们的testing,只是查询,到一个文件,并运行该脚本对数据库。 事实上,如果我们将视图定义存储在脚本中(或者脚本,我推荐每个相关视图有一个文件),我们可以将每个视图的testing添加到同一个脚本中,以便 – )创build我们的视图也运行视图的testing。 这样,当我们重新创build视图时,我们都会得到回归testing,而当视图创build运行时,视图也将在生产中被testing。
创build一个testing系统数据库,您可以随时重新加载。 加载您的数据或创build您的数据并保存。 生成一个简单的方法来重新加载它。 在开始生产之前,将开发系统连接到该数据库并validation您的代码。 每当你设法让问题投入生产时,踢自己。 创build一套testing来validation已知问题,并随着时间的推移增加您的testing套件。
你可能想检查DbUnit ,所以你可以尝试用一组固定的数据为你的程序编写unit testing。 这样,你应该能够写或多或less的可预测的结果的查询。
你可能想要做的另一件事是分析你的SQL Server执行堆栈,并找出所有的查询是否确实是正确的,例如,如果你只使用一个查询返回正确和不正确的结果,那么显然查询使用是有问题的,但是如果您的应用程序在代码中的不同位置发送不同的查询呢?
任何尝试修复您的查询然后将是徒劳无益的…无论如何,stream氓查询可能仍然是引发错误的结果。
Re:tpdi
case when ( select count(*) from cm_abs_month a join cm_abs_month b on (( am + 1 = bm and ay = by) or (am = 12 and bm = 1 and ay + 1 = by) ) where a.am + 1 <> b.am ) = 0
请注意,这只会检查连续月份的值是否连续,而不是连续的数据是否存在(这可能是您最初想要的)。 如果您的源数据都不是连续的(例如,您只有偶数个月),则这将始终通过,即使您的计算完全closures。
我也是错过了一些东西,或者ON子句的后半部分碰到了错误的月份值? (即检查12/2011是在2010年1月以后)
更糟糕的是,如果我没有记错的话,SQL Server至less可以让你在不到10级的视图之前优化器抛出虚拟手,并开始对每个请求进行全表扫描,所以不要过度使用这种方法。
记住要testing你的testing用例!
否则,创build一个非常广泛的数据集,以包含大部分或所有可能的inputforms,使用SqlUnit或DbUnit或任何其他*单元来自动检查预期的结果,并根据需要审查,维护和更新要走的路。