Delphi ClientDataSet Sorting by changing IndexName - sql

I've been learning about the ClientDataSet in delphi and how it can help sort my SQL database. The data is showing fine in my TDBGrid and i've enabled sorting by clicking on a header by changing the IndexField of the ClientDataset. I want to make it go descending on sorts sometimes though so have been trying to use 2 IndexNames outlined here https://stackoverflow.com/a/13130816/4075632
However, when I swap the IndexName from DEFAULT_ORDER to CHANGEINDEX, all data in my DBGrid disappears. I'm pretty new to all of this, and I know it will depend on my situation, but what are some of the ways this happens and I will try to troubleshoot them.
I have 1 TSQLConnection connected to TSQLQuery, and that connected to TDataSetProvider, and that connected to my ClientDataSet which leads to a TDataSource to the TDBGrid. Why might the ClientDataSet which is usually fine cause problems when I change its name? Please bear in mind that most of the settings are default because I'm not too sure about these components. Thanks, I hope you can provide some useful help and I'm sorry it may be difficult to se my situation.
Toby

I use the following code to build indexes for a clientdataset:
Procedure BuildIndices (cds: TClientDataSet);
var
i, j: integer;
alist: tstrings;
begin
with cds do
begin
open;
logchanges:= false;
for i:= 0 to FieldCount - 1 do
if fields[i].fieldkind <> fkCalculated then
begin
j:= i * 2;
addindex ('idx' + inttostr (j), fieldlist.strings[i], [], '', '', 0);
addindex ('idx' + inttostr (j+1), fieldlist.strings[i], [ixDescending], '', '', 0);
end;
alist:= tstringlist.create;
getindexnames (alist);
alist.free;
close;
end;
end;
As a result, there is an index 'idx0' for sorting column 0 ascending and 'idx1' for sorting column 0 descending; 'idx2' and 'idx3' for column 1, etc.
Then, in the grid's OnTitleClick event, I have the following
procedure Txxx.DBGrid1TitleClick(Column: TColumn);
var
n, ex: word;
begin
n:= column.Index;
try
dbgrid1.columns[prevcol].title.font.color:= clNavy
except
end;
dbgrid1.columns[n].title.font.color:= clRed;
prevcol:= n;
directions[n]:= not directions[n];
ex:= n * 2;
if directions[n] then inc (ex);
with clientdataset do
try
disablecontrols;
indexname:= 'idx' + inttostr (ex);
finally
first;
enablecontrols
end;
end;
In each form, I define an array of booleans ('directions'), one element per grid column. These elements track whether a column should be sorted ascending or descending.

The ClientDataSet comes with two predefined indexes: DEFAULT_ORDER and CHANGEINDEX, which are of no real use for your task, because you cannot tweak them to your needs. So you have to create your own indexes. A comprehensive description by Cary Jensen can be found in this article as well as in his highly recommended book about ClientDataSets.

Related

I couldn't find the error in my binary search function

I don't know why this function I made in pascal only gives the index of the number I'm searching for (n) and doesn't give -1 when it doesnt find it..
(i checked theres no problem with other functions the only problem is that is doesnt print the message 'num is not here' when it doesnt exist) i would also appreciate it if someone points out where my code could have been more efficient.
`
Function binary_search(L : Array Of Integer; n : Integer) : Integer;
Var
i, p, middle, first, last : Integer;
Begin
first := 0;
binary_search := -1;
last := Sizeof(L) Div Sizeof(L[0]);
While (first <= last) Do
Begin
middle := (first + last) Div 2;
If (middle = n) Then
Begin
binary_search := middle;
break;
End;
If (middle < n) Then first := middle +1;
If (middle > n) Then last := middle -1;
End;
End;
Begin
Write('num of elements in array : ');
read(m);
fillup(arr, m);
For i :=0 To m-1 Do
Begin
permutarr[i] := arr[i];
End;
Write('the num youre looking for : ');
read(A);
sort(arr, 1, m);
If (binary_search(arr, A)= -1) Then Writeln('the number isnt here') //this doesnt work
Else
Begin
For i:=0 To m-1 Do
Begin
If (permutarr[i] = binary_search(arr, A)) Then
Begin
Writeln('index : ', i);
break;
End;
End;
End;
End.
`
I am not familiar with your dialect of Pascal (it's been 15 years since I did any serious programming), but I'm quite sure that the chief problem is that
Your code is not looking at the entries of the array L at all!
All the comparisons in the function binary_search only involve the indices, middle, first, last. The first thing I would try is to edit all the comparisons with middle to use L[middle] instead (on lines 11, 16 and 17). Then your code will, at least, actually be looking at the array :-)
Other things:
I'm not 100 per cent sure that your code handles arrays of all lengths correctly (can't test right now). It might happen that first and last never meet. I'm probably wrong about this, because then your code would get stuck in an endless loop.
When I was coding I took care never to assign a value to the function_name, here binary_search prematurely. In other words, I would not be surprised to learn that your function always returns $-1$, because I fully expect the execution of a function to end the instant anything is assigned to it, so here at the line binary_search:=-1;. As I said, my dialect was different, and the recollection is "dated" at best.
Anyway, the problem in bold fits your description of the problematic behavior. You can test my theory by giving A a value that exceeds the length of the array. Then it should be unable to find it with your current code.

