奇怪的SQL查询行为

时间:2012-07-02 11:12:52

标签: sql sql-server tsql sql-server-2008-r2

我有两张表Test1Corr_table

Test1表创建脚本:

CREATE TABLE [dbo].[Test1](
    [id] [bigint] NOT NULL,
    [Country] [varchar](3) NULL,
    [PeriodKey] [varchar](max) NULL,
    [a] [varchar](3) NULL,
    [b] [varchar](3) NULL,
    [c] [varchar](3) NULL
) 

Test1数据:

id  Country  PeriodKey  a   b   c
1   E      201201   1   5   9
1   E      201202   1   5   9
3   G      201203   3   7   11
4   H      201204   4   8   12

Corr_table创建脚本:

CREATE TABLE [dbo].[corr_table](
    [Country] [varchar](5) NULL,
    [id] [bigint] NULL,
    [Field] [varchar](10) NULL,
    [Value] [varchar](50) NULL,
    [Start_date] [varchar](50) NULL,
    [End_date] [varchar](50) NULL
) 

corr_table数据:

Country     id  Field   Value   Start_date  End_date
E            1  a   4       201201  201202
E            1  b   6       201201  201202

现在,如果我写这个查询,

select
    a = case when x.Field = 'a' then x.value else a end,
    b = case when x.Field = 'b' then x.value else b end,
    y.*
from 
    dbo.Test1 y,dbo.corr_table x
where  
     y.id = 1
     and y.Country = 'E'
     and y.PeriodKey in (201201)

它给出了以下结果:

a   b   id  Country  PeriodKey  a   b   c
4   5   1   E    201201         1   5   9
1   6   1   E    201201         1   5   9

而我期待以下结果:

a   b   id    Country   PeriodKey   a   b   c
4   6   1   E    201201         1   5   9

为什么两列都不会在一行中更新?它一次只更新一列,但应该更新两列

即a应为4,b应为单行6。但只更新一个,为什么会这样?

2 个答案:

答案 0 :(得分:0)

要使@ marc_s的注释更明确,结果SQL将如下所示:

select
    a = case when x.Field = 'a' then x.value else a end,
    b = case when x.Field = 'b' then x.value else b end,
    y.*
from 
    dbo.Test1 y
    INNER JOIN dbo.corr_table x ON y.Country = x.Country
where  
     y.id = 1
     and y.Country = 'E'
     and y.PeriodKey in (201201)

此外,您的数据模型在其列名中应该更加明确,以便关系结构很明显 - 我不得不猜测连接条件。

答案 1 :(得分:0)

为什么您的查询会返回两行

要理解为什么查询返回两行而不是您期望的行,有助于理解SQL Server在执行查询时理论上采取的逻辑步骤。

第一个逻辑步骤是处理FROM子句。您的查询指定了两个表dbo.Test1dbo.corr_table之间的交叉连接,以生成包含8行的中间虚拟表(我们称之为V1):

ID  COUNTRY PERIODKEY   A   B   C   FIELD   VALUE   START_DATE  END_DATE
1   E   201201  1   5   9   a   4   201201  201202
1   E   201202  1   5   9   a   4   201201  201202
3   G   201203  3   7   11  a   4   201201  201202
4   H   201204  4   8   12  a   4   201201  201202
1   E   201201  1   5   9   b   6   201201  201202
1   E   201202  1   5   9   b   6   201201  201202
3   G   201203  3   7   11  b   6   201201  201202
4   H   201204  4   8   12  b   6   201201  201202

第二个逻辑步骤是处理WHERE子句。您的查询指定只有ID为1,Country为'E'且PeriodKey为201201的行才能通过。中间表中的两行满足此条件以填充另一个中间表(让我们称之为V2):

ID  COUNTRY PERIODKEY   A   B   C   FIELD   VALUE   START_DATE  END_DATE
1   E   201201  1   5   9   a   4   201201  201202
1   E   201201  1   5   9   b   6   201201  201202

第三个也是最后一个逻辑步骤是处理SELECT子句。您的查询指定应根据现有列中的值计算两个新列ab,并且应该不修改表dbo.Test1中的所有列。 SELECT子句基本上是一个列的列表,用于定义结果集的结构。它不控制结果集中有多少行。这个中间表(我们称之为V3)与结果集相同:

a   b   id  Country  PeriodKey  a   b   c
4   5   1   E    201201         1   5   9
1   6   1   E    201201         1   5   9

V2中的两行都包含您希望在结果集中看到的单行元素。基表dbo.Test1中的所有列都包含相同的值,因为这些列中的值仅从dbo.Test1中的一行复制。第一列ab中的值是从dbo.corr_table中的两个不同的行复制的。

如果我们能够将V2中的这两行组合在一起,我们可以制作一行符合我们预期的行。幸运的是,我们可以在SQL中轻松表达这一点。

如何生成预期的结果集

在SQL中,SELECT语句具有GROUP BY子句,该子句将多行组合成一行代表该组。逻辑上,它在WHERE子句之后和SELECT子句之前处理。

对于您的查询,我添加了一个GROUP BY子句,并为每个计算列调用了一个聚合函数。此查询产生您的预期结果:

SELECT
  MAX(
    CASE
      WHEN dbo.corr_table.Field = 'a'
      THEN dbo.corr_table.Value
    END
  ) AS corr_a,
  MAX(
    CASE
      WHEN dbo.corr_table.Field = 'b'
      THEN dbo.corr_table.Value
    END
  ) AS corr_b,
  dbo.Test1.*
FROM dbo.Test1
INNER JOIN dbo.corr_table ON
  dbo.Test1.id = dbo.corr_table.id
WHERE
  dbo.Test1.id = 1
  and dbo.Test1.Country = 'E'
  and dbo.Test1.PeriodKey in (201201)
GROUP BY
  dbo.Test1.id,
  dbo.Test1.Country,
  dbo.Test1.PeriodKey,
  dbo.Test1.a,
  dbo.Test1.b,
  dbo.Test1.c;

我现在没有时间解释,但我稍后会扩展我的答案并解释为什么这样做。现在,我建议您阅读有关逻辑查询处理和SELECT语句的内容。 Itzik Ben-Gan发表了一篇有用的PDF flow chart来解释逻辑步骤。它被复制为下面的图像,复制自sqlwithmanoj.wordpress.com

enter image description here