在sql查询中传递整数列表,最佳实践

时间:2010-05-12 12:40:15

标签: .net sql-server performance linq-to-sql

我目前正在寻找在SQL查询中传递整数列表的方法,并尝试确定哪种方式最适合哪种情况,每种情况的好处是什么,以及存在哪些缺陷,哪些应该避免:)

现在我知道我们目前在申请中使用的3种方法。

1)表值参数: 在sql server中创建一个新的表值参数:

CREATE TYPE [dbo].[TVP_INT] AS TABLE(
    [ID] [int] NOT NULL
)

然后针对它运行查询:

using (var conn = new SqlConnection(DataContext.GetDefaultConnectionString))
{
    var comm = conn.CreateCommand();
    comm.CommandType = CommandType.Text;
    comm.CommandText = @"
UPDATE DA
    SET [tsLastImportAttempt] = CURRENT_TIMESTAMP
FROM [Account] DA
JOIN @values IDs ON DA.ID = IDs.ID";
    comm.Parameters.Add(new SqlParameter("values", downloadResults.Select(d => d.ID).ToDataTable()) { TypeName = "TVP_INT" });
    conn.Open();
    comm.ExecuteScalar();
}

这种方法的主要缺点是Linq不支持表值参数(如果你创建一个带有TVP参数的SP,linq将无法运行它):(

2)将列表转换为Binary并在Linq中使用它! 这样更好..创建一个SP,你可以在linq中运行它:)

要做到这一点,SP将有一个IMAGE参数,我们将使用用户定义的函数(udf)将其转换为表。我们目前有用C ++和汇编语言编写的这个函数的实现,两者都有相同的性能:) 基本上,每个整数由4个字节表示,并传递给SP。在.NET中,我们有一个将IEnumerable转换为字节数组的扩展方法

扩展方法:     public static Byte [] ToBinary(此IEnumerable intList)     {         return ToBinaryEnum(intList).ToArray();     }

private static IEnumerable<Byte> ToBinaryEnum(IEnumerable<Int32> intList)
{
    IEnumerator<Int32> marker = intList.GetEnumerator();
    while (marker.MoveNext())
    {
        Byte[] result = BitConverter.GetBytes(marker.Current);
        Array.Reverse(result);
        foreach (byte b in result)
            yield return b;
    }
}

SP:

CREATE PROCEDURE [Accounts-UpdateImportAttempts]
    @values IMAGE
AS
BEGIN

UPDATE DA
    SET [tsLastImportAttempt] = CURRENT_TIMESTAMP
FROM [Account] DA
JOIN dbo.udfIntegerArray(@values, 4) IDs ON DA.ID = IDs.Value4

END

我们可以直接运行SP,或者在我们需要的任何linq查询中使用它

using (var db = new DataContext())
{
    db.Accounts_UpdateImportAttempts(downloadResults.Select(d => d.ID).ToBinary());
    // or
    var accounts = db.Accounts
        .Where(a => db.udfIntegerArray(downloadResults.Select(d => d.ID).ToBinary(), 4)
            .Select(i => i.Value4)
            .Contains(a.ID));
}

此方法的好处是可以在linq中使用已编译的查询(它将具有相同的sql定义和查询计划,因此也将被缓存),并且也可以在SP中使用。

这两种方法在理论上都是无限的,因此您可以一次传递数百万的整数:)

3)简单的linq .Contains() 这是一种更简单的方法,在简单的场景中是完美的。但当然受此限制。

using (var db = new DataContext())
{
    var accounts = db.Accounts
        .Where(a => downloadResults.Select(d => d.ID).Contains(a.ID));
}

此方法的最大缺点是downloadResults变量中的每个整数都将作为单独的int传递。在这种情况下,查询受sql限制(sql查询中允许的最大参数,这是一对千万,如果我没记错的话。)


所以我想问一下......您认为这些中最好的是什么,以及我错过了哪些其他方法和方法?

1 个答案:

答案 0 :(得分:2)

您可以使用XML数据类型作为参数

DECLARE @input xml
SET @input = '<Inputs><Input>1</Input><Input>2</Input></Inputs>'

            SELECT
                Inputs.Input.value('.','int') Input
            FROM
                @input.nodes('/Inputs/Input) as Inputs(Input)