【问题标题】:SQL Server Procedure with dynamic IN operator and INT datatype [duplicate]具有动态 IN 运算符和 INT 数据类型的 SQL Server 过程 [重复]
【发布时间】:2020-02-19 20:46:46
【问题描述】:

我有一个 SQL Server 表,其中包含一些基本的销售信息,如下所示

SaleID  |   TotalValue  |   PaymentMethod   |   User    |     Date
--------|---------------|-------------------|-----------|--------------
   1    |     10        |   CASH            |   USER 1  |   2020-01-01
   2    |     120       |   DEBIT CARD      |   USER 2  |   2020-01-01
   3    |     1000      |   CREDIT CARD     |   USER 2  |   2020-01-01
   4    |     305       |   CREDIT CARD     |   USER 1  |   2020-01-01
   5    |     15        |   CASH            |   USER 5  |   2020-01-01

我需要编写一个动态过程操作系统,它能够使用 SaleID 数组作为过滤器来搜索销售信息。这是一个简单的选择查询,如下:

SELECT
     [SaleID]
    ,[TotalValue] as 'Total Value'
    ,[Date] as 'Date'
FROM
    [Sales]
WHERE
    [SaleID] in (1, 2, 3, 4)

我创建了以下存储过程,因此我可以输入一个 SalesID 数组:

CREATE PROCEDURE [dbo].[usp_SearchSales]
(
      @SaleID NVARCHAR(max)
)
AS
BEGIN

    SET NOCOUNT ON;  

    DECLARE @SQL NVARCHAR(MAX)
    DECLARE @ParameterDef NVARCHAR(500)

    SET @ParameterDef = '@SaleID NVARCHAR(max)'

SET @SQL = 'SELECT
                 [SaleID]
                ,[TotalValue]
                ,[PaymentMethod]
                ,[User]
                ,[Date]
            FROM
                [Sales]
            WHERE 
                -1=-1'

IF @SaleID IS NOT NULL
SET @SQL = @SQL+ ' AND SaleID in (@SaleID)'

EXEC sp_Executesql @SQL, @ParameterDef, @SaleID = @SaleID

END

GO

然后我通过Exec命令执行Procedure

EXEC [usp_SearchSales] @SaleID = '1, 2, 3, 4'
GO

然后程序在执行时返回以下错误:

消息 245,级别 16,状态 1,过程 usp_SearchSales,第 26 行 [批处理开始第 49 行]

将 nvarchar 值 '1, 2, 3, 4' 转换为数据类型 int 时转换失败。

这是一个 SQL 查询,用于在设计的表上创建和插入一些值

CREATE TABLE [Sales] (
    [SaleID] int,
    [TotalValue] float,
    [PaymentMethod] varchar(50),
    [User] varchar(50),
    [Date] date
)

insert into sales values (1, 10, 'CASH', 'USER 1', '2020-01-01')
insert into sales values (2, 120, 'DEBIT CARD', 'USER 2', '2020-01-01')
insert into sales values (3, 10000, 'CREDIT CARD', 'USER 2', '2020-01-01')
insert into sales values (4, 305, 'CREDIT CARD', 'USER 1', '2020-01-01')
insert into sales values (5, 15, 'CASH', 'USER 5', '2020-01-01')

我需要一些帮助来创建一个过程,或者类似的东西,其中一个 SalesID 数组将这些销售的销售信息返回给我。

【问题讨论】:

  • 这一行 SET @SQL = @SQL+ ' AND SaleID in (@SaleID)' 应该是 SET @SQL = @SQL+ ' AND SaleID in (' + @SaleID + ')' 即你需要将传入的 id 添加到动态 SQL,而不是引用变量。
  • 这是一种相当复杂的方法,但它确实有效,谢谢@EricBrandt!!
  • 感谢@DaleK,这实际上解决了这个问题。我确实尝试过这种方法,但是我在编写查询时一定犯了一个错误,因为它不起作用你的工作正常,谢谢!!!
  • 需要明确的是,这不是最好的方法,它会让你对 SQL 注入持开放态度。请参阅下面的答案。

标签: sql sql-server tsql dynamic-sql dynamicquery


【解决方案1】:

不幸的是,SQL Server 本身并不支持数组,这使您很难(或繁琐)地实现。

虽然应该可以使用字符串函数在 CSV 列表中搜索值,但让我建议一个更简洁的选项,通过创建 table type 来工作。

