使用Firebird TIBQuery插入并返回... INTO

3

我有一个带有Generator和触发器的Firebird 2.x数据库,用于生成键字段。 我需要从以下查询中获取返回值。

INSERT INTO XXXX (vdate,description) values ('"+ VDate +"','"+ Description +"') returning vno INTO :ParamVoucherNo

我尝试了下面的几个版本的代码,但它们都不起作用,我得到以下错误:

动态 SQL 错误,SQL 错误代码 = -104

使用 TIBQuery 在 Delphi 中获取返回值真的可能吗?

Query1->SQL->Clear();
Query1->SQL->Add("INSERT INTO XXXX (vodate,description) values ('"+ VDate +"','"+ Description +"') returning vno INTO :ParamVoucherNo");

Query1->Params->ParamByName("ParamVoucherno")->ParamType = ptResult;
Query1->Params->ParamByName("ParamVoucherno")->DataType = ftInteger;
Query1->Params->ParamByName("ParamVoucherno")->Value = "";
Query1->Prepare();
Query1->ExecSQL();

有什么建议吗?

我对TADO有点生疏,但我相信你可以从数据库中插入数据并获得返回值(我假设是自增)。在TADOQuery或TADOConnection对象上应该有一个选项来返回自增的值。我不太了解firebird,但这需要驱动程序实现该选项。你的另一个选择是跟进一个SELECT MAX(ID) FROM Table,但这会导致并发错误,应该避免使用。 - Daniel B. Chapman
10个回答

5

来自Firebird README.returning:

"INTO"部分(即变量列表)仅允许在PSQL中使用(用于分配本地变量),并在DSQL中被拒绝。

由于IBX使用DSQL,您应该从查询中排除“INTO”部分。

对于DSQL的INSERT ... RETURNING看起来与调用返回结果集的存储过程相同。因此,您必须使用Open而不是ExecSQL


感谢您的回复。但是我已经尝试过了。如果我使用Open(),那么我就不会得到DSQL错误,但是结果数据集为空(即Query1->RecordCount == 0),所以我无法获得返回的值。 - topmop75
2
我对IBX不太熟悉,但可能它不支持RETURNING子句。这在我正在使用的AnyDAC上可以实现。 - oodesigner

3
您混合使用动态SQL和参数只会让人更加困惑。
请改用以下方法:
Query1->SQL->Clear();
Query1->SQL->Add("INSERT INTO table1 (vodate,description) VALUES"+
                 "(:VoDate,:Description) RETURNING vno INTO :VoucherNo ");
Query1->Params->ParamByName("VoDate")->Value = VDate;
Query1->Params->ParamByName("description")->Value = Description;

Query1->Prepare();
Query1->ExecSQL();
VoucherNo = Query1->Params->ParamByName("VoucherNo")->AsInteger;

谢谢您的回复,我已经尝试过了,但是它引发了我在第一篇帖子中提到的相同错误。 - topmop75

2

我使用 Delphi 6,并成功通过 EXECUTE BLOCK 语句获取了 ID:

EXECUTE BLOCK
RETURNS ( DeptKey INT )
AS
BEGIN
  INSERT INTO DEPARTMENT 
      ( COMPANY_KEY, DEPARTMENT_NAME ) 
      VALUES ( 1, 'TEST1' ) RETURNING DEPARTMENT_KEY INTO :DeptKey;
  SUSPEND;
END;

从Delphi中,您可以进行以下操作:

FQuery.SQL.Text := '<Execute Block Statement>';
FQuery.Open();
ANewKey := FQuery.Fields[0].AsInteger;

1
为什么不先获取VoucherNo的下一个值,然后再执行后续操作呢?
"INSERT INTO table1 (vno, vodate,description) VALUES (:VoucherNo,:VoDate,:Description)");

?

然后您的触发器可以被取消(这很好),或者修改为检测null(或者小于等于零也很有用),然后才填充vno字段。

create trigger bi_mytable
  active before insert position 1
  on mytable
as
begin
  if (new.vno is null)
    then new.vno = next value for gen_VoucherNos;
end

在客户端,您可以:

select gen_id(gen_VoucherNos, 1) from rdb$database;

通过以这种方式修改触发器,如果/当您想要插入记录块时,您可以避免以后的麻烦。

