我有一个表格,其中包含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)
UPDATE:为了隐藏复杂性,我将ID列放在一个int中。在现实生活中它是GUID(而不是顺序类)。我已经更新了上面的表def以使用uniqueidentifier。
答案 0 :(得分:20)
我认为表结构的唯一解决方案是使用子查询:
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”就更快。不确定优化器完成其工作后哪个版本会更快。)
答案 1 :(得分:2)
我会尝试类似下面的子查询,忘记改变数据结构。
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不起作用。马吕斯,我不能测试以下但是试试;
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连接,因此您只有一列。但这会有点讨厌。
答案 2 :(得分:2)
如果子查询路径太慢,我会将您的价格更新视为审计日志并维护ThingPrice表 - 可能是价格更新表的触发器:
ThingID int not null,
UpdateID int not null,
PriceDateTime datetime not null,
Price decimal(18,4) not null
主键只是ThingID,“UpdateID”是原始表中的“ID”。
答案 3 :(得分:2)
由于您使用的是SQL Server 2005,因此可以使用新的(CROSS | OUTTER)APPLY子句。 APPLY子句允许您使用表值函数连接表。
要解决这个问题,首先要定义一个表值函数来从Thing中检索前n行的特定id,命令日期:
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子句完成的,将函数应用于左结果集中的每一行,然后与函数返回的结果集连接,然后返回最终结果集。 (注意:要执行类似apply的左连接,请使用从左侧返回所有行的OUTTER APPLY,而CROSS APPLY仅返回右侧匹配的行)
BLAM: 因为我还不能发表评论(由于低点数),甚至不能回答我自己的答案^^,我将在消息正文中回答: - 甚至APPLY子句,如果它使用表值函数,它由SQL Server在内部进行优化,它不会为左结果集中的每一行调用函数,而是从函数中获取内部sql,使用查询的其余部分将其转换为join子句,因此性能相当于甚至更好(如果计划由sql server选择,并且可以进行进一步的优化),而不是使用子查询的查询性能),并且我的个人经验当数据库被正确编入索引并且统计信息是最新的时,APPLY没有性能问题(就像子查询在这种情况下的常规查询一样)
答案 4 :(得分:1)
这取决于您的数据使用方式的性质,但如果旧的价格数据几乎不会像当前价格数据那样经常使用,那么这里可能存在价格历史表的参数。这样,当新价格进入时,非当前数据可以存档到价格历史表中(可能通过触发器)。
正如我所说,根据您的访问模式,这可能是一个选项。
答案 5 :(得分: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)
答案 6 :(得分: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)
请注意,我更改了“&gt;”至于“&gt; =”,因为你可以在一天开始时得到一个价格
答案 7 :(得分: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
答案 8 :(得分: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
答案 9 :(得分:-1)
也许我误解了这个任务但是怎么样:
SELECT ID, ThingID, max(PriceDateTime), Price
FROM Thing GROUP BY ThingID