D. *_*ert 63 sql-server-2008 sql-server
我有一个进程可以抓取一堆记录(1000 条)并对它们进行操作,当我完成后,我需要将其中的大量记录标记为已处理。我可以用一个大的 ID 列表来表明这一点。我试图避免“循环更新”模式,所以我想找到一种更有效的方法来将这个 ID 包发送到 MS SQL Server 2008 存储过程中。
提案#1 - 表值参数。我可以定义一个只有一个 ID 字段的表类型,然后发送一个包含要更新的 ID 的表。
建议 #2 - XML 参数 (varchar) 和 OPENXML() 在 proc 主体中。
建议 #3 - 列表解析。如果可能的话,我宁愿避免这种情况,因为它看起来笨拙且容易出错。
其中有什么偏好,或者我错过了什么想法?
Mar*_*ian 49
关于这个问题的最好的文章是 Erland Sommarskog 的:
他涵盖了所有选项并且解释得很好。
抱歉回答的简短,但 Erland 关于数组的文章就像 Joe Celko 的关于树和其他 SQL 处理的书:)
Nic*_*mas 28
在StackOverflow上有一个很好的讨论,涵盖了许多方法。对于 SQL Server 2008+,我更喜欢使用table-valued parameters。这本质上是 SQL Server 对您的问题的解决方案——将值列表传递给存储过程。
这种方法的优点是:
但是,请注意:如果您通过 ADO.NET 或 ODBC 调用使用INSERTTVP的存储过程并查看 SQL Server Profiler 的活动,您会注意到 SQL Server 收到多个语句来加载 TVP,每行一个在 TVP 中,接着调用程序。这是设计使然 (链接断开),它使用类似于 BCP 的高效插入(另请参阅Dan Guzman对此)。INSERT每次调用过程都需要编译这批s,构成很小的开销。然而,即使有这种开销,TVP在大多数用例的性能和可用性方面仍然击败了其他方法。
如果您想了解更多信息,Erland Sommarskog对表值参数的工作原理有完整的了解,并提供了几个示例。
这是我编造的另一个例子:
CREATE TYPE id_list AS TABLE (
id int NOT NULL PRIMARY KEY
);
GO
CREATE PROCEDURE [dbo].[tvp_test] (
@param1 INT
, @customer_list id_list READONLY
)
AS
BEGIN
SELECT @param1 AS param1;
-- join, filter, do whatever you want with this table
-- (other than modify it)
SELECT *
FROM @customer_list;
END;
GO
DECLARE @customer_list id_list;
INSERT INTO @customer_list (
id
)
VALUES (1), (2), (3), (4), (5), (6), (7);
EXECUTE [dbo].[tvp_test]
@param1 = 5
, @customer_list = @customer_list
;
GO
DROP PROCEDURE dbo.tvp_test;
DROP TYPE id_list;
GO
Run Code Online (Sandbox Code Playgroud)
gbn*_*gbn 21
整个主题是在讨论了由厄兰Sommarskog明确的文章:“数组和列表在SQL Server”。选择要选择的版本。
综上所述,对于预SQL Server 2008的台湾居民入境许可证哪里王牌休息
这篇文章无论如何都值得一读,看看其他的技巧和思路。
编辑:其他地方巨大列表的迟到答案:将数组参数传递给存储过程
A-K*_*A-K 14
我知道我参加这个聚会迟到了,但我过去遇到过这样的问题,不得不发送多达 10 万个 bigint 数字,并做了一些基准测试。我们最终以二进制格式将它们作为图像发送 - 对于多达 10 万个数字,这比其他任何东西都快。
这是我的旧(SQL Server 2005)代码:
SELECT Number * 8 + 1 AS StartFrom ,
Number * 8 + 8 AS MaxLen
INTO dbo.ParsingNumbers
FROM dbo.Numbers
GO
CREATE FUNCTION dbo.ParseImageIntoBIGINTs ( @BIGINTs IMAGE )
RETURNS TABLE
AS RETURN
( SELECT CAST(SUBSTRING(@BIGINTs, StartFrom, 8) AS BIGINT) Num
FROM dbo.ParsingNumbers
WHERE MaxLen <= DATALENGTH(@BIGINTs)
)
GO
Run Code Online (Sandbox Code Playgroud)
以下代码将整数打包成二进制 blob。我在这里颠倒字节的顺序:
static byte[] UlongsToBytes(ulong[] ulongs)
{
int ifrom = ulongs.GetLowerBound(0);
int ito = ulongs.GetUpperBound(0);
int l = (ito - ifrom + 1)*8;
byte[] ret = new byte[l];
int retind = 0;
for(int i=ifrom; i<=ito; i++)
{
ulong v = ulongs[i];
ret[retind++] = (byte) (v >> 0x38);
ret[retind++] = (byte) (v >> 0x30);
ret[retind++] = (byte) (v >> 40);
ret[retind++] = (byte) (v >> 0x20);
ret[retind++] = (byte) (v >> 0x18);
ret[retind++] = (byte) (v >> 0x10);
ret[retind++] = (byte) (v >> 8);
ret[retind++] = (byte) v;
}
return ret;
}
Run Code Online (Sandbox Code Playgroud)
我在向您推荐 SO 或在这里回答它之间左右为难,因为这几乎是一个编程问题。但是因为我已经有了我使用的解决方案......我会发布它;)
这个方法的工作方式是将逗号分隔的字符串(简单拆分,不进行 CSV 样式拆分)作为 varchar(4000) 输入存储过程,然后将该列表输入到此函数中,然后返回一个方便的表,一个只有 varchars 的表。
这允许您只发送您想要处理的 id 的值,并且您可以在此时进行简单的连接。
或者,您可以使用 CLR DataTable 做一些事情并将其输入,但这需要更多的开销来支持并且每个人都理解 CSV 列表。
USE [Database]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[splitListToTable] (@list nvarchar(MAX), @delimiter nchar(1) = N',')
RETURNS @tbl TABLE (value varchar(4000) NOT NULL) AS
/*
http://www.sommarskog.se/arrays-in-sql.html
This guy is apparently THE guy in SQL arrays and lists
Need an easy non-dynamic way to split a list of strings on input for comparisons
Usage like thus:
DECLARE @sqlParam VARCHAR(MAX)
SET @sqlParam = 'a,b,c'
SELECT * FROM (
select 'a' as col1, '1' as col2 UNION
select 'a' as col1, '2' as col2 UNION
select 'b' as col1, '3' as col2 UNION
select 'b' as col1, '4' as col2 UNION
select 'c' as col1, '5' as col2 UNION
select 'c' as col1, '6' as col2 ) x
WHERE EXISTS( SELECT value FROM splitListToTable(@sqlParam,',') WHERE x.col1 = value )
*/
BEGIN
DECLARE @endpos int,
@startpos int,
@textpos int,
@chunklen smallint,
@tmpstr nvarchar(4000),
@leftover nvarchar(4000),
@tmpval nvarchar(4000)
SET @textpos = 1
SET @leftover = ''
WHILE @textpos <= datalength(@list) / 2
BEGIN
SET @chunklen = 4000 - datalength(@leftover) / 2
SET @tmpstr = @leftover + substring(@list, @textpos, @chunklen)
SET @textpos = @textpos + @chunklen
SET @startpos = 0
SET @endpos = charindex(@delimiter, @tmpstr)
WHILE @endpos > 0
BEGIN
SET @tmpval = ltrim(rtrim(substring(@tmpstr, @startpos + 1,
@endpos - @startpos - 1)))
INSERT @tbl (value) VALUES(@tmpval)
SET @startpos = @endpos
SET @endpos = charindex(@delimiter, @tmpstr, @startpos + 1)
END
SET @leftover = right(@tmpstr, datalength(@tmpstr) / 2 - @startpos)
END
INSERT @tbl(value) VALUES (ltrim(rtrim(@leftover)))
RETURN
END
Run Code Online (Sandbox Code Playgroud)
我定期收到从我们的应用程序发送的 1000 行和 10000 行的集合,以供各种 SQL Server 存储过程处理。
为了满足性能需求,我们使用 TVP,但您必须实现自己的 dbDataReader 抽象,以克服其默认处理模式中的一些性能问题。我不会讨论如何和为什么,因为它们超出了此请求的范围。
我没有考虑 XML 处理,因为我还没有找到一个 XML 实现,它仍然具有超过 10,000 个“行”的性能。
列表处理可以通过一维和二维计数(数字)表处理来处理。我们已经在各个领域成功地使用了这些,但是当“行”超过几百行时,管理良好的 TVP 的性能更高。
与有关 SQL Server 处理的所有选择一样,您必须根据使用模型进行选择。
我终于有机会做一些 TableValuedParameters 并且它们工作得很好,所以我将粘贴一个完整的 lotta 代码来展示我如何使用它们,以及我当前代码中的一些示例:(注意:我们使用 ADO 。网)
另请注意:我正在为服务编写一些代码,并且我在另一个类中有很多预定义的代码位,但是我将其编写为控制台应用程序以便我可以调试它,所以我从控制台应用程序。原谅我的编码风格(比如硬编码的连接字符串),因为它有点“构建一个扔掉”。我想展示我如何使用 aList<customObject>并将其作为表轻松推送到数据库中,我可以在存储过程中使用它。下面的 C# 和 TSQL 代码:
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using a;
namespace a.EventAMI {
class Db {
private static SqlCommand SqlCommandFactory(string sprocName, SqlConnection con) { return new SqlCommand { CommandType = CommandType.StoredProcedure, CommandText = sprocName, CommandTimeout = 0, Connection = con }; }
public static void Update(List<Current> currents) {
const string CONSTR = @"just a hardwired connection string while I'm debugging";
SqlConnection con = new SqlConnection( CONSTR );
SqlCommand cmd = SqlCommandFactory( "sprocname", con );
cmd.Parameters.Add( "@CurrentTVP", SqlDbType.Structured ).Value = Converter.GetDataTableFromIEnumerable( currents, typeof( Current ) ); //my custom converter class
try {
using ( con ) {
con.Open();
cmd.ExecuteNonQuery();
}
} catch ( Exception ex ) {
ErrHandler.WriteXML( ex );
throw;
}
}
}
class Current {
public string Identifier { get; set; }
public string OffTime { get; set; }
public DateTime Off() {
return Convert.ToDateTime( OffTime );
}
private static SqlCommand SqlCommandFactory(string sprocName, SqlConnection con) { return new SqlCommand { CommandType = CommandType.StoredProcedure, CommandText = sprocName, CommandTimeout = 0, Connection = con }; }
public static List<Current> GetAll() {
List<Current> l = new List<Current>();
const string CONSTR = @"just a hardcoded connection string while I'm debugging";
SqlConnection con = new SqlConnection( CONSTR );
SqlCommand cmd = SqlCommandFactory( "sprocname", con );
try {
using ( con ) {
con.Open();
using ( SqlDataReader reader = cmd.ExecuteReader() ) {
while ( reader.Read() ) {
l.Add(
new Current {
Identifier = reader[0].ToString(),
OffTime = reader[1].ToString()
} );
}
}
}
} catch ( Exception ex ) {
ErrHandler.WriteXML( ex );
throw;
}
return l;
}
}
}
-------------------
the converter class
-------------------
using System;
using System.Collections;
using System.Data;
using System.Reflection;
namespace a {
public static class Converter {
public static DataTable GetDataTableFromIEnumerable(IEnumerable aIEnumerable) {
return GetDataTableFromIEnumerable( aIEnumerable, null );
}
public static DataTable GetDataTableFromIEnumerable(IEnumerable aIEnumerable, Type baseType) {
DataTable returnTable = new DataTable();
if ( aIEnumerable != null ) {
//Creates the table structure looping in the in the first element of the list
object baseObj = null;
Type objectType;
if ( baseType == null ) {
foreach ( object obj in aIEnumerable ) {
baseObj = obj;
break;
}
objectType = baseObj.GetType();
} else {
objectType = baseType;
}
PropertyInfo[] properties = objectType.GetProperties();
DataColumn col;
foreach ( PropertyInfo property in properties ) {
col = new DataColumn { ColumnName = property.Name };
if ( property.PropertyType == typeof( DateTime? ) ) {
col.DataType = typeof( DateTime );
} else if ( property.PropertyType == typeof( Int32? ) ) {
col.DataType = typeof( Int32 );
} else {
col.DataType = property.PropertyType;
}
returnTable.Columns.Add( col );
}
//Adds the rows to the table
foreach ( object objItem in aIEnumerable ) {
DataRow row = returnTable.NewRow();
foreach ( PropertyInfo property in properties ) {
Object value = property.GetValue( objItem, null );
if ( value != null )
row[property.Name] = value;
else
row[property.Name] = "";
}
returnTable.Rows.Add( row );
}
}
return returnTable;
}
}
}
USE [Database]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROC [dbo].[Event_Update]
@EventCurrentTVP Event_CurrentTVP READONLY
AS
/****************************************************************
author cbrand
date
descrip I'll ask you to forgive me the anonymization I've made here, but hope this helps
caller such and thus application
****************************************************************/
BEGIN TRAN Event_Update
DECLARE @DEBUG INT
SET @DEBUG = 0 /* test using @DEBUG <> 0 */
/*
Replace the list of outstanding entries that are still currently disconnected with the list from the file
This means remove all existing entries (faster to truncate and insert than to delete on a join and insert, yes?)
*/
TRUNCATE TABLE [database].[dbo].[Event_Current]
INSERT INTO [database].[dbo].[Event_Current]
([Identifier]
,[OffTime])
SELECT [Identifier]
,[OffTime]
FROM @EventCurrentTVP
IF (@@ERROR <> 0 OR @DEBUG <> 0)
BEGIN
ROLLBACK TRAN Event_Update
END
ELSE
BEGIN
COMMIT TRAN Event_Update
END
USE [Database]
GO
CREATE TYPE [dbo].[Event_CurrentTVP] AS TABLE(
[Identifier] [varchar](20) NULL,
[OffTime] [datetime] NULL
)
GO
Run Code Online (Sandbox Code Playgroud)
另外,如果您有此建议(向遇到此问题的所有读者),我将对我的编码风格进行建设性的批评,但请保持建设性;) ...如果您真的需要我,请在此处的聊天室中找到我. 希望通过这段代码,人们可以看到他们如何使用List<Current>我将其定义为数据库中的表和List<T>应用程序中的表。
| 归档时间: |
|
| 查看次数: |
268605 次 |
| 最近记录: |