4

我必须查询一个有几百万行的表,并且我想做到最优化。

假设我们要控制对具有多个放映室的电影院的访问,并将其保存如下:

AccessRecord
  (TicketId,
   TicketCreationTimestamp,
   TheaterId,
   ShowId,
   MovieId,
   SeatId,
   CheckInTimestamp)

为简化起见,数据类型“bigint”和“时间戳”的“Id”列是“日期时间”。门票随时出售,人们随机进入剧院。主键(也是唯一的)是 TicketId。

我想为每个电影和剧院以及放映(时间)获取第一个和最后一个进入剧院看电影的人的 AccessRecord 信息。如果两个签到同时发生,我只需要 1 个,其中任何一个。

我的解决方案是在子查询中连接 PK 和分组列以获取行:

select
  AccessRecord.*
from
  AccessRecord
  inner join(
    select
      MAX(CONVERT(nvarchar(25),CheckInTimestamp, 121) + CONVERT(varchar(25), TicketId)) as MaxKey,
      MIN(CONVERT(nvarchar(25),CheckInTimestamp, 121) + CONVERT(varchar(25), TicketId)) as MinKey
    from
      AccessRecord
    group by
      MovieId,
      TheaterId,
      ShowId
  ) as MaxAccess
    on CONVERT(nvarchar(25),CheckInTimestamp, 121) + CONVERT(varchar(25), TicketId) = MaxKey
    or CONVERT(nvarchar(25),CheckInTimestamp, 121) + CONVERT(varchar(25), TicketId) = MinKey

转换 121 是数据时间的规范表达式,如下所示:aaaa-mm-dd hh:mi:ss.mmm(24h),因此作为字符串数据类型排序,它将给出与作为日期时间排序相同的结果。

如您所见,此连接不是很优化,有什么想法吗?


更新我如何测试不同的解决方案

我已经在使用 SQL Server 2008 R2 的真实数据库中测试了您的所有答案,其中包含超过 3M 行的表以选择正确的。

如果我只得到第一个或最后一个访问的人:

  • Joe Taras 的解决方案持续 10 秒。
  • GarethD 的解决方案持续 21 秒。

如果我执行相同的访问但按分组列排序结果:

  • Joe Taras 的解决方案持续 10 秒。
  • GarethD 的解决方案持续 46 秒。

如果我得到两个(第一个和最后一个)访问有序结果的人:

  • Joe Taras 的(做联合)解决方案持续 19 秒。
  • GarethD 的解决方案持续 49 秒。

其余的解决方案(甚至是我的)在第一次测试中持续了 60 多秒,所以我取消了它。

4

5 回答 5

1

使用分析函数可以加快查询速度,更具体地说是ROW_NUMBER,它应该减少读取次数:

WITH CTE AS
(   SELECT  TicketId,
            TicketCreationTimestamp,
            TheaterId,
            ShowId,
            MovieId,
            SeatId,
            CheckInTimestamp,
            RowNumber = ROW_NUMBER() OVER(PARTITION By MovieId, TheaterId, ShowId ORDER BY CheckInTimestamp, TicketID),
            RowNumber2 = ROW_NUMBER() OVER(PARTITION By MovieId, TheaterId, ShowId ORDER BY CheckInTimestamp DESC, TicketID)
    FROM    AccessRecord
)
SELECT  TicketId,
        TicketCreationTimestamp,
        TheaterId,
        ShowId,
        MovieId,
        SeatId,
        CheckInTimestamp,
FROM    CTE
WHERE   RowNumber = 1
OR      RowNumber2 = 1;

但是,与优化一样,您最适合调整自己的查询,您拥有要测试的数据和所有执行计划。尝试使用不同索引的查询,如果您显示实际的执行计划,SSMS 甚至会建议索引来帮助您查询。我希望(MovieId, TheaterId, ShowId)包含CheckInTimestamp作为非键列的索引会有所帮助。

于 2013-09-09T18:51:38.913 回答
1

试试这个:

select a.*
from AccessRecord a
where not exists(
    select 'next'
    from AccessRecord a2
    where a2.movieid = a.movieid
    and a2.theaterid = a.theaterid
    and a2.showid = a.showid
    and a2.checkintimestamp > a.checkintimestamp
)

通过这种方式,您可以选择最后一行作为同一部电影、电影、节目的时间戳。

每行的票(我想)都不同

于 2013-09-09T18:51:53.633 回答
0
SELECT
R1.*
FROM AccessRecord R1
LEFT JOIN AccessRecord R2
ON R1.MovieId = R2.MovieId
AND R1.TheaterId = R2.TheaterId
AND R1.ShowId = R2.ShowId
AND (
R1.CheckInTimestamp < R2.CheckInTimestamp
OR (R1.CheckInTimestamp = R2.CheckInTimestamp
AND R1.TicketId< R2.TicketId
))
WHERE R2.TicketId IS NULL

根据 CheckInTimestamp 选择最后一个条目。但是如果有一个匹配的,那么它是基于最高的 TicketId

对 MovieId、TheatreId 和 ShowId 的索引会有所帮助

这就是我学会诀窍的地方

于 2013-09-09T18:48:32.103 回答
0

将新列添加到表中并预先转换日期,或者将该访问表中的 pk 加入到一个新表中,该表已经包含转换后的值。查找转换而不是在连接上进行转换的新表将极大地加快您的查询速度。如果您可以这样做,以便访问记录获得一个整数 FK,该整数 FK 将进入查找(预转换的值)表,那么您将完全避免使用日期,并且事情会变得更快。

如果你对数据集进行标准化并将其分解为星形模式,事情会变得更快。

于 2013-09-09T18:51:29.703 回答
0

你也可以考虑一个联合 ALL qwuery 而不是那个讨厌的 OR。Ors 通常比联合 ALL 查询慢。

于 2013-09-09T19:32:44.463 回答