1. Home
  2. /
  3. Docs
  4. /
  5. Articles Report Writer
  6. /
  7. Database Components
  8. /
  9. FD Components
  10. /
  11. FD Memtable

FD Memtable

FD MemTable is an in-memory table that holds data during a report run. Unlike FD Query and FD Table, it does not connect to a database and does not need an FD Database component. You populate it yourself — from a file, from script code, or by copying data from another dataset.

FD MemTable is useful when you need to accumulate data as the report runs, load data from a file, transform data before displaying it, or carry information from one part of the report to another.

Adding an FD MemTable to Your Report

  1. Drag an FD MemTable component onto the designer surface
  2. Define the fields the table will hold using the FieldDefs property
  3. The table is created automatically when the report starts

You do not need to set a Database property. FD MemTable manages its own data entirely in memory.

Defining Fields

Before you can use an FD MemTable you need to define its fields. You can do this at design time using the FieldDefs property in the Object Inspector, or in script using the OnStartReport event.

Defining fields in script is the most common approach. Use the OnStartReport event to set up the table before the report runs:

procedure ReportOnStartReport(Sender: TObject);
begin
  FDMemTable1.FieldDefs.Clear;
  FDMemTable1.FieldDefs.Add('CheckReference', ftString,   50,  False);
  FDMemTable1.FieldDefs.Add('VendorName',     ftString,   100, False);
  FDMemTable1.FieldDefs.Add('Reference',      ftString,   50,  False);
  FDMemTable1.FieldDefs.Add('InvoiceDate',    ftDate,     0,   False);
  FDMemTable1.FieldDefs.Add('DueDate',        ftDate,     0,   False);
  FDMemTable1.FieldDefs.Add('InvoiceAmount',  ftFloat,    0,   False);
  FDMemTable1.FieldDefs.Add('AmountPaid',     ftFloat,    0,   False);
  FDMemTable1.FieldDefs.Add('CopyType',       ftString,   20,  False);
  FDMemTable1.FieldDefs.Add('SortOrder',      ftInteger,  0,   False);
  FDMemTable1.CreateDataSet;
  FDMemTable1.Open;
end;

The FieldDefs.Add method takes four parameters:

  • Name — the field name as a string
  • DataType — the field type (see Field Types below)
  • Size — the maximum length for string fields. Use 0 for numeric and date fields
  • Required — True if the field must have a value, False to allow empty values. In most cases use False

Always include all four parameters. Leaving out the Required parameter will cause an error.

Field Types

The most commonly used field types are:

ftString â€” text up to the size you specify
ftInteger â€” whole numbers
ftFloat â€” decimal numbers
ftDate â€” date only
ftDateTime â€” date and time
ftBoolean â€” True or False
ftCurrency â€” currency values
ftLargeint â€” large whole numbers

Adding Rows in Script

To add rows to the FD MemTable from script, use Append and Post. Always use Append rather than Insert — Append adds the new row at the end of the table which is faster and avoids disrupting any index ordering. Insert adds the row at the current cursor position which can cause unexpected behaviour when looping through records.

FDMemTable1.Append;
FDMemTable1.FieldByName('CheckReference').AsString  := 'CHK-001';
FDMemTable1.FieldByName('VendorName').AsString      := 'Acme Supplies';
FDMemTable1.FieldByName('Reference').AsString       := 'INV-500';
FDMemTable1.FieldByName('InvoiceDate').AsDateTime   := EncodeDate(2026, 1, 15);
FDMemTable1.FieldByName('DueDate').AsDateTime       := EncodeDate(2026, 2, 15);
FDMemTable1.FieldByName('InvoiceAmount').AsFloat    := 1250.00;
FDMemTable1.FieldByName('AmountPaid').AsFloat       := 1250.00;
FDMemTable1.FieldByName('CopyType').AsString        := 'Vendor Copy';
FDMemTable1.FieldByName('SortOrder').AsInteger      := 1;
FDMemTable1.Post;

Always call Append before setting field values and Post after. Forgetting Post will lose the row.

Reading Rows in Script

To loop through all rows in the FD MemTable:

FDMemTable1.First;
while not FDMemTable1.EOF do
begin
  // read values here
  ShowMessage(FDMemTable1.FieldByName('VendorName').AsString);
  FDMemTable1.Next;