首先创建类型:

create type dbo.id_array as table (id int);

那么你的过程可以简化为一个简单的查询:

create procedure dbo.usp_searchsales
    @sale_ids as dbo.id_array readonly
as
begin
    set nocount on;  
    select
        s.saleid,
        s.totalvalue,
        s.paymentmethod,
        s.[user],
        s.date
    from sales s
    inner join @sale_ids a on a.id = s.saleid;
end

最后,当你想调用过程时,你创建一个给定类型的变量,填充它,并将它作为参数传递:

declare @my_sale_ids dbo.id_array;
insert into @my_sale_ids values (1), (2), (3), (4);
execute dbo.usp_searchsales @my_sale_ids;

Demo on DB Fiddle

销售ID |总值 |付款方式 |用户 |日期 -----: | ---------: | :------------ | :----- | :--------- 1 | 10 |现金 |用户 1 | 2020-01-01 2 | 120 |借记卡 |用户 2 | 2020-01-01 3 | 10000 |信用卡 |用户 2 | 2020-01-01 4 | 305 |信用卡 |用户 1 | 2020-01-01

【讨论】:

  • 这是一个非常好的方法,我自己永远无法想出它。非常感谢!!!!
  • 欢迎@IgorCattusso!很高兴它有帮助。
【解决方案2】:

改变 SP 如下: 我更改了查询的 SET @SQL = @SQL+ CONCAT(' AND SaleID in (',@SaleID,')') 部分。 关于注入,动态查询容易被注入,我只修复了你遇到的问题。

alter PROCEDURE [dbo].[usp_SearchSales]
(
      @SaleID NVARCHAR(max)
)
AS
BEGIN

    SET NOCOUNT ON;  

    DECLARE @SQL NVARCHAR(MAX)
    DECLARE @ParameterDef NVARCHAR(500)

    SET @ParameterDef = '@SaleID NVARCHAR(max)'

SET @SQL = 'SELECT
                 [SaleID]
                ,[TotalValue]
                ,[PaymentMethod]
                ,[User]
                ,[Date]
            FROM
                [Sales]
            WHERE 
                -1=-1'

IF @SaleID IS NOT NULL
SET @SQL = @SQL+ CONCAT(' AND SaleID in (',@SaleID,')')
print @SQL

EXEC sp_Executesql @SQL, @ParameterDef, @SaleID = @SaleID

END

GO

您可以做的另一件事是创建一个用户定义表并在 SP 中调用它,如下所示:

CREATE TYPE [dbo].[udt_Sales] AS TABLE(
    [SaleID] [int] NULL
)

SP 会是这样的:

CREATE PROCEDURE [dbo].[sp_Name]
(
    @Ids [dbo].[udt_Sales] READONLY  
)
AS
BEGIN
SELECT [SaleID]
      ,[TotalValue]
      ,[PaymentMethod]
      ,[User]
      ,[Date]
FROM [Sales]
WHERE [SaleID] IN (SELECT * FROM @Ids)
END

你可以像下面这样调用 SP:

DECLARE @IDs udt_Sales

INSERT INTO @IDs VALUES (1)
INSERT INTO @IDs VALUES (2)
INSERT INTO @IDs VALUES (3)
INSERT INTO @IDs VALUES (4)

EXECUTE usp_SearchSales @IDs

这比动态查询快得多。 希望对你有帮助

【讨论】:

  • 请添加一些文字来解释您所做的更改以及更改原因 - 否则 OP 必须进行文件比较,但仍然不知道他们做错了什么。
  • 没有解释。但是您确实增加了 sql 注入风险并否定了动态 sql 参数的使用。这如何解决问题?
  • 嗨!这实际上可以解决它,但我忘了提到我有SQL Server 2008 R2,并且这个版本不存在CONCAT函数:(
  • 它可能会“解决”它,但它也会使此代码容易受到 sql 注入的攻击,这是边缘犯罪活动。当然,在这里使用 CONCAT 是没有意义的,因为它只是两个字符串。
  • 是的,确实如此,感谢您的提示,@SeanLange
猜你喜欢
  • 1970-01-01
  • 2015-06-16
  • 1970-01-01
  • 2015-02-28
  • 1970-01-01
  • 2013-08-17
  • 1970-01-01
  • 2018-08-13
  • 1970-01-01
相关资源
最近更新 更多