【问题标题】:Delphi: how to pass a list as a parameter to a SQL query?Delphi:如何将列表作为参数传递给 SQL 查询?
【发布时间】:2012-04-10 15:35:17
【问题描述】:

我有一个整数列表或字符串列表,需要将它作为 Delphi DataSet 的参数传递。怎么做?

这是一个例子。 MyQuery 类似于:

select * from myTable where intKey in :listParam

我会将参数设置为列表或数组或其他内容:

MyQuery.ParamByName('listParam').AsSomething := [1,2,3];

这会导致这个查询被发送到 sql server:

select * from myTable where intKey in (1, 2, 3)

如果解决方案也适用于字符串,那就更好了,进行以下查询:

select * from myTable where stringKey in :listParam

变成:

select * from myTable where stringKey in ('a', 'b', 'c')

我相信这是一个简单的问题,但“IN”不是搜索网络的好关键字。

请回答我应该如何在IDE中配置参数,查询以及如何传递参数。

我正在使用 Delphi 7。

已编辑:我正在考虑答案是“不可能直接做”。如果有人给我一个非骇人听闻的答案,接受的答案将被更改。

【问题讨论】:

  • 很遗憾,你不能。这是 SQL 语言的一个缺陷:它没有任何“列表类型”的概念。
  • 根据您使用的 DBMS,可能会有一些选项供您选择。你在用什么? SQL Server、甲骨文、....?
  • @MikaelEriksson:我正在使用 Sql Server,但我认为这是 Delphi 语言问题。
  • 这不是 Delphi 问题,而是 SQL 问题。有关更多信息以及一些可能的解决方法,请参阅 this answer to a similar question

标签: sql sql-server delphi delphi-7


【解决方案1】:

AFAIK,这是不可能的。

您必须将列表转换为纯文本形式的 SQL 列表。

例如:

function ListToText(const Args: array of string): string; overload;
var i: integer;
begin
  result := '(';
  for i := 0 to high(Args) do 
    result := result+QuotedStr(Args[i])+',';
  result[length(result)] := ')';
end;


function ListToText(const Args: array of integer): string; overload;
var i: integer;
begin
  result := '(';
  for i := 0 to high(Args) do 
    result := result+IntToStr(Args[i])+',';
  result[length(result)] := ')';
end;

这样使用:

SQL.Text := 'select * from myTable where intKey in '+ListToText([1,2,3]);
SQL.Text := 'select * from myTable where stringKey in '+ListToText(['a','b','c']);

【讨论】:

  • 当然,这是我目前的解决方案,但我想将其作为参数传递,而不是更改 SQL 属性。连接不会让 DBMS 准备查询并允许 sql 注入攻击。
  • 仍然可以进行参数化(尽管您必须更新 SQL 属性)。请参阅下面我冗长的答案。
  • @neves 上面的两个函数不允许 SQL 注入攻击,因为第一个将“引用”提供的文本,第二个将从整数创建值。使用现代数据库(至少使用我知道的 Oracle 数据库),它不会很慢 - 只要确保您在键上有一个正确的索引。在内部,数据库将准备 SQL 语句并将整个“in ()”表达式作为其执行计划中的一个参数重用。它是 SQL:编写您想要的代码,而不是您希望数据库如何检索它。
  • @neves,您希望从哪里进行 SQL 注入攻击?来自您自己提供的列表?
  • 哦,当然。哈。我的错。在解析之后,我会尝试将其用作参数。喜欢:MyQuery.ParamByName('listParam').AsString := ListToText(['a','b','c']);
【解决方案2】:

SQL 仅接受单个值作为参数,因此您无法创建一个带有一个参数的语句,该参数可以映射到可变数量的值,例如您给出的示例。

但是,在这种情况下,您仍然可以使用参数化 SQL。解决方案是遍历您拥有的值列表,将参数标记添加到 SQL 中,并将参数添加到每个值的参数列表中。

使用位置参数而不是命名参数最容易做到这一点,但也可以适应命名参数(您可能需要调整此代码,因为我没有可用的 Delphi 并且不记得参数创建语法):

 //AValues is an array of variant values
 //SQLCommand is some TDataSet component with Parameters.
 for I := Low(AValues) to High(AValues) do
 begin

    if ParamString = '' then
       ParamString = '?'
    else
      ParamString = ParamString + ', ?';

    SQLCommand.Parameters.Add(AValues[I]);

  end

  SQLCommand.CommandText = 
     'SELECT * FROM MyTable WHERE KeyValue IN (' + ParamString + ')';

