Skip to content

SbQL Reference

SbQL (Sessionboard Query Language) is a SQL-like query language purpose-built for querying Sessionboard event data. It provides a familiar syntax for selecting, filtering, joining, and aggregating data across sessions, speakers, contacts, and other event entities.

SbQL follows standard SELECT-FROM-WHERE patterns:

SELECT session.title, session.status
FROM sessions
WHERE session.status = 'accepted'

Join entities to combine related data and use aggregate functions for analytics:

SELECT speaker.full_name, COUNT(session.id) AS session_count
FROM speakers
JOIN sessions
GROUP BY speaker.full_name
ORDER BY session_count DESC

Use standard comparison operators and logical connectives:

SELECT session.title, session.start_date, track.name
FROM sessions
JOIN tracks
WHERE session.status = 'accepted'
AND track.name = 'Keynotes'
ORDER BY session.start_date ASC

One Row per Submission: PARTICIPANTS and MESSAGES

Section titled “One Row per Submission: PARTICIPANTS and MESSAGES”

Joining people onto sessions returns one row per person. When you want one row per submission with everyone in a role in a single cell, use PARTICIPANTS. MESSAGES puts a submission’s whole message thread in one cell the same way. Both work with FIND Session and FIND Abstract only.

FIND Session
INCLUDE friendly_id, title,
PARTICIPANTS('submitter') AS "Submitter",
PARTICIPANTS('speaker') AS "Speakers",
PARTICIPANTS('co-author') AS "Authors/Co-Authors",
MESSAGES('reviewers') AS "Messages"
FunctionCellExample output
PARTICIPANTS('<roles>'[, '<format>'[, '<separator>']])Everyone in the listed role(s), in submission order. A person holding two of the listed roles appears once; a person in two columns’ roles appears in both columns.Taehyung Lee (Samsung), Rita Garcia (Synopsys)
MESSAGES('<thread>')The submission’s message thread, oldest first, one message per line, in the event’s time zone.Jason Rziha – Oct 26, 2026, 10:59 AM: Did you submit this paper twice?
  • <roles>: comma-separated role slugs from the event (participant_contacts.role_slug, listed as session_participant_roles in the event schema), plus submitter for the contact who submitted.
  • <format>: name_company (default, “Full Name (Company)”), name, or name_email.
  • <separator>: comma (default), semicolon, pipe, or newline.
  • <thread>: reviewers (Reviewers ↔ Submitter, the default), organizers (Organizers ↔ Submitter), or all (both, each line tagged [Reviewers] or [Organizers]).

Both columns contain names, so they count as personal data: Hide personal details redacts them. Internal team notes are never included. On a submission in a blind Review plan, a reviewer’s name reads Reviewer. A role restricted to part of the event sees only the participants it may see and cannot use MESSAGES. CSV exports never truncate a cell; XLSX caps a cell at Excel’s 32,767 characters.

Section titled “All, Any or None of a Record’s Related Rows: ALL, ANY, NONE”

A condition on a related field, such as sessions_as_speaker.status = 'declined', checks one related row at a time. It returns a row per matching session and keeps a speaker who also has an accepted session. To ask about all of a record’s related rows at once, wrap the condition in ALL, ANY or NONE and the relationship’s name:

FIND Contact
INCLUDE first_name, last_name, email
WHERE ANY sessions (is_subsession = true)
AND ALL sessions (is_subsession = false OR status = 'declined')
ORDER BY last_name ASC

This returns one row per person who is on at least one subsession and whose every subsession is declined. It looks at subsessions only: every session with is_subsession = false passes the ALL whatever its status, so someone whose subsessions were all declined but who is accepted on a standalone session (one without subsessions) is still returned. On an event that has both, check those people’s standalone sessions before you send a decline email. sessions covers every role. Someone declined as a speaker but accepted as a moderator on another subsession is left out. Use sessions_as_speaker to look at speaker seats only.

FormKeeps the record when
ANY <relationship> (<condition>)at least one related row matches
NONE <relationship> (<condition>)no related row matches
ALL <relationship> (<condition>)every related row matches, including when there are none. Pair it with ANY to require at least one.
  • The record stays one row, however many related rows it has, so DISTINCT is not needed.
  • Fields inside the parentheses belong to the relationship: status and sessions.status mean the same thing there. Custom fields work too: ANY speakers (["Speaker Type"] = 'Keynote').
  • ALL, ANY and NONE work in WHERE only, not in a CASE WHEN.
  • Inside the parentheses you can use AND, OR, NOT and every comparison operator. You cannot use fields of another relationship or put one ALL, ANY or NONE inside another.
  • Fields calculated after the query runs (review answers such as reviews_v2.is_complete, scorecard questions, Interest Form, Awards and reviewer fields) cannot be used inside the parentheses, or anywhere else in the same WHERE.
  • Task relationships (speaker_tasks, moderator_tasks and the others) are not available, because whether a task applies to a session is decided after the query.
  • On events with subsessions, speakers usually also sit on the parent session. Name the level with is_subsession inside the parentheses, as above.
  • A role restricted to part of the event cannot use ALL, ANY or NONE; the request returns 403.

