Use TQuery.Locate() function to find other then first matching

7.3k Views Asked by At

Locate moves the cursor to the first row matching a specified set of search criteria. Let's say that q is TQuery component, which is connected to the database with two columns TAG and TAGTEXT. With next code I am getting letter a. And I would like to use Locate() function to get letter d.

 If q.Locate('TAG','1',[loPartialKey]) Then
  begin
    tag60 := q.FieldByName('TAGTEXT');
  end

For example if I got table like this:

TAG | TAGTEXT
+---+--------+
| 1 | a      |
+---+--------+
| 2 | b      |
+---+--------+
| 3 | c      |
+---+--------+
| 1 | d      |
+---+--------+
| 4 | e      |
+---+--------+
| 1 | f      |
+---+--------+

is it possible to locate the second time number one occurred in table?

EDIT

My job is to find the occurrence of TAG with value 1 (which occurrence I need depends on the parameter I get), I need to iterate through table and get the values from all the TAGTEXT fields till I find that value in TAG field is again number 1. Number 1 in this case represents the start of new segment, and all between the two number 1s belongs to one segment. It doesn't have to be same number of rows in each segment. Also I am not allowed to do any changes on table.

What I thought I could do is to create a counter variable that is going to be increased by one every time it comes to TAG with value 1 in it. When the counter equals to the parameter that represents the occurrence I know that I am in the right segment and I am going to iterate through that segment and get the values I need. But this might be slow solution, and I wanted to know if there was any faster.

2

There are 2 best solutions below

3
André Müller Pereira On

You have much options to do this.

If your component don´t provide a locateNext you can make your on function locateNext, comparing the value and make next until find.

You can also bring the sql with order by then use locate for de the first value and test if the next value match the comparision.

If you use a clientDataset you can filter into the component filter propertie, or set IndexFieldNames to order values instead the "order by" of sql in the prior suggestion.

You can filter it on the SQL Where clausule too.

3
MartynA On

You need to be a bit wary of using Locate for a purpose like this, because some TDataSet descendants' implementation of Locate (or the underlying db-access layer) construct a temporary index on the dataset. which can be discarded immediately afterwards, so repeatedly calling Locate to iterate the rows of a given segment may be a lot more inefficient than one might expect it to be.

Also, TClientDataSet constructs, uses and then discards an expression parser for each invocation of Locate (in its internal call to LocateRecord), which is a lot of overhead for repeated calls, especial when they are entirely avoidable.

In any case, the best way to do this is to ensure that your table records which segment a given row belongs to, adding a column like the SegmentID below if your table does not already have one:

TAG | TAGTEXT|SegmentID
+---+--------+---------+
| 1 | a      |     1
| 2 | b      |     1
| 3 | c      |     1
| 1 | d      |     2
+---+--------+---------+  // btw, what happened to the 2 missing rows after this one?
| 4 | e      |     2
| 1 | f      |     3
+---+--------+---------+

Then, you could use code like this to iterate the rows of a segment:

procedure IterateSegment(Query : TSomeTypeOfQueryComponent; SegmentID : Integer);
var
  Sql; String;
begin
  Sql := Format('select * from mytable where SegmentID = %d order by Tag', [SegmentID]);
  if Query.Active then
    Query.Close;
  Query.Sql.Text := Sql;

  Query.Open;

  Query.DisableControls;
  try
    while not Query.Eof do begin
      //  process row here
      Query.Next;
    end;
  finally
    Query.EnableControls;
  end;

end;

Once you have the SegmentID column in the table, if you don't want to open a new query to iterate a block, you can set up a local index (by SegmentID then Tag), assuming your dataset type supports it, set a filter on the dataset to restrict it to a given SegmentID and then iterate over it