How to pass NULL value from Unidac Query component in Delphi? - delphi

When I am using StoredProc component in Delphi using
ParamByname('ParamName').Clear I'm able to send NULL value.
But how can I pass NULL value when using a Query component?
with Query do
begin
SQL.ADD('exec d_upd_calc'+Quoted(EditCalc.Text));
end
In the above scenario I want to send NULL if the edit box is blank.
I am using Delphi 2010, Unidac with Sybase.

Even in Queries you can work with parameters:
Query.SQL.Text := 'exec d_upd_calc :myparam';
Query.Prepare;
Query.ParamByName('myparam').Clear;
And it's better to use parameters than to build the complete string, because you must not handle quotes and avoid security leaks via SQL-injection.

With Advantage DB I would do something along these lines:
var
sqlText: string;
with Query do
begin
if EditCalc.Text = '' then
sqlText := 'exec d_upd_calc NULL' else
sqlText := 'exec d_upd_calc '+Quoted(EditCalc.Text);
SQL.ADD(sqlText);
end;
If the keyword is also NULL then this should work.
Does Quoted remove/escape any dangerous user input to prevent SQL injection? If yes then it's good. If not then it should.

Related

How to use a SQL where statement in delphi properly?

I have a problem with issuing a SQL statement. I know that the English value should be a string on its own and I've tried that but it keeps throwing me one of these errors
procedure TfrmPetersonGroup.btnEnglishClick(Sender: TObject);
var
sSqlQuery:string;
begin
//2.4
dmoBandB.qryQuery.SQL.Clear;
sSqlQuery:='DELETE FROM tblClients WHERE Nationality =' + ' English';
dmoBandB.qryQuery.SQL.Text := sSqlQuery;
dmoBandB.qryQuery.active := true;
end;
I suggest you get a safe query. As below:
procedure SafeDeleteReq(SQLQuery: TSQLQuery; del: string);
begin
SQLQuery.SQL.Text := 'DELETE FROM tblClients WHERE Nationality=:Nationality';
SQLQuery.ParamByName('Nationality').AsString := del;
SQLQuery.ExecSQL();
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
SafeDeleteReq(SQLQuery1, 'English');
end;
You could change your sSql to
sSqlQuery:='DELETE FROM tblClients WHERE Nationality = ' + QuotedStr('English');
just to get it working, but that's not the best idea, see below.
Your version of it caused the error because, without quotes around it, the Sql parser thought that English was an identifier, e.g. another column name like Nationality.
Using QuotedStr around column values ensures that single-quote characters embedded in the value, like
O'Brien
are escaped correctly.
The other thing is that you should replace
dmoBandB.qryQuery.active := true;
by
dmoBandB.qryQuery.ExecSql;
The reason is that setting Active to True is equivalent to calling .Open, which is invalid in this context because .Open only works if the Sql query returns a result set and DELETE does not (sorry, I should have noticed this problem first time around). Once you've called ExecSql, you can reopen the table by setting qryQuery's Sql.Text to a valid SELECT statement and then calling .Open.
However, a betteer way to avoid your initial problem would be to get into the habit of using parameterised Sql statements - see http://docwiki.embarcadero.com/RADStudio/Rio/en/Using_Parameters_in_Queries, which is applicable to all Sql DML statements (Insert, Delete, Update, Select, etc). Apart from anything else, this may help you avoid Sql Injection exploits (https://en.wikipedia.org/wiki/SQL_injection).

Firebird TIBQuery insert with returning ... INTO

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.

Cursor not returned from Query

I'm using Delphi XE and FireBird 2.5.
Try use a TSQLStoredProc and give me the error "Cursor not returned from Query" when I put the Active property to TRUE.
An dummy example of storedproc
CREATE PROCEDURE NEW_PROCEDURE
RETURNS(
RDO SMALLINT)
AS
BEGIN
Rdo = 5;
/* Procedure body */
SUSPEND;
END;
As a workaround, a query like SELECT * FROM NEW_PROCEDURE should work (using TSQLQuery).
I think you are supposed to use the ExecProc method instead of Open / Active. Setting Active to true should only be used if your SQL Statement returns a ResultSet (a set of records), which yours doesn't.
Regards,
Stefaan

