Howto fetch IDENTITY columns with ADO in Delphi - delphi

I´m having trouble to fetch IDENTITY columns with ADO in Delphi XE2.
Question
The ADO Fields property doesen´t return/contain any IDENTITY columns.
Is there a property or something in the connection that will enable to fetch IDENTITY columns or is this a known bug ?
Scenario
I have a stored procedure (in SYBASE ASE), that fetch 1 row with 4 columns.
One of the column is a IDENTITY column.
In this example: ADOQuery.Fields.Count will return 3 and not 4. The IDENTITY column is missing.
Ex:
procedure TForm1.FormCreate(Sender: TObject);
var
test: string;
Password: String;
UserName: string;
ServerName : string;
NoRec: integer;
MyValue: integer;
begin
Password:='xxxx';
UserName:='yyyy';
ServerName := 'zzzzz';
ADOConnection := TADOConnection.Create(Application);
with ADOConnection do
begin
CommandTimeout := 0;
IsolationLevel := ilSerializable;
Attributes := [xaAbortRetaining];
KeepConnection := True;
LoginPrompt := False;
ConnectionString := 'Provider=ASEOLEDB.1;Password=' + Password +
';Persist Security Info=True' + ';User ID=' +
UserName + ';Data Source=' + ServerName;
end;
if not Assigned(ADOQuery) then
begin
ADOQuery := TADOQuery.Create(Application);
with ADOQuery do
begin
CommandTimeout := 0;
DisableControls;
CacheSize := 500;
Connection := ADOConnection;
CursorType := ctOpenForwardOnly;
end;
end;
ADOConnection.Open();
ADOQuery.SQL.Text:= 'sp_echo';
ADOQuery.Open;
NoRec:=ADOQuery.Fields.Count; //This will return: 3, the IDENTITY col is missing
//Trying to fetch the column this way, will fail (the field doesen´t exist):
//MyValue:=ADOQUery.FieldByName('col_1').AsInteger
end;
Here is script, table, stored procedure for this example
create table tbl_echo
(
col_1 numeric(10,0) identity,
col_2 varchar(255),
col_3 int,
col_4 int
)
insert into tbl_echo (col_2, col_3, col_4) select 'testing', 100, 200
create proc sp_echo
as
select top 1 * from tbl_echo

You have two different ways to get what you want:
You declare an output parameter in your SP and, after inserting, assign ##IDENTITY to this parameter. Execute such a SP by using a TADOStoredProc component, not a TADOQuery component. After the SP finnishes, when the control comes back to your Delphi code you just read the value of such a parameter.
You simply select ##IDENTITY, what will give you a result set with a single row with a single column containing the generated value. In this case you can use a TADOQuery, but in your Delphi code you use Open (not ExecSQL) to retrieve such a resultset and just read the value from the only field is has.

Okey, I´ve tried to use some other providers with the same result.
One simple solution for this problem is just to cast the IDENTITY column to an integer in my procedure/query. By doing that it works like expected.
create proc sp_echo
as
select top 1 cast(col_1 as int) as col_1, col_2, col_3, col_4 from tbl_echo

Related

How can i add fields to TfrxDBDataset

if Length(idStrArray)>0 then
begin
with DataModule4.ADQueryTemp do
begin
Close;
SQL.Clear;
SQL.Add('SELECT id, pato, ftest, res FROM tbl ');
SQL.Add('WHERE id IN ('+idStrArray+')');
Open;
(rprMasterDataFish as Tfrxmasterdata).DataSet := frxDst_Multi;
(rprMasterDataFish as Tfrxmasterdata).DataSetName := 'Multi';
end;
end;
Hello,
I have TfrxDBDataset component. I can add fields from table like above. But i also want to add fields and values manually at runtime.
I have text file like this :
id note
1 sample
2 sample
I want to read this text file and insert note to frxDst_Multi. Is this possible ?
I dont want to create a new column as note in tbl. Because, i have too many mysql server.
Thanks in advice,
You can't add fields to a dataset while it is open, so you have to do it before
it is opened, either in code or using the TDataSet fields editor. If you are
doing it in code, you can add the field in the dataset's BeforeOpen
event.
The next problem is that is you don't want to field to be bound to the table the
dataset accesses, you need to add it as a calculated field and set its value
in the dataset's`OnCalcFields' event - see example below.
Ideally, the added field would be a TMemoField, but unfortunately a TMemoField
can't be a calculated field (FieldKind = ftMemo). So probably the best thing you can do is to make it a String field, but then you will need to
give it a fixed maximum size and truncate the field's value at that size when you calculate its value.
Btw, I don't know whether your TfrxDBDataset supports the fkInternalCalc fieldkind, but if it does, then you could try adding the note field as a TMemoField instead of a TStringField one.
The one thing I haven't been able to do is to load the field's value from
an external file because you haven't said in your q how to determine the
name of the file which is to be read.
Obviously, the IsNull check in the frxDst_MultiCalcFields event is to avoid the overhead of reloading the file if its contents have already been read.
const
NoteFieldSize = 4096;
procedure TForm1.AddNoteField;
var
NoteField : TField;
begin
if frxDst_Multi.FindField('Note') = Nil then begin
NoteField := TStringField.Create(frxDst_Multi);
NoteField.FieldName := 'Note';
NoteField.Size := NoteFieldSize;
NoteField.FieldKind := fkCalculated;
NoteField.DataSet := frxDst_Multi;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
frxDst_Multi.Open;
end;
procedure TForm1.frxDst_MultiCalcFields(DataSet: TDataSet);
var
S : String;
begin
if DataSet.FieldByName('Note').IsNull then begin
S := 'a note'; // replace by code to set the field value
DataSet.FieldByName('Note').AsString := Copy(S, 1, NoteFieldSize);
end;
end;
procedure TForm1.frxDst_MultiBeforeOpen(DataSet: TDataSet);
begin
AddNoteField;
end;

