我的查询有什么问题?

时间:2012-05-30 01:20:22

标签: sql sql-server sql-server-2008 subquery

我的查询(如下)不起作用。我知道为什么它不起作用,但我需要帮助修复它。基本上我正在努力做到以下几点:

从FireEvent获取所有行 获取HitEvent的所有行,这些行在FireEvent的-5/5秒内,如果HitEvent已经“配对”,那么FireEvent则不希望再次包含它。基本上,我不希望任何HitEvent不止一次出现在我的查询中!我被告知在SQL聊天室里玩RANK,如果我能弄清楚如何在我的子查询中包含来自FireEvent的EventTime,它似乎会起作用...

无论如何,这是查询...

SELECT FireEvent.ExerciseID, 
       FireEvent.FireEventID, 
       tempHitEvent.HitEventID, 
       FireEvent.AssociatedPlayerID, 
       tempHitEvent.AssociatedPlayerID, 
       FireEvent.EventTime, 
       tempHitEvent.EventTime, 
       FireEvent.Longitude, 
       FireEvent.Latitude, 
       tempHitEvent.Longitude, 
       tempHitEvent.Latitude, 
       tempHitEvent.HitResult, 
       FireEvent.AmmunitionCode, 
       FireEvent.AmmunitionSource, 
       FireEvent.FireEventID, 
       0 AS 'IsArtillery' 
FROM   FireEvent 
       LEFT JOIN (SELECT HitEvent.*, 
                         FireEvent.FireEventID, 
                         Rank() 
                           OVER ( 
                             ORDER BY HitEvent.EventTime) AS RankValue 
                  FROM   HitEvent 
                         INNER JOIN FireEvent 
                                 ON FireEvent.EventTime BETWEEN 
                                    Dateadd(millisecond, -5000, 
                                    HitEvent.EventTime) AND 
                                               Dateadd(millisecond, 
                                               5000, HitEvent.EventTime) AND HitEvent.FiringPlayerID = FireEvent.PlayerID 
                   AND HitEvent.AmmunitionCode = 
                       FireEvent.AmmunitionCode
                   AND HitEvent.ExerciseID = 
                       'D289D508-1479-4C17-988C-5F6A847AE51E' 
                        AND FireEvent.ExerciseID = 
                       'D289D508-1479-4C17-988C-5F6A847AE51E' 
                   AND HitEvent.HitResult NOT IN ( 0, 1 ) ) AS 
                 tempHitEvent 
              ON ( 
              RankValue = 1
            AND tempHitEvent.FireEventID = 
                     FireEvent.FireEventID 
                     )
WHERE  FireEvent.ExerciseID = 'D289D508-1479-4C17-988C-5F6A847AE51E' 
ORDER BY HitEventID

4 个答案:

答案 0 :(得分:1)

我相信以下内容将满足您的要求,假设我没有介绍错误。我无法测试代码,但我相信我的概念是正确的。你可能很容易找到一个错字或其他一些愚蠢的错误。但我相信您只需要在CTE中加入一次,您就可以直接从CTE中选择最终结果。

关于如何将点击事件与火灾事件进行匹配,您并不是非常具体。你永远不希望命中事件出现两次,所以我按HitEventID进行分区。我假设你想以最小化两个事件时间之间差异的方式配对它们。所以我按两个事件之间差异的绝对值排序,并且我通过FireEventID打破任何关系。

编辑 - 将HitResult过滤器从where子句移动到外连接子句 EDIT2 - 附加到最后的where子句以保留所有不匹配的FireEvent(HitEventID为null)

with RankedHits as (
  select F.ExcerciseID,
         F.FireEventID,
         H.HitEventID,
         F.AssociatedPlayerID as FireAssociatedPlayerID,
         H.AssociatedPlayerID as HitAssociatedPlayerID,
         F.EventTime as FireEventTime,
         H.EventTime as HitEventTime,
         F.Longitude as FireLongitude,
         F.Latitude as FireLatitude,
         H.Longitude as HitLongitude,
         H.Latitude as HitLatitude,
         H.HitResult,
         F.AmmunitionCode,
         F.AmmunitionSource,
         F.FireEventID,
         rank() over( partition by HitEventID
                      order by abs( datediff( ms, H.EventTime, F.EventTime ) ),
                               F.FireEventID
                    ) as HitRank
    from FireEvent F
    left join HitEvent H
      on F.AmmunitionCode = H.AmmunitionCode
     and F.PlayerID = H.FiringPlayerID
     and F.ExcerciseID = H.ExcerciseID
     and H.HitResult not in( 0, 1 )
     and F.EventTime between dateadd(millisecond, -5000, H.EventTime)
                         and dateadd(millisecond,  5000, H.EventTime)
   where F.ExcerciseID = 'D289D508-1479-4C17-988C-5F6A847AE51E'
)
select ExcerciseID,
       FireEventID,
       HitEventID,
       FireAssociatedPlayerID,
       HitAssociatedPlayerID,
       FireEventTime,
       HitEventTime,
       FireLongitude,
       FireLatitude,
       HitLongitude,
       HitLatitude,
       HitResult,
       AmmunitionCode,
       AmmunitionSource,
       FireEventID,
       0 as IsArtillery
  from RankedHits
 where HitRank=1 or HitEventID is null

