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.
Basic Syntax
Section titled “Basic Syntax”SbQL follows standard SELECT-FROM-WHERE patterns:
SELECT session.title, session.statusFROM sessionsWHERE session.status = 'accepted'Joins and Aggregations
Section titled “Joins and Aggregations”Join entities to combine related data and use aggregate functions for analytics:
SELECT speaker.full_name, COUNT(session.id) AS session_countFROM speakersJOIN sessionsGROUP BY speaker.full_nameORDER BY session_count DESCFiltering with Conditions
Section titled “Filtering with Conditions”Use standard comparison operators and logical connectives:
SELECT session.title, session.start_date, track.nameFROM sessionsJOIN tracksWHERE session.status = 'accepted' AND track.name = 'Keynotes'ORDER BY session.start_date ASCOne 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 SessionINCLUDE friendly_id, title, PARTICIPANTS('submitter') AS "Submitter", PARTICIPANTS('speaker') AS "Speakers", PARTICIPANTS('co-author') AS "Authors/Co-Authors", MESSAGES('reviewers') AS "Messages"| Function | Cell | Example 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 assession_participant_rolesin the event schema), plussubmitterfor the contact who submitted.<format>:name_company(default, “Full Name (Company)”),name, orname_email.<separator>:comma(default),semicolon,pipe, ornewline.<thread>:reviewers(Reviewers ↔ Submitter, the default),organizers(Organizers ↔ Submitter), orall(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.
All, Any or None of a Record’s Related Rows: ALL, ANY, NONE
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 ContactINCLUDE first_name, last_name, emailWHERE ANY sessions (is_subsession = true) AND ALL sessions (is_subsession = false OR status = 'declined')ORDER BY last_name ASCThis 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.
| Form | Keeps 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
DISTINCTis not needed. - Fields inside the parentheses belong to the relationship:
statusandsessions.statusmean the same thing there. Custom fields work too:ANY speakers (["Speaker Type"] = 'Keynote'). ALL,ANYandNONEwork inWHEREonly, not in aCASE WHEN.- Inside the parentheses you can use
AND,OR,NOTand every comparison operator. You cannot use fields of another relationship or put oneALL,ANYorNONEinside 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 sameWHERE. - Task relationships (
speaker_tasks,moderator_tasksand 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_subsessioninside the parentheses, as above. - A role restricted to part of the event cannot use
ALL,ANYorNONE; the request returns403.
One Row per Session: Review Summaries
Section titled “One Row per Session: Review Summaries”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 SessionWHERE 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"| Function | Cell | Example 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, LIST | As 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_namestill gives one row per speaker unless you pivot it (Speaker 1,Speaker 2, …) or list the speakers in one cell withPARTICIPANTS('speaker'), as above. - A
reviews_v2condition inWHEREkeeps only the sessions with a matching review and limits the reviews each summary counts. HAVINGis not available with summaries. Filter the sessions inWHEREinstead.COUNT_BY_VALUEandLIST_WITH_REVIEWERaccept review answers only. They also work inCOMPUTEwithGROUP 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.
Schema Discovery
Section titled “Schema Discovery”Use the schema endpoints to discover the entities and fields available for your organization:
- Organization schema:
GET /v1/insights/schemareturns all available entities and their fields. - Event schema:
GET /v1/insights/event/{eventId}/schemareturns 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.).
Natural Language to SbQL
Section titled “Natural Language to SbQL”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.
