1

我正在构建一个应用程序,它将汽车性能指标记录到一个由每个会话 100 列 X 500,000 行组成的表中。一些列非常重复(以华氏度为单位的冷却剂温度)。其他列不断变化(火花提前、歧管压力、空燃比)。

列存储和页面压缩是不可能的,因为该项目是针对开源受众的。它需要支持 MS SQL Express Edition 或其他可以很好地扩展到大表大小的免费数据库平台。

我最初的解决方案是在某些列中允许为 null,这样可以通过不插入重复值来显着减少存储空间,这使我可以将日志分辨率提高到更高的帧速率。

然而,这在选择离散行时引入了一个障碍,因为某些列将 99% 为空。因此,有必要创建一个视图(或计算列?),它将选择包含该字段中的值的最后一行。我的方法是对每个稀疏列使用子查询。这似乎非常不雅。我应该考虑一种更有效的方法吗?

SELECT ISNULL(
           val1,
           (
               SELECT TOP 1 val1
               FROM   [values] subv
               WHERE  subv.id <= v.id
                      AND subv.val1 IS NOT NULL
               ORDER BY
                      subv.id DESC
           )
       ) AS val1,
       ISNULL(
           val2,
           (
               SELECT TOP 1 val2
               FROM   [values] subv
               WHERE  subv.id <= v.id
                      AND subv.val2 IS NOT NULL
               ORDER BY
                      subv.id DESC
           )
       ) AS val2,
       ISNULL(
           val3,
           (
               SELECT TOP 1 val3
               FROM   [values] subv
               WHERE  subv.id <= v.id
                      AND subv.val3 IS NOT NULL
               ORDER BY
                      subv.id DESC
           )
       ) AS val3
FROM   [values] v
4

2 回答 2

1

也许最好创建一个只包含 3 列的新表,例如MetricType, Metricvalue, MeasurementTime. 仅当特定指标的值发生更改时才插入新的测量值。

于 2013-01-17T08:57:24.693 回答
0

我想出了一个部分解决方案。

我的第一个想法是找到一种方法在调用之间的标量函数中保持值。事实证明这是不可能的,因为参数是复制输入/复制输出的。表值参数是例外,但这些是只读的,所以这没有好处。

相反,我选择使用 SQL-CLR。我在 C# 中编写了一个 .NET 类来保存调用之间的值,并将其映射到 SQL 标量函数。该代码未签名,因此要运行:

  1. 数据库必须标记为受信任
  2. CLR 程序集必须标记为 UNSAFE 以允许静态字段
  3. 另外值得注意的是,这不是一个多线程数据库应用程序,并且没有经过测试或预期可以这样工作
  4. 还值得注意的是,这个解决方案的致命缺陷是它只适用于顺序的、有序的选择,所以我想这不是一个很好的解决方案。

这是 deploy.sql 代码(我的程序集名为 SqlClr):

sp_configure 'show advanced options', 1
GO
RECONFIGURE
GO
sp_configure 'clr enabled', 1
GO
RECONFIGURE
GO

USE Test

ALTER DATABASE Test SET TRUSTWORTHY ON
GO

IF OBJECT_ID ('cached_value') is not null
BEGIN

    DROP FUNCTION cached_value
END
GO

IF EXISTS (SELECT * FROM sys.assemblies WHERE name = 'SqlClr') 
BEGIN
    DROP ASSEMBLY SqlClr
END
GO

CREATE ASSEMBLY SqlClr 
FROM 'C:\SqlClr\bin\Debug\SqlClr.dll'
WITH PERMISSION_SET = UNSAFE 
GO 

CREATE FUNCTION cached_value(@rowid bigint, @col int, @current_value bigint) 
RETURNS bigint
AS EXTERNAL NAME SqlClr.[SqlClr.StoredProcedures].[cached_value]

这是 C#.NET 代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SqlServer.Server;
using System.Diagnostics;
using System.Data.SqlClient;

namespace SqlClr
{
    public class StoredProcedures
    {
        public static long last_rowid;
        public static Dictionary<int, long> values = new Dictionary<int, long>();

        [SqlFunction(DataAccess = DataAccessKind.Read, IsDeterministic = false)]
        public static Nullable<long> cached_value(long rowid, int col, Nullable<long> current_value)
        {
            if (rowid < last_rowid)
            {
                values.Clear();
            }

            last_rowid = rowid;

            if (current_value.HasValue)
            {
                values[col] = current_value.Value;
                return current_value.Value;
            }

            if (values.ContainsKey(col))
                return values[col];
            else
                return null;
        }
    }
}

注意 .NET 函数应该用IsDeterministic = false

最后,这是一个使用 25 列函数的示例调用:

SELECT 
dbo.cached_value(id, 1, val1) AS val1,
dbo.cached_value(id, 2, val2) AS val2,
......
dbo.cached_value(id, 25, val25) AS val25
FROM LotsOfValues 

使用 64 位整数和空值随机填充 25 列 x 500,000 行,执行 aselect * from LotsOfValues需要 7 秒才能完成。使用dbo.cached_value(id, n, valn)所有 25 列的调用,相同的查询需要 19 秒。我至少可以使用 50 列,其中 25 列将是稀疏的,但我认为这将扩展到我的上限 100。这对于应用程序来说是可以接受的性能损失,并且已经实现了节省空间的目标。

于 2013-01-18T06:33:41.717 回答