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

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;

Related

How to correctly call a SQL stored procedure using R's DBI library?

Below I have a stored procedure created in Oracle. I've cut out the majority of the middle and am just focusing on the input and output:
CREATE OR replace PROCEDURE datatrans_n.dc_cpt_patient_new
-- Declaring input and output.
( p_study_id IN patient.study_id%TYPE,
p_subject IN patient.subject%TYPE,
p_subject_dict IN patient.subject_dict%TYPE,
p_site IN patient.site%TYPE,
p_cancer_type IN patient.cancer_type%TYPE,
p_comments IN patient.comments%TYPE,
ps_patient_status_name IN patient_status.patient_status%TYPE,
pml_link_date IN NVARCHAR2,
status OUT NUMBER ) ...
-- Returning 1 if successful.
status := SQL%rowcount;
-- Committing.
COMMIT;
ELSE
-- Returning 0 if unsuccessful.
status := 0;
END IF;
-- Raising exceptions.
EXCEPTION
WHEN no_data_found THEN
NULL;
WHEN OTHERS THEN
ROLLBACK;
RAISE;
END dc_cpt_patient_new;
/
And I'm attempting to call this stored procedure in R using the DBI library like so:
# Creating SQL.
query <- DBI::sqlInterpolate(
conn = con,
sql = "DECLARE STATUS NUMBER;
BEGIN
DATATRANS_N.DC_CPT_PATIENT_NEW(?study_id, ?subject, ?subject_dict, ?site,
?cancer_type, ?comments, ?patient_status,
?icf_date, STATUS);
END;",
.dots = hold_patient[c("study_id", "subject", "subject_dict", "site",
"cancer_type", "comments", "patient_status", "icf_date")]
)
# Executing SQL.
return <- DBI::dbExecute(con, query)
The issue is that when executing my SQL query, I'm not correctly capturing the value of STATUS in my stored procedure in my R variable return. I've tested this by editing the line Status := SQL%ROWCOUNT; to Status := 3; --SQL%ROWCOUNT;, and both times return has a value of 1.
How can I call this stored procedure so that I can capture the value of STATUS in return?

Read a file inside a package procedure in pl sql

I am trying to read a text file inside a Pl Sql procedure but to no avail -- syntax errors it seems. What am I doing wrong? My suspicion is I am not declaring something where I should be. Here is the first path of the package body:
CREATE OR REPLACE PACKAGE BODY COP_DBO.PACKAGE_TEMPLATE
AS
--
--*****************************************************************************************************
-- Purpose: Just a template
--
-- Inputs:
-- in_vSTR String
--
-- Returns:
-- None
--
-- Mod History:
-- 06/29/2016 KEvin Palmer - Created initial version of this procedure
--
-- Error Handling:
-- An error is raised if errors are encountered building or executing the SQL.
--
--*****************************************************************************************************
f UTL_FILE.FILE_TYPE;
s VACHAR2(200);
BEGIN
f := UTL_FILE.FOPEN('\\sp0034avrt\winixdb$\cow\dev', 'certs_file.txt', 'R');
UTL_FILE.GET_LINE(f,s);
UTL_FILE.FLCOSE(f);
dbms_outpit.put_line(s);
end;
sql_statments arr_sql_t := arr_sql_t(); --initialize a empty lis
--------------------------------------------------------------------------------
/* PROCEDURE AND VARIABLE
INITILIZATION FOR COW_DATALOAD_V2
/***************************************************************************/
------------------------------------------------------------------------------
--*********** PUT YOUR LIST OF CERTS BELOW ******************
v_certList arr_claims_t := arr_claims_t('3803617642',
'3805126441',
'3876849047',
'3873116383',
'3873306670',
'3878876718');
--COP VARIABLES---
new_copId NUMBER; --NEW COP ID
prod_copId NUMBER; --PROD COP ID
new_seq_id NUMBER; --NEW SEQ ID
suppl_count NUMBER; --supplemental count
v_SQL VARCHAR2(7000);
v_certLst VARCHAR2(2000);
n_success NUMBER := 0; --Count of success found
n_total NUMBER := 0; --Total Records proccessed
n_suppl NUMBER := 0; --Total Records proccessed
n_orders NUMBER := 0; --Total lmso orders downloaded
/*cop procedure*/
PROCEDURE COP_DATALOAD_V2(arr_claims arr_claims_t,
arr_sql arr_sql_t) AS
BEGIN
After that begin I have two procedures. Everything after end for the file stuff is highlighted with some type of syntactic error. What am I doing wrong?
EDIT: How is this question a duplicate of that othre question? I don't see it. Sorry if it's obvious. I don't see the similarity at all.
Typical package body declaration looks like this
create or replace package body package_name is
var_name number;
procedure proc_name is
begin
do_smth;
end;
begin
init_smth;
end ;
Variables, methods, initialization. In your code as opposite:
create or replace package body package_name is
var_name number;
begin
init_smth;
end ;
var_name2 number;
procedure proc_name is
begin
...
Variables, initialization, variables again, methods. It looks like you should place pieces of code in right places

