How to get a table "saved" after INSERTING the first record and adding it to an ARRAY of objects? - sql

I have an empty table in access and add data with a SQL INSERT statement.There after I use a procedure to go through the table and use ADO to put all the values into an array of objects.
After debugging i can see that the procedure doesnt find any values in the table and says that the table is at EOF and doesnt extract any values out of the table.
However if i terminate my application (close the program) and run it again, it seems to have "inserted" and "saved" the table in access and then does find values in the table and extracs it with ADO, and inserts it into the array of objects.
somehow i need to het the access database "saved" through delphi
//inserts the new(first and following) records
begin
qryVote.Active := False;
qryVote.SQL.Text := 'insert INTO tblkandidate ([Leerder nr],Van,Naam,geboortedatum,[id nr],geslag,[sel nr],debietpunte,voogklas,deelname,stemme) VALUES ("'+leerdernr+'","'+van+'","'+naam+'",#'+gebdatum+'#,"'+idnr+'","'+geslag+'","'+selnr+'",'+inttostr(debiete)+',"'+voogklas+'","'+booltostr(bsport)+'",'+inttostr(stemme)+') ';
qryVote.ExecSQL;
qryVote.SQL.Text := 'select * from tblkandidate';
qryVote.Active := true;
KandidateNaSkik; //procedure that goes through the table and puts every record into the array of objects (see below foe precedure)
showmessage('Jou pesoonlike data is gestoor');
end;
procedure Tfrmvote.KandidateNaSkik;
var
leerdernr,naam,van,idnr,selnr,voogklas,gebdatum,geslag : STRING;
stemme, debiete: integer;
bsport, gestem : boolean;
begin
frmvote.ADOTableVotek.open;
with dbgviewp.DataSource.DataSet do
begin
frmvote.ADOTableVotek.first;
iaantkan :=0;
while not frmvote.ADOTableVotek.EOF do
begin
inc(iaantkan);
leerdernr := frmvote.ADOTableVotek['LEERDER NR'];
van := frmvote.ADOTableVotek['VAN'];
naam := frmvote.ADOTableVotek['NAAM'];
gebdatum := frmvote.ADOTableVotek['geboortedatum'];
idnr := frmvote.ADOTableVotek['id nr'];
geslag := frmvote.ADOTableVotek['geslag'];
selnr := frmvote.ADOTableVotek['sel nr'];
debiete := frmvote.ADOTableVotek['debietpunte'];
voogklas := frmvote.ADOTableVotek['voogklas'];
bsport := frmvote.ADOTableVotek['deelname'];
stemme := frmvote.ADOTableVotek['stemme'];
gestem := frmvote.ADOTableVotek['gestem'];
//the above variables get sent to the array below
arrkandidaat[iaantkan] := tVerkiesing.create(leerdernr, van, naam,
gebdatum,idnr,geslag,selnr,debiete,voogklas,bsport,gestem,stemme);
frmvote.ADOTableVotek.Next;
end;{while}
end;{with}
end;

Since you insert a record via qryVote.ExecSQL, the ADOTableVotek dataset dose not "know" that a new record was added to the DB, so either you call:
frmvote.ADOTableVotek.Requery;
e.g.:
procedure Tfrmvote.KandidateNaSkik;
begin
...
if not frmvote.ADOTableVotek.Active then
frmvote.ADOTableVotek.Open
else
frmvote.ADOTableVotek.Requery;
...
end;
Or add the new record via ADOTableVotek itself like this:
ADOTableVotek.Append; // add new record
ADOTableVotek.FieldByName('Leerder nr').AsString := leerdernr;
// ADOTableVotek.FieldByName('etc..')...
// etc...
ADOTableVotek.Post; // post new record to the DB
The changes will reflect imedeatlly into ADOTableVotek and in your dbgviewp.
This way the ADOTableVotek can be always active. You need to call ADOTableVotek.Open only once (or set ADOTableVotek.Active := True). And you don't need to fetch all records from your DB each time you insert a new record.

In order to reflect the changes try closing and reopening the dataset, like so
frmvote.ADOTableVotek.Close;
frmvote.ADOTableVotek.Open;

Related

How can I read a whole record at once from a TDataSet?

