Inserting after the last record (bottom) shown in DBGrid - delphi

What should be the code for inserting after the last record using the ClientDataSet?
I tried the following:
cdsSomething.Last;
cdsSomething.Insert:
But it appears it replaces the last record instead. I am sure there must be a quick code for this.

The method to append a record to the end of the Dataset (let alone any index) is Append. You don't even need to call Last before.
cdsSomething.Append;
Insert inserts a row before the selected record, so with your code, the new record should become the second to last record.

In general, where an added record (or, in fact, any record) appears in the DBGrid does not depend on the dataset operations which were used to insert it.
In fact, the DBGrid is irrelevant to this question, because it simply displays the added row in the ClientDataSet in the position the added row occurs in the CDS according to its current index order.
So, for example, if the CDS contains an integer ID field, and its current index is this ID field (e.g. because the CDS's IndexFieldNames property is set to 'ID'), to make the added row appear at the end, all you need to do is to set its ID value to something higher than any existing record in the CDS. If the field is of type ftAutoInc, this will, of course, happen automatically.
Uwe Raabe has answered this q a bit differently. What he says is correct if the CDS is not using any index, so the records are displayed in the physical order they appear in the CDS's datafile. However, relying on the physical order to determine the display order is not necessarily a good idea if the display order is important. If it is, then use an indexed field (or fields) to determine the order.

Related

What is the purpose of method First in a TAdoQuery in Delphi

Could you please explain to me the purpose of the following lines, especially for the First method ?
TestQ.First;
while not TestQ.Eof do
TDataSet and its descendants like TAdoAquery and TAdoTable have a standard set of navigation methods including First, Last, Next, Prior and MoveBy which move the dataset's "cursor" in the manner the method name describes.
Unless the dataset is empty, the dataset's cursor always points to exactly one record in the dataset, known as the "active" record, which is the one on which data-reading and data-writing operations take place. When db-aware components are used to display fields from the dataset, it is the values of the fields of the active record which are displayed. (In a TDBGrid, the current record is highlighted).
First simply moves the cursor to the first record in the dataset, according to the dataset's current order and taking into account any active filter on the dataset.
Btw, the standard behaviour when opening a non-empty TDataSet places the cursor on the first record in it, so has the same effect at calling First. Generally, First is called before traversing a dataset so that the traversal starts at a known position.
the idea , iterate a query with the following code
AnyQuery.first;
while (not AnyQuery.EOF) do
begin
/// enter your query code here
....
...
.
/// Move to the next query record
AnyQuery.Next ;
end;

appsheet prevent duplicate entries

I would like to know how I can prevent a duplicate entry (based on my own client/project definition of what that means-below), in an AppSheet mobile app connected to Google Sheets.
AppSheet talks alot about UNIQUEID() which they also encourage using and designating as the KEY field. row_number is another possibility.
This is fine for the KEY in the sense of its purpose is to be unique, meaningless, and uniquely identify a record, and relate to other tables.
However, it doesn't prevent a duplicate ("duplicate" again, as defined by my own client's business rules&process) from occurring. I mean, I assume the UniqueId() theoretically would, but that's abstract theory, because it would only produce unique ones anyway.
MY TABLE HAS THESE COLUMN: [FACILITY NUMBER] and [TIMESTAMP] (date and time of event). We consider it a duplicate event, and want to DISALLOW the adding of such a record to this table, if the 2nd record has the same DATE (time irrelevant), with the same FACILITY. (we just do one facility per day, ever).
In AppSheet how can I create some logic that disallows the add based on that criteria? I even basically know some ways I would do it. it just seems like I can't find a place to "put" it. I created an expression that perfectly evalutes to TRUE or FALSE and nothing else, (by referencing whether or not the FACILIY NUMBER on the new record being added is in a SLICE which I've defined as today's entries). I wanted to place this expression in another (random) field's VALIDIF. To me it seemed like that would meet the platform documentation. the other random field would be considered valid, only if the expression evaluated to true. but instead appsheet thought i wanted to conver the entire [other random column] to a dependent dropdown.
Please help! I will cry tears of joy when appsheet introduces FORM events and RECORD events that can be hooked into at the time of keying, saving, etc.
surprised to see this question here in stackoverflow --- most AppSheet questions are at http://community.appsheet.com.
The brief answer is that you are doing the right thing providing a Valid_If constraint. Your constraint is of the form IN([_THIS], ) so AppSheet is doing the "smart" thing by automatically converting that list into a dropdown of allowed values. From your post, it appears that you may instead want to say NOT(IN([_THIS], )) -- thereby saying that the value [_THIS] is valid as long as it is not in the list specified (making sure it is not a duplicate).
Old question, but in case someone stumbles upon the same:
The (not so simple) answer is given in https://help.appsheet.com/en/articles/961274-list-expressions-and-aggregates.
From the reference:
NOT(IN([_THIS], SELECT(Customers[State], NOT(IN([CustomerId],
LIST([_THISROW].[CustomerId])))))): when used as the Valid_If
condition for the State column, it ensures that every customer has a
unique value for State. In this example, we assume that CustomerId is
the key for the Customers table.
This could be written more schematic like this:
NOT(IN([_THIS], SELECT(<TableName>[<UnqiueColumnName>], NOT(IN([<KeyColumnName>], LIST([_THISROW].[<KeyColumnName>]))))))
Technically it says:
Get me a list of the current values of the column of the table
Ignore the value of the current row (identified by [_THISROW] and looking into the column)
Check, if the given value exists in the resulting list
This statement has to be defined - with the correct values for , & - as Valid_If statement.