Delphi - Query running slow

My query,when run takes about 7 seconds to do what is supposed to.But,since its inserting about 30 records,I think it is too slow.Now,either I am running the query that is not written well or it does actually takes this much time. But that would be strange. The underlying database is SQLite and the query looks like this :
procedure TForm1.cxButton1Click(Sender: TObject);
begin
with UNIquery2 do begin
Close;
SQL.Clear;
UNIQuery1.First;
while Uniquery1.EOF = false do begin
SQL.Text:= 'INSERT INTO MYTABLE (FIELD1,FIELD2,FIELD3,FIELD4) VALUES (:a1,:a2,:a3,:a4)';
ParamByName('a1').asString := AdvOfficeStatusBar1.Panels[0].Text;
ParamByName('a2').asString := UniTable1.FieldByName('FIELD2').asString;
ParamByName('a3').asString := Uniquery1.FieldByName(',FIELD3').asString;
ParamByName('a4').Value := Uniquery1.FieldByName('FIELD4').Value;//boolean field true/false
Uniquery1.Next;
ExecSQL;
end;
end;
end;
So can someone tell me if this is OK or am I missing something ?
All fields are text except the 'a4' which is boolean (true/false).
The answer,modified (based on suuggestion from LS_dev):
procedure TForm1.cxButton1Click(Sender: TObject);
begin
with UNIquery2 do begin
Close;
SQL.Clear;
SQL.Add('INSERT INTO MYTABLE (FIELD1,FIELD2,FIELD3,FIELD4) VALUES (:a1,:a2,:a3,:a4)');
SQL.Prepare;
UniTransaction.AddConnection(UniConnection2);
UniTransaction.StartTransaction;
try
UNIQuery1.First;
while Uniquery1.EOF = false do begin
Params[0].asString := AdvOfficeStatusBar1.Panels[0].Text;
Params[1].asString := UniTable1.FieldByName('FIELD2').asString;
Params[2].asString := Uniquery1.FieldByName(',FIELD3').asString;
Params[3].Value := Uniquery1.FieldByName('FIELD4').Value;//boolean field true/false
Uniquery1.Next;
ExecSQL;
end;
UniTransaction.Commit;
finally
if UNIquery2.Connection.InTransaction then
UNIquery2.Connection.Rollback;
end;
end;
end;
Don't know Delphi, but will suggest some improvements:
You are not using a transaction. You should have something like something like auto-commit disabled and COMMIT command after all insertions;
Your SQL.Text:=... should probably be out of while. If this property set compiles SQL statement, putting it out of while will prevent unnecessary VDBE compilations;
If your intent is copying rows from one table to another (with a static field), you may doing using a single SQL command like INSERT INTO MYTABLE SELECT :a1, FIELD2, FIEDL3, FIELD4 FROM source_table, setting ParamByName('a1').asString := AdvOfficeStatusBar1.Panels[0].Text
This is generic DB usage improvement, hope gives you some direction.
Suggestion using unique SQL:
procedure TForm1.cxButton1Click(Sender: TObject);
begin
with UNIquery2 do
begin
SQL.Clear;
SQL.Add('INSERT INTO MYTABLE (FIELD1,FIELD2,FIELD3,FIELD4) SELECT ?,FIELD2,FIELD3,FIELD4 FROM UNIquery1_source_table');
Params[0].asString := AdvOfficeStatusBar1.Panels[0].Text;
ExecSQL;
end;
end;
Suggestion using improved DB handling:
procedure TForm1.cxButton1Click(Sender: TObject);
begin
with UNIquery2 do
begin
Close;
SQL.Clear;
SQL.Add('INSERT INTO MYTABLE (FIELD1,FIELD2,FIELD3,FIELD4) VALUES (:a1,:a2,:a3,:a4)');
SQL.Prepare;
UniTransaction.AddConnection(UniConnection2);
UniTransaction.StartTransaction;
UNIQuery1.First;
while Uniquery1.EOF = false do
begin
Params[0].asString := AdvOfficeStatusBar1.Panels[0].Text;
Params[1].asString := UniTable1.FieldByName('FIELD2').asString;
Params[2].asString := Uniquery1.FieldByName(',FIELD3').asString;
Params[3].Value := Uniquery1.FieldByName('FIELD4').Value;//boolean field true/false
Uniquery1.Next;
ExecSQL;
end;
UniTransaction.Commit;
end;
end;
If the SQL INSERT itself is slow, I recommend to test its execution speed in an interactive client first. Or write a simple test application which performs one hard-coded INSERT and measures its execution time.
Also you can use the debugger, logging or a profiler to find out the operation in your code which consumes time - it could be the Uniquery1.Next or the ExecSQL for example.

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

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;

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.