obtain customer_id automatically on new record

I am pulling down records with a stored procedure of customers that have a customer_id=5.
Now the procedure gets the data and shows it in the grid and I want to add a new record .
How can I make sure that when I add new record in the grid it has automatically the same customer_id ( 5 ) ?
I dont want to write it down all the time.I used to know this but I forgot.The DB is sql server.
You can set the DefaultExpression to the used ID for the field you want. For example for a UniDAC stored procedure object you can do (ParamCustID is the procedure input parameter, FieldCustID is the returned dataset field name):
var
CustID: Integer;
begin
CustID := 123;
UniStoredProc1.StoredProcName := 'MyProcedure';
UniStoredProc1.PrepareSQL;
UniStoredProc1.ParamByName('ParamCustID').AsInteger := CustID;
UniStoredProc1.Execute;
UniStoredProc1.FieldByName('FieldCustID').ReadOnly := True;
UniStoredProc1.FieldByName('FieldCustID').DefaultExpression := IntToStr(CustID);
end;
The above code will setup the field for assigning same value for every appended tuple, and makes it read only. Out of curiosity, the same task for FireDAC would go like this (for applying changes, there must be an explicit method call for updating field attributes):
var
CustID: Integer;
begin
CustID := 123;
FDStoredProc1.StoredProcName := 'MyProcedure';
FDStoredProc1.Prepare;
FDStoredProc1.ParamByName('#ParamCustID').AsInteger := CustID;
FDStoredProc1.Open;
FDStoredProc1.FieldByName('FieldCustID').ReadOnly := True;
FDStoredProc1.FieldByName('FieldCustID').DefaultExpression := IntToStr(CustID);
FDStoredProc1.UpdateAttributes;
end;

Get the result from the Query

