Dynamic Fields#
Dynamic fields are Znuny’s extensible custom-field system. They are stored using the Entity–Attribute–Value (EAV) pattern across three tables. This pattern is efficient for a variable number of fields but requires a specific join pattern that differs from ordinary column lookups.
The Three Tables#
dynamic_field — field definitions#
One row per configured dynamic field. This table tells you what fields exist and what type they are.
Column |
Type |
Description |
|---|---|---|
|
INTEGER |
Primary key. Referenced by |
|
VARCHAR(200) |
Internal field name (no spaces). This is what you filter on in queries. |
|
VARCHAR(200) |
Display label shown in the UI. |
|
VARCHAR(200) |
The field type: |
|
VARCHAR(100) |
Which object type this field is attached to: |
|
INTEGER |
Display order in the UI. |
|
SMALLINT |
|
dynamic_field_value — stored values#
One row per field value per object. Three value columns exist; only one is populated per row depending on the field type.
Column |
Type |
Description |
|---|---|---|
|
INTEGER |
Primary key. |
|
INTEGER |
Which field this value belongs to. Joins to |
|
BIGINT |
The ID of the object this value is attached to. For |
|
TEXT |
Populated for: Text, TextArea, Dropdown, Multiselect, Checkbox fields. |
|
DATETIME |
Populated for: Date, DateTime fields. |
|
BIGINT |
Populated for: integer-type fields. |
dynamic_field_obj_id_name — object name bridge#
This table is only needed when querying CustomerUser dynamic fields. Because customer_user.login is a string, the system cannot use it directly as object_id (which must be an integer). This bridge table maps a surrogate integer object_id to the actual string login name.
Column |
Type |
Description |
|---|---|---|
|
INTEGER |
The surrogate integer used as |
|
VARCHAR(200) |
The actual |
|
VARCHAR(100) |
Always |
How the EAV Pattern Works#
For a Ticket dynamic field, the relationship is direct:
ticket.id ──────────────────► dynamic_field_value.object_id
dynamic_field_value.field_id ──► dynamic_field.id
For a CustomerUser dynamic field, a bridge table is needed:
customer_user.login ──► dynamic_field_obj_id_name.object_name
dynamic_field_obj_id_name.object_id ──► dynamic_field_value.object_id
dynamic_field_value.field_id ──► dynamic_field.id
Which Value Column to Read#
Field type |
Value column |
|---|---|
Text, TextArea |
|
Dropdown, Multiselect |
|
Checkbox |
|
Date, DateTime |
|
Integer fields |
|
Multi-Value Fields#
Multiselect and similar fields store one row per selected option. If a field has three options selected, dynamic_field_value will have three rows with the same field_id and object_id.
A straight JOIN on a multi-value field will multiply your result rows. Use GROUP_CONCAT or a subquery to aggregate:
-- Collect all selected options for a Multiselect field into one cell
SELECT
t.tn,
GROUP_CONCAT(dfv.value_text ORDER BY dfv.value_text SEPARATOR ', ') AS selected_options
FROM ticket t
JOIN dynamic_field_value dfv ON dfv.object_id = t.id
JOIN dynamic_field df ON df.id = dfv.field_id
WHERE df.name = 'YourMultiselectFieldName'
AND df.object_type = 'Ticket'
AND t.archive_flag = 0
GROUP BY t.id, t.tn;
Query Patterns#
Get all dynamic field values for one ticket:
SELECT
df.name AS field_name,
df.field_type,
dfv.value_text,
dfv.value_date,
dfv.value_int
FROM dynamic_field df
JOIN dynamic_field_value dfv ON dfv.field_id = df.id
WHERE df.object_type = 'Ticket'
AND dfv.object_id = :ticket_id;
Filter tickets by a specific dynamic field value (Dropdown):
SELECT t.tn, t.title, t.create_time
FROM ticket t
JOIN dynamic_field_value dfv ON dfv.object_id = t.id
JOIN dynamic_field df ON df.id = dfv.field_id
WHERE df.name = 'Category'
AND df.object_type = 'Ticket'
AND dfv.value_text = 'Hardware'
AND t.archive_flag = 0;
Filter tickets by a DateTime dynamic field within a range:
SELECT t.tn, t.title, dfv.value_date AS due_date
FROM ticket t
JOIN dynamic_field_value dfv ON dfv.object_id = t.id
JOIN dynamic_field df ON df.id = dfv.field_id
WHERE df.name = 'DueDate'
AND df.object_type = 'Ticket'
AND dfv.value_date BETWEEN '2024-01-01' AND '2024-12-31'
AND t.archive_flag = 0;
Get a CustomerUser dynamic field value:
SELECT
cu.login,
dfv.value_text AS department
FROM customer_user cu
JOIN dynamic_field_obj_id_name dion ON dion.object_name = cu.login
AND dion.object_type = 'CustomerUser'
JOIN dynamic_field_value dfv ON dfv.object_id = dion.object_id
JOIN dynamic_field df ON df.id = dfv.field_id
WHERE df.name = 'Department'
AND df.object_type = 'CustomerUser';