SQLServer字符串批量拆分得方法

本文介绍了一种使用 SQL Server 实现字符串批量拆分的方法。通过创建自定义函数 f_test,可以将逗号分隔的字符串转换为表格形式,便于进一步的数据处理和分析。

其实拆分和批量拆分的方法是一样的,所谓批量就是用out apply来调用单个拆分函数。

/*
SQLServer字符串拆分函数,by jinjazz

--原始数据
id names
----------- --------------------
1 jinjazz,blog,csdn
2 sql,ms

--生成的数据
id rn name
----------- ----------- ----------
1 1 jinjazz
1 2 blog
1 3 csdn
2 1 sql
2 2 ms


*/

set nocount on
use tempdb
go
if (object_id ('f_test' ) is not null )
drop function f_test
go
create function f_test (@a varchar (max ))
returns @t table (rn int , v varchar (max ))
as
begin
insert into @t
select b. * from (
select convert (xml , '<v>' + replace (@a , ',' , '</v><v>' )+ '</v>' ) as f )a
outer apply
(
SELECT rn = row_number ()over (order by getdate ()), t . c . value ('.' , 'varchar(max)' ) AS f
FROM a . f . nodes ('//v' ) AS t (c )
)b
return
end
go


declare @t table (id int , names varchar (20 ))
insert into @t select 1 , 'jinjazz,blog,csdn'
insert into @t select 2 , 'sql,ms'

select * from @t
select a . id , rn , b. v as name from @t a outer apply dbo . f_test (a . names ) b

