从大表中按日期选择每第 n 个值

m__*_*m__ 4 sql-server sql-server-2012

为了清楚起见,在 2012-06-20 重写了这个问题,检查旧版本的编辑

背景和问题

我为某些设备上的几个传感器收集数据。我想在图形中以曲线的形式为用户显示这些数据,即我想绘制测量点并在它们之间画线。用户可以通过在图表中缩放和平移来决定他想要显示值的日期范围。

测量值的数量开始增加,此时我有许多值在500'000'000范围内,并且还在计数。对于单个传感器,到目前为止,我最多可以找到大约2'000'000 个值,但它很可能会增加。这些值不是以均匀间隔(即每秒)记录的,而是在可以测量更改时记录的(请参阅下面日期列中的差异)。

Date  SensorValue
----- -----------
10    123
30    118
70    114
85    115
90    116
95    117
Run Code Online (Sandbox Code Playgroud)

由于此图形的显示区域为1000 像素,因此获取所有这些值毫无意义。相反,我尝试获取最多1000 个值,并将它们均匀分布在用户放大的日期范围内。确切地返回日期范围内的哪些值并不重要,只要它们在日期方面均匀分布(记住这一点很重要,因为它们不是以均匀间隔记录的)。

我将这些值存储在一个非常简单的表中,如下所示,只有一个(聚集)索引。请注意,Date存储为整数(“unix 时间”)而不是datetime2字段。该表按Date是否重要进行分区。

CREATE TABLE [dbo].[SensorValues](
  [DeviceId] [int] NOT NULL,
  [SensorId] [int] NOT NULL,
  [SensorValue] [int] NOT NULL,
  [Date] [int] NOT NULL,
CONSTRAINT [PK_SensorValues] PRIMARY KEY CLUSTERED 
(
  [DeviceId] ASC,
  [SensorId] ASC,
  [Date] DESC
);
Run Code Online (Sandbox Code Playgroud)

我正在寻找两件事之一,要么是一个改进的查询,它可以更快地返回这些值,要么是一种关于如何提取这些值的不同方法。如果有什么不清楚的,请继续问我,我会尽力回答。

我目前的做法

为了实现这一点,我计算了我正在获取数据的日期范围的时间差,并将其除以我最多想要获取的值的数量。我将其存储在@resolution下面调用的变量中。

当我查询数据库时,它会按日期除以计算并分组匹配的行,@resolution并为每组挑选一个值。我知道这不是一个万无一失的算法。在某些情况下,它返回的值比请求的多,但对我来说这并不重要,因为它至少将值的数量保持在可管理的水平。

对于具有少量数据(数万个值)的传感器,这相当快,但是当涉及具有大量数据(数百万行)的传感器时,它会变慢并需要几秒钟(至少)进行查询数据库。应该如何以更好的方式查询这些信息?

SELECT sv.SensorValue, sv.Date
  FROM SensorValues sv
  INNER JOIN (SELECT MAX(svInner.Date) AS svInner.Date
                FROM SensorValues svInner
                WHERE svInner.SensorId = @sensorid
                  AND svInner.DeviceId = @deviceid
                  AND svInner.Date BETWEEN @startdate AND @stopdate
                  AND svInner.Sensorvalue != -32767
                  AND svInner.SensorValue != -32768
                  GROUP BY svInner.Date / @resolution) j ON sv.Date = j.Date
  WHERE sv.SensorId = @sensorid
    AND sv.DeviceId = @deviceid
    AND sv.SensorValue != -32767
    AND sv.SensorValue != -32768
  ORDER BY sv.Date DESC
Run Code Online (Sandbox Code Playgroud)

上述查询的查询计划

示例:如果我想从上面的表格示例中选择时间 0 到 95 之间的 3 个值,我会执行以下操作。

我计算@resolutionto(95-0)/3=31然后对于时间 0 和 95 之间的每一行,计算Date/@resolution(到处都是整数除法):

Date  SensorValue Date/@resolution
----- ----------- ----------------
10    123         0
30    118         0
70    114         2
85    115         2
90    116         2
95    117         3
Run Code Online (Sandbox Code Playgroud)

然后我通过使用从每个组中选择一个值MAX(Date)

Date  SensorValue Date/@resolution
----- ----------- ----------------
30    118         0
90    116         2
95    117         3
Run Code Online (Sandbox Code Playgroud)

Rem*_*anu 5

从技术上讲,要显示第 n 个值,您可以使用诸如RANKDENSE_RANK 或 之类的排名函数ROW_NUMBER。哪一个完全取决于,但你描述的最匹配ROW_NUMBER

with cte as (
 select row_number() over (order by Date) as rn, *
 from table)
where rn % 1000 = 0;
Run Code Online (Sandbox Code Playgroud)

但是您是说出于性能原因想要这样做,避免遍历数百万行。这样的查询已经造成了损害,它从磁盘读取所有行并必须对它们进行排序,因此性能代价已经支付。

选择随机数据样本的更好替代方法是使用TABLESAMPLE子句,请参阅使用 TABLESAMPLE 限制结果集

select * 
from table tablesample (100 rows);
Run Code Online (Sandbox Code Playgroud)

TABLESAMPLE 会更高效,因为它实际上避免了读取所有数据,它只对表中的一些页面进行采样并返回采样页面中的所有行。

但请考虑,如果您使用 TABLESAMPLE,您的 WHERE 子句将采样应用。因此,样本可能不包含您感兴趣的设备/传感器的任何行。对于具有小数据的传感器尤其如此。