I am fetching data from a mySQL database using a query (actually a TMyQuery from Devart, inherited from a TDataSet). Usually I process one field at a time inside a while not eof loop using fieldbyname().
As some processing is complex I'd like instead to fetch the whole record and pass that to a procedure to do the processing of each field away from the loop.
I think it might be to do with the GetCurrentRecord() method which seems to return a pointer to Byte if it's overridden but then I'm not clear on how to extract my fields from that pointer.
If this is possible to do, please can someone show me the syntax to fetch one whole record, pass it as a parameter and then extract each field?
The structure of the code I usually use
Procedure ProcessTable;
begin
Query1.SQL.Clear;
Query1.SQL.Add(SQL);
Query1.Open;
Query1.first ;
while not Query1.Eof do
begin
var1 := Query1.FieldByName('field1').AsString;
var2 := Query1.FieldByName('field2').AsInteger;
//etc.
Query1.Next;
end;
Query1.close;
end;
Pseudo code of what I would like to do
procedure ProcessRecord( TheRecord : PByte );
begin
..
<extract the fields from TheRecord and process them>
..
end;
Procedure ProcessTable;
var
buffer : PByte;
begin
Query1.SQL.Clear;
Query1.SQL.Add(SQL);
Query1.Open;
Query1.first ;
while not Query1.Eof do
begin
buffer := Query1.GetCurrentRecord;
ProcessRecord(buffer);
Query1.Next;
end;
Query1.close
end;
The solution is not to try to pass a pointer to a buffer containing the record in question but to pass the whole query object. Then the relevant records can simply be recovered with FieldByName() as usual. The query object contains within it the position of the record currently pointed at so it doesn't need extracting into a buffer first.
So the solution, using the same pseudocode is.
procedure ProcessRecord( TheRecord : TQuery );
var
var1 : string;
var2 : integer;
begin
var1 := TheRecord .FieldByName('field1').AsString;
var2 := TheRecord .FieldByName('field2').AsInteger;
<process var1 and var 2>
end;
Procedure ProcessTable;
begin
Query1.SQL.Clear;
Query1.SQL.Add(SQL);
Query1.Open;
Query1.first ;
while not Query1.Eof do
begin
ProcessRecord(Query1); //pass the whole query object as type TQuery
//(or in my specific case, of type TmyQuery Query1.Next;
end;
Query1.close
end;

How to read all database entries?

This is the code I've got at the moment:
procedure TfrmLogin.performQuery;
begin
query.SQL.Text := 'SELECT * FROM LoginDatabase';
query.Open;
end;
procedure TfrmLogin.FormCreate(Sender: TObject);
begin
con := TFDConnection.Create(nil);
query := TFDQuery.Create(con);
con.LoginPrompt := false;
con.Open('DriverID=SQLite;Database=C:\Users\katiee\Documents\Embarcadero\' +
'Studio\Projects\ProgramDatabase;');
query.Connection := con;
performQuery;
username := query.FieldByName('Username').AsString;
passcode := query.FieldByName('Passcode').AsString;
end;
procedure TfrmLogin.btnLoginClick(Sender: TObject);
begin
if (edtUsername.Text = username) and (edtPasscode.Text = passcode) then
frmPayInfo.show
else
if MessageDlg('The username or password entered is incorrect.', mtWarning,
[mbOK, mbCancel], 0) = mrCancel then
frmLogin.Close;
end;
Right now the code, as it doesn't read all database entries, only allows the first database entry as correct answer.
I need to be able to enter any correct combination of username and pass-code from the database and open the form.
Why not call the .Locate method on the dataset? This way you will not be opening and closing the dataset, as you would need to with using a parameterised query.
Using the .Locate method will allow you to check (based off one, or many fields) if a certain record can be found.
If the record is found .Locate will return true.
For example.
if query.Locate('username;passcode', VarArrayOf([query.FieldByName('Username').AsString, query.FieldByName('Passcode').AsString]), [loPartialKey]) then
begin
frmPayInfo.show;
end
else
begin
Showmessage('User not found, or password incorrect');
end;
I haven't got Delphi on this machine, so syntax may not be 100%.

How to put a PLSQL parameter (:old.numero) within an UTL_HTTP.REQUEST statement?