How to implement a TDataSet-bound combobox with not-in-list values

I need to implement a combobox, which is bound to a TpFIBDataSet (descendant of TDataSet). I've done this several times before. It's not a big thing if it contains only predefined values.
This time, I'd like to have a combobox that accepts custom values entered by the user, also giving the ability to the user to select some predefined value. Newly entered values shall be inserted into some table of the database just before the record the combobox's field belongs to is posted.
The main problem seems to me, that predefined values are internally represented as integer IDs (the combobox I use is TwwDBComboBox from Roy Woll's InfoPower package, as it implements maplist functionality) because the field is a foreign key, while custom values may be nearly everything (only restricted by a mask).
How can I distinguish between an integer ID and integer user-input, for example?
See the set properties of the combobox:
AComboBox.Style := csDropDown;
AComboBox.MapList := True;
I don't request a solution as take this piece of code and be happy. I'm rather looking for some advice by others who might have or had a similar problem.
How can I distinguish between an integer ID and integer user-input, for example?
You go back to the database. Either query directly select count(*) from table where id = ComboBoxId.
Or use the Locate method of the dataset.
Or keep a cache handy in a MyList: TList<Integer> and do a MyList.BinarySearch to see if the item is already in the DB.
Obviously the cache will only work if the DB is single-user, because otherwise you will not be able to keep it up-to-date.
If it is not in the DB, you run the insert query.
After it's inserted you run the default combobox behavior, because now the values is sure to be in the DB.

Delphi TClientDataSet sorting (insert) issue

as far is I know sorting in a ClientDataSet works over the indexDefs.
I can add an indexDef, set the field(s) I want to sort, and over the ixDescending property I can define the direction to sort.
I have a ClientDataSet connected to a dataSource which is the source for a DBGrid.
When I now insert a new record in the ClientDataSet, it's either inserted at the top of the table (ixDescending = false) or at the bottom of the table (ixDescending = true).
But I want a descending order AND new records should be at the top at the table - not at the bottom.
I tried to change the the indexDefs at runtime to achieve this - but without success.
Somebody has an idea how to do this?
When you have inserted a record unless you set values for the indexed fields they are NULL, and sorted accordingly.
I don't know of an elegant solution (although I'm a novice in this area.)
Perhaps there's some way for you to create a temporary boolean field in your ClientDataSet... (Maybe a calculated field?) Suppose you name the new field "NewRecord" and include it in your IndexDef so that it is the most significant sort criteria.
In an .OnCreate event, you set it true (which would be 1 internally?). In an .OnPost event you set it false.
If you don't want to depend on the internal representation of booleans, then you could create a string field and put "ZZZZZZZ" in it. Or an integer field and put MaxInt in it.

Return no records if FIndKey results in False?

Using TDataSet.FindKey you can locate records. When it results in True the datasets cursor will be positioned on the found record.
I use this when I select items in a list on the left, corresponding data should appear on the right.
When it results in False the cursor is not moved. This results in the record data prior to FindKey being displayed in data aware components on the right.
How can I code the result of FindKey to return an empty record?
if Not tblSomeTable.FindKey([SomeSearchData]) then
begin
< code to return empty or move data cursor to neutral position >
end;
Update: (Waited a few days before selecting right answer as I believe that is the custom and didn't wan to discourage further feedback.) There were several suggestions on tackling this situation although I believe the correct answer was from Marcelo in that it is not possible to have a cursor not be on a record. Several workarounds were suggested. I chose one of my own. It went something like:
If Not tblSomeTable.FindKey([SomeSearchData]) then
begin
tblSomeTable.FindKey([-1,2010]);
end
What I did is create a dummy, blank record with an index that the actual data can never be, ie: The first index value will never be -1. If the initial search comes up empty then the FindKey will position the cursor on this empty record. This will provide the visual effect I was after.
TDataSet does not have a "neutral position". But as always you have few options:
Set dataset into insert / append record mode. So, the controls and code will see empty record. Be careful, as something may incidentally assign data to a field and then the new record may be posted to DB.
Depending on the data access components, you are using, you can set dataset to Cached Updates mode, insert and post new empty record into dataset, and mark all changes as applied. Then assign a filter, normally rejecting this empty record. Then in your code you have to switch the filter over, so it will reject all records, excluding this empty one.
Consider to disconnect the TDataSource from the dataset and later connect it again.
Note sure, but probably there may be invented some other approaches :)
Hallo,
use SetRange instead of FindKey.
tblSomeTable.SetRange([SomeSearchData],[SomeSearchData]);
try
while not tblSomeTable.Eof do begin
<do something with the Record>
tblSomeTable.Next;
end;
finally
tblSomeTable.CanelRange;
end;
when you criteria ensures that the maximum of matching records is one you retrieve with the statement above zero or one record.
This is not possible as far as I know. The cursor must always be on a record unless Bof and Eof are both true (empty data set).

Resources