In order to show my data in some order, I have ORDER column. So I can move up and down rows. That is already working, I just swap ORDER number for the affected rows. My problem is, when I delete a row, I need to reorder all other rows. To show the rows in the proper order I use the .IndexFieldNames := 'ORDER', but when I iterate through all rows and assign the new ORDER number, with active index its pretty slow. When I remove the .IndexFieldNames := '', then the rows are not in the correct order anymore, and the new order is wrong.
Here is my code:
class procedure TDataModuleEx.ReOrderColumn(const aDataSet: TClientDataSet;
const aFieldName: string; aResetClone: Boolean);
var
clone: TClientDataSet;
newOrderNo: Integer;
begin
newOrderNo := 0;
clone := TClientDataSet.Create(nil);
try
clone.CloneCursor(aDataSet, aResetClone);
clone.IndexFieldNames := aFieldName;
clone.IndexFieldNames := ''; //Indexed Edit is too slow
clone.First;
while not clone.eof do
begin
Inc(newOrderNo);
clone.Edit;
clone.FieldByName(aFieldName).AsInteger := newOrderNo;
clone.Post;
clone.Next;
end;
finally
clone.Free;
end;
end;
How can I reorder my rows after one is deleted efficiently?
I made some testing on a table with 10 000 records. Procedure ReOrderColumn made renumbering for about 3 seconds. ClientDataSet. ApplyUpdate took about 30 seconds.
Total about 33 seconds.
I wrote stored procedure in Firebird database which made renumbering for about 3 seconds.
SET TERM ^ ;
create or alter procedure REORDER
as
declare variable ID integer;
declare variable I integer;
BEGIN
i = 0;
FOR
select new_table.id
from new_table
order by FORDER
INTO :ID
as cursor tcur
DO
BEGIN
i = :i + 1;
update new_table t set t.forder = :i
where current of tcur;
END
END^
SET TERM ; ^
From Delphi
procedure Reorder;
begin
//Apply changes to server
dm1.ClientDataSet1.ApplyUpdates(0);
//execute stored procedure
dm1.spReorder.ExecProc; <--- ABOUT 3 SEC FOR 10 000 records
//Refresh dataset
dm1.ClientDataSet1.Close;
dm1.ClientDataSet1.Open;
end;
Related
I use this code for multi selecting rows in dataset. It works fine but if I execute it second, third etc time new rows are added to last rows. How empty bookmarklist of last selected rows?
var
sql: string;
i:integer;
query1.close;
query1.open;
query1.Filtered:=false;
for i := 0 to dbgrid1.SelectedRows.Count-1 Do Begin
dbgrid1.DataSource.DataSet.GotoBookmark(dbgrid1.SelectedRows.Items[i]);
s:=s+','+inttostr(dbgrid1.DataSource.DataSet.FieldByName('ID').AsInteger);
end;
delete(s,1,1);
sql:='ID in('+s+')';
query1.Filter:= sql;
query1.Filtered:=true;
frxReport1.LoadFromFile(ExtractFileDir(ExtractFileDir(ExtractFileDir(ExtractFilePath(ParamStr(0)))))+'\FastReports\Labels_DocOut_id.fr3');
frxReport1.PrepareReport;
frxReport1.ShowPreparedReport;
I tried:
query1.close; query1.open;
DBGrid1.SelectedRows.CurrentRowSelected := false;
dbgrid1.DataSource.DataSet.delete;
DBGrid1.SelectedRows.clear;
DBGrid1.SelectedRows.delete;
but without success.
I Have TQuery With Calculated Field N.
How To Increment Numbers in the example (N starts with 5):
I tried this but Nothing:
procedure TForm1.Query1CalcFields(DataSet: TDataSet);
var i:integer;
begin
i := strtoint(edit2.Text);
Query1['N'] := inttostr(i+1);
end;
result:
N
2
2
2
2
.
.
Note: Foxpro database ,i use BDE to connect with ,It does not have to be a calculated field ,i want the Incremented value to use it in print of quickreport like a single reference for each Page (not pagenumber).
I Found This Solution With The Help Of #kobik
In Printing Of TQRLabel I Add This Code And No Needed To The Calculated Field Or Other Varible:
procedure TForm1.QRLabel1Print(sender: TObject; var Value: string);
begin
value:=inttostr(Query1.RecNo+strtoint(edit2.Text)-1);
end;
The Tedit To Costume The Start Number At Runtime.
This is a simple way that I test it:
1- declare a global variable for saving auto number
2- Set it in FormShow to 5
3- In OnCalcFields assign global variable to the new field
4- increment global variable
Notes: Do not use TEdit or any thing for show the result of calculate field, because it will just show the first result. but all the result will save in table or query correctly.
Codes
Global Variable:
var
Form1: TForm1;
i : Integer;
Form Show:
procedure TForm1.FormShow(Sender: TObject);
begin
i := 5;
end;
Calc Filed:
procedure TForm1.adoqry1CalcFields(DataSet: TDataSet);
begin
adoqry1['n'] := i;
//OR adoqry1N.AsInteger := i;
//OR adoqry1.FieldByName('n').AsInteger := i;
i := i + 1;
end;
At the end, I test it with ADOQuery.
I have a string grid, from which i can delete columns. I defined a CustomStringGrid type that allows me to use DeleteColumn method.
This is how it looks:
TCustomStringGrid = class(TStringGrid)
[...]
With tCustomStringGrid(mygrid) do
DeleteColumn(col)
end;
IS there something similar to add a column? I've tried InsertColumn but it doesn't seem to exist. I want to add a column at a particular position. In fact, if a user deletes a column i have an undo button which i want to reinsert the deleted column (i'm keeping the data in an array so i can recreate the column but i don't know how to insert one in a particular position).
Thank you!
It's not built in but easy to emulate, with ColCount = ColCount + 1 and MoveColumn from a HackClass.
type
THackGrid=Class(Grids.TCustomGrid)
End;
Procedure InsertColumn(G:TStringGrid;Position:Integer);
begin
if Position<G.ColCount then
begin
G.ColCount := G.ColCount + 1;
THackGrid(g).MoveColumn(G.ColCount - 1,Position);
end;
end;
procedure TMyForm.Button1Click(Sender: TObject);
begin
InsertColumn(StringGrid1,1);
end;
THack grid is not working, maybe it is ok when both cols are visible, but that works always :
Procedure MoveColumn(G:TStringGrid;OldPosition : integer;NewPosition:Integer);
var
i : integer;
temp : string;
begin
for i := 0 to g.rowcount - 1 do
begin
temp := g.cells[OldPosition,i];
g.cells[OldPosition,i] := g.cells[NewPosition,i];
g.cells[NewPosition,i] := temp;
end;
end;
I want to delete multiple selected records which I have displayed at TDBAdvGrid. A number of records are being selected by checking checkbox in front of them. After clicking on Delete button it triggers procedure as follows. It is returning values of selected rows id successfully only problem with deletion of all records. It deletes only first selected record.
procedure TForm5.Button3Click(Sender: TObject);
var
i,j,idn: Integer;
State: Boolean;
begin
j := 0;
for i := 1 to DBAdvGrid1.RowCount - 1 do
begin
if DBAdvGrid1.GetCheckBoxState(1,i,state) then
begin
if state then
begin
idn := StrToInt(DBAdvGrid1.Cells[6,i]);
UniQuery1.SQL.Text := 'Delete from userplays where id = :id';
UniQuery1.ParamByName('id').AsInteger := idn;
UniQuery1.ExecSQL;
end;
end;
end;
end;
It is deleting only first record in lineup. After deleting first record it breaks for loop and control goes back to TDBAdvGrid with updated data after delete.
Final code based on suggestion is as follows
j := 0;
DBAdvGrid1.BeginUpdate;
for i := 1 to DBAdvGrid1.RowCount - 1 do
begin
showmessage('inside rowcount loop');
if DBAdvGrid1.GetCheckBoxState(1,i,state) then
begin
if state then
begin
DBAdvGrid1.DataSource.DataSet.First;
DBAdvGrid1.DataSource.DataSet.MoveBy(i - 1 - j);
DBAdvGrid1.DataSource.DataSet.Delete;
j := j+1;
continue;
end;
end;
end;
This code works perfectly only when PageMode := False
Based on the suggestion above, I would like to make another suggestion...
//DBAdvGrid1.BeginUpdate;
query.First;
for i := 1 to DBAdvGrid1.RowCount - 1 do
begin
if DBAdvGrid1.GetCheckBoxState(1,i,state) then
begin
if state then
begin
//you can do whatever you want with the current selected record
end;
end;
query.Next;
end;
I think this is easier then using the MoveBy...
I even never changed PageMode, it's still on true. No idea if it's coincidence or not.
You can use cyBookmarks in the OnCheck event , with Virtual Table.
It creates a list of all records selected, and you can list them with
cyBookmarks.bookmarklits.count, so my example is like this :
procedure TForm2.cyDBGrid1CheckBoxClick(Sender: TObject);
begin
if cyBookmarks1.AddCurrentRecord then
begin
cyBookmarks1.BookmarkList.InsertBookmark(cyDBGrid1.CheckedList.CurrentRecord);
cyDBGrid1.Invalidate ;
end
else
begin
cyBookmarks1.RemoveCurrentRecord;
cyDBGrid1.Invalidate;
end;
if cyBookmarks1.BookmarkList.Count>0 then
begin
VirtualTable1.GotoBookmark(cyBookmarks1.BookmarkList.Items[0]);
lbl1.Caption:=VirtualTable1.FieldByName('N_ENREGISTREMENT').AsString;
end;
end;
I'm using ZEOS components to connect to an (ancient) MDB database.
I'm doing a query that reads in lots of data to bridge into a different database.
Is there a way to indicate progress as a percentage?
procedure TForm13.ActionReadInMemoryExecute(Sender: TObject);
var
QueryLine: string;
FullQuery: string;
Tablename: string;
i: integer;
begin
i:= 0;
TableMeter.DisableControls;
try
TableMeter.First;
FullQuery:= '';
while not TableMeter.eof do begin
Tablename:= TableMeter.FieldByName('tabelnaam').AsString;
QueryLine:= ReplaceStr(ImportQuerySjabloon, cTabelname, Tablename);
FullQuery:= FullQuery + QueryLine;
if (TableMeter.RecNo < (TableMeter.RecordCount -1)) then begin
FullQuery:= FullQuery + ' UNION ALL ';
end;
TableMeter.Next;
end; {while}
QueryImportMeterreadings.Close;
QueryImportMeterreadings.SQL.Text:= FullQuery;
QueryImportMeterreadings.Open; <<-- takes a long time
finally
TableMeter.EnableControls;
end;
end;
Is there a way to indicate progress of the query, or can I only do this if I split up the individual queries and eliminate the UNION's.
It takes about 1 minute to run, involving 8 unions.
I don't see any event that I can use for this purpose:
Or should I fake an OnCalcField on a field in the Query to do this (not sure if that will even work in principle).
Or attach a sequence? nope, gives unsupported operation on a Access DB
I say split up the individual queries and eliminate the union, make a timer around each query, depending on the avg time taken * number of queries remaining you should give an estimate / update a text field to say x out of y queries completed (time remaining: -time-)
I would split the huge query into individual queries; in code, you iterate over each query's result set and manually insert the values into a clientdataset (cds). The cds can be connected to a dbgrid. Then you can show when each query completes - you could also show progress after each tuple is handled, but you won't know how many tuples in total there are, unless you perform a separate query which returns a count of tuples. The problem with using such an unconnected cds is that you have to define the fields in code. Here is an example of something similar which I wrote last night - the queries all update one field in the cds.
const
field1 = 'id';
field2 = 'customer name';
field3 = 'total debt';
procedure TTotalCustDebt.FormCreate(Sender: TObject);
var
strings: tstrings;
begin
with qTotalDebt do // this is the clientdataset
begin
fielddefs.add (field1, ftInteger, 0, false);
fielddefs.add (field2, ftString, 32, false);
fielddefs.add (field3, ftInteger, 0, false);
createdataset;
fieldbyname (field1).visible:= false;
open;
addindex ('idx0', field2, [], '', '', 0);
addindex ('idx1', field2, [ixDescending], '', '', 0);
addindex ('idx2', field3, [], '', '', 0);
addindex ('idx3', field3, [ixDescending], '', '', 0);
strings:= tstringlist.create;
getindexnames (strings);
strings.free;
end;
end;
procedure TTotalCustDebt.PopulateCDS;
begin
dsTotalDebt.dataset:= nil;
with qTotalDebt do
begin
emptydataset;
indexfieldnames:= field1; // initially sort by customer.id
end;
with qDBills do
begin
params[0].asdate:= dt;
open;
while not eof do
begin
qTotalDebt.append;
qTotalDebt.fieldbyname (field1).asinteger:= qDBillsID.asinteger;
qTotalDebt.fieldbyname (field2).asstring:= qDBillsName.asstring;
qTotalDebt.fieldbyname (field3).asinteger:= qDBillsTot.asinteger;
qTotalDebt.post;
next
end;
close
end;
// show progress indicator
with qDReceipts do
begin
params[0].asdate:= dt;
open;
while not eof do
begin
if qTotalDebt.findkey ([qDReceiptsID.asinteger]) then
begin // customer already exists
qTotalDebt.edit;
qTotalDebt.fieldbyname (field3).asinteger:= - qDReceiptsTot.asinteger
+ qTotalDebt.fieldbyname (field3).asinteger;
end
else
begin // add new record
qTotalDebt.append;
qTotalDebt.fieldbyname (field1).asinteger:= qDReceiptsID.asinteger;
qTotalDebt.fieldbyname (field2).asstring:= qDReceiptsName.asstring;
qTotalDebt.fieldbyname (field3).asinteger:= - qDReceiptsTot.asinteger;
end;
qTotalDebt.post;
next
end;
close
end;
// show progress indicator
// more queries
// at end, attach the clientdataset to the TDataSource
dsTotalDebt.dataset:= qTotalDebt;
end;