Comparing Tables in Delphi without SQL - database

I'm having problems while using Tables in a Paradox Data Base in Delphi.
I need to compare two tables and verify which fields are identical and which are different. At the end, both tables must contain the same values on the fields.
However, everything must be done without using SQL, only pure Delphi.
Here is the code so far, but I'm not getting the expected result:
procedure TForm1.Button3Click(Sender: TObject);
var
s1,s2:string;
begin
Table1.First;
while not (Table1.Eof) do
Begin
s1 := Table1.FieldByName('Campo').AsString;
Table2.First;
while not (Table2.Eof) do
Begin
s2 := Table2.FieldByName('Campo').AsString;
if (s1 <> s2) then
begin
Table2.Append;
Table2.FieldByName('Campo').AsString :=
Table1.FieldByName('Campo').AsString;
end
else if (s1 = s2) then
begin
Table2.Next;
end;
Table2.Next;
End;
Table1.Next;
End;
End;

You need to make two passes to copy missing data from one table into the other. The easiest way is to just create a procedure to do it for you, and then call it twice:
procedure TForm1.CopyData(const Src, Dest: TTable);
var
CompareVal: string;
begin
Src.First;
while not Src.Eof do
begin
CompareVal := Src.FieldByName('Campo').AsString;
if not Dest.Locate('Campo', CompareVal, []) then
begin
Dest.Insert; // Or Dest.Append;
Dest.FieldByName('Campo').AsString := CopmareVale;
Dest.Post;
end;
Src.Next;
end;
end;
Call it like this:
procedure TForm1.Button3Click(Sender: TObject);
begin
CopyData(Table1, Table2);
CopyData(Table2, Table1);
end;
This works fine if the databases aren't really large. If they are, you can improve performance by adding indexes on the fields that you're comparing. See the Delphi help for TDataSet.Locate, which has more info and some links to other topics that might help. (Link is to current Delphi help, but the info is applicable to Delphi 7 as well for the most part.)

Read through all records of Table2 and put Table2.FieldByName('Campo').AsString values in a sorted TStringList. Then iterate through the records of Table1 and check stringList.IndexOf for all Table1.FieldByName('Campo').AsString. When IndexOf returns -1, append record to Table2. Switch Table1 and Table2 and repeat.

Related

Comparison of two arrays in Lazarus

I have a problem with Pascal, especially Lazarus.
First of all, I created two random arrays of integer:
procedure TForm1.b_arraycreate1Click(Sender: TObject);
begin
randomize;
for i := 1 to 5 do
arr1[i] := random(10);
end;
And
procedure TForm1.b_arraycreate2Click(Sender: TObject);
begin
randomize;
for j := 1 to 5 do
arr2[j] := random(10);
end;
I know, I could put it in one procedure as well but doesn't matter now.
I want to compare these two. I wrote the following code:
procedure TForm1.b_comparisonClick(Sender: TObject);
var v:boolean;
begin
for i := 1 to 5 do begin
for j := 1 to 5 do begin
if arr1[i] = arr2[j]
then
begin
v:=true;
end
else
begin
v:=false;
end;
end;
end;
if v = true
then
begin
ShowMessage('Yes, there is a similarity! You can find number ' +IntToStr(arr1[i])+ ' in array 1, position ' +IntToStr(i)+ ' and also in array 2, position ' +IntToStr(j)+ '.');
end
else
begin
ShowMessage('No similarities... Generate new ones!');
end
end;
In my own words: I want to push a button and then there should be a message window with the information if there is one number (for example 7) which exists in array 1 and array 2. If yes, it should also write the position (index) of this number.
Unfortunately, this program doesn't work and I don't know why. It always shows "No similarities" (and don't worry about the creation of the arrays. I also have a label where I can test the content of the arrays every time).
Is there a (silly) mistake in my code here?
As explained already by MartynA in his comment, your algorithm is wrong. Your words are:
if there is one number which exists in array 1 and array 2
To see if it is so, you must scan all array1 and, for each number, see if it exists somewhere in array2.
So yes, you need two cycles, one nested in the other. As soon as you find a correspondence, you must stop. Or, if you want more results (find multiple duplicates), show a message instead of stopping - and go ahead. Third possibility (more complicated): when found, store the couple of indexes (without overwrite old results...) and go ahead. I will only show the first option:
procedure TForm1.b_comparisonClick(Sender: TObject);
var
i,j: integer;
v: boolean;
begin
v := false;
for i := 1 to 5 do begin
for j := 1 to 5 do begin
if arr1[i] = arr2[j] then begin
v := true;
break
end
end // inner, j
end; // outer, i
if v = true
then ShowMessage(.....)
else ShowMessage('No similarities...');
end; // proc comparison
I tried to respect your code a bit, there are a few possible "shortcuts"; for example, if v is a boolean variable, it is better to write if v then instead of if v=true then, and some others, like
v := arr1[i]=arr[j];
...or... the outer loop does not need begin+end.
******* BEWARE (see comment below about break)
To stop/exit from two nested cycle is not so simple... perhaps a goto... the code above works, but the break does little work.
******* second update, as described by comment below. IT DOES NOT WORK, because if the break does not exit BOTH loops, the outer index gets modified. The correct cycle using TWO breaks is as follows:
for i := 1 to 5 do begin
for j := 1 to 5 do begin
if arr1[i] = arr2[j] then begin
v := true;
break
end
end; // inner, j
if v then break
end; // outer, i
Sorry for the mistakes... :-)
I would prefer a GOTO to exit both loops: it is faster, single instruction, and more clear ("goto found" instead of a generic break). But GOTOs are not very popular... so I've been afraid!

