How to allow selecting a NULL value in a TDBLookupComboBox? - delphi

I have a TDBLookupComboBox showing a TStringField of kind fkLookup, which allows Null values (from a nullable Integer database column).
The dropdown list displays the items from the assigned LookupDataSet, which comes from a joined table. If the field is Null, none of the list items are displayed, the combobox is empty. If the field has a value, the correct description is shown.
I can reset it to Null by pressing the assigned NullValueKey.
That's all ok, but the users prefer using the mouse. So I could provide a clear button, but I think an additional row on top of the list would be much better. How can I do that?

You can put the empty row in your query, and if you need it sorted you can make it appear at the top in your list like this:
select 0 as sort,
convert(int, null) as UserID,
'Clear' as Name
union all
select 1 as sort,
u.UserID,
u.Name
from tblUser u
order by sort, Name
The sort column will make it appear at the top, after that you can sort on whatever you need.

This is not exactly what was requested, but it might be the better solution for developers having a DevExpress VCL ExpressEditors subscription: there is a hidden feature in TcxDBLookupComboBox which can provide a nice clear button inside the combobox!
It works exactly like in the TcxButtonEdit, where you can add buttons at designtime, just that this Buttons property isn't exposed in TcxDBLookupComboBox, so it can only be set at runtime :(
procedure TForm1.FormCreate(Sender: TObject);
begin
AddClearButton(cxDBLookupComboBox1.Properties);
end;
procedure TForm1.AddClearButton(Prop: TcxCustomEditProperties);
begin
with Prop.Buttons.Add do begin
Kind:= bkText;
Caption:= #$2715; //Unicode X-symbol
end;
Prop.OnButtonClick:= ClearButtonClick;
end;
procedure TForm1.ClearButtonClick(Sender: TObject; AButtonIndex: Integer);
begin
if AButtonIndex = 1 then
with TcxCustomEdit(Sender) do begin
EditValue:= Null;
PostEditValue;
end;
end;
This might also work with other edit controls, however, at least with TcxDBSpinEdit it does not.

You can use as your LookupDataSet a query with the next SQL (Firebird syntax)
SELECT CAST (NULL AS INTEGER) AS ID, CAST ('clear' AS VARCHAR(80)) AS NAME FROM table_name
UNION
SELECT ID, NAME FROM table_name
However, in this implementation clear item will be at the end of the list.
Added after our discussion in the chat
I'm afraid we never reach to have null-value field's behavior like normal fields, because null is not a value, as was correctly mentioned here: https://petercai.com/null-is-not-a-value/ . We can only make some workarounds for it.
For example, we can see custom displayed value for null such as SELECT CASE WHEN OurField IS NULL THEN '(empty)' ELSE OurField END AS OurField. And we can set null with artificial menu item. But this is not a full, complex solution.

Related

select for update in stored procedure (concurrently increment a field)

I want to retrieve the value of a field and increment it safely in Informix 12.1 when multiple users are connected.
What I want in C terms is lastnumber = counter++; in a concurrent environment.
The documentation mentions one way of doing this which is to make everyone connect with a wait parameter, lock the row, read the data, increment it and release the lock.
So this is what I tried:
begin work;
select
lastnum
from tbllastnums
where id = 1
for update;
And I can see that the row is locked until I commit or end my session.
However when I put this in a stored procedure:
create procedure "informix".select_for_update_test();
define vLastnum decimal(15);
begin work;
select
lastnum
into vLastnum
from tbllastnums
where id = 1
for update;
commit;
end procedure;
The database gives me a syntax error. (tried with different editors) So why is it a syntax error to write for update clause within a stored procedure? Is there an alternative to this?
Edit
Here's what I ended up with:
DROP TABLE if exists tstcounter;
^!^
CREATE TABLE tstcounter
(
id INTEGER NOT NULL,
counter INTEGER DEFAULT 0 NOT NULL
)
EXTENT SIZE 16
NEXT SIZE 16
LOCK MODE ROW;
^!^
ALTER TABLE tstcounter
ADD CONSTRAINT PRIMARY KEY (id)
CONSTRAINT tstcounter00;
^!^
insert into tstcounter values(1, 0);
^!^
select * from tstcounter;
^!^
drop function if exists tstgetlastnumber;
^!^
create function tstgetlastnumber(pId integer)
returning integer as lastCounter
define vCounter integer;
foreach curse for
select counter into vCounter from tstcounter where id = pId
update tstcounter set counter = vCounter + 1 where current of curse;
return vCounter with resume;
end foreach;
end function;
^!^
SPL and cursors 'FOR UPDATE'
If you manage to find the right bit of the manual — Updating or Deleting Rows Identified by Cursor Name under the FOREACH statement in the SPL (Stored Procedure Language) section of the Informix Guide to SQL: Syntax manual — then you'll find the magic information:
Specify a cursor name in the FOREACH statement if you intend to use the WHERE CURRENT OF cursor clause in UPDATE or DELETE statements that operate on the current row of cursor within the FOREACH loop. Although you cannot include the FOR UPDATE keywords in the SELECT ... INTO segment of the FOREACH statement, the cursor behaves like a FOR UPDATE cursor.
So, you'll need to create a FOREACH loop with a cursor name and take it from there.
Access to the manuals
Incidentally, if you go to the IBM Informix Knowledge Center and see this icon:
that is the 'show table of contents' icon and you need to press it to see the useful information for navigating to the manuals. If you see this icon:
it is the 'hide table of contents' icon, but you should be able to see the contents down the left side. It took me a while to find out this trick. And I've no idea why the contents were hidden by default for me, but I think that was a UX design mistake if other people also suffer from it.