编辑以回应评论

以下是从您的评论链接派生的固定代码。关键错误是HitRank = 1过滤器从最后丢失。也在选择列表中替换了HitRank的HitResult。

没有必要,但我还将玩家运动ID与火灾事件运动ID进行了比较,而不是字符串文字。这样可以更容易在将来更改练习ID,因为字符串文字现在只在查询中出现一次。

我更喜欢CTE语法,但我保留了FROM子句中的内联视图语法,如您所愿。

EDIT2 - 附加到最后的where子句以保留所有不匹配的FireEvent(HitEventID为null)

INSERT #Events (
        exerciseid,
        fireeventid,
        hiteventid,
        firingplayerid,
        hitplayerid,
        firingplayerunitid,
        hitplayerunitid,
        firingplayerassociatedplayerid,
        hitplayerassociatedplayerid,
        firingtime,
        hittime,
        firinglongitude,
        firinglatitude,
        hitlongitude,
        hitlatitude,
        hitresult,
        ammunitioncode,
        ammunitionsource,
        eventid,
        isartillery
)
(
  SELECT ExerciseID, FireEventID, HitEventID, FiringPlayerID, HitPlayerID,
         FiringUnitID, HitUnitID, FireAssociatedPlayerID, HitAssociatedPlayerID,
         FireEventTime, HitEventTime, FireLongitude, FireLatitude, HitLongitude,
         HitLatitude, HitRank, AmmunitionCode, AmmunitionSource, EventID, 0
  FROM (
  select FireEvent.ExerciseID,
         FireEvent.FireEventID,
         HitEvent.HitEventID,
         FireEvent.PlayerID as FiringPlayerID,
         HitEvent.PlayerID as HitPlayerID,
         FiringPlayer.UnitID as FiringUnitID,
         HitPlayer.UnitID as HitUnitID,
         FireEvent.AssociatedPlayerID as FireAssociatedPlayerID,
         HitEvent.AssociatedPlayerID as HitAssociatedPlayerID,
         FireEvent.EventTime as FireEventTime,
         HitEvent.EventTime as HitEventTime,
         FireEvent.Longitude as FireLongitude,
         FireEvent.Latitude as FireLatitude,
         HitEvent.Longitude as HitLongitude,
         HitEvent.Latitude as HitLatitude,
         HitEvent.HitResult,
         FireEvent.AmmunitionCode,
         FireEvent.AmmunitionSource,
         FireEvent.FireEventID AS 'EventID',
         rank() over( partition by HitEventID
                      order by abs( datediff( ms, HitEvent.EventTime, FireEvent.EventTime ) ),
                               FireEvent.FireEventID
                    ) as HitRank
    from FireEvent
    left join HitEvent
      on FireEvent.ExerciseID = HitEvent.ExerciseID
      and FireEvent.AmmunitionCode = HitEvent.AmmunitionCode
     and FireEvent.PlayerID = HitEvent.FiringPlayerID
     and HitEvent.HitResult not in( 0, 1 )
     and FireEvent.EventTime between dateadd(millisecond, -5000, HitEvent.EventTime)
                         and dateadd(millisecond,  5000, HitEvent.EventTime)
    LEFT JOIN Player FiringPlayer
           ON FiringPlayer.PlayerID = FireEvent.PlayerID
          AND FiringPlayer.ExerciseID = FireEvent.ExcerciseID
    LEFT JOIN Player HitPlayer
           ON HitPlayer.PlayerID = HitEvent.PlayerID
          AND HitPlayer.ExerciseID = FireEvent.ExcerciseID
   where FireEvent.ExerciseID = 'D289D508-1479-4C17-988C-5F6A847AE51E'
  ) as temp
  where HitRank=1 or HitEventID is null
)

答案 1 :(得分:1)