UDF on DB2 11.0

I was asked to build a user defined function on our Mainframe environment that checks for a search string in a longer string. The only catch is that if we search for example for 'AA' in 'ABCAADAA' the only valid result is the last AA because the first AA actually split in CA and AD.
CREATE FUNCTION F#CRE#WK (WK CHAR(02), WKATTR CHAR(10))
RETURNS INTEGER
LANGUAGE SQL
READS SQL DATA
BEGIN
DECLARE INDEX INTEGER DEFAULT 1;
WHILE (INDEX < 9) DO
SET INDEX = LOCATE_IN_STRING(WKATTR, WK, INDEX);
IF (MOD(INDEX, 2) <> 0) THEN
RETURN 1;
END IF;
END WHILE;
RETURN 0;
END;
It is working fine when I implement it using Data Studio but if I put it onto the host directly (we're using Quick32770) I'm getting a bunch of errors which don't make sense at all. I couldn't find any helpful resources(searched the whole IBM page and Google of course).
First error I'm getting is:
SQLCODE = -104, ERROR: ILLEGAL SYMBOL "<END-OF-STATEMENT>". SOME
SYMBOLS THAT MIGHT BE LEGAL ARE: ;
Which refers to the line I'm declaring my index variable. If I remove the semicolon it tells me that the SET is illegal there because it is expecting a semicolon.
I cannot think of anything else I could try(I messed around with the code a lot but errors just kept getting more weird.). I started working in this field while being in college just a couple of weeks ago and nobody here has actual knowledge about this so I was hoping to find some help here.
If there's anything else you need, just let me know!
Thanks in advance.
This might help you:
https://bytes.com/topic/db2/answers/754686-db2-udf-need-eliminate-if-statement
It says the if statement is not allowed on the mainframe in UDF ?
So this user bend it around to a CASE function.
In order to fix this you need to go into the SPUFI settings and change the TERMINATOR option to something else than a semicolon. If I changed it to & my code must look like this:
CREATE FUNCTION F#CRE#WK (WK CHAR(02), WKATTR CHAR(10))
RETURNS INTEGER
LANGUAGE SQL
READS SQL DATA
BEGIN
DECLARE INDEX INTEGER DEFAULT 1;
WHILE (INDEX < 9) DO
SET INDEX = LOCATE_IN_STRING(WKATTR, WK, INDEX);
IF (MOD(INDEX, 2) <> 0) THEN
RETURN 1;
END IF;
END WHILE;
RETURN 0;
END&

SQL table change sequence

I have a table displayed in DBGrid the sort order is based on a Sequence field and I want to be able to move an item up or down one place at a time. I have researched here and cannot find anything exactly like I need.
The problem comes when I disable the Sort order to make a change, the list reverts to the order in which the data was originally entered, so I have lost the item next in line.
This is what I have...
Folder Sequence
----------------
Buttons 1
Thread 2 << Current Row
Cotton 3
Rags 4
On clicking the "MoveDown" button I want...
Folder Sequence
----------------
Buttons 1
Cotton 2
Thread 3 << Current Row
Rags 4
But - when I remove the Sort order on Sequence I get the order I entered the items...
Folder Sequence
----------------
Buttons 1
Cotton 2
Rags 4
Thread 3 << Current Row
So far my attempts are proving pretty cumbersome and involve loading the Rows into a listbox, shuffling them and then writing them back to the Table. Gotta be a better way, but it is beyond my current grasp of SQL.
Can someone please point me in the direction to go.
I don't want to trouble anyone too much if it is a difficult thing to do in SQL, as I can always stay with the listbox approach. If it is relatively simple to an SQL-expert, then I would love to see the SQL text.
Thanks
My solution is based on the TDataSet being sorted by the Sequence field:
MyDataSet.Sort := 'Sequence';
And then swapping the Sequence field between the current row and the Next (down) / Prior (up) records e.g.:
type
TDBMoveRecord = (dbMoveUp, dbMoveDown);
function MoveRecordUpDown(DataSet: TDataSet; const OrderField: string;
const MoveKind: TDBMoveRecord): Boolean;
var
I, J: Integer;
BmStr: TBookmarkStr;
begin
Result := False;
with DataSet do
try
DisableControls;
J := -1;
I := FieldByName(OrderField).AsInteger;
BmStr := DataSet.Bookmark;
try
case MoveKind of
dbMoveUp: Prior;
dbMoveDown: Next;
end;
if ((MoveKind = dbMoveUp) and BOF) or ((MoveKind = dbMoveDown) and EOF) then
begin
Beep;
SysUtils.Abort;
end
else
begin
J := DataSet.FieldByName(OrderField).AsInteger;
Edit;
FieldByName(OrderField).AsInteger := I;
Post;
end;
finally
Bookmark := BmStr;
if (J <> -1) then
begin
Edit;
FieldByName(OrderField).AsInteger := J;
Post;
Result := True;
end;
end;
finally
EnableControls;
end;
end;
Usage:
MoveRecordUpDown(MyDataSet, 'Sequence', dbMoveDown);
// or
MoveRecordUpDown(MyDataSet, 'Sequence', dbMoveUp);
If i understand right, you want to find "next sequence item"?
May be you may do something like "get first min value, grater then X"?
In this way, you can pass prev. Row sequence value.

Variable structure for database results

A lot of times when we query the database, we just need one column with varchar.
So I've made a nice function for querying the database and putting the results in a stringlist:
function Getdatatostringlist(sqlcomponent, sqlquery: string): TStringlist;
What I'm looking for now is basically the same function but for results with multiple columns where you don't know in advance what type the data is, be it varchar, int, datetime.
What kind of datastructure would be good to use here.
The reason I want this is that I try not to work on open datasets. I like much more to fetch all results into a temporary structure, close the dataset and work on the results.
After Kobiks reply about using in Memory datasets I came up with the following, it's fast put together to test the concept:
procedure TForm1.Button2Click(Sender: TObject);
var
MyDataSet : TAdoDataSet;
begin
MyDataSet := GetDataToDataSet('SELECT naam FROM user WHERE userid = 1', ADOConnection1);
try
Form1.Caption := MyDataSet.FieldByName('naam').AsString;
finally
MyDataSet.free;
end;
end;
function TForm1.GetDataToDataSet(sSql: string; AdoConnection: TADOConnection): TAdoDataSet;
begin
Result := TAdoDataSet.Create(nil);
Result.LockType := ltBatchOptimistic;
Result.Connection := AdoConnection;
Result.CommandText := sSql;
Result.Open;
Result.Connection := nil;
end;
I think this is something to build on.
You should use any disconnected in-memory TDataSet descendant, such as TClientDataSet.
Do not attempt to re-invent the wheel by storing a record-set in some new "Variant" structure. A TClientDataSet already contains all features you need to manipulate a "temporary" data structure.
Here is how you create a TClientDataSet structure:
cds.FieldDefs.Add('id', ftInteger);
cds.FieldDefs.Add('name', ftString, 100);
// ...
// create it
cds.CreateDataSet;
// add some data records
cds.AppendRecord([1, 'Foo']);
cds.AppendRecord([2, 'Bar']);
Many TDataSets has an ability to be used as an in-memory (client) datasets depending on the provider and LockType, for example a TADODataSet with LockType=ltBatchOptimistic could fetch results-set from the server, and then remain disconnected.
For exchanging Data with Excel this structure is usefull, might be useful for other purposes.
Function GetDatasetasDynArray(Ads: TDataset; WithHeader: Boolean = true): Variant;
// 20130118 by Thomas Wassermann
var
i, x, y: Integer;
Fields: Array of Integer;
begin
x := 0;
y := Ads.RecordCount;
if WithHeader then
inc(y);
SetLength(Fields, Ads.FieldCount);
for i := 0 to Ads.FieldCount - 1 do
if Ads.Fields[i].Visible then
begin
Fields[x] := i;
inc(x);
end;
SetLength(Fields, x);
Result := VarArrayCreate([0, y - 1 , 0, length(Fields) - 1], VarVariant);
y := 0;
if WithHeader then
begin
for i := Low(Fields) to High(Fields) do
begin
Result[y, i] := Ads.Fields[Fields[i]].DisplayLabel;
end;
inc(y);
end;
try
Ads.DisableControls;
Ads.First;
while not Ads.EOF do
begin
for i := Low(Fields) to High(Fields) do
begin
Result[y, i] := Ads.Fields[Fields[i]].Value;
end;
Ads.Next;
inc(y);
end;
finally
Ads.EnableControls;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
DynArray:Variant;
begin
DynArray := GetDatasetasDynArray(Adodataset1,true);
//DynArray[0,x] Header or First row
//DynArray[1,x] First row or SecondRow
Excel.Range.Value := DynArray;
end;
Why don't you like working with open datasets? They usually do not block the server. Copying the data from the dataset to whatever you want is extra overhead which is most likely not necessary.
A dataset provides exactly the functionality you want: A matrix with variable columns and rows.
EDIT: However, if you have iterate through the dataset often, you should consider creating a class holding the relevant information and then copy the data into a generic list, dictionary, tree or whatever you need as fast lookup structure.
Of course you could think of building something smart which can be as flexible as a dataset but: The more general things get, the poorer the performance (usually).

How to union data from different databases?

I came across the necessity to union two selects from different databases, namely paradox (in bde) and ms sql server.
Currently bde (through TQuery) is used only in this part of the programm (i.e. dbgrid). Now I need to add some data stored in ms sql server database (with which I usually use TADOQuery) to the same grid.
Although queries are executed over completely different tables, the result set of columns is named and typed similarly (I mean, if I had these tables, say, in ms sql server database, I could use a trivial union for that).
Is there any way to unite recordsets selected from these in delphi7 that I could use the result as a data source for a dbgrid?
You could use a clientdataset, created by the definitions of eg. the dataset of your SQL-Server dataset and add data of your paradox dataset. TFieldDefArray can be empty in your case.
type
TMyFieldDef = Record
Name: String;
Size: Integer;
DataType: TFieldType;
end;
TFieldDefArray = array of TMyFieldDef;
function GetClientDSForDS(ADataSet: TDataSet; AFieldDefArray: TFieldDefArray; AClientDataSet: TClientDataSet = nil; WithRecords: Boolean = true)
: TClientDataSet;
var
i: Integer;
Function NoAutoInc(ft: TFieldType): TFieldType;
begin
if ft = ftAutoInc then
Result := ftInteger
else
Result := ft;
end;
begin
if Assigned(AClientDataSet) then
Result := AClientDataSet
else
Result := TClientDataSet.Create(nil);
Result.Close;
Result.FieldDefs.Clear;
for i := 0 to ADataSet.FieldCount - 1 do
begin
Result.FieldDefs.Add(ADataSet.Fields[i].FieldName, NoAutoInc(ADataSet.Fields[i].DataType), ADataSet.Fields[i].Size);
end;
for i := 0 to High(AFieldDefArray) do
Result.FieldDefs.Add(AFieldDefArray[i].Name, AFieldDefArray[i].DataType, AFieldDefArray[i].Size);
Result.CreateDataSet;
for i := 0 to ADataSet.FieldCount - 1 do
begin
Result.FieldByName(ADataSet.Fields[i].FieldName).DisplayLabel := ADataSet.Fields[i].DisplayLabel;
Result.FieldByName(ADataSet.Fields[i].FieldName).Visible := ADataSet.Fields[i].Visible;
end;
if WithRecords then
begin
ADataSet.First;
while not ADataSet.Eof do
begin
Result.Append;
for i := 0 to ADataSet.FieldCount - 1 do
begin
Result.FieldByName(ADataSet.Fields[i].FieldName).Assign(ADataSet.Fields[i]);
end;
Result.Post;
ADataSet.Next;
end;
end;
end;
another attempt might be creating a linked server for paradox, I didn't try that...
http://www.experts-exchange.com/Microsoft/Development/MS-SQL-Server/SQL_Server_2008/Q_24067488.html
No problem with AnyDAC LocalSQL. You can execute SQL's with any DataSet, not only select SQL, insert, update, delete SQL too.
You can use the Built-in TClientDataSet functionality to union the data by appending the data from the second dataset to the data of the first one.
There are different ways to do it, my preferred one because the simple code would be to add two DataSetProviders and link it to each of your DataSets, for example
dspBDE.DataSet := MyTQuery;
dspADO.DataSet := MyAdoQuery;
Then, to open your DataSets, you can just do:
MyClientDataSet.Data := dspBDE.Data;
MyClientDataSet.AppendData(dspADO.Data, True);
To make this to work, both DataSets have to match the field number and data types. Since your structures are similar, you can work by typecasting in your SQL if this does not happen automatically.
BDE supports (or supported) heterogeneous queries
This allows queries to span more than one dataset, but with a limited SQL syntax.
IIRC I used a few over a decade ago for some quick'n'dirty datamerges, but I can't remember the specifics - I haven't touched BDE for years.
Several years ago (Delphi 7) i used TxQuery, but i'dont know if it is still in development
I have found this link