DBLookUpComboBox selected keyvalue edits table in DBGrid

I have a DBLookUpComboBox and DBGrid connected to the same table with two different data source components. Im trying to narrow the DBGrid results by filtering that datasource with the selected key value from the DBLookUpComboBox. It does filter the grid however when I select a key value it edits the first row with the selected key value in the grid. why is this happening?
procedure TForm1.DBCBtypeClick(Sender: TObject);
begin
showmessage('Book Type: ' + dbcbtype.KeyValue);
dmpub.tbooks.Filter := 'type = ' + quotedstr(dbcbtype.KeyValue);
dmpub.tbooks.Filtered := true;
end;
Everything else is in the properties of the components
Setting the Datasource property of the TDbLookupComboBox will place the dataset in dsEdit state. If you just want to use the TDbLookupCombo box for selecting a value from the lookup, just leave the Datasource property blank. The control will still provide a list by using the ListSource, ListField and ListFieldIndex properties.

Creating a Load More filter using a TListView Search - Firemonkey Mobile Application

I managed to make a "Search" bar through a TEdit that seeks whatever i type from inside
a ListView that gets its information from a DataBase and goes through a filter and updates the ListView's items on the fly after a key is pressed.
Now i am trying to learn how to implement a way of limiting the results i get in my ListView temporarily until i press a Show More button or something like that in order to get some more relevant results.
Since the Database might return over 500 results by the time i press "A" and that would be harsh to a mobile phone's capabilities so i need that feature to make my Search button more efficient.
Could someone give me some pointers on what i can use in order to make something like that?
EDIT.
The current code i am using for searching in the ListView is this...
procedure TContactsForm.Edit1ChangeTracking(Sender: TObject);
var
Lower: string;
i: integer;
begin
Lower:= LowerCase(Edit1.Text.Trim);
if Lower= '' then
begin
if Filtered then
begin
ListView1.Items.Filter := nil;
ListView1.ItemIndex := BindSourceDB1.ComponentIndex;
end;
end
else
begin
ListView1.ItemIndex := -1;
ListView1.Items.Filter :=
function(X: string): Boolean
begin
Result:= (Lower = EmptyStr) or LowerCase(X).Contains(Lower);
end;
end;
end;
function TContactsForm.Filtered: Boolean;
begin
Result := Assigned(ListView1.Items.Filter);
end;
The easiest way is to model your select statement so it only returns a limited about of rows (you can always remove the limitation upon user request).
For SQLite, MySQL and PostgreSQL you'd use a LIMIT clause:
SELECT acolumn FROM atable WHERE afield LIKE :param LIMIT 4;
In SQL Server you'd have to do something like:
SELECT * FROM (
SELECT column, ROW_NUMBER() OVER (ORDER BY name) as row FROM atable
) a WHERE a.row <= 4
This has the added benefit that less data is generated and transmitted by the database.
When doing the full search you simple omit the limit clause.
If you want to keep the results you already have and just add to extra results, use a
LIMIT 20 OFFSET 5 clause (without the offset keyword the operands are reversed LIMIT 5,20).
You always want to limit so as to make the experience snappy.
Every new page, you fetch the next x records.
You can even do this in real time, as the user is scrolling the list down.
Fetch new records as he nears the bottom of the list.