2
关于 INSERT ... RETURNING 的整个概念是您不需要先获取该值,而是让触发器生成它,然后返回该值。 - Mark Rotteveel
@HughJones:我认为这是一个完全可接受的解决IBX无法处理INSERT..RETURNING问题的方法(顺便说一下,这是一种非常方便的语法)。已点赞。 - Fabricio Araujo
@Hugh Jones:它可以减少多次与数据库的往返,因此应该表现得稍微更好,其次它可以节省在插入之前处理生成器值检索的编码。 - Mark Rotteveel

1
我想知道那个INSERT能否包含在EXECUTE BLOCK命令中。 然后,IBX会处理EXECUTE BLOCK吗? 希望在XE2中尝试使用IBX和统一Interbase。
附注:即使不行,我找到了一个库,可以在Delphi XE2的IBX上工作(x86和x64),并添加EXECUTE BLOCK支持:http://www.loginovprojects.ru/index.php?page=ibxfbutils#eb

1

IBX不支持Firebird

您可以查看FIBPLUS,它支持Firebird功能

FIBPlus还支持FB2.0 insert ... into ... returning。现在您不必担心从客户端获取生成器值,而是将它们留在触发器中。您还可以使用RDB$DB_KEY。插入返回和RDB$DB_KEY的新可能变体在示例“FB2InsertReturning”中显示。


0

从IBx2源代码中,您可以这样做:

//Uses IBSql;
//var   Cur: IResults;
  IBSQL1.SQL.Text := 'delete from tbl_document where id = 120 returning id;';
  IBSQL1.Prepare;
  if IBSQL1.Prepared then
  begin
    Cur := IBSQL1.Statement.Execute(IBTransaction1.TransactionIntf);
    WriteLn(Cur.Data[cou].AsString);
    Cur.GetTransaction.Commit(True);
  end;

结果接口代码:

  IResults = interface
   function getCount: integer;
   function GetTransaction: ITransaction;
   function ByName(Idx: String): ISQLData;
   function getSQLData(index: integer): ISQLData;
   procedure GetData(index: integer; var IsNull:boolean; var len: short; var data: PChar);
   procedure SetRetainInterfaces(aValue: boolean);
   property Data[index: integer]: ISQLData read getSQLData; default;
   property Count: integer read getCount;
  end;

测试环境: Arch Linux X86 Firebird 3 Lazarus 1.9 FPC 3.0.4 注意事项:此方案适用于 IBX 中的新 Firebird API,但我尚未在遗留的 Firebird API 中进行测试。


0
如果你有一张包含这两个字段的表:GRP_NO和GROUPNAME,并且你想要获取新的GRP_NO,你需要使用前缀RET_,如下例:
procedure TFormDatenbank.Button1Click(Sender: TObject);
var
  q: Uni.TUniQuery;
  ID: Integer;
  GroupName: String;
begin
  GroupName := 'MyGroupName';

  q := TUniQuery.Create(nil);
  try
    q.Connection := Datenmodul.UniConnection;
    q.ParamCheck := true; // the default value of ParamCheck is true.
    q.SQL.Clear;
    q.SQL.Add('SELECT GRP_NO, GROUPNAME FROM GROUPDATA WHERE GROUPNAME = :GROUPNAME');
    q.ParamByName('GROUPNAME').AsString := GroupName;
    q.Open;

    if q.RecordCount > 0 then
      ID := q.FieldByName('GRP_NO').AsInteger
    else
    begin
      // there exist no group with this name, so insert this new name
      q.SQL.Clear;
      q.SQL.Add('INSERT INTO GROUPDATA');
      q.SQL.Add('(GROUPNAME)');
      q.SQL.Add('VALUES');
      q.SQL.Add('(:GROUPNAME)');
      q.SQL.Add('RETURNING GRP_NO;');

      q.ParamByName('GROUPNAME').AsString := GroupName;
      q.Execute;
      ID := q.ParamByName('RET_GRP_NO').AsInteger;
    end;
  finally
    q.Free;
  end;
end;

0

我知道这个问题很久以前就被回答了,但我必须尽可能清晰地写下来,为那些像我一样需要的人。

我也需要“INSERT..RETURNING”这个东西。 Delphi让我疯狂了很长一段时间,直到我改变了我的数据访问组件。 我甚至从Delphi XE2移动到XE5,只是因为那个...

结论:IBX不支持RETURNING! FireDAC非常适合我在Firebird中所需的一切。

只要转到FireDAC,你就能做到你需要的一切,并且具有高性能。


0
据我所知,IBX 应该进行一些更改。内部应该将 INSERT ... RETURNING 与带有返回参数的可选过程视为相同处理方式。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接