如何将空值显式插入参数化查询?
Posted
技术标签:
【中文标题】如何将空值显式插入参数化查询?【英文标题】:How do I explicitly insert nulls into a parametized query? 【发布时间】:2011-09-15 04:22:47 【问题描述】:我使用的是 Delphi 7 和 Firebird 1.5。
我有一个在运行时创建的查询,其中一些值可能为空。我无法弄清楚如何让 Firebird 接受我需要保留为空的值的显式空值。在这个阶段,我正在构建 SQL,以便不包含 null 参数,但这很乏味且容易出错。
var
Qry: TSQLQuery;
begin
SetConnection(Query); // sets the TSQLConnection property to a live database connection
Query.SQL.Text := 'INSERT INTO SomeTable (ThisColumn) VALUES (:ThisValue)';
Query.ParamByName('ThisValue').IsNull := true; // read only, true by default
Query.ParamByName('ThisValue').Clear; // does not fix the problem
Query.ParamByName('ThisValue').IsNull = true; // still true
Query.ParamByName('ThisValue').Bound := true; // does not fix the problem
Query.ExecSQL;
目前在 DB.pas 中引发了 EDatabaseError "No value for parameter 'ThisValue'"',所以我怀疑这是设计使然,而不是 firebird 问题。
我可以将参数设置为 NULL 吗?如果有,怎么做?
(编辑:抱歉之前没有明确尝试 .Clear。我把它省略了,而是为了提到 IsNull。添加了声明和更多代码)
抱歉,还有一件事:表上没有“NOT NULL”约束。我不认为它走得那么远,但我认为我应该说。
在我结束时显示问题的完整控制台应用程序:
program InsertNull;
$APPTYPE CONSOLE
uses
DB,
SQLExpr,
Variants,
SysUtils;
var
SQLConnection1: TSQLConnection;
Query: TSQLQuery;
begin
SQLConnection1 := TSQLConnection.Create(nil);
with SQLConnection1 do
begin
Name := 'SQLConnection1';
DriverName := 'Interbase';
GetDriverFunc := 'getSQLDriverINTERBASE';
LibraryName := 'dbexpint.dll';
LoginPrompt := False;
Params.clear;
Params.Add('Database=D:\Database\ZMDDEV12\clinplus');
Params.Add('RoleName=RoleName');
//REDACTED Params.Add('User_Name=');
//REDACTED Params.Add('Password=');
Params.Add('ServerCharSet=');
Params.Add('SQLDialect=1');
Params.Add('BlobSize=-1');
Params.Add('CommitRetain=False');
Params.Add('WaitOnLocks=True');
Params.Add('ErrorResourceFile=');
Params.Add('LocaleCode=0000');
Params.Add('Interbase TransIsolation=ReadCommited');
Params.Add('Trim Char=False');
VendorLib := 'gds32.dll';
Connected := True;
end;
SQLConnection1.Connected;
Query := TSQLQuery.Create(nil);
Query.SQLConnection := SQLConnection1;
Query.Sql.Text := 'INSERT INTO crs_edocument (EDOC_ID, LINKAGE_TYPE) VALUES (999327, :ThisValue)';
//Query.ParamByName('ThisValue').IsNull := true; // read only, true by default
// Query.ParamByName('ThisValue').Value := NULL;
Query.ParamByName('ThisValue').clear; // does not fix the problem
Query.ParamByName('ThisValue').Bound := True; // does not fix the problem
// Query.ParamByName('ThisValue').IsNull; // still true
Query.ExecSQL;
end.
【问题讨论】:
@moz,你使用哪些组件来执行sql语句? SQlExpr.pas 中抛出此问题的行是对检查“if iFldType = fldUNKNOWN then”的响应,但设置 .DataType := ftInteger 会给出 Firebird ISC ERROR CODE:335544347 - 验证错误. @moz,显然你在 dbexpress 中遇到了一个错误。谷歌搜索实际上发现了一个类似的 SQL server QC,以及与各种数据类型相关的其他几个相似的错误。有些应该通过对 Delphi 2010 的更新来修复(我在这里使用 D2010 并遇到相同的错误,当我到达办公室时将能够在 XE 上进行测试)。NULL
一直为我工作,但我使用 Interbase Express
组件(即使是那些不支持 Firebird 的组件)。
@Cosmin Prund:发现这一点非常重要,这非常令人放心。现在,如果我们能改变我们使用的连接组件就好了:(
@moz 你不应该仅仅为了接受而接受答案。它解决了你的问题吗?如果不是,请不要接受:发布更新结果,获得更多答案。这样,社区获得知识,您最终将解决您的问题。即使您要发布最终答案。
【参考方案1】:
使用TParam.Clear
Query.ParamByName('ThisValue').Clear;
“使用 Clear 将 NULL 值分配给参数。” (来自文档)
【讨论】:
此外,您必须将Bound
属性设置为true Query.ParamByName('ThisValue').Bound:=True;
这并不能解决问题。调用 clear 前参数为 null 并抛出异常。调用后参数为null,抛出异常。
@RRUZ:我已经尝试过了。 IsNull 在我调用 Clear 之前是真的,之后更是如此。错误仍然存在。
我使用 TParam.DataType := ...
、TParam.Clear
和 TParam.Bound := True
(按此顺序),它对我来说一直很好用。
-1 因为将 NULL 分配给已分配为 NULL 的参数是没有意义的。在解析 SQL 时,参数的“FNull”在创建时设置为 true。由于之后没有赋值,所以还是NULL。【参考方案2】:
您确定仅通过设置 SQL 的文本来创建参数吗?
试试
if Query.Params.count <> 0 then
// set params
.
.
反正为什么不做 SQL 文本:
'INSERT INTO crs_edocument (EDOC_ID, LINKAGE_TYPE) VALUES (999327, NULL)';
如果您知道该值将为空...
【讨论】:
因为如果我要为每个参数更改 SQL,那是一段非常难看的代码。这个问题实际上发生在具有 ~20 个参数的插入上(诚然,其中只有 ~10 个参数可以为空)。同样,如果 paramcount=0,查询将永远不会起作用,而不是仅在参数为 null 时才会失败。【参考方案3】:错误的原因是'dbx'不知道参数的数据类型。由于它从未被赋值,因此它的数据类型在执行时为ftUnknown
,因此出现错误。 'ParamType' 也一样,但默认假定为 'ptInput',所以没问题。
Query.ParamByName('ThisValue').DataType := ftString;
您肯定不需要Clear
参数,因为它已经是NULL
。我们怎么知道? IsNull
正在返回 true...
来自TParam.Clear Method:
使用 Clear 将 NULL 值分配给 参数。
来自TParam.IsNull Property:
表示是否赋值 到参数为NULL(空白)。
您肯定不需要Bound
参数,因为它完全不相关。当 'Bound' 为 false 时,数据集将尝试从其数据源中为参数提供默认值。 但您的数据集甚至没有链接到数据源。来自documentation:
[...] 代表查询的数据集 和存储过程使用的值 绑定判断是否分配一个 参数的默认值。如果 Bound 为 false,数据集 表示查询尝试分配一个 数据集中的值 他们的 DataSource 属性。 [...]
如果文档不够,请参考'sqlexpr.pas'中TCustomSQLDataSet.SetParamsFromCursor
中的代码。它是 dbx 框架中唯一引用参数“绑定”的地方。
【讨论】:
这实际上让我找到了一个解决方法 - 当我设置 DataType 时遇到一个不同的错误,这导致我进入一个建议不同数据库驱动程序的页面。但这不是我可以在生产中使用的东西,所以我已经修改了空参数的 SQL。不过谢谢你的建议,它有帮助。 @moz - 我有点理解你不想接受正确的答案,可能是因为你的 dbx 驱动程序不适合它对你没有帮助的任务。但是,请,不要接受不正确的答案,无论它有多少票.. 这适用于字符串和日期时间类型,我现在正在使用它。但是对于 FireBird 枚举类型来说,没有什么好处。所以我接受了。 对于 Firebird 枚举类型(即域),您需要修改约束检查以允许 null CREATE DOMAIN db_enum AS varchar(20) CHECK (value IS NULL or VALUE IN ('Firebird','mysql ','MSSQL')); ALTER DOMAIN mydomain DROP CONSTRAINT; ALTER DOMAIN mydomain ADD CONSTRAINT CHECK (Sertac 的回答是最正确的,但我也发现驱动程序的选择有所不同。
为了其他人的利益,这里有一个改进的测试程序,演示了如何使用 Firebird 1.5 的参数化查询插入空值。
program InsertNull;
$APPTYPE CONSOLE
uses
DB,
SQLExpr,
Variants,
SysUtils;
var
SQLConnection1: TSQLConnection;
Query: TSQLQuery;
A, B, C: variant;
begin
SQLConnection1 := TSQLConnection.Create(nil);
Query := TSQLQuery.Create(nil);
try
try
with SQLConnection1 do
begin
Name := 'SQLConnection1';
DriverName := 'InterXpress for Firebird';
LibraryName := 'dbxup_fb.dll';
VendorLib := 'fbclient.dll';
GetDriverFunc := 'getSQLDriverFB';
//DriverName := 'Interbase';
//GetDriverFunc := 'getSQLDriverINTERBASE';
//LibraryName := 'dbexpint.dll';
LoginPrompt := False;
Params.clear;
Params.Add('Database=127.0.0.1:D:\Database\testdb');
Params.Add('RoleName=RoleName');
Params.Add('User_Name=SYSDBA');
Params.Add('Password=XXXXXXXXXXXX');
Params.Add('ServerCharSet=');
Params.Add('SQLDialect=1');
Params.Add('BlobSize=-1');
Params.Add('CommitRetain=False');
Params.Add('WaitOnLocks=True');
Params.Add('ErrorResourceFile=');
Params.Add('LocaleCode=0000');
Params.Add('Interbase TransIsolation=ReadCommited');
Params.Add('Trim Char=False');
//VendorLib := 'gds32.dll';
Connected := True;
end;
Query.SQLConnection := SQLConnection1;
Query.SQL.Clear;
Query.Params.Clear;
// FYI
// A is Firebird Varchar
// B is Firebird Integer
// C is Firebird Date
Query.Sql.Add('INSERT INTO tableX (A, B, C) VALUES (:A, :B, :C)');
Query.ParamByName('A').DataType := ftString;
Query.ParamByName('B').DataType := ftInteger;
Query.ParamByName('C').DataType := ftDateTime;
A := Null;
B := Null;
C := Null;
Query.ParamByName('A').AsString := A;
Query.ParamByName('B').AsInteger := B;
Query.ParamByName('C').AsDateTime := C;
Query.ExecSQL;
writeln('done');
readln;
except
on E: Exception do
begin
writeln(E.Message);
readln;
end;
end;
finally
Query.Free;
SQLConnection1.Free;
end;
end.
【讨论】:
【参考方案5】:在TConnection Options
上有一些属性,名为HandlingStringType/将空字符串转换为null。保持真实并假设Query.ParamByName('ThisValue').AsString:=''
;
您可以在
TConnection.FetchOptions.FormatOptions.StrsEmpty2Null:=True
【讨论】:
以上是关于如何将空值显式插入参数化查询?的主要内容,如果未能解决你的问题,请参考以下文章
Swift 3.0 错误:转义闭包只能按值显式捕获 inout 参数
Swift 4:转义闭包只能通过值显式捕获 inout 参数