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.
PostgreSQL's "syntax error at or near" means the parser hit a token it can't place — usually just after the real mistake. The six causes, with fixes.
Quick answer
PostgreSQL raises "syntax error at or near" when its parser reads a token that cannot legally continue the statement — and the real mistake is usually one to three tokens before the one it names. To fix it, find the LINE number and caret in the error, then read to the left of the reported token for a missing or stray comma, an unbalanced quote or parenthesis, a misspelled keyword, a reserved word used as an identifier, or syntax borrowed from another SQL dialect.
FAQs
Why does PostgreSQL point at the wrong place in my query?
It points at the first token that couldn't continue a valid statement — the parser only knows something is wrong once it has read past the mistake. The real bug usually sits one to three tokens before the reported position, so read to the left of the caret.
What is the difference between "syntax error at or near" and "syntax error at end of input"?
"At or near" means the parser hit an unexpected token; "at end of input" means it ran out of tokens while still expecting more — typically an unclosed parenthesis or quote, or a query string truncated in application code. Both share SQLSTATE 42601.
How do I use a reserved word like "order" or "user" as a table name in PostgreSQL?
Wrap it in double quotes everywhere you reference it: SELECT * FROM "order". Quoted identifiers are case-sensitive, so the quoted name must match the stored name exactly. Renaming the table is usually the better long-term fix.
Why does LIMIT 5,10 fail in PostgreSQL when it works in MySQL?
The two-argument comma form of LIMIT is MySQL-specific. PostgreSQL uses LIMIT count OFFSET start, and the argument order flips: MySQL's LIMIT 5,10 becomes LIMIT 10 OFFSET 5 in PostgreSQL.
Does "syntax error at or near" mean a table or column is missing?
No. Syntax errors (SQLSTATE 42601) happen before PostgreSQL looks at your schema. A missing table raises "relation does not exist" (42P01) and a missing column raises "column does not exist" (42703) — those only appear after the statement parses cleanly.
Try it now
Paste your SQL into sqlfmt — format, validate, and catch typos free in your browser.
PostgreSQL raises syntax error at or near "..." when its parser reaches a token that can't fit the SQL grammar — and the real mistake is almost always one to three tokens before the one it names. Once you know that, the fix is mechanical: find the reported position, then read to the left.
This error (SQLSTATE 42601) covers everything from a stray comma to MySQL syntax that PostgreSQL doesn't understand. Below: how the parser decides where to point, the six causes behind nearly every occurrence, and a pre-flight checklist that catches them before you run the query.
What does "syntax error at or near" mean in PostgreSQL?
PostgreSQL parses a statement in two stages. The lexer first splits your SQL into tokens — keywords, identifiers, literals, operators — following the rules in the lexical structure documentation. The grammar then checks that the token sequence forms a valid statement. The moment the parser reads a token that can't legally continue the statement, parsing stops and you get syntax error at or near "<token>", plus a LINE number and a caret (^) marking the position.
The crucial detail: the named token is where parsing stopped, not where the bug lives. The parser was perfectly happy right up until that token, which means the mistake usually sits immediately before it.
The reported token is rarely the culprit
SELECT id, name,FROM users;-- ERROR: syntax error at or near "FROM"-- LINE 2: FROM users;-- ^-- The parser blames FROM, but the actual problem-- is the trailing comma at the end of line 1.
The six causes behind almost every 42601
1. A stray or missing comma
A trailing comma at the end of a SELECT list makes the parser blame the next keyword, as in the example above. The same applies to INSERT column lists and CREATE TABLE definitions, where two identifiers in a row with no comma between them stop the parser cold.
Missing comma in a column definition
-- Broken: error at or near "name"CREATE TABLE users (id int name text);-- FixedCREATE TABLE users (id int, name text);
One trap worth knowing: in a SELECT list, a missing comma often produces no error at all. PostgreSQL reads the second word as a column alias — SELECT id name returns one column called name. You get a silently misnamed (and missing) column instead of a syntax error, which is worse.
2. A misspelled keyword
Misspell the first keyword (SELCET, UPDTAE) and the parser names the typo itself. Misspell a keyword mid-statement and something sneakier happens: the typo gets parsed as an identifier, and the error blames the next word.
A typoed WHERE gets blamed on the wrong word
-- Broken: WEHRE parses as a table alias for users,-- so the error points at "active" insteadSELECT * FROM users WEHRE active = true;-- ERROR: syntax error at or near "active"-- FixedSELECT * FROM users WHERE active = true;
If the reported token looks completely innocent, spell-check the keyword before it. FORM, WEHRE, GROPU BY, and ORDR BY all fail this way.
3. Unbalanced quotes
A single quote inside a string literal ends the string early, and everything after it becomes tokens the parser can't place. Names with apostrophes are the classic trigger.
4. A reserved word used as an identifier
Tables named order, user, or group collide with PostgreSQL's reserved keywords, and the parser reports the name itself as the offending token.
Quoted identifiers are case-sensitive, so "Order" and "order" are different tables. If you control the schema, renaming the table beats quoting it in every query forever.
5. Syntax from another SQL dialect
MySQL habits are the usual source: backticks around identifiers and the two-argument LIMIT both fail in PostgreSQL, which uses double quotes and LIMIT … OFFSET instead.
6. The wrong placeholder style in application code
PostgreSQL uses numbered parameters ($1, $2, …). Send a JDBC- or MySQL-style ? placeholder in a raw query string and the parser reports it verbatim — a strong hint the query never went through your driver's parameter binding.
How is "syntax error at end of input" different?
Same error class, opposite failure: instead of hitting an unexpected token, the parser ran out of tokens while still expecting more. The cause is almost always an unclosed parenthesis, an unfinished string or dollar-quote, or a query string that was truncated while being built in application code.
How do you debug dynamic and ORM-generated SQL?
When the failing SQL is assembled at runtime, the template you wrote and the statement PostgreSQL received are different things. Debug the latter:
Log the exact statement string the driver sends — not the template with placeholders still in it.
Re-run that exact string in psql, which gives you the LINE number and caret position interactively.
Check placeholder style and count: $1 through $n, with one bound value per parameter.
For long scripts and migrations, bisect: run halves until the failing statement is isolated.
Pre-flight checklist: 7 checks before you re-run the query
Find the LINE number and caret, then inspect one to three tokens to the left of the reported token — that's where the bug usually is.
Scan every comma-separated list (SELECT, INSERT columns, CREATE TABLE): no trailing comma after the last item, exactly one comma between items.
Balance every single quote, double quote, parenthesis, and dollar-quote tag.
Spell-check keywords — a typo like WEHRE parses as an identifier and shifts the blame to the next word.
Double-quote (or better, rename) identifiers that collide with reserved words like order, user, or group.
Translate dialect-foreign syntax: backticks become double quotes, LIMIT x,y becomes LIMIT y OFFSET x.
For dynamic SQL, print the final statement, confirm $1-style placeholders, and reproduce it in psql before blaming your application code.
Wrapping up
Read the position, look left, and the fix is usually one token away: a comma, a quote, a keyword, or a borrowed piece of another dialect. The message is terse, but it's honest about where parsing stopped.
To catch all of this before PostgreSQL does, paste your query into sqlfmt — it formats your SQL and flags syntax errors and misspelled keywords (WEHRE → WHERE) across five dialects, with live as-you-type checking on Pro.
-- Broken: the quote in O'Brien ends the string at 'O'INSERT INTO users (name) VALUES ('O'Brien');-- ERROR: syntax error at or near "Brien"-- Fixed: double the quote, or use dollar-quotingINSERT INTO users (name) VALUES ('O''Brien');INSERT INTO users (name) VALUES ($$O'Brien$$);
Reserved keyword as a table name
-- Broken: "order" is a reserved keywordSELECT * FROM order;-- ERROR: syntax error at or near "order"-- Fixed: double-quote the identifierSELECT * FROM "order";
MySQL syntax translated to PostgreSQL
-- Broken: backticks and MySQL-style LIMITSELECT `name` FROM users LIMIT 5,10;-- ERROR: syntax error at or near "`"-- Fixed: note the argument order flips —-- LIMIT offset,count becomes LIMIT count OFFSET offsetSELECT "name" FROM users LIMIT 10 OFFSET 5;
JDBC-style placeholders sent raw to PostgreSQL
-- BrokenSELECT * FROM users WHERE id = ?;-- ERROR: syntax error at or near "?"-- Fixed: PostgreSQL's native parameter syntaxSELECT * FROM users WHERE id = $1;
A concatenation bug that truncates the query
-- Application code dropped the closing parenthesis:-- query = "SELECT * FROM users WHERE id IN (" + idsSELECT * FROM users WHERE id IN (1, 2, 3-- ERROR: syntax error at end of input