场景:我需要显示最近20个报告值的平均值。我需要为所有用户执行此操作。我使用的是Sql Server 2005 Express。这是我需要支持的最低版本的数据库服务器。
我现在这样做的方法是:1个查询来获取所有用户。每个用户1次查询以获取最近20个报告的值。虽然由于商业原因我实际上无法在sql中执行平均值,但我们暂时假设我可以。
有了这个假设,在我的脑海中,sql按日期排序,每个用户限制20行,最后按用户ID排序。不幸的是,在sql中似乎没有任何方法可以做到这一点。
有没有办法避免N + 1查询?
EDIT1:
Ericb的回答完成了工作。然而,我会等待一段时间才能将其标记为答案,原因有两个。
同样的问题,但删除了假设:
平均需要在最近的20份连续报告中完成。意思是,假设最新的20行(按顺序排列)包含15行(20到6),时间是下午2:25到2:40 PM。并且行5到1包含的时间是下午2:43到下午2:48 ... 最近的连续数据集是第5行到第1行。因此,仅需要对这5行进行平均< / EM> 即可。它不像数据会分批出现,所以数字15和5可以很容易地分为10和10或3和5和12甚至全部20连续(为简单起见我假设最新的20将全部是连续的。
你们觉得怎么样?可以在sql中完成,还是在c#中最好处理?
编辑2: 我在考虑它。在c#中,我将从最近的日期开始。减去1分钟。并检查下一个最近的日期是否与此值匹配。如果是,请将其添加到列表中。看看这些步骤,我无法想象如何在sql中复制这样的东西。事实上,我仍然不确定ericb答案的c#等价物是什么。这让我想知道,在sql中怎么想?
答案 0 :(得分:4)
希望我正在解释这一点。我假设一个非常基本的表设置:
CREATE TABLE Reports
(
UserId INT,
Report INT,
CreatedOn DATETIME
)
CREATE TABLE Users
(
UserId INT
)
SELECT x.UserId, AVG(x.Report) as Report_Avg
FROM
(
SELECT R.Report, U.UserId, ROW_NUMBER() OVER (PARTITION BY U.UserId ORDER BY R.CreatedOn DESC) as RowNum
FROM Reports R
INNER JOIN Users U
ON R.UserId = U.UserId
) x
WHERE x.RowNum <= 20
GROUP BY x.UserId
我的代码确实使用了PARTITION BY
和ROW_NUMBER
语法,该语法应该是ANSI SQL的一部分。
答案 1 :(得分:2)
根据您的更改,您可以尝试这样的事情......
NB:这是基于所有数据都是分钟的假设,并且不会重复时间戳。如果这个假设是错误的,我建议发布您的实际数据结构并描述可输入数据的确切行为。
WITH
mostRecentData AS
(
SELECT
userID,
MAX(TimeStamp) AS TimeStamp
FROM
yourData
GROUP BY
userID
)
,
ordered_data AS
(
SELECT
[reportData].*,
DATEDIFF(minute, [reportData].TimeStamp, [mostRecentData].TimeStamp) AS offset,
ROW_NUMBER() OVER (PARTITION BY [reportData].UserID ORDER BY [reportData].TimeStamp DESC) AS sequenceID
FROM
yourData AS [reportData]
INNER JOIN
[mostRecentData]
ON [reportData].userID = [reportData].UserID
)
SELECT
UserID,
AVG(someField)
FROM
orderedData
WHERE
sequenceID <= 20 -- At most the 20 most recent values
AND sequenceID - offset = 1 -- Only Consecutive entries from the latest entry
GROUP BY
UserID
假设您有适当的索引,sequenceID <= 20
将快速解决,确保您不会为每个用户解析每条记录。
然而,sequenceID - offset
不会使用索引,因此将对这20条记录中的每条记录进行处理。但这真的不是一个很大的开销。
示例数据显示sequenceID - offset = 1
确实获得了最新的连续数据集......
TimeStamp | Row_Number() | Offset | Row_Number() - Offset
12:24 1 0 1
12:23 2 1 1
12:22 3 2 1
12:20 4 4 0
12:19 5 5 0
12:17 6 7 -1
答案 2 :(得分:0)
可能是一个坏主意,但也许这会让你走上正轨?
select id
from users u
left outer join
(
select value
from reported_values
where user_id in (1,2,3)
order by created_at desc limit 20
) as v
on u.id = v.user_id
where id in (1,2,3)
答案 3 :(得分:0)
首先,如果您知道报告值的比率,或者至少是报告值的最小比率,您可以找到最早的日期并按日期过滤。只要您在日期列上建立索引,这应该通过减少查询的行数来提高性能。
接下来,您可以按用户名分组并使用sum()函数聚合每个用户。这样可以节省N-1个查询并避免使用第一个查询,这意味着:1个查询。
示例:
select username, sum(value), count(value) as numvals from table where date > [calculated earliest date/time] group by username
有了计数,你可以做两件事。
或者,您可以删除两个聚合和group by子句,首先按用户名排序,然后按日期排序,只需选择用户名和值。然后在进行平均计算时,在DB外部进行计数(最近20次)过滤。
select username, value from table order by username, date
我的建议的成本是,除非您的用户以相同的速率获得值,否则限制不起作用,因为它会限制所有用户。但是,如果查询的数量是主要问题,我认为这些问题可以解决这个问题。
警告:我不是数据库人,所以上面的语法可能很恐怖,我的想法可能是由于脑损伤。但是,我建议确定基准测试。