13

我有一张表,其中包含 MS SQL 2005 表中许多不同“事物”的价格。每件事情每天有数百条记录,不同的事情在不同的时间获得价格更新。

ID uniqueidentifier not null,
ThingID int NOT NULL,
PriceDateTime datetime NOT NULL,
Price decimal(18,4) NOT NULL

我需要获取一组物品的今天最新价格。下面的查询有效,但我得到了数百行,我必须遍历它们并且只提取每个 ThingID 的最新行。我如何(例如通过 GROUP BY)说我想要每个 ThingID 的最新版本?还是我必须使用子查询?

SELECT * 
FROM Thing
WHERE ThingID IN (1,2,3,4,5,6)
  AND PriceDate > cast( convert(varchar(20), getdate(), 106) as DateTime) 

更新:为了隐藏复杂性,我将 ID 列放在一个 int 中。在现实生活中,它是 GUID(而不是顺序类型)。我已经更新了上面的表 def 以使用 uniqueidentifier。

4

10 回答 10

21

我认为您的表结构的唯一解决方案是使用子查询:

SELECT *
   FROM Thing
   WHERE ID IN (SELECT max(ID) FROM Thing 
                   WHERE ThingID IN (1,2,3,4)
                   GROUP BY ThingID)

(给定最高的ID也意味着最新的价格)

但是,我建议您添加一个“IsCurrent”列,如果它不是最新价格,则为 0,如果是最新价格,则为 1。这会增加数据不一致的可能风险,但是当表变大时(如果它在索引中),它将大大加快整个过程。那么你需要做的就是...

SELECT *
   FROM Thing
   WHERE ThingID IN (1,2,3,4)
     AND IsCurrent = 1

更新

好的,Markus 更新了问题以表明 ID 是唯一 ID,而不是 int。这使得编写查询更加复杂。

SELECT T.* 
   FROM Thing T
   JOIN (SELECT ThingID, max(PriceDateTime)
            WHERE ThingID IN (1,2,3,4)
            GROUP BY ThingID) X ON X.ThingID = T.ThingID 
                                AND X.PriceDateTime = T.PriceDateTime
   WHERE ThingID IN (1,2,3,4)

我真的建议使用“IsCurrent”列或使用答案中的其他建议并使用“当前价格”表和单独的“价格历史”表(这最终将是最快的,因为它保持价格表本身很小)。

(我知道底部的 ThingID 是多余的。只需尝试使用或不使用“WHERE”是否更快。不确定优化器完成工作后哪个版本会更快。)

于 2008-09-08T10:16:28.780 回答
3

我会尝试类似下面的子查询,而忘记更改您的数据结构。

SELECT
 *
FROM
 Thing
WHERE 
 (ThingID, PriceDateTime) IN 
 (SELECT 
   ThingID, 
   max(PriceDateTime ) 
  FROM 
   Thing 
  WHERE 
   ThingID IN (1,2,3,4)
  GROUP BY 
   ThingID
 )

编辑上面是ANSI SQL,我现在猜测子查询中有多个列不适用于T SQL。Marius,我无法测试以下内容,但请尝试;

SELECT
 p.*
FROM
 Thing p,
 (SELECT ThingID, max(PriceDateTime ) FROM Thing WHERE ThingID IN (1,2,3,4) GROUP BY ThingID) m
WHERE 
 p.ThingId = m.ThingId
 and p.PriceDateTime = m.PriceDateTime

另一种选择可能是将日期更改为字符串并与 id 连接,这样您就只有一列。不过,这会有点讨厌。

于 2008-09-08T10:24:40.383 回答
2

如果子查询路由太慢,我会考虑将您的价格更新视为审计日志并维护 ThingPrice 表 - 可能作为价格更新表的触发器:

ThingID int not null,
UpdateID int not null,
PriceDateTime datetime not null,
Price decimal(18,4) not null

主键只是 ThingID,“UpdateID”是原始表中的“ID”。

于 2008-09-08T10:29:04.163 回答
2

由于您使用的是 SQL Server 2005,因此可以使用新的 (CROSS|OUTTER) APPLY 子句。APPLY 子句让您可以使用表值函数连接表。

为了解决这个问题,首先定义一个表值函数来从 Thing 中检索特定 id 的前 n 行,按日期排序:

CREATE FUNCTION dbo.fn_GetTopThings(@ThingID AS GUID, @n AS INT)
  RETURNS TABLE
AS
RETURN
  SELECT TOP(@n) *
  FROM Things
  WHERE ThingID= @ThingID
  ORDER BY PriceDateTime DESC
GO

然后使用该函数检索查询中的前 1 条记录:

SELECT *
   FROM Thing t
CROSS APPLY dbo.fn_GetTopThings(t.ThingID, 1)
WHERE t.ThingID IN (1,2,3,4,5,6)

这里的魔法是由 APPLY 子句完成的,它将函数应用于左侧结果集中的每一行,然后与函数返回的结果集连接,然后重新调整最终结果集。(注意:要像应用一样进行左连接,请使用 OUTTER APPLY 从左侧返回所有行,而 CROSS APPLY 仅返回在右侧匹配的行)

BlaM:因为我还不能发表评论(由于重复点低),甚至不能对我自己的答案发表评论 ^^,我会在消息正文中回答:-即使是 APPLY 子句,如果它使用表值函数它由 SQL Server 在内部进行优化,它不会为左侧结果集中的每一行调用函数,而是从函数中获取内部 sql 并将其转换为与查询的其余部分的连接子句,因此性能与使用子查询的查询性能相当甚至更好(如果该计划由 sql server 正确选择并且可以进行进一步优化),并且根据我的个人经验,当数据库正确时,APPLY 没有性能问题索引和统计信息是最新的(就像在这种情况下带有子查询的普通查询一样)

于 2008-09-08T11:44:08.007 回答
1

这取决于如何使用您的数据的性质,但如果旧的价格数据不会像当前价格数据那样经常使用,那么这里可能存在价格历史表的争论。这样,当新价格出现时,非当前数据可以存档到价格历史表(可能通过触发器)。

正如我所说,根据您的访问模式,这可能是一种选择。

于 2008-09-08T10:25:01.973 回答
1

我正在将 uniqueidentifier 转换为二进制文件,以便获得 MAX 个。这应该确保您不会从具有相同 ThingID 和 PriceDateTimes 的多条记录中获得重复项:

SELECT * FROM Thing WHERE CONVERT(BINARY(16),Thing.ID) IN
(
 SELECT MAX(CONVERT(BINARY(16),Thing.ID))
  FROM Thing
  INNER JOIN
   (SELECT ThingID, MAX(PriceDateTime) LatestPriceDateTime FROM Thing
    WHERE PriceDateTime >= CAST(FLOOR(CAST(GETDATE() AS FLOAT)) AS DATETIME)
    GROUP BY ThingID) LatestPrices
  ON Thing.ThingID = LatestPrices.ThingID
   AND Thing.PriceDateTime = LatestPrices.LatestPriceDateTime
 GROUP BY Thing.ThingID, Thing.PriceDateTime
) AND Thing.ThingID IN (1,2,3,4,5,6)
于 2008-09-08T10:44:43.207 回答
1

由于 ID 不是连续的,我假设您在 ThingID 和 PriceDateTime 上有一个唯一索引,因此对于给定项目,只有一个价格可以是最新的。

如果今天定价,此查询将获取列表中的所有项目。如果您删除 PriceDate 的 where 子句,无论日期如何,您都将获得最新价格。

SELECT * 
FROM Thing thi
WHERE thi.ThingID IN (1,2,3,4,5,6)
  AND thi.PriceDateTime =
     (SELECT MAX(maxThi.PriceDateTime)
      FROM Thing maxThi
      WHERE maxThi.PriceDateTime >= CAST( CONVERT(varchar(20), GETDATE(), 106) AS DateTime)
        AND maxThi.ThingID = thi.ThingID)

请注意,我将“>”更改为“>=”,因为您可以在一天开始时获得价格

于 2008-09-08T11:44:07.587 回答
0

试试这个(前提是您只需要最新价格,而不是该价格的标识符或日期时间)

SELECT ThingID, (SELECT TOP 1 Price FROM Thing WHERE ThingID = T.ThingID ORDER BY PriceDateTime DESC) Price
FROM Thing T
WHERE ThingID IN (1,2,3,4) AND DATEDIFF(D, PriceDateTime, GETDATE()) = 0
GROUP BY ThingID
于 2008-09-08T12:01:56.560 回答
0

它必须在不使用全局 PK 列的情况下工作(例如,对于复杂的主键):

SELECT t1.*, t2.PriceDateTime AS bigger FROM Prices t1 
LEFT JOIN Prices t2 ON t1.ThingID = t2.ThingID AND t1.PriceDateTime < t2.PriceDateTime 
HAVING t2.PriceDateTime IS NULL
于 2013-06-24T14:09:34.093 回答
-1

也许我误解了 taks 但那又如何:

SELECT ID, ThingID, max(PriceDateTime), Price FROM Thing GROUP BY ThingID

于 2013-06-12T11:08:44.530 回答