Warning: function result variable of a managed type does not seem to be initialized

The task I have requires me to create two routines one of which reads in data from a terminal and the other outputs data to the terminal, and another two routines which utilize an array to loop through these two routines to perform them multiple times.
The issue I am having is that the terminal crashes after one run through of the ReadComputer function instead of looping multiple times. The compiler is also providing me the following warning:
"Warning: function result variable of a managed type does not seem to be initialized"
although after extensive research and due to the fact that no one uses pascal I cannot find a solution. Any help is much appreciated! :)
I have provided a copy of my code here for reference:
program CompupterProgram;
uses TerminalUserInput;
type
Computer = Record
id: integer;
manafacturer: String;
year: integer;
warranty: integer;
end;
type Computers = Array of Computer;
function ReadComputer(): Computer;
begin
ReadComputer.id := ReadInteger('PLease Enter Computer Id:');
ReadComputer.manafacturer := ReadString('PLease Enter Computer Manafacturer:');
ReadComputer.year := ReadInteger('PLease Enter Computer Year:');
ReadComputer.warranty := ReadInteger('PLease Enter Computer Warranty:');
result := ReadComputer;
end;
procedure WriteComputer(c: Computer);
begin
WriteLn('Computer ID: ', c.id);
WriteLn('Computer Manafacturer ', c.manafacturer);
WriteLn('Computer Year ', c.year);
WriteLn('Computer Warranty ', c.warranty);
ReadLn();
end;
function ReadAllComputers(count: Integer): Computers;
var i: Integer;
begin
for i := 0 to count do
begin
ReadAllComputers[i] := ReadComputer();
end;
result := ReadAllComputers;
end;
procedure WriteAllComputers(computerArray: Computers);
var i: Integer;
begin
for i:= 0 to (length(computerArray)) do
begin
WriteComputer(computerArray[i]);
end;
end;
procedure Main();
var computers: Array of Computer;
index: Integer;
begin
computers := ReadAllComputers(3);
WriteAllComputers(computers);
end;
begin
Main();
end.
Computers is a dynamic array, and you need to set its length before use in ReadAllComputers with SetLength().
All dynamic arrays are zero based, so you need to count from zero to Length(aDynArray)-1 in a couple of places. Or use the High(aDynArray) function to express the highest possible value of it's index.
Note: The Result use in ReadComputer is superfluous. Either use the function name or the Result variable to return the function result. The latter is to prefer, since code will be more clear.
In freepascal the Result variable is defined only in ObjFPC or Delphi mode.

Insert DBGrid data into a multi-dimensional array