end;

Loading Data from a File

FD MemTable can load its data from a file automatically when the report starts. Set the DataFileName property to the full path of the file and set DataFileFormat to match the file type.

Supported file formats:

mffJSON â€” FireDAC JSON format
mffXML â€” FireDAC XML format
mffBinary â€” FireDAC binary format
mffCSV â€” comma-separated text file
mffAuto â€” Articles detects the format from the file extension

When DataFileName is set, Articles loads the file automatically at the start of each report run. You do not need to write any script code to load the file.

For CSV files, three additional properties control how the file is parsed:

CSVDelimiter â€” the character that separates fields. Default is a comma. Change to a tab character for tab-separated files.
CSVQuoteChar â€” the character used to wrap fields that contain the delimiter. Default is a double-quote.
CSVHasHeader â€” when True (default), the first row of the file is treated as field names. When False, fields are named F1, F2, and so on.

For full details on CSV import and export see CSV Import and Export.

Properties

FieldDefs
Defines the fields (columns) of the in-memory table. Set these before calling CreateDataSet. See Defining Fields above.

DataFileName
The full path to a file to load when the report starts. Leave blank if you are populating the table from script instead.

DataFileFormat
The format of the file specified in DataFileName. Set to mffAuto to detect automatically from the file extension.

IndexFieldNames
The field name or names to sort the table by. Separate multiple names with a semicolon. For example SortOrder;CheckReference.

IndexActive
Set to True to activate the index specified by IndexFieldNames. Default is True.

ReadOnly
When True, the table does not allow edits. Default is False — FD MemTable is editable by default since you normally populate it from script.

AutoCalcFields
When True, calculated fields are evaluated automatically. Default is True.

Active
Indicates whether the table is open. Do not set this directly in the Object Inspector — let Articles manage it through CreateDataSet and the report lifecycle.

Saving Data to a File

FD MemTable can save its data to a file from PascalScript. Four formats are supported:

SaveAsJSON â€” saves in FireDAC JSON format
SaveAsXML â€” saves in FireDAC XML format
SaveAsBinary â€” saves in FireDAC binary format
SaveAsCSV â€” saves as a comma-separated text file

Example:

FDMemTable1.SaveAsCSV('C:\Reports\Export\overflow.csv');
FDMemTable1.SaveAsJSON('C:\Reports\Export\overflow.json');

The table must be open before calling any save method. For full details see Saving Data to Files and CSV Import and Export.

Copying Data from Another Dataset

FD MemTable can copy data from an FD Query, FD Table, or another FD MemTable using preset methods. These methods are designed for use in PascalScript and do not require knowledge of FireDAC internals.

CopyStructureAndData(Source)
Copies both the structure and all rows from the source dataset. Clears any existing data in the MemTable first. Use this when you want the MemTable to become an exact copy of the source.

FDMemTable1.CopyStructureAndData(FDQuery1);

CopyDataOnly(Source)
Copies rows from the source into an existing MemTable structure. Clears existing rows first but keeps the field definitions. Use this when the MemTable structure is already defined and you just want to refresh the data.

FDMemTable1.CopyDataOnly(FDQuery1);

CopyAppend(Source)
Appends rows from the source to the existing data without clearing. Use this to accumulate rows from multiple sources.

FDMemTable1.CopyAppend(FDQuery1);

Merging Data

Three merge methods are available for combining data from another dataset into the MemTable:

MergeAppend(Source) â€” adds new rows from the source that do not already exist in the MemTable
MergeUpdate(Source) â€” updates existing rows in the MemTable with values from matching rows in the source
MergeAppendAndUpdate(Source) â€” does both: adds new rows and updates existing ones

FDMemTable1.MergeAppendAndUpdate(FDQuery1);

Events

FD MemTable supports the same events as FD Query:

BeforeOpen â€” fires before the table opens
AfterOpen â€” fires after the table opens
BeforeScroll â€” fires before the cursor moves to a new row
AfterScroll â€” fires after the cursor moves to a new row
OnFilterRecord â€” fires for each row when filtering is active

Finding and Filtering Records

FD MemTable provides several ways to search for records or limit which rows are visible.

LocateEx — finding a specific row by field value
LocateEx searches for a row where a field matches a value and moves the cursor to it. It returns True if the row was found, False if not.

