pra*_*nth 3 sql-server stored-procedures sql-server-2008-r2
我需要根据存储过程参数中发送的逗号分隔值从表中检索数据。到目前为止,我已经使代码适用于单个值,但不确定如何使其适用于多个值。
示例表:
CREATE TABLE [dbo].FinalStatus
(
[ID] [int] Primary key IDENTITY(1,1) NOT NULL,
[Col1] [varchar](15) NULL,
[Col2] [varchar](15) NULL,
[Col3] [varchar](100) NOT NULL,
[LastUpdatedDate] [datetime] NOT NULL DEFAULT (getdate())
)
Run Code Online (Sandbox Code Playgroud)
测试数据:
Insert into FinalStatus (Col1, Col2, Col3) values ('10','ABC21','Msg1')
Insert into FinalStatus (Col1, Col2, Col3) values ('10','ABC21','Msg2')
Insert into FinalStatus (Col1, Col2, Col3) values ('11','C21','Some Msg1')
Insert into FinalStatus (Col1, Col2, Col3) values ('12','BC21','Some Msg2')
Run Code Online (Sandbox Code Playgroud)
存储过程:
CREATE PROCEDURE [dbo].[FindResult]
(@col1 VARCHAR(15) = NULL,
@col2 VARCHAR(15) = NULL)
AS
SET NOCOUNT ON
BEGIN
DECLARE @c1 VARCHAR(15)
DECLARE @c2 VARCHAR(15)
SET @c1 = @col1
SET @c2 = @col2
SELECT
Col2, Col1,
LastUpdatedDate, Col3
FROM
dbo.FinalStatus
WHERE
(Col1 = @c1 OR @c1 IS NULL)
AND (Col2 = @c2 OR @c2 IS NULL)
ORDER BY
LastUpdatedDate DESC
END
Run Code Online (Sandbox Code Playgroud)
单值的执行脚本(它一直工作到这里):
--To get all data
EXEC [dbo].[FindResult]
--passing first parameter alone
EXEC [dbo].[FindResult] @col1 = '10', @col2 = NULL
--passing second parameter alone
EXEC [dbo].[FindResult] @col1 = null , @col2 = 'c21'
Run Code Online (Sandbox Code Playgroud)
现在即使我们将多个值传递给参数,如何让它返回适当的结果?
像这样的东西:
EXEC [dbo].[FindResult] @col1 = '10,12', @col2 = NULL
EXEC [dbo].[FindResult] @col1 = null , @col2 = 'ABC21, c21'
Run Code Online (Sandbox Code Playgroud)
在任何给定的时间点,基础表至少有 100000 条记录。
曾尝试阅读Erland Sommarskog 的“SQL Server 2008 中的数组和列表”,但它让我无法理解。所以在修改这个存储过程方面寻找一些帮助。
有几种方法可以做到。如果可以的话,更改数据模型也可能是更好的选择。
如果你想坚持使用逗号分隔的字符串,你可以在网上找到很多使用 CLR、CTE、XML 的字符串拆分函数……我不会把它们都放在这里,我也不会对它们进行基准测试。但你必须知道,他们都有自己的问题。有关更多信息,您可以查看Aaron Bertrand 的这篇文章:以正确的方式拆分字符串 – 或下一个最佳方式
DECLARE @c1 nvarchar(100) = N'10,15,13,14';
DECLARE @delimiter nvarchar(1) = N',';
SELECT v1 = LTRIM(RTRIM(vals.node.value('(./text())[1]', 'nvarchar(4000)')))
FROM (
SELECT x = CAST('<root><data>' + REPLACE(@c1, @delimiter, '</data><data>') + '</data></root>' AS XML).query('.')
) v
CROSS APPLY x.nodes('/root/data') vals(node);
Run Code Online (Sandbox Code Playgroud)
v1
10
15
13
14
Run Code Online (Sandbox Code Playgroud)
这个想法是将其转换为表格。然后可以在相关子查询中使用它:
DECLARE @c1 nvarchar(100) = N'10,15,13,14';
DECLARE @c2 nvarchar(100) = N'C21, B21';
DECLARE @delimiter nvarchar(1) = N',';
SELECT *
FROM FinalStatus f
WHERE EXISTS (
SELECT 1
FROM (
SELECT CAST('<root><data>' + REPLACE(@c1, @delimiter, '</data><data>') + '</data></root>' AS XML) AS x
)t
CROSS APPLY x.nodes('/root/data') vals(node)
WHERE LTRIM(RTRIM(vals.node.value('.[1]', 'nvarchar(4000)'))) = f.Col1
)
OR EXISTS (
SELECT 1
FROM (
SELECT CAST('<root><data>' + REPLACE(@c2, @delimiter, '</data><data>') + '</data></root>' AS XML) AS x
)t
CROSS APPLY x.nodes('/root/data') vals(node)
WHERE LTRIM(RTRIM(vals.node.value('.[1]', 'nvarchar(4000)'))) = f.Col2
);
Run Code Online (Sandbox Code Playgroud)
WHERE在您的程序中只需要更新子句。参数仍然是带有逗号分隔值的 varchar 类型。
ID Col1 Col2 Col3 LastUpdatedDate
1 10 ABC21 Msg1 2016-07-20 09:06:19.380 => match c1
2 10 ABC21 Msg2 2016-07-20 09:06:19.390 => match c1
3 11 C21 Some Msg1 2016-07-20 09:06:19.390 => match c2
Run Code Online (Sandbox Code Playgroud)
如果您可以更改参数的类型,则可以使用 XML 数据类型。它可以很容易地被过程和查询反序列化。
此查询与前一个查询非常相似。然而,它可以更好地控制无效值和特殊字符,因为没有转换或搜索和替换。
DECLARE @c xml = N'
<root>
<c1>10</c1><c1>15</c1><c1>13</c1><c1>14</c1>
<c2>C21</c2><c2>B21</c2>
</root>';
DECLARE @delimiter nvarchar(1) = N',';
SELECT *
FROM FinalStatus f
WHERE EXISTS (
SELECT 1
FROM (
SELECT x = @c.query('.')
) v
CROSS APPLY x.nodes('/root/c1') val1(node)
WHERE LTRIM(RTRIM(val1.node.value('.[1]', 'nvarchar(4000)'))) = f.Col1
)
OR EXISTS (
SELECT 1
FROM (
SELECT x = @c.query('.')
) v
CROSS APPLY x.nodes('/root/c2') val1(node)
WHERE LTRIM(RTRIM(val1.node.value('.[1]', 'nvarchar(4000)'))) = f.Col2
);
Run Code Online (Sandbox Code Playgroud)
参数的类型必须更改为 xml。我只使用了一个变量c1和c2节点,但每个节点(c1、c2、...)使用了 1 个变量,也可以使用不同的节点名称:
DECLARE @c1 xml = N'<root><data>10</data><data>15</data><data>13</data><data>14</data></root>';
DECLARE @c2 xml = N'<root><data>C21</data><data>B21</data></root>';
DECLARE @c3 xml = N'...';
Run Code Online (Sandbox Code Playgroud)
正确的路径和变量必须nodes在CROSS APPLY.
使用 .Net、Powershell 或其他语言,可以轻松地将数组或其他类型转换为 xml 并用作过程的参数。
...
另一种选择是创建可由存储过程参数使用的表值类型。
CREATE TYPE [dbo].[TableTypeCols] AS TABLE
(
[col] varchar(15)
);
Run Code Online (Sandbox Code Playgroud)
然后使用这个新创建的类型更新存储过程:
CREATE OR ALTER PROCEDURE [dbo].[FindResult]
@c1 [dbo].[TableTypeCols] READONLY
, @c2 [dbo].[TableTypeCols] READONLY
AS
SELECT * -- fs.[...], fs.[...], ...
FROM [dbo].[FinalStatus] fs
WHERE
EXISTS (
SELECT 1 FROM @c1 c1 WHERE c1.col = fs.Col1
)
OR EXISTS (
SELECT 1 FROM @c2 c2 WHERE c2.col = fs.Col2
);
GO;
Run Code Online (Sandbox Code Playgroud)
READONLY 在参数声明中是强制性的。
使用 SQL,您可以使用表变量以相同的方式调用它:
DECLARE @tc1 [dbo].[TableTypeCols];
DECLARE @tc2 [dbo].[TableTypeCols];
INSERT INTO @tc1(col) VALUES('10'), ('15'), ('13'), ('14');
INSERT INTO @tc2(col) VALUES('C21'), ('B21');
EXEC dbo.FindResult @c1 = @tc1, @c2 = @tc2;
Run Code Online (Sandbox Code Playgroud)
ID Col1 Col2 Col3 LastUpdatedDate
1 10 ABC21 Msg1 2016-07-20 09:06:19.380 => match c1
2 10 ABC21 Msg2 2016-07-20 09:06:19.390 => match c1
3 11 C21 Some Msg1 2016-07-20 09:06:19.390 => match c2
Run Code Online (Sandbox Code Playgroud)
请注意,如果您计划为每个参数使用多于几行,您应该使用真实数据运行一些性能测试并确保它在您的系统上正常工作。
该程序可以从您那里调用.Net代码
// Create & Fill Parameter 1
DataTable dt1 = new DataTable();
dt1.Columns.Add("col", typeof (string));
DataRow row = dt1.NewRow();
row["col"] = ("10");
dt1.Rows.Add(row);
/*
... add more row ...
*/
// Create & Fill Parameter 2
DataTable dt2 = new DataTable();
dt2.Columns.Add("col", typeof (string));
DataRow row = dt2.NewRow();
row["col"] = ("C21");
dt2.Rows.Add(row);
/*
... add more row ...
*/
// Output dataset
DataSet ds = new DataSet("SQLDatabase");
using (SqlConnection conn = new SqlConnection(...))
{
// Stored Procedure with table parameters
SqlCommand sqlComm = new SqlCommand("dbo.FindResult", conn);
sqlComm.CommandType = CommandType.StoredProcedure;
// Parameter 1
SqlParameter param = new SqlParameter("@c1", SqlDbType.Structured)
{
TypeName = "dbo.TableTypeCols",
Value = dt1
};
sqlComm.Parameters.Add(param);
// Parameter 2
SqlParameter param = new SqlParameter("@c2", SqlDbType.Structured)
{
TypeName = "dbo.TableTypeCols",
Value = dt2
};
sqlComm.Parameters.Add(param);
// Call Stored Procedure
SqlDataAdapter da = new SqlDataAdapter();
da.SelectCommand = sqlComm;
da.Fill(ds);
}
Run Code Online (Sandbox Code Playgroud)
SqlDbType.Structured 是强制性的。
请注意,我不是 .Net 专家,还有其他或更好的方法来编写和使用此代码。
通过最少的更改,您可以使用 json 字符串声明 NVARCHAR(MAX) 参数。
DECLARE @C1 NVARCHAR(MAX);
SET @jsonC1 =
N'[
{ "col" : "10"}, { "col" : "15"}, { "col" : "13"}, { "col" : "14"}
]';
DECLARE @C2 NVARCHAR(MAX);
SET @jsonC2 =
N'[
{ "col" : "C21"}, { "col" : "B21"}
]';
Run Code Online (Sandbox Code Playgroud)
SELECT *
FROM dbo.FinalStatus f
WHERE
EXISTS (
SELECT 1 FROM OPENJSON(@C1) WITH(col nvarchar(15)) AS c1 WHERE c1.col = f.Col1
)
OR
EXISTS (
SELECT 1 FROM OPENJSON(@C2) WITH(col nvarchar(15)) AS c2 WHERE c2.col = f.Col2
);
Run Code Online (Sandbox Code Playgroud)
请注意,OPENJSON需要 SQL Server 2016。
| 归档时间: |
|
| 查看次数: |
23165 次 |
| 最近记录: |