Pedigree Forge includes a SQL-like query language for searching and filtering your genealogy data. It provides a powerful alternative to the built-in canned queries, allowing you to ask custom questions about your database without writing Lua scripts.
Queries can be entered in the Queries panel (Tools > Queries) by selecting the text editor mode.
SELECT surname, given_names, birth.date AS "Born"
FROM persons
WHERE birth.date.year > 1800
ORDER BY surname
A query has four main clauses:
| Clause | Purpose |
|---|---|
| SELECT | Which columns to show |
| FROM | Which type of record to search |
| WHERE | Filter conditions (optional) |
| ORDER BY | Sort order (optional) |
Two additional optional clauses:
| Clause | Purpose |
|---|---|
| DESCRIPTION | A human-readable name (appears before SELECT) |
| LIMIT | Maximum number of result rows |
The FROM clause specifies which type of record to query:
| Table | Root type | Description |
|---|---|---|
| persons | person | All people in the database |
| families | family | All families (couples) |
| facts | fact | All facts and events |
| citations | fact | Citation facts |
| sources | source | All source records |
| archives | archive | All archive records |
| places | place | All places |
Columns are specified using dot-notation paths that navigate the record structure. The root type (determined by the FROM clause) is omitted from paths:
– When FROM persons, "birth.date" means "person.birth.date"
SELECT surname, given_names, birth.date, death.date
FROM persons
Use AS to give a column a display name:
SELECT surname AS "Family Name", birth.date AS "Born"
FROM persons
Use COALESCE() to provide fallback values — common in genealogy where you might have a baptism but not a birth:
SELECT name, COALESCE(birth.date, baptism.date) AS "Born/Baptised"
FROM persons
Use SELECT * to include a default set of columns for the table:
| Table | Default columns |
|---|---|
| persons | id, name, sex, birth.date, birth.place.name, death.date, death.place.name |
| families | id, names, marriage.date, marriage.place.name, status |
| facts | owner.name, label, date, place.name, address |
| citations | description, page, source.title |
| sources | id, title, author, archive.name |
| archives | id, name, town, county |
| places | name, town, county, country |
Facts have two fields for the event type: label returns a human-readable name ("Birth", "Census", "Baptism"), while kind returns the raw GEDCOM tag ("BIRT", "CENS", "BAPM"). Use label in SELECT for display and kind in WHERE for filtering:
SELECT label AS "Event", date, place
FROM facts
WHERE kind = ’census’
Use SELECT DISTINCT to deduplicate results — useful for data discovery:
SELECT DISTINCT trade
FROM persons
WHERE trade IS NOT NULL
ORDER BY trade
| Operator | Example |
|---|---|
| =, != | surname = ’Darwin’ |
| <, >, <=, >= | birth.date.year > 1800 |
| BETWEEN…AND | birth.date.year BETWEEN 1800 AND 1900 |
| IN | birth.place.county IN (’Kent’, ’Sussex’, ’Surrey’) |
| IS NULL | death.date IS NULL |
| IS NOT NULL | birth.date IS NOT NULL |
| WITHIN…OF | birth.place WITHIN 10 MILES OF ’Canterbury, Kent’ |
| Operator | Description |
|---|---|
| CONTAINS | Case-insensitive substring match |
| STARTS_WITH | Matches the start of a value |
| ENDS_WITH | Matches the end of a value |
SELECT name, trade
FROM persons
WHERE trade CONTAINS ’smith’
These operators help find variant spellings — particularly useful for surnames, which were often recorded inconsistently:
| Operator | Description |
|---|---|
| SOUNDS_LIKE | Broad phonetic match (Soundex) — ’Smith’ matches Smyth, Smythe |
| PHONETICALLY_LIKE | Tighter phonetic match (Double Metaphone) — fewer false positives |
| SIMILAR_TO | Fuzzy match (Levenshtein distance) — catches typos and OCR errors |
SELECT name, birth.date
FROM persons
WHERE surname SOUNDS_LIKE ’Smith’
Find people associated with places near a given location:
SELECT name, birth.place.name AS "Birthplace"
FROM persons
WHERE birth.place WITHIN 10 MILES OF ’Canterbury, Kent’
Units can be MILES or KM. Places without coordinates are excluded from the results.
Combine conditions with AND, OR, and NOT. Use parentheses for grouping:
SELECT name, birth.date, death.date
FROM persons
WHERE birth.place.county = ’Kent’
AND birth.date.year > 1800
AND (death.date IS NULL OR death.date.year > 1850)
Sort by one or more paths, with optional ASC (ascending, the default) or DESC (descending):
SELECT surname, given_names, birth.date
FROM persons
ORDER BY surname ASC, birth.date DESC
Sorting is type-aware — each field sorts in a way that makes sense for its data type. Dates sort chronologically (including non-Gregorian calendars), ages sort by total duration, and numeric sub-fields like year, month, and day sort as numbers rather than text. See the Sorts as column in the Data Expressions field reference for details. Empty or missing values always sort last, regardless of ASC or DESC.
A common research question is "who is missing X?" The path system supports .count on filtered collections:
– People with no 1881 census
SELECT name, birth.date
FROM persons
WHERE facts[kind=census & year=1881].count = 0
– People with no death record who are not flagged as living
SELECT name, birth.date
FROM persons
WHERE facts[kind=death].count = 0 AND living = ’false’
Queries can include @parameter placeholders in WHERE values, turning saved queries into reusable templates with input fields. Use a DECLARE block before SELECT to declare the parameter name, type, and optional label and default value:
DESCRIPTION ’Facts near a place’
DECLARE
@place PLACE LABEL ’Near’;
@radius NUMBER LABEL ’Miles’ DEFAULT 5;
SELECT owner.name, label AS "Event", date, place.name
FROM facts
WHERE place WITHIN @radius MILES OF @place
ORDER BY date
Each declaration has the form:
@name TYPE [LABEL ’display text’] [DEFAULT value];
| Type | Input |
|---|---|
| TEXT | Plain text field |
| NUMBER | Numeric field |
| DATE | Date field with calendar picker |
| PLACE | Place field with place search |
| PERSON | Person name field with autocomplete |
| SEX | Choice of M or F |
The type determines the input control shown in the Queries panel when the query is selected. Place and date fields include the same entry assistants used elsewhere in the application.
A @parameter used in WHERE but not listed in a DECLARE block is treated as TEXT with no default. This keeps simple ad-hoc queries lightweight:
SELECT * FROM persons WHERE surname CONTAINS @name
Parameters can be supplied from Lua scripts and MCP tool calls. See Scripting API and the query MCP tool for details.
Use – for line comments:
– Find people born in Kent after 1800
SELECT name, birth.date
FROM persons
WHERE birth.place.county = ’Kent’ – restrict to Kent
AND birth.date.year > 1800
DESCRIPTION ’People born in Kent’
SELECT surname, given_names, birth.date AS "Born",
birth.place.name AS "Birthplace"
FROM persons
WHERE birth.place.county = ’Kent’
ORDER BY surname, given_names
SELECT title, archive.name AS "Repository"
FROM sources
WHERE archive.name CONTAINS ’National’
ORDER BY title
SELECT label AS "Event", date, place.name, owner.name AS "Person"
FROM facts
WHERE kind = ’census’ AND date.year = 1881
ORDER BY owner.name
Query results are displayed in an editable grid. Where the underlying data supports it, you can edit values directly in the results — the changes are written back to the database. This makes the query language an efficient tool for bulk data review and correction.
In addition to query-language queries, the Queries panel also shows Lua script queries (prefixed with "Script:") when @query scripts are installed. See Scripts for how to write query scripts and where to place them.
Saved queries are loaded from .pql text files. Pedigree Forge searches two directories:
Each .pql file contains a single query. To save a query you have written, copy its text into a .pql file in your user queries directory. It will appear in the drop-down the next time the panel is opened.