// Find the row where Reference = 'INV-500'
if FDMemTable1.LocateEx('Reference', 'INV-500', []) then
  ShowMessage('Found: ' + FDMemTable1.FieldByName('VendorName').AsString)
else
  ShowMessage('Not found');

To search on multiple fields, separate the field names with a semicolon and pass an array of values:

if FDMemTable1.LocateEx('CopyType;SortOrder', VarArrayOf(['Vendor Copy', 1]), []) then
  // cursor is now on the matching row

Filter — limiting visible rows
The Filter property lets you show only rows that match an expression. Set the Filter expression and set Filtered to True to activate it. Set Filtered to False to show all rows again.

// Show only Vendor Copy rows
FDMemTable1.Filter   := 'CopyType = ''Vendor Copy''';
FDMemTable1.Filtered := True;

// Show all rows again
FDMemTable1.Filtered := False;

Note that string values in filter expressions must be wrapped in single quotes. Because the whole expression is already inside a string, you double up the single quotes as shown above.

OnFilterRecord — custom filtering in script
For more complex filtering that a simple expression cannot handle, use the OnFilterRecord event. This fires for every row and lets you set Accept to False to hide a row:

procedure FDMemTable1OnFilterRecord(DataSet: TDataSet; var Accept: Boolean);
begin
  Accept := FDMemTable1.FieldByName('AmountPaid').AsFloat > 0;
end;

Activate custom filtering by setting Filtered to True. The OnFilterRecord event fires for every row whenever the table is navigated.

FindKey — finding a row by index value
FindKey searches for a row using the current index. Before using FindKey you must set IndexFieldNames to the field or fields you want to search on. FindKey returns True if the row was found and moves the cursor to it, False if not.

Searching on a single field:

// Find the row where SortOrder = 1
FDMemTable1.IndexFieldNames := 'SortOrder';
if FDMemTable1.FindKey(1) then
  ShowMessage('Found: ' + FDMemTable1.FieldByName('VendorName').AsString)
else
  ShowMessage('Not found');

Searching on multiple fields requires the index to include all the fields you are searching on. Pass the values as an array using square brackets, in the same order as the fields in IndexFieldNames:

// Find the row where CopyType = 'Vendor Copy' AND SortOrder = 1
FDMemTable1.IndexFieldNames := 'CopyType;SortOrder';
if FDMemTable1.FindKey(['Vendor Copy', 1]) then
  ShowMessage('Found: ' + FDMemTable1.FieldByName('VendorName').AsString)
else
  ShowMessage('Not found');

The values in the array must match the field types exactly — strings as strings, integers as integers, dates as TDateTime values. The order of values in the array must match the order of field names in IndexFieldNames. If the order does not match, FindKey will either find the wrong row or return False.

FindKey is faster than LocateEx on large tables because it uses the index to search rather than scanning every row. Use FindKey when you are searching on the indexed field or fields repeatedly, and LocateEx when you need to search on any field without setting up an index first.

FindFirst, FindNext, FindPrior, FindLast — navigating filtered rows
When Filtered is True, use these methods to move between the rows that pass the filter:

FDMemTable1.Filtered := True;
FDMemTable1.FindFirst;
while not FDMemTable1.EOF do
begin
  // process filtered row
  FDMemTable1.FindNext;
end;

Common Problems

“Dataset not in edit or insert mode” error
You tried to set a field value without calling Append or Edit first. Always call Append before setting values on a new row, or Edit before changing an existing row.

Rows are lost after the table closes
FD MemTable data only exists in memory during the report run. When the report finishes the data is gone. If you need the data to persist, save it to a file using SaveAsJSON or SaveAsCSV before the report ends.

The table is empty when I try to read it
If you are populating the table in one event and reading it in another, make sure the population happens first. Use OnStartReport to set up and populate the table, then read from it in band events later in the report.

FieldDefs.Add causes an error
Make sure you are passing all four parameters including the Required parameter at the end. For example:
FDMemTable1.FieldDefs.Add('MyField', ftString, 50, False);
Leaving out the False will cause an error.

Loading a CSV file produces wrong field names
Check that CSVHasHeader is True if your file has a header row. If CSVHasHeader is False, FireDAC generates names like F1, F2 instead of reading the header.

Related Pages

Articles