Delphi / ADO : how to get result of Execute()?

I have declared AdoConnection : TADOConnection; and successfully connected to the default "mysql" database (so, no need to pass that code).
Now, taking baby steps to learn, I would like to AdoConnection.Execute('SHOW DATABASES', cmdText); which seems to work ok, in the sense that it doesn't throw an exception, but I am such a n00b that I don't know how I can examine the result of the command :-/
Halp!
#mawg, the SHOW DATABASES command returns an dataset with one column called 'Database', so you can use the TADOQuery component to read the data.
try this code.
var
AdoQuery : TADOQuery;
begin
AdoQuery:=TADOQuery.Create(nil);
try
AdoQuery.Connection:=AdoConnection;
AdoQuery.SQL.Add('SHOW DATABASES');
AdoQuery.Open;
while not AdoQuery.eof do
begin
Writeln(AdoQuery.FieldByname('DataBase').AsString);
AdoQuery.Next;
end;
finally
AdoQuery.Free;
end;
end;
What you need is to reach the returned _Recordset.
If you don't mind using the Delphi components, you should use TADODataSet or TADOQuery to execute a SQL command which can return any results (or the more low-evel TADOCommands' Execute methods).
If you wanty something realy simple, w/o all the VCL balast, you mignt want to try the TADOWrap class from ExplainThat (MIT licenced).
You must use an TADOQuery connected to TADODataBase. At property SQL you must include the SQl statament to retrive databases in SGDB. In SQL Server you can do this:
SELECT * FROM sysdatabases
In MySQL must exist something similar to retrieve the names.
You can connect this TADOQuery to a TDataSource and a DBGrid to see visual result or use code to explore the result of the query (some similar to this):
ADOQuery1.Open;
while not ADOQuery1.eof do begin
Name := ADOQuery1.FieldByName('DBName').AsString;
ADOQuery1.Next;
end;
Regards
You need TAdoQuery to execute statements, that return results.
If you simply want to populate a grid why dont you use adotable ?
Adotable.open;
Adotable.first;
While NOT adotable.eof do(not sure about not or <>)
Begin
Put values in variant or array;
Adotable.next;
End
Then you can let any UI access the array or variant.
Or populate a dataset.

EXECUTE IMMEDIATE with USING clause giving errors

All,
I am very new to stored procedures in general but I am struggling especially with those in Oracle. I have created a very simple example of what I am trying to accomplish and I am still getting the same error with this simplified version.
The example stored procedure is as follows:
CREATE OR REPLACE PROCEDURE ashish_test
AUTHID CURRENT_USER IS
BEGIN
DECLARE
v_tab VARCHAR2(50);
v_strSQL VARCHAR2(50);
BEGIN
v_strSQL := 'SELECT * FROM :1';
v_tab := 'ex.emp';
EXECUTE IMMEDIATE v_strSQL USING v_tab;
END;
END;
When I call the above stored procedure using CALL ashish_test(), I get :
Error Message http://web1.twitpic.com/img/12831839-06a3ea536df5d5a0a839eb83d9e59d25.4a3936b8-scaled.jpg
Based on this article (Look for Example 7-1), USING keyword should replace the numbered placeholder (:1) within v_strSQL with the value stored in v_tab. However, I keep getting invalid table error. I am guessing it's because EXECUTE IMMEDIATE is unable to replace the placeholder with the value for some reason but I am not sure why that is. Does anyone know if I am doing something stupid here?
I am running this on Oracle 10g database & using PL/SQL Developer.
The USING clause is only for bind variables (i.e. where you would use column names in a select statement), not table names. Typical usage would look like this:
Select col1 from table1 where col2 = :a
If you want to use variable table names use something like this:
v_tab := 'ex.emp';
v_strSQL := 'SELECT * FROM ' || v_tab;
EXECUTE IMMEDIATE v_strSQL;

Resources