自连接内部和外部联接查询

时间:2013-03-07 15:22:26

标签: sql sql-server inner-join outer-join self-join

我有一个设置的表,以便一列(属性)包含诸如名字,姓氏,帐号和与数据库中的事物相关的任何其他信息之类的信息。另一列(attributeType)包含一个数字,表示属性是什么,例如1可能是名字,2个姓氏和3个帐号等。还有另一列(enddate)通过在那里设置日期来指示记录是否是最新的。通常它将被设置为当前的9999年,否则将设置为过去的某个日期。描述同一事物的所有数据在另一列(实体)中也具有唯一值,因此实体列中具有相同编号的每个记录将描述一个人。 E.g。

entity  attribute  attributetype  enddate
------  ---------  -------------  --------
1       ben        1              9999-1-1
1       alt        2              9999-1-1
1       12345      3              9999-1-1
2       sam        1              9999-1-1
2       smith      2              9999-1-1
2       98765      3              1981-1-1

我想从上表中选择一个具有特定名字和姓氏的人,其名称将是最新的,但如果不是,则不输出帐号。假设该表名为tblAccount,我将对名称部分执行以下操作:

select ta1.attribute '1st Name', ta2.attribute 'last name'
from tblAccount ta1
inner join tblAccount ta2 on ta1.entity = ta2.entity
where ta1.attribute = 'sam' and ta2.attribute = 'smith'
      and ta1.attributetype = 1 and ta2. attributetype = 2
      and ta1.enddate > getdate() and ta2.enddate > getdate()

并按预期输出名字和姓氏,但是当我想要包含帐号列时,我什么都得不输出:

select ta1.attribute '1st Name', ta2.attribute 'last name', ta3.attribute 'account#'
from tblAccount ta1
inner join tblAccount ta2 on ta1.entity = ta2.entity
left join tblAccount ta3 on ta1.entity = ta3.entity
where ta1.attribute = 'sam' and ta2.attribute = 'smith'
      and ta1.attributetype = 1 and ta2. attributetype = 2
      and ta1.enddate > getdate() and ta2.enddate > getdate()
      and ta3.attributetype = 3 and ta3.enddate > getdate()

我希望看到的是在上面的情况下,帐号#列中没有输出的名字和姓氏,它不是最新的。我做错了什么以及如何更正此查询?

2 个答案:

答案 0 :(得分:4)

您必须将日期比较移至连接条件:

select ta1.attribute '1st Name'
    , ta2.attribute 'last name'
    , ta3.attribute 'account#'
from tblAccount ta1
inner join tblAccount ta2 
    on ta1.entity = ta2.entity
     and ta1.attributetype = 1 and ta2. attributetype = 2
     and ta1.enddate > getdate() and ta2.enddate > getdate()
left join tblAccount ta3 on ta1.entity = ta3.entity
      and ta3.attributetype = 3 and ta3.enddate > getdate()
where ta1.attribute = 'sam' and ta2.attribute = 'smith'

当它在where子句中时,如果没有帐户,则将getdate()与NULL进行比较,返回NULL。所以没有记录。

编辑:

响应对多个有效记录的有效关注,并使代码更加便于维护:

DECLARE @FNAME VARCHAR(50) = 'sam'
    , @LNAME VARCHAR(50) = 'smith'
    , @now DATETIME2(7) = GETDATE();

SELECT 
    name.[1st Name]
    , name.[last name]
    , name.entity
    , 
        (
            select 
                top 1 
                ta3.attribute
            FROM tblAccount ta3 
            WHERE 
                ta3.entity = name.entity
                and 
                ta3.attributetype = 3 
                and 
                ta3.enddate > @now
            ORDER BY 
                ta3.enddate 
        )
FROM 
    (        
        select 
            ta1.attribute '1st Name'
            , ta2.attribute 'last name'
            , ta.entity
            , ROW_NUMBER()
                OVER(
                    PARTITION BY 
                        ta1.entity
                    ORDER BY 
                        ta1.enddate
                    ) r
        from 
            tblAccount ta1
        inner join tblAccount ta2 
            on 
            ta1.entity = ta2.entity
            and 
            ta2. attributetype = 2
            and 
            ta2.enddate > @now
            and 
            ta2.attribute = @LNAME
        where 
            ta1.attributetype = 1 
            and 
            ta1.attribute = @fname 
            and 
            ta1.enddate > @now
    ) name
WHERE    
    NAME.r = 1

此代码围绕每个名/姓的一个实体的隐含假设以及执行时间之后的一个完整日期。变量存储过程友好,并允许您更改“截至日期”。如果你坚持使用EAV,你可能会想要存储过程。我假设任何后来的记录只有在该记录到期后才有效,我将在有关日期之后结束第一个记录。也许这太过分了,因为它超出了OP问题的范围,但这是一个有效的观点。

我说“坚持使用EAV”。虽然EAV并不总是坏事;也没有在后面射击某人。在任何一种情况下,如果你希望通过陪审团,你最好有充分的理由。在NoSQL存储模式中它很好,但EAV通常是RDBMS范例的不良实现模式。

虽然从OP后来的评论来看,看起来他受到了更好的原因之一。

答案 1 :(得分:1)

每个属性实际上是此模型中的一个独特实体,但它们都在同一个物理表中共享相同的存储(为什么?)。这会产生:

with data as (
   select entity = 1, attribute = 'ben',   attributeType=1, enddate = convert(datetime,'99990101') union all
   select entity = 1, attribute = 'alt',   attributeType=2, enddate = convert(datetime,'99990101') union all
   select entity = 1, attribute = '12345', attributeType=3, enddate = convert(datetime,'99990101') union all
   select entity = 2, attribute = 'sam',   attributeType=1, enddate = convert(datetime,'99990101') union all
   select entity = 2, attribute = 'smith', attributeType=2, enddate = convert(datetime,'99990101') union all
   select entity = 2, attribute = '67890', attributeType=3, enddate = convert(datetime,'99990101') union all
   select entity = 2, attribute = '68790', attributeType=3, enddate = convert(datetime,'20130331') union all
   select entity = 2, attribute = '876', attributeType=3, enddate = convert(datetime,'19810101') 
) 
select top 1
    FirstName, LastName, AccountNum
from (
  select top 1 
    a1.entity, FirstName, LastName
  from (
    select entity, enddate, attribute as FirstName
    from data d 
    where d.enddate >= getdate()
      and attributeType = 1
  ) a1
  join (
    select entity, enddate, attribute as LastName
    from data 
    where enddate >= getdate()
      and attributeType = 2
  ) a2 on a1.entity = a2.entity
     and a1.enddate = a2.enddate
  where FirstName = 'sam' and LastName = 'smith'
    and a1.enddate >= getdate() and a2.enddate >= getdate()
  order by a1.enddate
) E
left join (
  select entity, enddate, attribute as AccountNum
  from data 
  where enddate >= getdate()
    and attributeType = 3
) a3 on a3.entity = E.entity
order by a3.enddate

返回:

FirstName LastName AccountNum
--------- -------- ----------
sam       smith    68790

请注意,至少,会计部门在月份的安静时段输入未来的交易是很常见的,特别是如果这些交易将在月份的繁忙时段(即月末)生效。年度交易也是如此。人们不应该假设只有一个记录可以存在且有效期> GETDATE()。