I write this code :
Var Q : TFDQuery;
begin
Q := TFDQuery.Create(Self);
Q.Connection := FDConnection1;
Q.Params.CreateParam(ftString,'N',ptOutput);// Try also ptResult
Q.Params.CreateParam(ftInteger,'ID',ptInput);
Q.SQL.Text := 'SELECT NOM FROM EMPLOYEE WHERE ID_EMP = :ID';
Q.Params.ParamByName('ID').Value := 1;
Q.Active := True;
ShowMessage( VarToStr(Q.Params.ParamByName('N').Value) );
The result should be the name of the employer.
I get an error :
'N' parameter not found
How can I get the result from the Query using the parameter?
If I can't , what is the the function of :
ptOutput
ptResult
Try this code:
procedure TForm1.ExecuteQuery;
var
SQL : String;
Q : TFDQuery;
begin
SQL := 'select ''Sami'' as NOM'; // Tested with MS Sql Server backend
try
Q := TFDQuery.Create(Self);
Q.Connection := FDConnection1;
Q.Params.CreateParam(ftString, 'Nom', ptOutput);// Try also ptResult
Q.SQL.Text := SQL;
Q.Open;
ShowMessage( IntToStr(Q.ParamCount));
Caption := Q.FieldByName('Nom').AsString;
finally
Q.Free; // otherwise you have a memory leak
end;
end;
You'll see that the created parameter no longer exists once the FDQuery is opened, because FireDAC "knows" that there is nothing it can do with it.
Then, replace Q.Open by Q.ExecSQL. When that executes you get an exception
with the message
Cannot execute command returning result set.
Hint: Use Open method for SELECT-like commands.
And that's your problem. If you use a SELECT statement, you get a result set whether
you like it or not, and the way to access its contents is to do something like
Nom := Q.FieldByName('Nom').AsString
You asked in a comment what is the point of ptOutput parameters. Suppose your database has a stored procedure defined like this
Create Procedure spReturnValue(#Value varchar(80) out)
as
select #Value = 'something'
Then, in your code you could do
SQL := 'exec spReturnValue :Value'; // note the absence of the `out` qualifier in the invocation of the SP
try
Q := TFDQuery.Create(Self);
Q.Connection := FDConnection1;
Q.Params.CreateParam(ftString, 'Value', ptOutput);// Try also ptResult
Q.SQL.Text := SQL;
Q.ExecSQL;
ShowMessage( IntToStr(Q.ParamCount));
Caption := Q.ParamByName('Value').AsString;
finally
Q.Free; // otherwise you have a memory leak
end;
which retrieves the output parameter of the Stored Proc into Q's Value parameter.
There is no need to manually create parameters. Data access components are smart enough to parse the SQL string and populate the parameters collection by themselves
Also to get the result you must read the query's fields. When you call Open on a Query component, the fields collection will be populated with the fields that you specified in the SELECT [fields] SQL statement
As a side note, I advice that you use the type-safe version to get the value from a TField or TParameter object: See more here
var
q : TFDQuery;
begin
q := TFDQuery.Create(Self);
q.Connection := FDConnection1;
q.SQL.Text := 'SELECT NOM FROM EMPLOYEE WHERE ID_EMP = :ID';
q.ParamByName('ID').AsInteger := 1;
q.Open;
ShowMessage(q.FieldByName('Nom').AsString);
end;

Multiline parameter in Delphi7 TAdoQuery

With Delphi 7 and SQL Server 2005 I'm trying to pass a multiline parameter (a Stringlist.text) to a TAdoQuery insert script.
The insert is successful, but when i take back data from the field, i take
Line 1 Line 2 Line 3
instead of
Line 1
Line 2
Line 3
The fieldtype in the table is nvarchar(MAX) and i can't change it to any other type, the table is not mine. I tried to change the parameter type from widestring to ftMemo, but nothing changes.
Any idea?
var
QRDestLicenze: TADOQuery;
LsLic := TStringList;
begin
LsLic := TStringList.Create;
LsLic.Add('Line 1');
LsLic.Add('Line 2');
LsLic.Add('Line 3');
QRDestLic.Parameters.FindParam('FieldName).Value := LsLic.Text;
QRDestLic.ExecSQL;
end;
I created a demo doing exactly the same thing, although using Delphi 6 and SQL Server 2008.
Memo1.Lines.Clear;
Memo1.Lines.Add('Line 1');
Memo1.Lines.Add('Line 2');
Memo1.Lines.Add('Line 3');
ADOQuery1.SQL.Text := 'INSERT INTO Absences '+
'(Employee, Date_from, Notes) '+
'VALUES (99999, ''16/04/2013'', :sNotes)';
ADOQuery1.Parameters.ParamValues['sNotes'] := Memo1.Lines.Text;
ADOQuery1.ExecSQL;
ADOQuery1.SQL.Text := 'SELECT Notes FROM Absences '+
'WHERE Employee = 99999';
ADOQuery1.Open;
Memo2.Lines.Text := ADOQuery1.FieldByName('Absence_notes').AsString;
This worked as expected, showing:
Line 1
Line 2
Line 3
in both memos.
The "Notes" field is of type VARCHAR(Max).
I left the parameter type as the default (ftString), and changed no other default settings on the TADOConnection or TADOQuery.
I was using the "Microsoft OLE DB Provider for SQL Server" as the ADO data link provider.
Could there be something else we might be able to try to reproduce your issue?

Delphi - Check if a LINKED table exists in Access 2003 DB using ADO

I have 2 mdb linked. Original.mdb (not to be touched) and a copy.mdb
I've been using this for checking if tables exists:
function CheckIfTableExists(myTable : AnsiString): boolean;
var
x := Integer;
bTrue : boolean;
begin
f1.ADOConnection1.GetTableNames(f1.ListBox1.Items,False);
bTrue := false;
for x := 0 to f1.ListBox1.Items.Count -1 do
begin
if (f1.ListBox1.Items.Strings[x] = myTable) then
begin
bTrue := true;
end;
end;
if (bTrue = true) then
begin
Result := True;
end
else
Result := false;
end;
end;
I'm sure there is a better way to do this, but so far it worked nice for me.
Now I need to check if a LINK to another mdb table exist. Does anyone know how to do that?
I.Bagon
Every Access MDB file has a hidden table named MSysObjects which is basically a list of all objects in the MDB.
You can query this table to find out if an object with a given name exists in the MDB.
MSysObjects contains ALL objects in the MDB. Not only tables, but forms and records as well.
You can filter on the Type column to get only the tables:
Type = 1 --> local table
Type = 6 --> linked table

Resources