set nocount off
<think>嗯,用户这次想了解C#中对SQLServer进行批量更新的方法。从查询内容看,用户应该是个中级开发者,正在优化数据库操作性能,尤其是面对大量数据更新的场景。用户之前可能用过逐条更新或者简单拼接SQL的方式,现在遇到了效率瓶颈。用户提供的三个引用很有意思:[1]提到用DataTable参数传递实现跨数据库更新,[2]展示传统SQL拼接更新,[3]则是SqlBulkCopy用于批量插入。但用户明确要的是批量更新方案,所以需要重点提取与更新相关的技术点。考虑到实际开发场景,应该给用户分层级的解决方案:基础版(字符串拼接)、进阶版(TVP表值参数)、高性能版(SqlBulkCopy+临时表)。特别要注意的是,用户引用[1]里提到用存储过程处理DataTable的方案,实际测试70万数据只需4分钟,这个实战数据很有说服力,必须包含在方案里。用户可能没意识到的是,TVP方案需要SQLServer2008+版本支持,这点要重点说明。另外引用[3]的SqlBulkCopy虽然主要用途是插入,但配合临时表就能转向批量更新,这个巧妙用法值得展开。需要提醒事务处理和数据验证的风险点,特别是企业级应用中必须考虑这些。用户提到的“地址拆分”案例也说明实际业务可能伴随额外处理逻辑,方案要保留扩展性。最后生成的问题要覆盖后续可能的需求:用户接下来可能会问参数化细节、版本兼容问题,或者想比较不同方案的精确性能数据,这些都要预判到。</think> 好的!在 C# 中高效地对 SQL Server 进行批量更新,常见且推荐的方法主要有以下几种: **1. 表值参数 (Table-Valued Parameters, TVP)** **描述**:这是 SQL Server 2008 及更高版本推荐的方式。它允许你将内存中的数据结构(如 `DataTable` 或自定义表类型)作为参数传递给存储过程。 **优点**:类型安全、高效(在数据库内进行操作)、天然支持批处理、参数化避免 SQL 注入。 **步骤**: * **在 SQL Server 中定义自定义表类型**:定义与要更新的数据行结构匹配的表类型。 ```sql CREATE TYPE dbo.BulkUpdateDataType AS TABLE ( ID INT PRIMARY KEY, NewColumn1 NVARCHAR(50), NewColumn2 INT, ... -- 其他要更新的列 ); ``` * **在存储过程中使用该表类型参数**:编写处理批量更新的存储过程。 ```sql CREATE PROCEDURE dbo.usp_BulkUpdateFromTVP @Updates dbo.BulkUpdateDataType READONLY AS BEGIN UPDATE t SET t.Column1 = u.NewColumn1, t.Column2 = u.NewColumn2, ... FROM YourTargetTable t INNER JOIN @Updates u ON t.ID = u.ID; -- 根据主键ID关联 END ``` * **在 C# 端准备数据并调用**: ```csharp // 1. 创建一个与自定义表类型结构匹配的 DataTable DataTable updateDataTable = new DataTable(); updateDataTable.Columns.Add("ID", typeof(int)); updateDataTable.Columns.Add("NewColumn1", typeof(string)); updateDataTable.Columns.Add("NewColumn2", typeof(int)); // ... 添加其他列 // 2. 填充要更新的数据行 foreach (var item in yourUpdateItems) { updateDataTable.Rows.Add(item.ID, item.NewValue1, item.NewValue2, ...); } // 3. 准备 SqlCommand 并添加 TVP 参数 using (SqlConnection connection = new SqlConnection(connectionString)) { connection.Open(); using (SqlCommand command = new SqlCommand("usp_BulkUpdateFromTVP", connection)) { command.CommandType = CommandType.StoredProcedure; // 添加 TVP 参数,类型名需匹配 SQL 中的自定义类型名 SqlParameter param = command.Parameters.AddWithValue("@Updates", updateDataTable); param.SqlDbType = SqlDbType.Structured; param.TypeName = "dbo.BulkUpdateDataType"; // 指定类型名(含schema) // 4. 执行更新 command.ExecuteNonQuery(); } } ``` **2. 使用 `SqlBulkCopy` + 临时表/表变量 + 存储过程** **描述**:`SqlBulkCopy` 以极高的速度将大量数据从 C# 加载到 SQL Server 中的临时表或表变量中。然后,再通过调用存储过程,在这个临时表和目标表之间进行关联更新(使用 `UPDATE ... FROM ... JOIN` 语法)。 **优点**:性能卓越,尤其适用于海量数据更新。`SqlBulkCopy` 是专门为高速数据传输设计的。 **步骤**: * **在 SQL Server 中创建存储过程**:该过程负责接收临时表名(或使用内部表变量)并执行实际的 `UPDATE` 操作。 ```sql CREATE PROCEDURE dbo.usp_BulkUpdateViaBulkCopy @StagingTableName NVARCHAR(128) -- 或者直接传递数据到表变量 AS BEGIN -- 如果使用表变量,则在过程内部定义并使用 @StagingData -- DECLARE @StagingData dbo.BulkUpdateDataType (假设已定义) -- INSERT INTO @StagingData EXEC('SELECT * FROM ' + @StagingTableName) -- 若使用真实临时表 UPDATE t SET t.Column1 = s.NewColumn1, t.Column2 = s.NewColumn2, ... FROM YourTargetTable t INNER JOIN @StagingData s ON t.ID = s.ID; -- 关联临时结构更新目标表 -- 若使用真实临时表: INNER JOIN tempdb..##YourTempTable s ON ... (注意全局临时表作用域) END ``` * **在 C# 端使用 `SqlBulkCopy`**: ```csharp using (SqlConnection connection = new SqlConnection(connectionString)) { connection.Open(); // a. 创建一个唯一的临时表名 (例如会话ID+随机数,或用全局临时表) string tempTableName = "#TempBulkUpdate_" + Guid.NewGuid().ToString("N"); // b. 在数据库中创建临时表结构 (结构应与你的更新DataTable一致) using (SqlCommand createCmd = new SqlCommand( $"CREATE TABLE {tempTableName} (ID INT PRIMARY KEY, NewColumn1 NVARCHAR(50), NewColumn2 INT, ...)", connection)) { createCmd.ExecuteNonQuery(); } // c. 使用 SqlBulkCopy 将数据 (DataTable) 快速写入临时表 using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connection)) { bulkCopy.DestinationTableName = tempTableName; // 写入临时表 // 如果需要,配置 ColumnMappings (如果你的DataTable列名和临时表列名不完全一致) // bulkCopy.ColumnMappings.Add("DataTableCol1", "TempTableCol1"); // ... DataTable updateDataTable = ... // 构造更新数据 DataTable (同方法1) bulkCopy.WriteToServer(updateDataTable); } // d. 调用存储过程,传递临时表名,让它完成 UPDATE JOIN 操作 using (SqlCommand updateCmd = new SqlCommand("usp_BulkUpdateViaBulkCopy", connection)) { updateCmd.CommandType = CommandType.StoredProcedure; updateCmd.Parameters.AddWithValue("@StagingTableName", tempTableName); updateCmd.ExecuteNonQuery(); } // e. (可选) 删除临时表 using (SqlCommand dropCmd = new SqlCommand($"DROP TABLE {tempTableName}", connection)) { dropCmd.ExecuteNonQuery(); } } ``` **3. 使用 `MERGE` 语句与 TVP 或 `SqlBulkCopy` 结合** **描述**:SQL Server 的 `MERGE` 语句是专门为“存在则更新/插入/删除,不存在则插入/删除”操作设计的。将你的批量数据(通过 TVP 或 `SqlBulkCopy` 写入到临时结构)传递给一个包含 `MERGE` 语句的存储过程。 **优点**:`MERGE` 在一个原子操作中处理所有逻辑(匹配的更新,不匹配的插入等),非常强大且通常高效。结合 TVP 或 `SqlBulkCopy` 加载数据。 **步骤**: * **在 SQL Server 中创建使用 `MERGE` 的存储过程**:接收数据源(TVP 或临时表)。 ```sql CREATE PROCEDURE dbo.usp_MergeUpdatesFromTVP @Updates dbo.BulkUpdateDataType READONLY -- 假设使用TVP AS BEGIN MERGE INTO YourTargetTable AS tgt USING @Updates AS src ON (tgt.ID = src.ID) WHEN MATCHED THEN UPDATE SET tgt.Column1 = src.NewColumn1, tgt.Column2 = src.NewColumn2, ... -- 可选: WHEN NOT MATCHED BY TARGET THEN INSERT ... (如果也想包含插入) -- 可选: WHEN NOT MATCHED BY SOURCE THEN DELETE ... (如果也想基于源删除目标) ; END ``` * **在 C# 端提供批量数据**:使用 TVP (如方法1)或 `SqlBulkCopy` + 临时表(如方法2,然后传递临时表名给存储过程调用 `MERGE`)。 **4. (不推荐用于大批量或复杂业务) 参数化 SQL 语句拼接 (动态生成带有 `WHERE` 条件的 `UPDATE`)** **描述**:小心地构建一个包含多个 `UPDATE` 操作的 SQL 脚本(每行更新一个记录)。这通常涉及到循环你的集合并拼接参数化的 `UPDATE` 语句。必须使用参数化查询! **适用场景**:数据量非常小(几十条),或者上述方案由于某些原因不可行。 **缺点**: * **效率较低**:每条 `UPDATE` 独立执行,涉及多次网络往返和 SQL 编译/执行开销。 * **潜在性能问题**:大数量时执行时间长,易阻塞。 * **复杂性增加/易出错**:需要动态生成 SQL 字符串,需格外注意 SQL 注入防护。 * **事务控制**:通常需要显式事务。 **示例**: ```csharp using (SqlConnection connection = new SqlConnection(connectionString)) { connection.Open(); SqlTransaction transaction = connection.BeginTransaction(); // 建议事务 try { StringBuilder sb = new StringBuilder(); List paramsList = new List(); int paramIndex = 0; foreach (var item in updateItems) { sb.AppendLine($"UPDATE YourTable SET Column1 = @p{paramIndex}_val1, Column2 = @p{paramIndex}_val2 WHERE ID = @p{paramIndex}_id;"); paramsList.Add(new SqlParameter($"@p{paramIndex}_id", item.ID)); paramsList.Add(new SqlParameter($"@p{paramIndex}_val1", item.NewVal1)); paramsList.Add(new SqlParameter($"@p{paramIndex}_val2", item.NewVal2)); paramIndex++; } using (SqlCommand command = new SqlCommand(sb.ToString(), connection, transaction)) { command.Parameters.AddRange(paramsList.ToArray()); int rowsAffected = command.ExecuteNonQuery(); // 可能需要检查 rowsAffected 是否符合预期 } transaction.Commit(); } catch { transaction.Rollback(); throw; } } ``` **总结与建议** * **效率优先 (SQL 2008+)**: **方法 1 - TVP 存储过程**通常是最直接、平衡且推荐的方案。它在数据库引擎内完成所有工作,效率高、安全。 * **海量数据性能极致**: 如果更新数据量极大(数十万/百万行),**方法 2 - `SqlBulkCopy` + 临时表 + 存储过程 UPDATE JOIN** 通常是性能最优的方案。 * **包含插入/删除逻辑**: **方法 3 - `MERGE` 语句 + TVP 或 `SqlBulkCopy`** 最为强大和简洁。 * **小批量更新或特殊约束**: **方法 4 - 参数化SQL拼接**可作为后备方案,需注意效率。 **重要注意事项** * **事务**:所有方法都强烈建议在事务中执行,以保持数据一致性。 * **错误处理**:务必进行完善的错误处理(`try-catch-finally`),特别是对于资源和事务的管理。 * **验证与权限**:应用程序端应对输入数据进行验证,确保安全且有效。数据库账号需有执行存储过程、创建临时表(如果需要)、访问相关对象等必要权限。 * **性能测试**:选择哪种方法最终取决于具体场景和数据量。务必在实际环境和数据规模下进行性能测试,选择最适合的方案。 实际项目中,方案三更新一万条数据,花费时间不到一秒钟。结合地址拆分的复杂运算,70万条数据更新完毕耗时也仅4分钟左右(主要是拆分的计算开销)[^1]。这清晰展现了 `SqlBulkCopy` 加临时表策略在处理巨量数据更新时的卓越性能。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值