I am working on Delphi application which deals with stored procedures.I created stored procedure for insert statement and it works fine for my application.
Now,I wanted to create the same for select statement.
CREATE TYPE list_all_firstname AS ( first_name character varying);
CREATE FUNCTION select_sp()
RETURNS SETOF list_all_firstname AS
$$
DECLARE
rec record;
BEGIN
FOR rec IN (SELECT first_name FROM person) LOOP
RETURN NEXT rec;
END LOOP;
END;
$$ LANGUAGE plpgsql;
The call is:
SELECT * FROM select_sp();
Till this everything is fine in postgres.I wanted to access this stored procedure in my delphi application.
My code is:
with StoredProc2 do begin
StoredProcName :='select_sp';
ExecProc;
Edit5.Text:=ParamByName('list_all_firstname').AsString ;
end;
But i gets the error saying "Could not find object".How do i access return values of stored procedure in delphi??
I got the answer..could not find object is BDE error...
Next thing is accessing values,no need to use stored procedure component.We can use TQuery as below...:
Query1.SQL.Clear;
Query1.SQL.Add('SELECT * FROM select_sp()');
Query1.Active := True;
for i:=0 to Query1.RowsAffected-1 do
begin
sval:=Query1.FieldByName('first_name').AsString;
ShowMessage(sval);
Query1.Next;
end;
Related
I have an MS SQL Server 2019 table that has a column named "Char" and it is defined as nvarchar(4).
In my sample Delphi 10.3.3 code, I have a line that says:
found := ADODataSet1.Locate('Char', '⓪', []);
There is no record in the table with such a value, but when I execute the code, Locate returns True and positions to the first record in the result set. If I add a record with that value, Locate still returns True, but positions to the first record in the result set, not the record with the desired character.
With an ASCII character, the code works as expected.
Update:
The first record in my table has "0" in the Char column. If I delete that, then Locating "⓪" returns False. If I add "⓪" to the table then Locate finds that one, but if both "⓪" and "0" are included, then it finds the ASCII digit. If I try to locate "②", it returns the record with "2" in it. The Char column, by the way is a unique index on the table.
Steps to recreate problem.
SQL Server 2019, default US installation
Create table
CREATE TABLE [dbo].[Things](
[Thing] nvarchar NOT NULL
) ON [PRIMARY]
I created a VCL application with a TADODataSet, a TMemo and a TButton
Here is the code for the button:
procedure TForm2.Button1Click(Sender: TObject);
{} procedure Add2Table(aString: string);
begin
with ADODataSet1 do begin
Insert;
FieldByName('Thing').AsString := aString;
Post;
Memo1.Lines.Add('Added: ' + aString);
end;
end;
const
cTarget = '①';
begin
with ADODataSet1 do begin
Close;
CommandText := 'Select * from Things';
Open;
Memo1.Clear;
Memo1.Lines.Add('RecordCount ' + IntToStr(RecordCount));
Add2Table('0');
Add2Table('1');
Add2Table('2');
Add2Table('⓪');
Add2Table(cTarget);
Add2Table('②');
Close; Open;
Memo1.Lines.Add('Trying to locate: ' + cTarget);
if Locate('Thing', cTarget, []) then
Memo1.Lines.Add(Format('Found %s in record %d', [
FieldByName('Thing').AsString, Recno]))
else
Memo1.Lines.Add('Not found');
end;
end;
When the program runs, instead of finding the target character, "①", it finds '1'.
We are using MSSQL 2012.
Trying to update Client photo with Stored Procedure
spui_SetClientPhoto
int ClientID
VarBinary(Max) Photo
Program runs fine with pure ADO:
ADO.ProcedureName:='spui_SetClientPhoto';
ADO.Parameters.CreateParameter('#ClientsID',ftInteger,pdInput,0,95075);
ADO.Parameters.CreateParameter('#Photo',ftBlob,pdInput,0,NULL);
ADO.Parameters[1].LoadFromFile('C:\Photo.png',ftBlob);
ADO.ExecProc;
But with CDS it cause an error:
Implicit Conversion from datatype Varchar(max) to Varbinary(max) is not allowed.
ADO.ProcedureName:='spui_SetClientPhoto';
cds.SetProvider(ADO);
cds.Params.CreateParam(ftInteger,'#ClientsID',ptInput).AsInteger:=95075;
cds.Params.CreateParam(ftBlob,'#Photo',ptInput).LoadFromFile('C:\Photo.png', ftBlob);
cds.Execute;
e.g. cannot run CDS with Parameters of BLOB type. Any solution for this?
The following works fine for me, with the Picture field type in the AdoQuery and
CDS set to ftGraphic, and the Stored Proc's DDL set to
CREATE PROCEDURE [dbo].[SetClientPhoto](#ClientID int, #Picture Image)
AS
BEGIN
SET NOCOUNT ON;
update table_2
set picture = #Picture
where
ID = #ClientID
END
Code
procedure TForm1.SavePictureViaStoredProc;
var
PrvCommandText,
PrvSql : String;
ID : Integer;
const
scTestImage = 'D:\TestPictures\TestBMP.BMP';
begin
// First, save the text of the AdoQuery's Sql and the CDS's CommandText
PrvCommandText := CDS1.CommandText;
PrvSql := AdoQuery1.SQL.Text;
// Save the iD of the row we want to use
ID := CDS1.FieldByName('ID').AsInteger;
try
// Allow CommandText changes on the DSP
DataSetProvider1.Options := DataSetProvider1.Options + [poAllowCommandText];
CDS1.Close;
// construct a Sql statement to invoke the Stored Proc
CDS1.CommandText := 'exec dbo.SetClientPhoto #ClientID = :' + IntToStr(ID) + ', #Picture = :Picture';
// Set up parameters
CDS1.Params.Clear;
CDS1.Params.CreateParam(ftInteger, '#ClientID', ptInput);
CDS1.Params.CreateParam(ftGraphic, '#Picture', ptInput);
CDS1.Params.ParamByName('#ClientID').Value := ID;
CDS1.Params.ParamByName('#Picture').LoadFromFile(scTestImage, ftGraphic);
AdoQuery1.Close;
AdoQuery1.SQL.Text := '';
CDS1.Execute; // This executes the stored proc
CDS1.Params.Clear;
finally
ADoQuery1.SQL.Text := PrvSql;
CDS1.CommandText := PrvCommandText;
CDS1.Open;
end;
end;
Note: I very rarely store images in databases, and have not yet managed to get this to work with .Jpg and .Png files. I vaguely recall that there is an extra step needed with storing those in a DB without getting a "Stream read error" or "Invalid image" exception, and I'll see if I can remind myself of it later.
I have the same query only the connections change :
if DataModule1.1_CONNECTION.Connected = true
then begin
DataModule1.ZAM_GESLO.SQL.Text:='select user,pwd from users where user = :a';
DataModule1.ZAM_GESLO.Params.ParamByName('a').AsString := DataModule1.LOGIN_QUERY.FieldByName('user').AsString;
DataModule1.ZAM_GESLO.Open;
cxGrid1.ActiveLevel.GridView := MYGRIDVIEW1;
end else
if DataModule1.2_CONNECTION.Connected = true
then begin
DataModule1.ZAM_GESLO.SQL.Text:='select user,pwd from users where user = :a';
DataModule1.ZAM_GESLO.Params.ParamByName('a').AsString := DataModule1.LOGIN_QUERY.FieldByName('user').AsString;
DataModule1.ZAM_GESLO.Open;
cxGrid1.ActiveLevel.GridView := MYGRIDVIEW2;
end;
.......
This is a long way arround,so I was wondering if this can be done in any other optimized way so I dont have to write the same query all over again?
To save duplicated code, you could add a procedure to your form
procedure TxForm.UseDataSet(ADataSet : TxDataSet; AUserName : String);
begin
if ADataSet.Active then
ADataSet.Close;
ADataSet.SQL.Text:='select user, pwd from users where user = :a';
ADataSet.Params.ParamByName('a').AsString := AUser;
ADataSet.Open;
cxGrid1.ActiveLevel.GridView := AGridView;
end;
Note: I've used "x" in the ADataset parameter's type because I didn't know which type of dataset you're using, likewise your Form's type.
Then, you could re-write your code as
if DataModule1.1_CONNECTION.Connected then
begin
UseDataSet(DataModule1.ZAM_GESLO,
DataModule1.LOGIN_QUERY.FieldByName('user').AsString);
end
else
if DataModule1.2_CONNECTION.Connected then
begin
UseDataSet(DataModule1.ZAM_GESLO,
DataModule1.LOGIN_QUERY.FieldByName('user').AsString);
end;
Btw, it's good that you obviously wondered about duplicated code when you asked your q. Whenever you find yourself writing essentially the same code using different objects, consider writing a procedure (or function) which contains the repetitive code and which operates on the objects as parameters.
I have sample firebird stored procedure
PROCEDURE PROCEDURE01
RETURNS (
PARAMETER01 VARCHAR(50))
AS
BEGIN
PARAMETER01 = 'Hello';
END
and in the Delphi side
LCommand := SQLConnection1.DBXConnection.CreateCommand;
LCommand.CommandType := TDBXCommandTypes.DbxStoredProcedure;
LCommand.Text := 'PROCEDURE01';
LIdOut := LCommand.CreateParameter;
LIdOut.ParameterDirection := TDBXParameterDirections.OutParameter;
LIdOut.DataType := TDBXDataTypes.WideStringType;
LIdOut.Name := 'PARAMETER01';
LCommand.Parameters.AddParameter(LIdOut);
LCommand.Prepare;
LReader := LCommand.ExecuteQuery;
and receive exception
"Arithmetic exception, numeric overflow, or string truncation"
It seems a limitation of the TDBXCommand (or maybe of the Dbexpress firebird driver), because using output parameters of another types all works fine. As workaround you can use the TSQLStoredProc class.
Try this sample.
var
LSQLStoredProc : TSQLStoredProc;
begin
LSQLStoredProc:=TSQLStoredProc.Create(nil);
try
LSQLStoredProc.SQLConnection:=SQLConnection1;
LSQLStoredProc.StoredProcName:='PROCEDURE01';
LSQLStoredProc.ExecProc;
ShowMessage(LSQLStoredProc.ParamByName('PARAMETER01').AsString);
finally
LSQLStoredProc.Free;
end;
end;
SET the database to UTF-8 as an example.
CREATE DATABASE localhost:mybase
USER SYSDBA
PASSWORD masterkey
PAGE_SIZE 8192
DEFAULT CHARACTER SET UTF8;
SET NAMES ISO8859_1;
CREATE TABLE scales (
ID ...,
byteken VARCHAR(50) COLLATE DE_DE,
look at my answer at Arithmetic exception, numeric overflow, or string truncation
first put "SUSPEND" before "END" in stored procedure
I have a firebird 2.x database with Generator and a trigger to generate the key field.
I need to get the returned value from below query.
INSERT INTO XXXX (vdate,description) values ('"+ VDate +"','"+ Description +"') returning vno INTO :ParamVoucherNo
I tried several versions of below code but it dont wrok and I get
Dynamic sql error sql error code = -104
Is it really possible to get the return value in delphi using TIBQuery ?
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();
Any suggestions?
From Firebird README.returning:
The INTO part (i.e. the variable list) is allowed in PSQL only (to
assign local variables) and rejected in DSQL.
As IBX uses DSQL, you should exclude INTO part from your query.
INSERT ... RETURNING for DSQL looks the same as a call of a stored procedure, which returns result set. So, you have to use Open instead of ExecSQL.
Your mixing of dynamic SQL with parameters is just confusing.
Do this instead:
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;
Using Delphi 6 I have the ID returning successfully using an EXECUTE BLOCK statement:
EXECUTE BLOCK
RETURNS ( DeptKey INT )
AS
BEGIN
INSERT INTO DEPARTMENT
( COMPANY_KEY, DEPARTMENT_NAME )
VALUES ( 1, 'TEST1' ) RETURNING DEPARTMENT_KEY INTO :DeptKey;
SUSPEND;
END;
From Delphi you can do the folliowing:
FQuery.SQL.Text := '<Execute Block Statement>';
FQuery.Open();
ANewKey := FQuery.Fields[0].AsInteger;
IBX is not Firebird ready
you can take a look at FIBPLUS who support Firebird features
FIBPlus also supports FB2.0 insert ... into ... returning. Now you
should not bother about getting generator values from the client but
leave them in the trigger. You can also use RDB$DB_KEY. New possible
variants of work with insert returning and RDB$DB_KEY are shown in the
example “FB2InsertReturning”.
Why not get the next value for VoucherNo first, followed by
"INSERT INTO table1 (vno, vodate,description) VALUES (:VoucherNo,:VoDate,:Description)");
?
Your trigger can then either be dispensed with (which is nice), or modified to detect null (or <= zero can be useful too) and only then populate the vno field.
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
Client-side you can :
select gen_id(gen_VoucherNos, 1) from rdb$database;
By modifying the trigger in this manner you save yourself a headache later on if/when you want to insert blocks of records
I wonder if that INSERT can be wrapped into EXECUTE BLOCK command.
Would IBX manage EXECUTE BLOCK then?
http://www.firebirdsql.org/refdocs/langrefupd20-execblock.html
http://firebirdsql.su/doku.php?id=execute_block
Hope to try it in both IBX and Unified Interbase in XE2
PS: Even if it does not, I found the library, that tells to work on top of IBX of Delphi XE2 (both x86 and x64) and to add EXECUTE BLOCK support: http://www.loginovprojects.ru/index.php?page=ibxfbutils#eb.
As I know there should be some changes to IBX made. Internally INSERT ... RETURNING should be treated the same way as a selectable procedure with returning parameters.
i know this question was answered a long time ago, but i must write this as clear as possible, for those who need this as i was.
i too, needed the "INSERT..RETURNING" thing.
the Delphi drove me crazy for a long time, until i changed my Data access components.
i even moved from Delphi XE2, to XE5 only because of that...
conclusion : IBX does NOT support RETURNING!
FireDAC is PERFECT for what i need with Firebird.
just move to FireDAC and you'll be able to do everything you need, and with high performance.
If you have a table with this 2 Fields: GRP_NO and GROUPNAME and you want to get the new GRP_NO you have to use RET_ as prefix, see example:
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;
From the IBx2 sources, you can do it like this:
//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 Code:
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;
Test enviroment:
Arch Linux X86
Firebird 3
Lazarus 1.9
FPC 3.0.4
Quick note: This works on new Firebird API in the IBX, But I didn't test it in Legacy Firebird API with the IBX.