问题在于您使用的是RANK()而不是ROW_NUMBER()。如果任何hitevent与匹配的事件的数量相同,则它们的排名均为1,这意味着您将获得重复。根据我们在聊天中的对话,将整个查询包装在子查询中,现在摆脱内部子查询并进行直接连接,然后从外部查询中限制为HitRank = 1,您将成为黄金。

答案 2 :(得分:0)

我不确定这是否是您想要的,但您可以在查询开头使用CTE

  With cte as (
  SELECT FireEvent.ExerciseID, 
   FireEvent.FireEventID, 
   tempHitEvent.HitEventID, 
   FireEvent.AssociatedPlayerID, 
   tempHitEvent.AssociatedPlayerID, 
   FireEvent.EventTime, 
   tempHitEvent.EventTime, 
   FireEvent.Longitude, 
   FireEvent.Latitude, 
   tempHitEvent.Longitude, 
   tempHitEvent.Latitude, 
   tempHitEvent.HitResult, 
   FireEvent.AmmunitionCode, 
   FireEvent.AmmunitionSource, 
   FireEvent.FireEventID, 
   0 AS 'IsArtillery' 
 FROM   FireEvent )
 Select * from CTE 
   LEFT JOIN (SELECT HitEvent.*, 
                     FireEvent.FireEventID, 
                     Rank() 
                       OVER ( 
                         ORDER BY HitEvent.EventTime) AS RankValue 
              FROM   HitEvent 
                     INNER JOIN FireEvent 
                             ON FireEvent.EventTime BETWEEN 
                                Dateadd(millisecond, -5000, 
                                HitEvent.EventTime) AND 
                                           Dateadd(millisecond, 
                                           5000, HitEvent.EventTime) AND      HitEvent.FiringPlayerID = FireEvent.PlayerID 
               AND HitEvent.AmmunitionCode = 
                   FireEvent.AmmunitionCode
               AND HitEvent.ExerciseID = 
                   'D289D508-1479-4C17-988C-5F6A847AE51E' 
                    AND FireEvent.ExerciseID = 
                   'D289D508-1479-4C17-988C-5F6A847AE51E' 
               AND HitEvent.HitResult NOT IN ( 0, 1 ) ) AS 
             tempHitEvent 
          ON ( 
          RankValue = 1
        AND tempHitEvent.FireEventID = 
                 FireEvent.FireEventID 
                 )
  WHERE  FireEvent.ExerciseID = 'D289D508-1479-4C17-988C-5F6A847AE51E' 
  ORDER BY HitEventID

答案 3 :(得分:0)

以下大致简化的示例应该产生最佳对。考虑到当前时间,它可能会产生沙鼠,我不会注意到。

declare @Fires as Table ( FireId Int Identity, FireTime Int )
insert into @Fires ( FireTime ) values
  ( 1 ), ( 2 ), ( 3 ), ( 5 ), ( 8 ), ( 13 ), ( 21 ), ( 34 )

declare @Hits as Table ( HitId Int Identity, HitTime Int )
insert into @Hits ( HitTime ) values
  ( 5 ), ( 7 ), ( 12 ), ( 12 ), ( 20 ), ( 21 ), ( 30 )

-- Create a temporary table of all possible pairs meeting the time criteria.
select F.*, Dense_Rank() over ( partition by H.HitTime, H.HitId order by F.FireTime, F.FireId ) as [FireRank],
  H.*, Dense_Rank() over ( order by H.HitTime, H.HitId ) as [HitRank]
  into #Pairs
  from @Fires as F inner join
    @Hits as H on F.FireTime between H.HitTime - 5 and H.HitTime + 5

-- Clean up the pairs.
declare @HitRank as Int = 1
declare @MaxHitRank as Int = ( select Max( HitRank ) from #Pairs )
declare @MinFireRank as Int
while ( @HitRank <= @MaxHitRank )
  begin
  select @MinFireRank = Min( FireRank ) from #Pairs where HitRank = @HitRank
  delete from #Pairs
    where
      -- It is not the first   FireRank   for the current   HitRank .
      ( HitRank = @HitRank and FireRank > @MinFireRank ) or
      -- Once we pair a fire event don't pair it against another hit event.
      ( HitRank > @HitRank and FireId in ( select FireId from #Pairs where HitRank = @HitRank and FireRank = @MinFireRank ) )
  set @HitRank = @HitRank + 1
  end

-- Select the paired events.
select FireId, FireTime, HitId, HitTime
  from #Pairs
union
-- Add in the unpaired fire events.
select FireId, FireTime, NULL, NULL
  from @Fires
  where FireId not in ( select FireId from #Pairs )
order by FireTime, FireId, HitTime, HitId

drop table #Pairs