Adding a review answer to a session report usually returns one row per reviewer. To keep one row per session, put an aggregate over the reviews_v2 field in INCLUDE, next to your other columns, with no GROUP BY. Each aggregate then summarises that session’s reviews in one cell. Works with FIND Session and FIND Abstract.

FIND Session
WHERE reviews_v2.plan_name = 'CFP 2027 Review Plan'
INCLUDE title AS "Session Title", track.name AS "Track",
AVG(reviews_v2.total_score) AS "Cumulative Rating",
PARTICIPANTS('speaker') AS "Speakers",
COUNT_BY_VALUE(reviews_v2.rubric_label_is_this_session_a_sales_pitch) AS "Sales pitch?",
LIST_WITH_REVIEWER(reviews_v2.rubric_label_final_comments) AS "Final comments"
FunctionCellExample output
COUNT_BY_VALUE(reviews_v2.<answer>)How many reviews gave each answer. The question’s options come first, in order, zeros included.Yes: 1 / No: 3
LIST_WITH_REVIEWER(reviews_v2.<answer>)Each answer once, with the reviewer’s initials, ordered by initials. On a blind Review plan the initials are left out.[AB] Strong outline | [CD] Too salesy
AVG, MIN, MAX, SUM, COUNT, COUNT_DISTINCT, STDDEV_POP, LISTAs usual, over the session’s reviews.AVG(reviews_v2.total_score) → 24.5
  • Each review counts once, however many speakers or tags the report also lists, and abstentions are left out.
  • The summaries add no rows. A related field such as speakers.full_name still gives one row per speaker unless you pivot it (Speaker 1, Speaker 2, …) or list the speakers in one cell with PARTICIPANTS('speaker'), as above.
  • A reviews_v2 condition in WHERE keeps only the sessions with a matching review and limits the reviews each summary counts.
  • HAVING is not available with summaries. Filter the sessions in WHERE instead.
  • COUNT_BY_VALUE and LIST_WITH_REVIEWER accept review answers only. They also work in COMPUTE with GROUP BY.

Scorecard questions are fields named after their label: reviews_v2.rubric_label_<label>, the label in lower case with every run of other characters replaced by _. The schema lists each one with its label, its options (values) and its usual summary (reviewSummary).

One column per reviewer instead: include the plain answers (reviews_v2.rubric_label_<label>, reviews_v2.reviewer_full_name) and pivot them with the prefix Reviewer. Each review gets a block of columns (Reviewer 1: …, Reviewer 2: …); abstentions take no block.

Use the schema endpoints to discover the entities and fields available for your organization:

  • Organization schema: GET /v1/insights/schema returns all available entities and their fields.
  • Event schema: GET /v1/insights/event/{eventId}/schema returns entities and fields scoped to a specific event.

The schema response includes entity names, field names, data types, and relationships between entities that you can use in JOIN clauses.

For event-scoped schema (GET /v1/insights/event/{eventId}/schema), the payload also includes session_participant_roles: each event’s configured session roles (name, name_plural, slug, core_role). Use participant_contacts.role_slug in SbQL to filter custom roles (e.g. panelists); match user-facing labels to the slug from this list.

Session vs Abstract: Both use the same underlying sessions table. FIND Session returns only program sessions (non-abstract). FIND Abstract returns only abstract / CFP submissions. Prefer these entities over filtering on is_abstract in queries.

The REST Sessions API exposes the same distinction via is_abstract on every session payload, plus composition_status for merge/link state. Linked composition sources are excluded from default list/search unless you pass expand=linked_sources. See Sessions & Composition.

Relationship entries may include a description flagging legacy evaluation paths (ratings, criteria_ratings, etc.).

If you prefer to write queries in plain English, use the POST /v1/insights/ai/generate endpoint to convert natural language into SbQL. For example, sending “How many sessions are in each track?” will return a valid SbQL query that you can execute directly.

Get release notes by email

A short digest on the days something new goes live — each change with a link to its note in the release notes. No marketing, and one click to stop.

Company email addresses only. We’ll send one email to confirm it’s you. Already use Sessionboard? You can also turn this on under Community → What’s new.