设为首页 收藏本站
查看: 1336|回复: 0

[经验分享] 好用的SQL TVP~~独家赠送[增-删-改-查]的例子

[复制链接]

尚未签到

发表于 2017-7-14 14:43:09 | 显示全部楼层 |阅读模式
  以前总是追求新东西,发现基础才是最重要的,今年主要的目标是精通SQL查询和SQL性能优化。
  本系列主要是针对T-SQL的总结。


  【T-SQL基础】01.单表查询-几道sql查询题
  【T-SQL基础】02.联接查询
  【T-SQL基础】03.子查询
  【T-SQL基础】04.表表达式-上篇
  【T-SQL基础】04.表表达式-下篇
  【T-SQL基础】05.集合运算
  【T-SQL基础】06.透视、逆透视、分组集
  【T-SQL基础】07.数据修改
  【T-SQL基础】08.事务和并发
  【T-SQL基础】09.可编程对象
  ----------------------------------------------------------
  【T-SQL进阶】01.好用的SQL TVP~~独家赠送[增-删-改-查]的例子
  ----------------------------------------------------------
  【T-SQL性能调优】01.TempDB的使用和性能问题
  【T-SQL性能调优】02.Transaction Log的使用和性能问题
  【T-SQL性能调优】03.执行计划
  【T-SQL性能调优】04.死锁分析
  持续更新......欢迎关注我!

一、什么是TVP?
  表值参数Table-Value Parameter (TVP) 提供一种将客户端应用程序中的多行数据封送到 SQL Server 的简单方式,而不需要多次往返或特殊服务器端逻辑来处理数据。 您可以使用表值参数来包装客户端应用程序中的数据行,并使用单个参数化命令将数据发送到服务器。 传入的数据行存储在一个表变量中,然后您可以通过使用 Transact-SQL 对该表变量进行操作。
  可以使用标准的 Transact-SQL SELECT 语句来访问表值参数中的列值。  
  简单点说就是当想传递aaaa,bbbb,cccc,dddd给存储过程时,可以先将aaa,bbb,ccc,dddd存到一张表中:

aaaa
bbbb
cccc
dddd




  
  然后将这张表传递给存储过程。
  如:当我们需要查询指定产品的信息时,通常可以传递一串产品ID到存储过程里面,如"1,2,3,4",然后查询出ID=1或ID=2或ID=3或ID=4的产品信息。
  可以先将"1,2,3,4"存到一张表中,然后将这张表传给存储过程。

1
2
3
4




  那么这种方法有什么优势呢?请接着往下看。

二、早期版本是怎么在 SQL Server 中传递多行的?




  在 SQL Server 2008 中引入表值参数之前,用于将多行数据传递到存储过程或参数化 SQL 命令的选项受到限制。 开发人员可以选择使用以下选项,将多个行传递给服务器:




  •   使用一系列单个参数表示多个数据列和行中的值。 使用此方法传递的数据量受所允许的参数数量的限制。 SQL Server 过程最多可以有 2100 个参数。 必须使用服务器端逻辑才能将这些单个值组合到表变量或临时表中以进行处理。

  •   将多个数据值捆绑到分隔字符串或 XML 文档中,然后将这些文本值传递给过程或语句。 此过程要求相应的过程或语句包括验证数据结构和取消捆绑值所需的逻辑。

  •   针对影响多个行的数据修改创建一系列的单个 SQL 语句,例如通过调用 SqlDataAdapter 的 Update 方法创建的内容。 可将更改单独提交给服务器,也可以将其作为组进行批处理。 不过,即使是以包含多个语句的批处理形式提交的,每个语句在服务器上还是会单独执行。

  •   使用 bcp 实用工具程序或 SqlBulkCopy 对象将很多行数据加载到表中。 尽管这项技术非常有效,但不支持服务器端处理,除非将数据加载到临时表或表变量中。


三、例子
  当我们需要查询指定产品的信息时,通常可以传递一串产品ID到存储过程里面,如"1,2,3,4",然后查询出ID=1或ID=2或ID=3或ID=4的产品信息。
  我们可以先将“1,2,3,4”存到一张表中,然后作为参数传给存储过程。在存储过程里面操作这个参数。

1.使用TVP 查询产品
  查询产品ID=1,2,3,4,5的产品



public static void TestGetProductsByIDs()
{
Collection<int> productIDs = new Collection<int>();
Console.WriteLine();
Console.WriteLine("----- Get Product ------");
Console.WriteLine("Product IDs: 1,2,3,4,5");
productIDs.Add(1);
productIDs.Add(2);
productIDs.Add(3);
productIDs.Add(4);
productIDs.Add(5);
Collection<Product> dtProducts = GetProductsByIDs(productIDs);
foreach (Product product in dtProducts)
{
Console.WriteLine("{0}   {1}", product.ID, product.Name);
}
}
  查询的方法:



/// <summary>
/// Data access layer. Gets products by the collection of the specific product' ID.
/// </summary>
/// <param name="conn"></param>
/// <param name="productIDs"></param>
/// <returns></returns>
public static Collection<Product> GetProductsByIDs(SqlConnection conn, Collection<int> productIDs)
{
Collection<Product> products = new Collection<Product>();
DataTable dtProductIDs = new DataTable("Product");
dtProductIDs.Columns.Add("ID", typeof(int));
foreach (int id in productIDs)
{
dtProductIDs.Rows.Add(
id
);
}
SqlParameter tvpProduct = new SqlParameter("@ProductIDsTVP", dtProductIDs);
tvpProduct.SqlDbType = SqlDbType.Structured;
//SqlHelper.ExecuteNonQuery(conn, CommandType.StoredProcedure, "procGetProducts", tvpProduct);
using (SqlDataReader dataReader = SqlHelper.ExecuteReader(conn, CommandType.StoredProcedure, "procGetProductsByProductIDsTVP", tvpProduct))
{
while (dataReader.Read())
{
Product product = new Product();
product.ID = dataReader.IsDBNull(0) ? 0 : dataReader.GetInt32(0);
product.Name = dataReader.IsDBNull(1) ? (string)null : dataReader.GetString(1).Trim();
products.Add(product);
}
}
return products;
} 
  创建以产品ID作为列名的TVP:



IF NOT EXISTS(  SELECT * FROM sys.types WHERE name = 'ProductIDsTVP')
CREATE TYPE [dbo].[ProductIDsTVP] AS TABLE
(
[ID] INT
)
GO

  查询产品的存储过程:



/****** Object:  StoredProcedure [dbo].[procGetProductsByProductIDsTVP]******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[procGetProductsByProductIDsTVP]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
DROP PROCEDURE [dbo].[procGetProductsByProductIDsTVP]
GO
Create PROCEDURE [dbo].[procGetProductsByProductIDsTVP]
(
@ProductIDsTVP ProductIDsTVP READONLY
)
AS
SELECT p.ID, p.Name
FROM Product as p
INNER JOIN @ProductIDsTVP as t on p.ID = t.ID
2.使用TVP 删除产品
  删除产品ID=1,5,6的产品



public static void TestDeleteProductsByIDs()
{
Collection<int> productIDs = new Collection<int>();
Console.WriteLine();
Console.WriteLine("----- Delete Products ------");
Console.WriteLine("Product IDs: 1,5,6");
productIDs.Add(1);
productIDs.Add(5);
productIDs.Add(6);
DeleteProductsByIDs(productIDs);
}
  删除的方法:



/// <summary>
/// Deletes products by the collection of the specific product' ID
/// </summary>
/// <param name="conn"></param>
/// <param name="productIDs"></param>
public static void DeleteProductsByIDs(SqlConnection conn, Collection<int> productIDs)
{
Collection<Product> products = new Collection<Product>();
DataTable dtProductIDs = new DataTable("Product");
dtProductIDs.Columns.Add("ID", typeof(int));
foreach (int id in productIDs)
{
dtProductIDs.Rows.Add(
id
);
}
SqlParameter tvpProduct = new SqlParameter("@ProductIDsTVP", dtProductIDs);
tvpProduct.SqlDbType = SqlDbType.Structured;
SqlHelper.ExecuteNonQuery(conn, CommandType.StoredProcedure, "procDeleteProductsByProductIDsTVP", tvpProduct);
}

  删除产品的存储过程:



/****** Object:  StoredProcedure [dbo].[procDeleteProductsByIDsByProductIDsTVP]******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[procDeleteProductsByProductIDsTVP]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
DROP PROCEDURE [dbo].[procDeleteProductsByProductIDsTVP]
GO
Create PROCEDURE [dbo].[procDeleteProductsByProductIDsTVP]
(
@ProductIDsTVP ProductIDsTVP READONLY
)
AS
DELETE p FROM Product AS p
INNER JOIN @ProductIDsTVP AS t on p.ID = t.ID
3.使用TVP 增加产品
  增加产品
  ID=5,Name=bbb
  ID=6,Name=abc



public static void TestInsertProducts()
{
Collection<Product> products = new Collection<Product>();
Console.WriteLine();
Console.WriteLine("----- Insert Products ------");
Console.WriteLine("Product IDs: 5-bbb,6-abc");
products.Add(
new Product()
{
ID = 5,
Name = "qwe"
});
products.Add(
new Product()
{
ID = 6,
Name = "xyz"
});
InsertProducts(products);
}

  增加的方法:



/// <summary>
/// Inserts products by the collection of the specific products.
/// </summary>
/// <param name="conn"></param>
/// <param name="products"></param>
public static void InsertProducts(SqlConnection conn, Collection<Product> products)
{
DataTable dtProducts = new DataTable("Product");
dtProducts.Columns.Add("ID", typeof(int));
dtProducts.Columns.Add("Name", typeof(string));
foreach (Product product in products)
{
dtProducts.Rows.Add(
product.ID,
product.Name
);
}
SqlParameter tvpProduct = new SqlParameter("@ProductTVP", dtProducts);
tvpProduct.SqlDbType = SqlDbType.Structured;
SqlHelper.ExecuteNonQuery(conn, CommandType.StoredProcedure, "procInsertProductsByProductTVP", tvpProduct);
}

  增加产品的存储过程:



/****** Object:  StoredProcedure [dbo].[procInsertProductsByProductTVP]******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[procInsertProductsByProductTVP]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
DROP PROCEDURE [dbo].[procInsertProductsByProductTVP]
GO
Create PROCEDURE [dbo].[procInsertProductsByProductTVP]
(
@ProductTVP ProductTVP READONLY
)
AS
INSERT INTO Product (ID, Name)
SELECT
t.ID,
t.Name
FROM @ProductTVP AS t
GO
4.使用TVP 更新产品
  将ID=2的产品的Name更新为bbb
  将ID=6的产品的Name更新为abc



public static void TestUpdateProducts()
{
Collection<Product> products = new Collection<Product>();
Console.WriteLine();
Console.WriteLine("----- Update Products ------");
Console.WriteLine("Product IDs: 2-bbb,6-abc");
products.Add(
new Product()
{
ID = 2,
Name = "bbb"
});
products.Add(
new Product()
{
ID = 6,
Name = "aaa"
});
UpdateProducts(products);
}

  更新的方法:



/// <summary>
/// Updates products by the collection of the specific products
/// </summary>
/// <param name="conn"></param>
/// <param name="products"></param>
public static void UpdateProducts(SqlConnection conn, Collection<Product> products)
{
DataTable dtProducts = new DataTable("Product");
dtProducts.Columns.Add("ID", typeof(int));
dtProducts.Columns.Add("Name", typeof(string));
foreach (Product product in products)
{
dtProducts.Rows.Add(
product.ID,
product.Name
);
}
SqlParameter tvpProduct = new SqlParameter("@ProductTVP", dtProducts);
tvpProduct.SqlDbType = SqlDbType.Structured;
SqlHelper.ExecuteNonQuery(conn, CommandType.StoredProcedure, "procUpdateProductsByProductTVP", tvpProduct);
}

  创建以产品ID和产品Name作为列名的TVP:



IF NOT EXISTS(  SELECT * FROM sys.types WHERE name = 'ProductTVP')
CREATE TYPE [dbo].[ProductTVP] AS TABLE(
[ID] [int] NULL,
[Name] NVARCHAR(100)
)
GO
  增加产品的存储过程:



/****** Object:  StoredProcedure [dbo].[procUpdateProductsByIDs]******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[procUpdateProductsByProductTVP]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
DROP PROCEDURE [dbo].[procUpdateProductsByProductTVP]
GO
Create PROCEDURE [dbo].[procUpdateProductsByProductTVP]
(
@ProductTVP ProductTVP READONLY
)
AS
Update p
SET
p.ID = t.ID,
p.Name = t.Name
FROM product AS p
INNER JOIN @ProductTVP AS t on p.ID = t.ID
GO
  结果:
DSC0000.png



  注意:
  (1)无法在表值参数中返回数据。 表值参数是只可输入的参数;不支持 OUTPUT 关键字。
  (2)表值参数为强类型,其结构会自动进行验证。
  (3)表值参数的大小仅受服务器内存的限制。
  (4)删除表值参数时,需要先删除引用表值参数的存储过程。

四、写在最后
  后期会将TVP的性能问题和SQL Bulk Copy的用法补上。

五、参考资料
  表值参数 https://msdn.microsoft.com/zh-cn/library/bb675163.aspx

表值参数(数据库引擎)https://msdn.microsoft.com/zh-CN/Library/bb510489(SQL.100).aspx  推荐阅读:30分钟全面解析-SQL事务+隔离级别+阻塞+死锁
  推荐阅读:T-SQL基础博客目录

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-393800-1-1.html 上篇帖子: SQL Server2014 SP2新增的数据库克隆功能 下篇帖子: 防止SQL注入方法总结
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表