这将产生一个注入安全的参数化查询。

【讨论】:

  • 这是我第一次看到有人使用Parameters.Add。分配 CommandText 不会覆盖它们吗?
【解决方案3】:

您有多种选择,但基本上您需要将值放入表格中。我会为此建议一个表变量。

这是一个解压 int 列表的版本。

declare @IDs varchar(max)
set @IDs = :listParam

set @IDs = @IDs+','

declare @T table(ID int primary key)

while len(@IDs) > 1
begin
  insert into @T(ID) values (left(@IDs, charindex(',', @IDs)-1))
  set @IDs = stuff(@IDs, 1, charindex(',', @IDs), '')
end

select *
from myTable
where intKey in (select ID from @T)

可以进行多语句查询。参数:listParam应该是字符串:

MyQuery.ParamByName('listParam').AsString := '1,2,3';

您可以对字符串使用相同的技术。您只需要将ID 的数据类型更改为例如varchar(10)

你可以使用split function而不是使用while循环解包

declare @T table(ID varchar(10))

insert into @T 
select s
from dbo.Split(',', :listParam)

select *
from myTable
where  charKey in (select ID from @T)

字符串参数可能如下所示:

MyQuery.ParamByName('listParam').AsString := 'Adam,Bertil,Caesar';

【讨论】:

  • ...如果您的字符串之一在其内容中包含“,”字符(这很可能),它将不起作用...
  • @ArnaudBouchez - 此解决方案不需要逗号。它可以是你的选择的任何字符。您甚至可以将分隔符添加为它自己的参数。无论如何,如果您有非常多样化的数据而无法确定分隔符,则可以求助于设置 XML 字符串,而不是在 TSQL 代码中分解为表。
  • @ArnaudBouchez - 如果我也添加 XML 版本,您会感兴趣吗?
【解决方案4】:

创建一个临时表并将您的值插入其中。然后将该表用作子查询的一部分。

例如,在您的数据库中创建 MyListTable。将您的值插入 MyListTable。然后做

select * from myTable where keyvalue in (select keyvalue from MyListTable)

这样可以避免 SQL 注入攻击。但它并不优雅,对性能不友好,因为您必须在运行查询之前插入记录,并且可能导致并发问题。

不是我处理您的情况的首选,但它解决了您对 sql 注入的担忧。

【讨论】:

    【解决方案5】:

    如果有人仍然遇到同样的问题,如果您使用 fireac,则可以使用如下宏:

    查询 -> "select * from myTable where intKey in (&listParam)"

    设置宏 -> MyQuery.MacroByName('listParam').AsRaw := '1, 2, 3';

    【讨论】:

      【解决方案6】:

      我使用了一些“IN”替换。这是我使用的查询:

      SELECT * FROM MyTable WHERE CHARINDEX(','+cast(intKey as varchar(10))+',', :listParam) > 0
      

      发送参数的代码:

      MyQuery.ParamByName('listParam').AsString := ',1,2,3,';  
      

      数组项值可以部分匹配其他一些值。例如,“1”可以是“100”的一部分。为了防止它,我使用逗号作为分隔符

      【讨论】:

        【解决方案7】:

        为什么不做动态sql:

        又快又脏,但仍在使用参数。 检查 10 个元素。我不知道它的扩展性如何。

            MyQuerySQL.Text:='SELECT * FROM myTable WHERE intKey in (:listParam0'
            for i := 1 to 9 do begin
              MyQuerySQL.Text := MyQuerySQL.Text + ',:listParam'+IntToStr(i)
            end;
            MyQuerySQL.Text := MyQuerySQL.Text+')';
            for i:=0 to 9 do begin
              MyQuery.ParamByName('listParam'+IntToStr(i)).AsInteger := ArrayofInt[0];
            end;
        

        【讨论】:

          猜你喜欢
          • 2019-12-09
          • 2023-03-06
          • 2019-09-11
          • 2019-01-02
          • 1970-01-01
          • 2020-11-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多