How do I test if a table exists in Interbase? - delphi

I have just recently started using Interbase, and I need to verify if the database has a table, how do I check in Interbase if a table exists in the database?

For IBX there are at least these ways disponible:
1. Using SQL query
You can query the RDB$RELATIONS system table in which you filter by the RDB$RELATION_NAME column. For example this query returns 1 when table called MyTable exists in the database:
SELECT 1 FROM RDB$RELATIONS
WHERE RDB$RELATION_NAME = 'MyTable'
With IBX on client side you can write for instance this:
function TableExists(Database: TIBDatabase; const TableName: string): Boolean;
var
Query: TIBSQL;
begin
Query := TIBSQL.Create(Database);
try
Query.SQL.Text := 'SELECT 1 FROM RDB$RELATIONS WHERE RDB$RELATION_NAME = ?';
Query.Params[0].AsString := TableName;
Query.ExecQuery;
{ RecordCount reports only visited records, so it should be either 0 or 1 now }
Result := Query.RecordCount > 0;
finally
Query.Free;
end;
end;
This version is case sensitive and is efficient when you need to determine if table exists from code only ocassionally (for checking frequently I would cache the list of all table names returned by the GetTableNames method and query just such list).
2. Using TIBDatabase.GetTableNames method
The TIBDatabase class is able to list all the table names by the GetTableNames method. In the returned string list collection you can then verify if the name exists by the IndexOf method. For example:
function TableExists(Database: TIBDatabase; const TableName: string): Boolean;
var
Tables: TStrings;
begin
Tables := TStringList.Create;
try
Database.GetTableNames(Tables, True);
Result := Tables.IndexOf(TableName) <> -1;
finally
Tables.Free;
end;
end;
This version is case insensitive (so long you won't change the default value of the CaseSensitive property of the returned collection) and is naturally not as efficient as the first way for single or ocassional use because this one fetches the whole table name collection from server to client. But the GetTableNames method itself can be useful for frequent use if you cache the returned collection.
3. Using TIBExtract class
The TIBExtract IBX class is intended for fetching metadata. Unfortunately it't not so efficient and easy to use for just checking whether certain table exists in database because it can fetch either list of all tables, or details of the table itself. So I leave this option without example.

Related

Storing Component Name as a String for later use

On my form I have a number of TMyQuery Components. Their names identify which MySQL Tables they work with. For example, COMPONENTSTABLE works with the COMPONENTS TABLE, etc.
There are about 30 tables, but that might change in the future.
I also use a basic String List to read field names from a Table called TIMESTAMPS. This table is updated via triggers when an UPDATE, INSERT, or DELETE occurs. Each field within the TIMESTAMPS Table refers to which Table was modified. There's only one record in the table! Based on the field values I can see which table changed so I can refresh it rather than refreshing all of them.
I don't want to do this;
If fieldbyname['COMPONENTSTABLE'] <> CurrentTimeStamp
then ComponentsTable.Refresh;
If fieldbyname['ORDERSTABLE'] <> CurrentTimeStamp
then OrdersTable.Refresh;
{ and so on forever }
What I want to do is;
Right now I have a String List with "Names / Values". Each "Name" is the Fieldname within the Table and "Value" is the TIMESTAMP provided by MySQL Triggers.
I've got the following;
For Idx := 0 to MyStringList.Count -1 do
Begin
If MyStringlist.ValueFromIndex[Idx] <> SomethingElse then
Begin
with (MyStringList.Names[Idx] as tMyQuery).Refresh;
End;
End;
I've got the String List functioning, the Names, the Values etc are all correct.
My question is this;
Is there a way I can use a String ("Names" column in the list) to refer to an Object if that Object exists?
I already have a function I use to refresh individual tables by passing an Object to it, but that's an Object and easy to work with. I'd like to pass the "Object" based on it's name retrieved from a String.
I hope this makes sense and you can follow what I'm after.
I am not sure what your question actually is. In the first part of the answer I assume that you don't really care about names of the objects but rather want some automated way of getting all the tables available refer to a field in another table. Below that, I answer your question about referring to an object if you know its name.
Automated way of handling all tables
It depends on what class your objects are.
From your description, I assume your TMyQuery are TComponent descendants owned by the form. Then the solution is very simple, as each TComponent has both a public Name and a list of owned components Components. You can then use something like this:
var
i: integer;
MyQuery: TMyQuery;
begin
for i := 0 to Pred(MyForm.ComponentCount) do
if MyForm.Components[i] <> TimeStampsTable then
if MyForm.Components[i] is TMyQuery then
begin
MyQuery := TMyQuery(MyForm.Components[i]);
if TimeStampsTable.FieldByName(MyQuery.Name).AsDateTime >= LastAccess then ...
end;
end;
Note that you may want to add extra checks, e.g. to make sure that MyQuery.Name is not empty or that it exists as a field in TimeStampsTable.
If your objects are only TObjects, then there is no "standard" name property and no standard registration of these objects. Name can be handled, apparently your component already has one so it's just a question of a proper type coercion, but object registration is a different matter. You may have to create some kind of a global list for all your created TMyQuery instances.
Getting an object instance based on that object's name
function TMyForm.GetQueryByName(const Name: string): TMyQuery;
var
Obj: TObject;
begin
Result := nil;
Obj := Self.FindComponent(Name);
if Obj <> nil then
if Obj is TMyQuery then
Result := TMyQuery(Obj);
end;
Or you could simply loop over all Components and use your own Name matching.
While the first part of the accepted Answer from #pepak isn't what I was looking for ( I've used similar code in the app previously and found it slow ), the second part of the Answer pointed my in the right direction.
My (thanks to Pepak) eventual solution was;
Function RefreshQueryByName(Const Name: String): Boolean;
Var
Obj: TComponent;
Begin
Result := False;
Obj := Self.FindComponent(Name);
If Obj <> nil Then
If Obj Is TMyQuery Then
With Obj As TMyQuery Do
If Active Then
Begin
Refresh;
Result := True;
End;
End;
Which I use by by passing a String I get from a Field Value that identifies which table I want to refresh.
Now, my Database App automatically refreshes a table changed by other users. It will now refresh any of the 30 tables of they are modified by another user without refreshing all tables.
Thanks for your help Pepak, I've accepted your answer and hope it is useful to others.

How to get the AUTOINC value when connecting via ADO?

I am trying to connect my database (Advantage 7.1 Server) using the Advantage OLE DB provider. So far so good...It connects with no problem with the code below:-
const
// the database we'll be connecting to
ConnectionString = 'Provider=Advantage OLE DB Provider;Data Source=C:\Data\'+
'UsersData.add;ServerType=ADS_REMOTE_SERVER|ADS_LOCAL_SERVER;User ID=ISUsers;Password=aAoO31';
My problem is, even though I'm able to connect to the database, any field with AUTOINC as the data type does not generate next numbers. "ID" as AUTOINC keeps on giving me zero (0) anytime I append the data instead of moving to the next number 1, 2, 3.... But for the same code if I switch to MS ACCESS, it works perfectly. What am I doing wrong? please find code below.
// Add template to database. Returns added template ID.
function TDBClass.addTemplate(template: TTemplate): Integer;
var
rs: TADODataSet;
tptStream: TMemoryStream;
id: Integer;
p: PChar;
begin
// get DB data and append one row
rs := TADODataSet.Create(nil);
rs.Connection := connection;
rs.CursorType := ctStatic;
rs.LockType := ltOptimistic;
rs.CommandText := 'SELECT * FROM enroll';
rs.Open();
rs.Append();
tptStream := TMemoryStream.Create();
// write template data to memory stream.
SafeArrayAccessData(template.tpt, Pointer(p));
tptStream.write(p^, template.size);
SafeArrayUnaccessData(template.tpt);
// save template data from memory stream to database.
(rs.FieldByName('template') as TBlobField).LoadFromStream(tptStream);
// update the database with added template.
rs.post();
// get the ID of enrolled template.
id := rs.FieldByName('ID').AsInteger;
// close connection
tptStream.Free();
rs.Close();
rs.Free();
addTemplate := id;
end;
You should consider using the TADSConnection, TADSQuery, etc. components that you can still download from the DevZone (http://devzone.advantagedatabase.com/dz/content.aspx?key=1) even for ADS 7.1.
If you have to use ADO, you probably have to use a different approach. (But see also bummi's comment about a possible bug in Delphi).
One way would be to use the LASTAUTOINC scalar function:
INSERT INTO
enroll
(
template
)
VALUES
(
:template
);
SELECT
LASTAUTOINC(CONNECTION) AS "id"
FROM
system.iota
In Delphi ADO you can pass more than 1 statement in your SQL query string - This might true for ADO in general.
Insert into test (text) Values('ddd'); Select * from test where AutoIncColumn = Scope_identity()
This will return the inserted row with the Autoinc value
or if you need just the autoinc value
Insert into test (text) Values('ddd'); Select Scope_identity() AutoIncColumn

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.

how to show Only relevant information in dbgrid delphi

information:
I have an order form.
With "keuze" and "aantal" it wright a new line. The Orderline gets an OrderID.
But the user may only see the orderline from his OrderID.
How can i make it work that it only shows, for example the OrderID "47" ?
procedure TfmOrder.btInvoerenClick(Sender: TObject);
begin
dm.atOrder.open;
dm.atOrder.Append;
dm.atOrder ['OrderStatus'] := ('Aangemeld');
dm.atOrder ['klantID'] := fminloggen.userid;
dm.atOrder ['OrderDatum'] := Kalender.date;
dm.atOrder ['Opmerkingen'] := leOpmerkingen.text;
dm.atOrder.post;
cbkeuze.Visible := true;
dbRegel.Visible := true;
leAantal.visible := true;
btOpslaan.Visible:= true;
end;
This is the code for making a new Order
procedure TfmOrder.btOpslaanClick(Sender: TObject);
var orderid:string;
begin
dm.atOrderregel.Open;
dm.atDier.open;
dm.atorderregel.Append;
dm.atOrderregel ['AantalDieren'] := leAantal.text;
dm.atOrderregel ['OrderID'] := dm.atOrder ['OrderID'];
dm.atOrderregel ['Diernaam'] := cbKeuze.Text;
dm.atOrderregel.Post;
leaantal.clear;
cbkeuze.ClearSelection;
end;
And this for a new orderline
thanks in advance
I know got a different error using this code:
begin
dm.atorder.Open;
dm.atorder.filter := 'KlantID = ' + (fminloggen.userid);
dm.atorder.filtered := true;
while not dm.atorder.Eof do
begin
cbOrder.Items.Add (dm.atorder['OrderID']);
dm.atOrder.Next;
end;
dm.atOrder.Close;
end;
It gives an error: The arguments are from the wrong type, or doesn't have right reach or are in conflict with each other.
here is userid declared.
var Gevonden: boolean;
userid : string;
begin
dm.atInlog.open;
Gevonden := false;
while (not Gevonden) and (not dm.atInlog.eof) do
begin
if dm.atInlog['email'] = leUser.Text
then
begin
Gevonden := true ;
inlognaam := dm.atInlog['email'];
userid := dm.atInlog['KlantID'];
end
else
dm.atInlog.Next
end;
this is obviously in another form
You can use the Filter property of the data set:
atOrderregel.Filter := 'OrderID = 47';
atOrderregel.Filtered := True;
You can add the grid's columns property statically in the object inspector, showing only the fields you need. If the columns list is empty (default) it is filled with all available fields.
Just add as many columns as you need and link each column to the corresponding field. You can reorder the columns and set the widths and titles individually. There are still some more properties available which are worth to explore.
Im assuming your grid is bound to a datasource component. This datasource is then linked with a TDataset descendant. There are a couple of ways you could acheive the desired filtering of the dataset to display only orderid 47.
Firstly, you could set the Datasets SQL property to contain a (server side) SQL query such as:
SELECT * from table WHERE OrderID = #OrderID
You would also need to create a parameter in the dataset to pass the (changing) value for the required OrderID. So add a new Parameter to the dataset (#OrderID), and then at runtime you can set this parameter value in code, something like:
DataSet.Parameters['#OrderID'].Value := ParameterValue;
Alternatively, you could also FILTER the dataset (client side) to just show the correct data:
Set your SQL property of the dataset to retrive the entire table, something like:
SELECT * FROM table
And then at runtime you could set the Filter property of the dataset to only get OrderID 47:
Dataset.Filter := 'OrderID = '+InttoStr(ParameterValue);
Depending on your needs one method may suit better (performance/memory) wise.
As Najem has commented, there is also a third method - using a Master-Detail dataset relationship. This method works using two datasets, one is the master of the other. When the master table record is changed, the detail dataset is then filtered using the value defined in the Key or MasterFields property of the M-D relatioship.
If you are connected to some datasource you could always create a SQL Query. Something like:
SELECT * FROM YourDBTable WHERE OrderID=47

Executing queries using DAO

I want to execute a list of queries against an Access database using DAO. The "Database.Execute()" method seems suited for this with the remark that it can only execute "action queries", that is queries which don't return a result set (MSDN reference). For queries which return records "Database.OpenRecordset()" can be used. Both methods throw exceptions if the wrong type of query is passed.
Having both action queries and select queries in the list how can I decide upfront which will return records and which will not?
Note that the ADO Execute method can be used regardless of whether or not the query returns a resultset.
But do you really want to execute all 'queries'? What if they contain SQL DDL? Consider you created a PROCEDURE using this SQL DDL code:
CREATE PROCEDURE qryDropMe AS DROP PROCEDURE qryDropMe;
;)
Access maintains a hidden system table called MSysObjects which includes a field (Flags) which stores a value indicating the query type. You could try the following function with each of the query names from your list and use the return value to determine whether to use Database.Execute() or Database.OpenRecordset()
The function requires read permission for MSysObjects. I have heard reports than some Access 2007 users are denied permission to read MSysObjects. However, I haven't encountered that problem with Access 2007.
I tested several query types to determine the Flags values. If one of your queries is a type I didn't test, the function will return the Flags value as unrecognized. You can modify the function to include that Flags type.
The only DDL query I tested was DROP TABLE (Flags = 96).
Also, please be aware that not all "SELECT ... FROM ..." queries are select queries for your purpose (returning a recordset). A query such as "SELECT fields INTO newtable FROM oldtable;" does not return records, and the Access UI classifies it as a Make Table query.
Public Function QueryType(ByVal pQueryName As String) As String
Dim lngFlags As Long
Dim strType As String
Dim strCriteria As String
strCriteria = "[Name] = """ & pQueryName & """ And [Type] = 5"
lngFlags = DLookup("Flags", "MSysObjects", strCriteria)
Select Case lngFlags
Case 0
strType = "Select"
Case 16
strType = "Crosstab"
Case 32
strType = "Delete"
Case 48
strType = "Update"
Case 64
strType = "Append"
Case 80
strType = "Make Table"
Case 96
strType = "Drop Table"
Case 128
strType = "Union"
Case Else
strType = "Flags " & CStr(lngFlags) & " unrecognized"
End Select
QueryType = strType
End Function
Inspired by #HansUp's answer I investigated a bit more the QueryDef structure provided by the DAO interface. The structure has a "Type" property which I can use to differentiate between different query types (MSDN). I ended up with the following implementation:
function TAccessDatabase.SQLExec(AName, AQuery: String): Integer;
var
I: Integer;
QDef: QueryDef;
QDefExists: Boolean;
begin
Result := 0;
// Lookup querydef in the database
QDefExists := False;
for I := 0 to DB.QueryDefs.Count - 1 do
begin
QDef := DB.QueryDefs[I];
if QDef.Name = AName then
begin
QDefExists := True;
break; //-->
end;
end;
// Create query def if it doesn't exists
if not QDefExists then
begin
QDef := DB.CreateQueryDef(AName, AQuery);
// Refresh is required to get the correct QDef.Type_
DB.QueryDefs.Refresh;
end;
// Execute the query only if it is not a SELECT
if QDef.Type_ <> dbQSelect then
begin
db.Execute(AQuery, dbInconsistent);
Result := DB.RecordsAffected;
end;
end;
Thank you all for the helpful answers and remarks.
Why don't you catch the exception thrown and analyse it?
Do you have any way to use a beginTrans/Rollback instruction? You could then send your SQL command, collect the errors, then rollback your transactions, having your database left unchanged.
What about using ADO connection, somewhat smarter than the ADO one, where the connection hold an 'errors' collection and returns some other data like number of records affected?
This information applies to the type of the query. So:
All queries that execute a SELECT ... FROM ... are select queries
All INSERT, UPDATE, DELETE are action queries
You just have to inspect the query sql command text to look if it starts with any of the above keywords and act accordingly.

Resources