Sharon Ben-Moshe is the founder of sqlfmt, a browser-based SQL formatter and validator with misspelled-keyword suggestions across PostgreSQL, MySQL, SQLite, SQL Server, and BigQuery.
BigQuery syntax errors point past the real mistake. Here are 7 common causes—wrong clause order, reserved words, Legacy SQL—with runnable fixes.
Quick answer
BigQuery syntax errors report the position of the first token the parser cannot place — the real mistake is almost always one step earlier in your query. The most common causes are wrong clause order, an unquoted reserved word used as an identifier, a missing comma in a SELECT list or CTE, and Legacy SQL constructs pasted into a Standard SQL session. Find the line and column BigQuery reports, look at the token just before it, and the fix is usually a single character away.
FAQs
Why does BigQuery point to the wrong line in a syntax error?
BigQuery reports the first token the parser cannot place, not the one that caused the confusion. A missing comma in a SELECT list, for example, makes the next identifier look unexpected — BigQuery flags the identifier, not the missing comma. Always look one token earlier than the reported position.
What is the difference between Legacy SQL and Standard SQL in BigQuery?
Legacy SQL is BigQuery's original dialect, now discouraged. Standard SQL is the ANSI-compatible default and has been recommended since 2016. Standard SQL uses backticks for identifiers, -- for comments, and project.dataset.table for table references. Legacy SQL uses square brackets, # comments, and different function names. You can check and change the dialect in the BigQuery console under More > Query settings.
How do I use a reserved word as a column name in BigQuery?
Wrap the identifier in backticks: `date`, `end`, `table`, `value`. This works for column names, table names, and aliases. Single quotes and double quotes are for string literals and cannot be used as identifier escapes in Standard SQL.
Does BigQuery support LEFT() and RIGHT() string functions?
No. Use SUBSTR(str, 1, n) instead of LEFT(str, n), and SUBSTR(str, LENGTH(str) - n + 1, n) instead of RIGHT(str, n). The error you'll see when using LEFT or RIGHT is "Syntax error: Unexpected keyword LEFT" — BigQuery treats them as the JOIN direction keywords, not function names.
What does 'Expected end of input' mean in BigQuery?
It means BigQuery finished parsing a complete valid statement and then found more SQL it didn't expect. Common causes: an extra closing parenthesis, a stray semicolon splitting what should be a single query, or submitting multiple statements where the API accepts only one. Remove the extra token after the last valid statement.
Try it now
Paste your SQL into sqlfmt — format, validate, and catch typos free in your browser.
BigQuery's parser always reports the position of the first token it cannot place — not the token that caused the problem. If a comma is missing after the second column in your SELECT list, the error will point at the third column name, because that's where the parser first sees something unexpected.
This guide covers seven causes behind BigQuery syntax errors, each with a runnable before-and-after example. Whether you've pasted SQL from another dialect, hit a reserved word, or scrambled clause order, the fix is usually a one-line change once you know what to look for.
How BigQuery reports syntax errors
BigQuery Standard SQL error messages include a line and column number in brackets — for example: Syntax error: Unexpected keyword HAVING at [4:1]. The numbers are 1-based: line 4, first character. In the BigQuery console the editor underlines the token; through the API you get the raw string. The BigQuery lexical structure documentation lists every reserved word that can produce this class of error.
The key insight: the flagged token is valid on its own — the parser failed because of what came before it. Train yourself to look one logical step back from the error position. A subquery missing a closing parenthesis, for example, causes the FROM keyword on the next line to appear unexpected.
Cause 1: Wrong clause order
BigQuery Standard SQL requires clauses in a strict sequence: SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY → LIMIT. Swapping HAVING and GROUP BY, or placing LIMIT before ORDER BY, produces an "Unexpected keyword" error pointing at the misplaced clause. See the BigQuery query syntax reference for the full legal clause order.
Before: HAVING before GROUP BY
-- Error: Syntax error: Unexpected keyword GROUP at [5:1]SELECT department, COUNT(*) AS headcountFROM employeesHAVING headcount > 5GROUP BY departmentORDER BY headcount DESCLIMIT 10;
After: correct clause order
SELECT department, COUNT(*) AS headcountFROM employeesGROUP BY departmentHAVING headcount > 5ORDER BY headcount DESCLIMIT 10;
Cause 2: Reserved word used as an identifier
BigQuery has a large list of reserved words — DATE, END, TABLE, VALUE, and many more. When used as a column name, table name, or alias without backtick quoting, the parser reads them as SQL keywords and fails. Wrap any reserved word you use as an identifier in backtick characters (the key to the left of 1 on most keyboards).
Before: unquoted reserved words
-- Error: Syntax error: Unexpected keyword END at [2:15]SELECT start, end, valueFROM project_phases;
Cause 3: Legacy SQL syntax in a Standard SQL session
BigQuery has two incompatible dialects: Standard SQL (the default since 2016) and Legacy SQL (the original dialect, now discouraged). Pasting an older query — from a Stack Overflow answer, a colleague's script, or a pre-2016 runbook — into a Standard SQL session triggers immediate syntax errors. See Google's Standard SQL migration guide for a full list of incompatibilities.
Legacy SQL constructs that fail in a Standard SQL session:
[project:dataset.table] bracket table references → use `project.dataset.table`
# single-line comments → use --
TABLE_DATE_RANGE() and TABLE_QUERY() → use date partition filters on suffix-sharded tables
FLATTEN() and WITHIN RECORD → replaced by UNNEST()
Cause 4: Missing comma
A missing comma in a SELECT list or between CTE definitions makes the next identifier look like an unexpected continuation of the previous expression. This is one of the most frequent BigQuery syntax errors for people writing complex queries.
CTEs also need a comma between definitions — but not after the last one.
Cause 5: Unmatched parentheses
BigQuery counts parentheses strictly. A missing closing parenthesis inside a subquery causes the clause that follows to appear unexpected — the error often reads "Expected end of input but got keyword FROM" or "Expected end of input but got keyword WHERE." Count every ( in your query and confirm it has a matching ).
Cause 6: Unsupported functions from other dialects
Several functions common in PostgreSQL, MySQL, and SQL Server don't exist in BigQuery Standard SQL. The parser reports them as unexpected keywords because it doesn't recognise the function name. Here are the common substitutions:
LEFT(s, n) → SUBSTR(s, 1, n)
RIGHT(s, n) → SUBSTR(s, LENGTH(s) - n + 1, n)
NVL(x, y) → IFNULL(x, y)
CHARINDEX(needle, haystack) → STRPOS(haystack, needle) (note: argument order is reversed)
LEN(s) → LENGTH(s)
Cause 7: Wrong quote type for identifiers
BigQuery Standard SQL uses backticks (`) for identifiers that contain spaces, special characters, or that conflict with reserved words. Single quotes and double quotes are for string literals only. Using double quotes around a column or table name — a habit carried over from PostgreSQL — produces a string constant in an identifier position, causing a syntax error.
BigQuery syntax error checklist
Read the [line:column] offset — then look at the token just before it for the real mistake.
Check clause order: SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY → LIMIT.
Wrap any reserved word used as an identifier in backticks: `end`, `date`, `table`.
Count parentheses — every ( needs a matching ).
In SELECT lists and CTE chains, every item except the last needs a trailing comma.
Confirm you're writing Standard SQL: no # comments, no [project:dataset.table] bracket references.
Use backticks for identifiers, not double quotes. Double quotes are string literals in BigQuery.
Wrapping up
BigQuery syntax errors always flag a position one step past the real problem. Master the seven patterns in this guide — clause order, reserved words, Legacy SQL artifacts, missing commas, unmatched parentheses, unsupported functions, and wrong quote types — and you'll resolve any BigQuery syntax error in seconds. If you're migrating SQL across dialects, also watch out for common SQL keyword typos that produce the same class of error, and consider whether consistent keyword casing would make future errors easier to spot.
sqlfmt formats and validates SQL across BigQuery, PostgreSQL, MySQL, SQLite, and SQL Server — paste your query, pick the BigQuery dialect, and it flags keyword typos and formats the code before you run it. The Pro plan adds live-as-you-type validation so issues surface before you reach the run button.
-- Error: Syntax error: Unexpected [ at [2:6]SELECT *FROM [myproject:mydataset.users]WHERE active = TRUE# return only recent signups AND created_at > '2026-01-01';
After: Standard SQL syntax
SELECT *FROM `myproject.mydataset.users`WHERE active = TRUE -- return only recent signups AND created_at > '2026-01-01';
Before: missing comma in SELECT list
-- Error: Syntax error: Unexpected identifier "email" at [5:3]SELECT id, name emailFROM users;
After: comma after each column except the last
SELECT id, name, emailFROM users;
Before: missing comma between CTEs
-- Error: Syntax error: Unexpected identifier "recent_orders" at [6:3]WITH active_users AS ( SELECT * FROM users WHERE active = TRUE ) recent_orders AS ( SELECT * FROM orders WHERE created_at > '2026-01-01' )SELECT *FROM active_usersJOIN recent_orders USING (user_id);
After: comma after every CTE except the last
WITH active_users AS ( SELECT * FROM users WHERE active = TRUE ), recent_orders AS ( SELECT * FROM orders WHERE created_at > '2026-01-01' )SELECT *FROM active_usersJOIN recent_orders USING (user_id);
Before: missing closing parenthesis
-- Error: Syntax error: Expected end of input but got keyword WHERESELECT *FROM ( SELECT id, name FROM users WHERE active = TRUE-- missing ) hereWHERE id > 100;
After: parentheses balanced, subquery aliased
SELECT *FROM ( SELECT id, name FROM users WHERE active = TRUE) AS active_usersWHERE id > 100;
Before: functions not supported in BigQuery
-- Error: Syntax error: Unexpected keyword LEFT at [3:3]SELECT LEFT(email, 10) AS email_prefix, RIGHT(phone, 4) AS phone_suffix, NVL(nickname, full_name) AS display_nameFROM users;
After: BigQuery equivalents
SELECT SUBSTR(email, 1, 10) AS email_prefix, SUBSTR(phone, LENGTH(phone) - 3, 4) AS phone_suffix, IFNULL(nickname, full_name) AS display_nameFROM users;
After: no quotes for ordinary identifiers; backticks for reserved words
-- Ordinary identifiers need no quotes:SELECT id, name, emailFROM usersWHERE active = TRUE;-- Identifiers with spaces or reserved words use backticks:SELECT `user id`, `end`, `first name`FROM `my-project.my_dataset.users`WHERE active = TRUE;