过滤嵌套聚合SUM函数不起作用

时间:2014-05-08 22:20:05

标签: sql sql-server tsql

我有这两个表(为了示例,名称已复数化):

Locations

idlocation varchar(12)
name       varchar(50)

Answers

idlocation varchar(6)
question_number varchar(3)
answer_text1    varchar(300)
answer_text2    varchar(300)

此表格可以根据每个位置重复的编号问题列表保存多个位置的答案。

我要做的是将answer_text1answer_text2列中的值添加到位置表中可用的每个位置,但仅 特定问题,然后根据结果(1或0)输出一个值。

使用嵌套表Answers执行SUM操作的查询如下:

select
l.idlocation,    

'RESULT' = (
              case when (
                select 
                sum(cast(isnull(c.answer_text1,0) as int)) +
                sum(cast(isnull(c.answer_text2,0) as int)) 
                from Answers c
                where b.idlocation=c.idlocation and c.question_number='05'
              ) > 0 then
                           1
              else 
                           0
              end
            ) 

from Locations l, Answers b
where l.idlocation=b.idlocation and b.question_number='05'

在表格Answers中,我有时会为其字段answer_text2保存日期字符串类型的值,但会使用不同的问题编号。

当我运行查询时,我收到以下错误:

  

转换varchar值时转换失败' 27/12 / 2013'到数据类型int

我在'27/12/2013'字段上确实有这个值answer_text2,但是对于另一个问题,所以我的过滤器在嵌套的select语句后会被忽略:b.idlocation=c.idlocation,并且&#39 ; s显然加了更多问题因此发布了错误。

更新

根据Steve建议的解决方案,我最终实现了过滤器以避免char / varchar注意到我的SUM语句中的一个小变体:

每个可能不是INT字符串值的长度大于2(' 00'到' 99'对于我的问题编号)所以我使用此过滤器来确定我何时去应用演员。

'RESULT' = 
           case when (
          select sum( 
                          case when len(c.answer_text1) <= 2 then   
                             cast(isnull(c.answer_text1,'0') as int) 
                          else 
                             0 
                          end 
                         ) + 
               sum( 
                           case when len(c.answer_text2) <= 2 then  
                              cast(isnull(c.answer_text2,'0') as int) 
                           else 
                              0 
                           end 
                          )
        from Answers c 
                   where c.idlocation=b.idlocation and c.question_number='05' 
        ) > 0   
           then 
                1 
           else 
                0 
           end

3 个答案:

答案 0 :(得分:1)

您的查询显示正确,这意味着您在answer_text1answer_text2字段中有一个带有日期时间的问题05记录。

试一试,确定哪一行有日期:

select *
from Answers 
where question_number='05'
  and (isdate(answer_text1) = 1 or isdate(answer_text2) = 1)

此外,您可以过滤掉其中包含日期的所有行

where isdate(c.answer_text1) = 0
  and isdate(c.answer_text2) = 0
  and ...

答案 1 :(得分:1)

这是SQL Server查询处理器/优化器如何工作的不幸结果。在我看来,SQL标准禁止在有助于结果集的行被识别之前计算SELECT列表表达式,但优化器会考虑违反此禁止的计划。

您正在观察的是评估查询结果集中行上的SELECT列表项时出错。虽然这不应该发生,但确实如此,并且它有点可以理解,因为在每种情况下防止它都会排除许多有效的查询计划。无论数据如何,绝大多数SELECT表达式都不会引发错误。

你可以做的是尝试使用额外的CASE表达来防止这种情况。使用&#39; /&#39;来防止字符串字符,例如:

... SUM(CASE WHEN c.answer_text1 IS NOT NULL and c.answer_text1 NOT LIKE '%/%' THEN CAST(c.answer_text1 as int) ELSE 0 END)...

如果您正在使用SQL Server 2012,则有更好的选择:TRY_CONVERT:

...SUM(COALESCE(TRY_CONVERT(int,c.answer_text1),0)...

在您的特定情况下,整体数据库设计存在缺陷,因为数字信息应存储在数字类型列中。 (当然,这可能不是你的错。)所以重新设计是一个选项,将整数答案放在整数类型列中,将非整数answer_text放在其他地方。如果您不能重新设计表,我认为可以使用的折衷方案是添加一个值为TRY_CONVERT(int,c.answer_text1)的持久计算列(或者它的最佳等效值,基于您对此的了解)表中的实际数据 - 可能只包含不包含非数字字符且长度小于9的列的整数值。

答案 2 :(得分:1)

另一个与史蒂夫的优秀答案类似的选择是使用如下子查询过滤你的答案表:

select
        l.idlocation,    

        'RESULT' = (
                      case when (
                        select 
                        sum(cast(isnull(c.answer_text1,0) as int)) +
                        sum(cast(isnull(c.answer_text2,0) as int)) 
                        from (select answer_text1, answer_text2, idlocation from Answers where question_number ='05') c
                        where b.idlocation=c.idlocation
                      ) > 0 then
                                   1
                      else 
                                   0
                      end
                    ) 

        from Locations l, Answers b
        where l.idlocation=b.idlocation and b.question_number='05'

但更一般地说,您可以像这样查询

select locations.idlocation, case when sum(case when is_numeric(answer_text1) then answer_text1 else 0 end) + sum(case when is_numeric(answer_text2) then answer_text2 else 0 end) > 0 then 1 else 0 end as RESULT  from locations
inner join answers on answers.idlocation = locations.idlocation
where answers.question_number ='05'
group by locations.idlocation

哪会产生相同的结果。