How do you add a lookup field to a dataset?

I've got a dataset that I need a lookup field for. Problem is, this dataset's structure is defined by the result of a query. I can't add the field as a TFieldDef before setting .Active = true; because it gets overwritten, and I can't add it after running the query because you can't alter the structure of an open dataset.
There has to be some way to do this. Does anyone know how?
EDIT: There seems to be some confusion about what I'm looking for. I'm not looking for a lookup at query time. I'm looking for a lookup field, a TField object where FieldKind = fkLookup, so that it can be used with a data-aware lookup combo box, for editing the data after the query has returned its result. This has nothing whatsoever to do with the SQL and everything to do with Delphi's dataset model and data-aware controls.
The easiest way is to define persistent fields at design time.
You could also modify your SQL statement to get the calculated values from the server.
You need to create the fields yourself before you open the dataset.
First get all your field definitions from the database
DataSet.FieldDefs.Update;
Then loop through each fielddef and create the normal field, and also create the lookup field where appropriate. Simplified version of the code like this;
for I := 0 to DataSet.FieldDefs.Count - 1 do
begin
// Allocate a normal field
AField := DataSet.FieldDefs[I].CreateField(DataSet);
// Add lookup field if we have another table to look it up from
if (??? this is the key field of a lookup table) then
begin
AField := TStringField.Create(DataSet.Owner);
AField.FieldName := ???;
AField.DataSet := DataSet;
AField.FieldKind := fkLookup;
AField.KeyFields := ???;
AField.LookupKeyFields := ???;
AField.LookupDataSet := ???;
AField.LookupResultField := ???;
end;
end;
Then you can open the dataset.
You have two datasets on your form (say tblOrder,tblCustomer)
One field in the order is a foreign key to the customer table customerId
The Key field of the customer table is Id, Name = Name
Add all fields (right click on the datasets, fields editor , add all fields.
Then right click on the order table and choose fields editor then right click new field.
Name = myLookup,Type is string,Size is xx, FieldType = Lookup.
Key field = customerid,dataset = tblCustomer, lookup Key = Id,Result field = Name.
Now your lookup field is defined.
To make it work in an editor (say in a TDBLookupCombo)
Add a datasources to the form dsOrder
Connect it to tblOrder.
Now set datasource = dsOrder,Field = myLookup
You don't have to set the lookup source...
Let you have table Orders with field (among others) CustomerId (of type Integer), and table Customers with fields CustomerId (of type Integer) and CustomerName (of type String). Then, in Delphi IDE, drop on the form datasets table_main and table_lookup (for tables Orders and Customers respectively). Open fields editor for table_main, add (among others) field CustomerId, then create new field with field properties:
name: CustomerName (for example)
type: integer
field type: lookup
and with lookup properties:
dataset: table_lookup
Key fields: CustomerId
Lookup Keys: CustomerId
Result Fields: CustomerName
Hope it is still relevant now.

Delphi: Displaying a subset of a data set in data-aware controls

I've got an in-memory dataset with several fields, one of which is a primary key that another dataset references as a foreign key. Thing is, the master dataset can have multiple references to the detail dataset. (This is modeling an object that contains a dynamic array of other objects.)
If there was only one of each sub-object, I could make the right association with the KeyFields and LookupKeyFields properties of the reference field in the master dataset, but that's only designed to return one result. I want to load all the records whose primary key matches the right ID key and display them in a listbox.
I thought a TDBListBox would help with this, but it turns out that's not what they do. So how would I populate a listbox or similar control with the result set of a multiple-match check like that for further editing? I want something similar to the result of a SQL query like this:
select field1, field2, field3
from client_dataset
where client_dataset.primary_key = master_dataset.id
Only thing is, this is done entirely with in-memory datasets. No real databases are being used here. Does anyone know how this can be done?
The dataset has a Filter property which can be set with a condition. You also have to set the filtered flag on true. And with he datacontrols you can select which fields are visible.
So:
var
c : TColumn;
begin
clientdataset.Filter := Format('primary_key = %d', [master_dataset.id]);
clientdataset.Filtered := True;
c := DBGrid1.Columns.Add;
c.FieldName := 'field1';
c := DBGrid1.Columns.Add;
c.FieldName := 'field2';
c := DBGrid1.Columns.Add;
c.FieldName := 'field3';
end;
Should do the trick.

Resources