I have a trigger in ORACLE PL/SQL like following :
create or replace TRIGGER HISTORISATION_PRIX
BEFORE UPDATE OF PRIX
ON PRODUIT
FOR EACH ROW
DECLARE
var VARCHAR2(30000);
BEGIN
var := SYS.UTL_HTTP.REQUEST('163.81.56.131:8019/Coctos/insertion?numero=:old.NUMERO&prix=:old.PRIX');
END;
As you see, inside my request, I want to put the parameters :old.NUMERO and :old.PRIX. But when I do that, the trigger does not work, I update my table PRODUIT but nothing happens.
But if I put some random numbers :
var := SYS.UTL_HTTP.REQUEST('163.81.56.131:8019/Coctos/insertion?numero=45&prix=555');
END;
then it works perfectly.
So, how can I introduce the parameters :old.NUMERO and :old.PRIX within my request ?
It looks like you've got ":old" within a string, which would mean that the literal value ":old.NUMERO" and ":old.PRIX" are being sent in your request instead of their values.
Try:
create or replace TRIGGER HISTORISATION_PRIX
BEFORE UPDATE OF PRIX
ON PRODUIT
FOR EACH ROW
DECLARE
var VARCHAR2(30000);
BEGIN
var := SYS.UTL_HTTP.REQUEST('163.81.56.131:8019/Coctos/insertion?numero=' || :old.NUMERO || '&prix=' || :old.PRIX);
END;
If that doesn't work, temporarily add an exception handler (before the END;) in your trigger and run an update directly on the table (from SQLPlus, with serveroutput on):
exception when others then
dbms_output.put_line(' Error Msg : ' || SYS.UTL_HTTP.get_detailed_sqlcode || SYS.UTL_HTTP.Get_Detailed_Sqlerrm);

I cant get my SQL INSERT statement to work with 2 tables in relationship in Delphi7

I Keep getting the following error
Project PAT_p.exe raised exception class EOleException with message 'Current provider does not support returning multiple recordsets from a single execution'. Process stopped. Use Step or Run to continue.
My tables in my database are linked in a one-to-many relationship where ID is the PK in tblDeelname and Nommer is a autonumber and the PK in tblAntwoorde. Unfortunately this is for a Practical assessment task at school and the relationship have to be there.
My Delphi code is as follow:
Procedure TfrmDN.btnBDClick(Sender: TObject);
var
sNaam, sVan, sKNommer, sAntwoord, sInteger : string;
begin
sNaam := lblNaam2.Caption; //
sVan := lblVan2.Caption; //Declaring Strings
sKNommer := lblKN2.Caption; //
sAntwoord := lblAntwoord1.Caption; //
inc(iTInskrywings); // Global var starting at 100 which is declared on form activate
sInteger := intostr(iTInskrywings);
frmData.qryVGKompetisieDB.Active := false; // query is on another form
frmData.qryVGKompetisieDB.SQL.Text := 'insert into tblDeelnemers (ID, Naam, Van, Kontaknommer) VALUES ("'+sInteger+'", "'+sNaam+'", "'+sVan+'","'+sKNommer+'")'; // Inserting Data into first Table
frmData.qryVGKompetisieDB.ExecSQL;
frmData.qryVGKompetisieDB.SQL.Text := 'insert into tblAntwoorde (ID, Antwoord) VALUES ("'+sInteger+'", "'+sAntwoord+'")'; // Inserting Data into second Table
frmData.qryVGKompetisieDB.ExecSQL;
frmData.qryVGKompetisieDB.Active := true;
end;
All the info going into the tables come from edit boxes and the ID is a global variable(Also apart of the assessment that have to be there). I made the Integer into a String due to another error that appeared before this one witch did not want me to insert a integer value with sql.
PLEASE HELP!!!
Thanks to #TLama I corrected my stupid mistake by only adding the select statement after the last execute.
Procedure TfrmDN.btnBDClick(Sender: TObject);
var
sNaam, sVan, sKNommer, sAntwoord, sInteger : string;
begin
sNaam := lblNaam2.Caption;
sVan := lblVan2.Caption;
sKNommer := lblKN2.Caption;
sAntwoord := lblAntwoord1.Caption;
inc(iTInskrywings);
sInteger := intostr(iTInskrywings);
frmData.qryVGKompetisieDB.Active := false;
frmData.qryVGKompetisieDB.SQL.Text := 'insert into tblDeelnemers (ID, Naam, Van, Kontaknommer) VALUES ("'+sInteger+'", "'+sNaam+'", "'+sVan+'","'+sKNommer+'")';
frmData.qryVGKompetisieDB.ExecSQL;
frmData.qryVGKompetisieDB.SQL.Text := 'insert into tblAntwoorde (ID, Antwoord) VALUES ("'+sInteger+'", "'+sAntwoord+'")';
frmData.qryVGKompetisieDB.ExecSQL;
frmData.qryVGKompetisieDB.SQL.Text := 'SELECT * FROM tblDeelnemers'; // The select statement needs to be there so that the dbgrid can display properly
frmData.qryVGKompetisieDB.Active := true;
end;

Looping on values, creating dynamic query and adding to result set

