测试SQL查询的最佳方法

测试SQL查询的最佳方法,sql,unit-testing,Sql,Unit Testing,我遇到了一个问题,就是我们一直有复杂的SQL查询出错。从本质上讲,这会导致向不正确的客户发送邮件以及其他类似的“问题” 每个人创建这样的SQL查询的经验是什么?我们每隔一周创建一组新的数据 以下是我的一些想法和局限性: 创建测试数据虽然这将证明我们拥有所有正确的数据,但并不强制排除生产中的异常。这些数据在今天被认为是错误的,但在10年前可能是正确的;它没有文档记录,因此我们只有在提取数据后才知道它 创建维恩图和数据映射这似乎是测试查询设计的可靠方法,但不能保证实现是正确的。它让开发人员提前计划

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

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

以下是我的一些想法和局限性:

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

  • 创建维恩图和数据映射这似乎是测试查询设计的可靠方法,但不能保证实现是正确的。它让开发人员提前计划,并在编写时考虑正在发生的事情

感谢您为我的问题提供的任何信息。

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

您可能要做的另一件事是分析SQL Server执行堆栈,并确定所有查询是否都是正确的,例如,如果您只使用一个返回正确和不正确结果的查询,那么显然所使用的查询有问题,但是,如果您的应用程序在代码中的不同点发送不同的查询,该怎么办


任何试图修复您的查询的尝试都是徒劳的。。。不管怎样,恶意查询可能仍然会触发错误的结果。

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

为什么要这样编写SQL

分解查询,就像分解函数一样。这使得它们更短、更简单、更容易理解、更容易测试、更容易重构。它允许您在它们之间添加“垫片”,并在它们周围添加“包装器”,就像您在过程代码中所做的那样

你是怎么做到的?通过将查询所做的每一件重要的事情放入视图中。然后从这些简单的视图中组合出更复杂的查询,就像从更原始的函数中组合出更复杂的函数一样

最棒的是,对于大多数视图组合,您将从RDBMS中获得完全相同的性能。(对某些人来说,你不会;那又怎样?过早优化是万恶之源。首先要正确编码,然后在需要时进行优化。)

在本例中,由于每个视图只添加一个转换,因此可以独立测试每个视图以查找错误,并且测试非常简单

下面是示例中的基表:

create table month_value( 
    eid int not null, month int, year int,  value int );
此表有缺陷,因为它使用两列(月和年)来表示一个数据,即绝对月。以下是我们对新的计算列的规范:

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

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 );
现在我们要测试的是我们的规范中固有的,即对于任何元组(年、月),都有一个且只有一个(绝对月),并且(绝对月)是连续的。让我们编写一些测试

我们的测试将是一个SQL
select
查询,具有以下结构:一个测试名称和一个case语句链接在一起。测试名称只是一个任意字符串。当测试语句然后“通过”或“失败”结束时,case语句就是case

测试语句将只是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.
运行该查询将生成以下结果:
对于每个(年、月)都有且只有一个(绝对月):已通过

只要月_值中有足够的测试数据,该测试就可以工作

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

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 );
select concat('(绝对月份)是连续的:',
从cm_absolute_month a选择计数(*)加入cm_absolute_month b时的情况
在((a.month+1=b.month和a.year=b.year)
或者(a.month=12,b.month=1,a.year+1=b.year))
其中a.绝对月+1 b.绝对月)=0
然后“通过”或“失败”结束);

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

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

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  
从cm\U abs\U月a加入cm\U abs\U月b时(选择计数(*)的情况
on((a.m+1=b.m和a.y=b.y)或(a.m=12和b.m=1和a.y+1=b.y))
其中a.am+1 b.am)=0
请注意,这仅检查连续月份的am值是否连续,而不是连续