I have set a connection from Delphi to pgsql using ADOConnection, ADOQuery, DataSource and a DBGrid to present the results of my query.
The database contains 2 columns of values of type double, of some thousands of rows, which I would like to insert into a two-dimensional array.However, as am quite new I am not sure how to insert the contents of a DBGrid into an array. Any help much appreciated.
First of all if there are thousands of rows you need to assign fields into variables before reading to get rid of unnecessary text lookup time when using FieldByName.
I dont't have Delphi at hand but this should work or at least help you get started.
uses Math;
procedure ProcessArray(ADataSet: TDataSet);
var
field1: TField;
field2: TField;
len: Integer;
a: array of array[2] of double;
begin
len := 0;
SetLength(a, 0);
field1 := ADataSet.FieldByName('field1');
field2 := ADataSet.FieldByName('field2');
ADataSet.First;
while not ADataSet.Eof do
begin
Inc(len);
if len > Length(a) then
SetLength(a, len + Min(len, 16384));
a[len - 1][0] := field1.Value;
a[len - 1][1] := field2.Value;
ADataSet.Next;
end;
SetLength(a, len);
// Process the results in the a array
end;
What AlexSC is suggesting is to actually use TADODataSet.RecordCount property to set the size of an array initially. Please note that if TDataSet is not loaded completely from the database (uses server cursor for example), RecordCount will not necessarily contain the number of all records and the original solution above would be able to cope with this. I made a correction to it so it won't grow more than 16k items at once and the overhead will be at most 16k - 1 array entries. For information about TDataSet "lazy loading" refer to DBGrid with read ahead capability using ADO
Please find the code using RecordCount below:
procedure ProcessArray(ADataSet: TDataSet);
var
field1: TField;
field2: TField;
len: Integer;
a: array of array[2] of double;
begin
len := 0;
SetLength(a, ADataSet.RecordCount);
field1 := ADataSet.FieldByName('field1');
field2 := ADataSet.FieldByName('field2');
ADataSet.First;
while not ADataSet.Eof do
begin
a[len][0] := field1.Value;
a[len][1] := field2.Value;
Inc(len);
ADataSet.Next;
end;
// Process the results in the a array here
end;

What data structure to use in order to sort this data in PL/SQL?