I have the following problem. I am an experienced Java programmer but am a bit of a n00b at SQL and PL/SQL.
I need to do the following.
1 Pass in a few arrays and some other variables into a procedure
2 Loop on the values in the arrays (they all have the same number of items) and dynamically create an SQL statement
3 Run this statement and add it to the result set (which is an OUT parameter of the procedure)
I already have experience of creating an SQL query on the fly, running it and adding the result to a result set (which is a REF CURSOR) but I'm not sure how I'd loop and add the results of each call to the query to the same result set. I'm not even sure if this is possible.
Here's what I have so far (code edited for simplicity). I know it's wrong because I'm just replacing the contents of the RESULT_SET with the most recent query result (and this is being confirmed in the Java which is calling this procedure).
Any and all help would be greatly appreciated.
TYPE REF_CURSOR IS REF CURSOR;
PROCEDURE GET_DATA_FASTER(in_seq_numbers IN seq_numbers_array, in_values IN text_array, in_items IN text_array, list IN VARCHAR2, RESULT_SET OUT REF_CURSOR) AS
query_str VARCHAR2(4000);
seq_number NUMBER;
the_value VARCHAR2(10);
the_item VARCHAR2(10);
BEGIN
FOR i IN 1..in_seq_numbers.COUNT
LOOP
seq_number := in_seq_numbers(i);
the_value := trim(in_values(i));
the_item := trim(in_items(i));
query_str := 'SELECT distinct '||seq_number||' as seq, value, item
FROM my_table ai';
query_str := query_str || '
WHERE ai.value = '''||the_value||''' AND ai.item = '''||the_item||'''
AND ai.param = ''BOOK''
AND ai.prod in (' || list || ');
OPEN RESULT_SET FOR query_str;
END LOOP;
EXCEPTION WHEN OTHERS THEN
RAISE;
END GET_DATA_FASTER;
A pipelined table function seems a better fit for what you want, especially if all you're doing is retrieving data. See http://www.oracle-base.com/articles/misc/pipelined-table-functions.php
What you do is create a type for your output row. So in your case you would create an object such as
CREATE TYPE get_data_faster_row AS OBJECT(
seq NUMBER(15,2),
value VARCHAR2(10),
item VARCHAR2(10)
);
Then create a table type which is a table made up of your row type above
CREATE TYPE get_data_faster_data IS TABLE OF get_data_faster_row;
Then create your table function that returns the data in a pipelined manner. Pipelined in Oracle is a bit like a yield return in .net (not sure if you're familiar with that). You find all of the rows that you want and "pipe" them out one at a time in a loop. When your function completes the table that's returned consists of all the rows you piped out.
CREATE FUNCTION Get_Data_Faster(params) RETURN get_data_faster_data PIPELINED AS
BEGIN
-- Iterate through your parameters
--Iterate through the results of the select using
-- the current parameters. You'll probably need a
-- cursor for this
PIPE ROW(get_data_faster_row(seq, value, item));
LOOP;
LOOP;
END;
EDIT: Following Alex's comment below, you need something like this. I haven't been able to test this but it should get you started:
CREATE FUNCTION Get_Data_Faster(in_seq_numbers IN seq_numbers_array, in_values IN text_array, in_items IN text_array, list IN VARCHAR2) RETURN get_data_faster_data PIPELINED AS
TYPE r_cursor IS REF CURSOR;
query_results r_cursor;
results_out get_data_faster_row := get_data_faster_row(NULL, NULL, NULL);
query_str VARCHAR2(4000);
seq_number NUMBER;
the_value VARCHAR2(10);
the_item VARCHAR2(10);
BEGIN
FOR i IN 1..in_seq_number.COUNT
LOOP
seq_number := in_seq_numbers(i);
the_value := trim(in_values(i));
the_item := trim(in_items(i));
query_str := 'SELECT distinct '||seq_number||' as seq, value, item
FROM my_table ai';
query_str := query_str || '
WHERE ai.value = '''||the_value||''' AND ai.item = '''||the_item||'''
AND ai.param = ''BOOK''
AND ai.prod in (' || list || ');
OPEN query_results FOR query_str;
LOOP
FETCH query_results INTO
results_out.seq,
results_out.value,
results_out.item;
EXIT WHEN query_results%NOTFOUND;
PIPE ROW(results_out);
END LOOP;
CLOSE query_results;
END LOOP;
END;
Extra info from Alex's comment below useful for the answer:
you can have multiple loops from different sources, and as long as the
data from each be put into the same object type, you can just keep
pumping them out with pipe row statements anywhere in the function.
The caller sees them as a table with the rows in the order you pipe
them. Rather than call a procedure and get a result set as an output
parameter, you can query as select seq, value, item from
table(package.get_data_faster(a, b, c, d)), and of course you can
still have an order by clause if the order they're piped isn't what
you want.