This is Oracle 11.2g. In a PL/SQL function, I've got a loop whereby each iteration, I create a string and an integer associated with that string. The function returns the final concatenation of all the generated strings, sorted (depending on a function input parameter), either alphabetically or by the value of the integer. To give an idea, I'm generating something like this:
Iteration String Integer
1 Oslo 40
2 Berlin 74
3 Rome 25
4 Paris 10
If the input parameter says to sort alphabetically, the function output should look like this :
Berlin, Oslo, Paris, Rome
Otherwise, we return the concatenated strings sorted by the value of the associated integer:
Paris, Rome, Oslo, Berlin
What is the most appropriate data structure to achieve this sort? I've looked at collections, associative arrays and even varrays. I've been kind of shocked how difficult this seems to be to achieve in Oracle. I saw this question but it doesn't work in my case, as I need to be able to sort by both index and value: How to sort an associative array in PL/SQL? Is there a more appropriate data structure for this scenario, and how would you sort it?
Thanks!
It is very easy if you use PL/SQL as SQL and not like other languages. It is quite specific and sometimes is very nice exactly because of that.
Sometimes I really hate PL/SQL, but this case is absolutely about love.
See how easy it is:
create type it as object (
iter number,
stringval varchar2(100),
intval integer
);
create type t_it as table of it;
declare
t t_it := new t_it();
tmp1 varchar2(32767);
tmp2 varchar2(32767);
begin
t.extend(4);
t(1) := new it(1,'Oslo',40);
t(2) := new it(2,'Berlin',74);
t(3) := new it(3,'Rome',25);
t(4) := new it(4,'Paris',10);
select listagg(stringval,', ') within group (order by stringval),
listagg(stringval,', ') within group (order by intval)
into tmp1, tmp2
from table(t);
dbms_output.put_line(tmp1);
dbms_output.put_line(tmp2);
end;
/
drop type t_it;
drop type it;
Here you can see the problem that you must create global types, and this is what I hate it for. But they say in Oracle 12 it can be done with locally defined types so I am waiting for it :)
The output is:
Berlin, Oslo, Paris, Rome
Paris, Rome, Oslo, Berlin
EDIT
As far as you do not know the amount of iterations from the beginning the only way is to do extend on each iteration (this is only example of extending):
declare
iterator pls_integer := 1;
begin
/* some type of loop*/ loop
t.extend();
-- one way to assign
t(t.last) := new it(1,'Oslo',40);
-- another way is to use some integer iterator
t(iterator) := new it(1,'Oslo',40);
iterator := iterator + 1;
end loop;
end;
I prefer the second way because it is faster (does not calculate .last on each iteration).
This is an example of pure PL/SQL implementation that is based on the idea associative array (aka map or dictionary in other domains) is an ordered collection that is sorted by a key. That is a powerful feature that I have used multiple times. For input data structure in this example I decided to use a nested table of records (aka a list of records).
In this particular case however I'd probably go for similar implementation than in simon's answer.
create or replace package so36 is
-- input data structures
type rec_t is record (
iter number,
str varchar2(20),
int number
);
type rec_list_t is table of rec_t;
function to_str(p_list in rec_list_t, p_sort in varchar2 default 'S')
return varchar2;
end;
/
show errors
create or replace package body so36 is
function to_str(p_list in rec_list_t, p_sort in varchar2 default 'S')
return varchar2 is
v_sep constant varchar2(2) := ', ';
v_ret varchar2(32767);
begin
if p_sort = 'S' then
-- create associative array (map) v_map where key is rec_t.str
-- this means the records are sorted by rec_t.str
declare
type map_t is table of rec_t index by varchar2(20);
v_map map_t;
v_key varchar2(20);
begin
-- populate the map
for i in p_list.first .. p_list.last loop
v_map(p_list(i).str) := p_list(i);
end loop;
v_key := v_map.first;
-- generate output string
while v_key is not null loop
v_ret := v_ret || v_map(v_key).str || v_sep;
v_key := v_map.next(v_key);
end loop;
end;
elsif p_sort = 'I' then
-- this branch is identical except the associative array's key is
-- rec_t.int and thus the records are sorted by rec_t.int
declare
type map_t is table of rec_t index by pls_integer;
v_map map_t;
v_key pls_integer;
begin
for i in p_list.first .. p_list.last loop
v_map(p_list(i).int) := p_list(i);
end loop;
v_key := v_map.first;
while v_key is not null loop
v_ret := v_ret || v_map(v_key).str || v_sep;
v_key := v_map.next(v_key);
end loop;
end;
end if;
return rtrim(v_ret, v_sep);
end;
end;
/
show errors
declare
v_list so36.rec_list_t := so36.rec_list_t();
v_item so36.rec_t;
begin
v_item.iter := 1;
v_item.str := 'Oslo';
v_item.int := 40;
v_list.extend(1);
v_list(v_list.last) := v_item;
v_item.iter := 2;
v_item.str := 'Berlin';
v_item.int := 74;
v_list.extend(1);
v_list(v_list.last) := v_item;
v_item.iter := 3;
v_item.str := 'Rome';
v_item.int := 25;
v_list.extend(1);
v_list(v_list.last) := v_item;
v_item.iter := 4;
v_item.str := 'Paris';
v_item.int := 10;
v_list.extend(1);
v_list(v_list.last) := v_item;
dbms_output.put_line(so36.to_str(v_list));
dbms_output.put_line(so36.to_str(v_list, 'I'));
end;
/
show errors

Query from array

I am getting error while trying to select values from an array, like following code
declare result CLOB;
myarray selected_pkg.num_array := selected_pkg.num_array();
begin
myarray.extend(3);
myarray(1) := 1; myarray(2) := 5; myarray(3) := 9;
EXECUTE IMMEDIATE 'select column_value from table (cast(myarray AS selected_pkg.num_array))';
COMMIT;
end;
ORA-00904: "MYARRAY": invalid identifier
Please suggest.
Thanks, Alan
First off, there doesn't appear to be any reason to use dynamic SQL here.
Second, if you want to run a SELECT statement, you need to do something with the results. You'd either need a cursor FOR loop or you'd need to BULK COLLECT the results into a different collection or otherwise do something with the results.
Third, if you want to use a collection in SQL, that collection must be defined in SQL not in PL/SQL.
Something like this will work (I'm not sure if that's what you want to do with the results)
SQL> create type num_arr is table of number;
2 /
Type created.
SQL> declare
2 l_nums num_arr := num_arr( 1, 2, 3, 7 );
3 begin
4 for i in (select column_value from table( l_nums ))
5 loop
6 dbms_output.put_line( i.column_value );
7 end loop;
8 end;
9 /
1
2
3
7
PL/SQL procedure successfully completed.
execute immediate is not need at this point.
Use fetch or loop cursors in proc.

Resources