Fix Postgres: Operator Does Not Exist (42883)
Chat2DB TeamPostgreSQL is strict about types. Where MySQL quietly converts '42' to 42 to compare it with a number, Postgres refuses and raises:
ERROR: operator does not exist: integer = text
LINE 1: SELECT * FROM customers WHERE id = '1'::text;
^
HINT: No operator matches the given name and argument types. You might need to add explicit type casts.The SQLSTATE is 42883 (undefined_function), the same code used for function ... does not exist, because in Postgres an operator is just a function with special syntax. The message always tells you the left type, the operator and the right type. The fix is to make those types match, ideally by fixing the data type at its source, not by sprinkling casts everywhere.
This guide covers the variants you will actually meet, with tested fixes for each.
How Postgres picks an operator
When you write a = b, Postgres looks for an = operator whose arguments match the types of a and b. Two rules explain almost every case of this error:
- Quoted literals start as type
unknown, and Postgres can turnunknowninto whatever type the other side needs. SoWHERE id = '1'works, because'1'becomes an integer. - Values with a known type are not converted implicitly between unrelated categories. A
textcolumn, avarcharbind parameter or an explicit::textwill not be turned intointeger,uuid,dateorjsonbfor you.
SELECT * FROM customers WHERE id = '1'; -- works: unknown -> integer
SELECT * FROM customers WHERE id = '1'::text; -- fails: integer = textThat is why the same query works in psql and fails from application code: the driver sends a typed parameter.
integer = text (or text = integer)
Typical causes:
- A join between an
intkey and atext/varcharcolumn that stores the same IDs. - A bind parameter sent as a string (
setStringin JDBC, a Pythonstr). - A value pulled out of JSON with
->>, which always returnstext.
Joining mismatched columns:
-- orders.customer_ref is varchar, customers.id is int
SELECT o.id, c.email
FROM orders o
JOIN customers c ON c.id = o.customer_ref;
-- ERROR: operator does not exist: integer = character varyingCast one side. Which side you cast matters for indexes: cast the side without the index you want to use, so Postgres can still use the index on the other column:
-- Uses the index on customers.id
SELECT o.id, c.email
FROM orders o
JOIN customers c ON c.id = o.customer_ref::int;If customer_ref contains values that are not valid integers, that cast fails with invalid input syntax for type integer. The permanent fix is to give the column the right type:
ALTER TABLE orders
ALTER COLUMN customer_ref TYPE int USING customer_ref::int;That rewrites the table and takes an ACCESS EXCLUSIVE lock, so plan it for a quiet period on large tables.
uuid = character varying (Java, .NET and ORMs)
This one is very common with JDBC and Hibernate:
ERROR: operator does not exist: uuid = character varying
HINT: No operator matches the given name and argument types. You might need to add explicit type casts.The driver binds the parameter as varchar (setString), and there is no uuid = varchar operator. You can reproduce it in SQL with a prepared statement:
CREATE TABLE sessions (id uuid PRIMARY KEY DEFAULT gen_random_uuid(), user_id int);
PREPARE q(varchar) AS SELECT * FROM sessions WHERE id = $1;
-- ERROR: operator does not exist: uuid = character varyingFixes, from best to worst:
- Bind the right type. In JDBC use
setObject(1, uuid)with ajava.util.UUID. In Hibernate, map the field asUUID. In Npgsql pass aGuid. - Let the server infer the type. Add
stringtype=unspecifiedto the JDBC URL. String parameters are then sent asunknown, which Postgres can convert, just like a literal:
jdbc:postgresql://db:5432/shop?stringtype=unspecified- Cast in the SQL:
WHERE id = CAST(? AS uuid)orWHERE id = ?::uuid.
Option 2 is a quick global fix, but be aware that it changes how all string parameters are typed.
jsonb = integer and text = integer from JSON
The JSON operators return different types: -> returns jsonb and ->> returns text. Neither compares to a number directly:
SELECT '{"a":1}'::jsonb -> 'a' = 1;
-- ERROR: operator does not exist: jsonb = integer
SELECT '{"a":1}'::jsonb ->> 'a' = 1;
-- ERROR: operator does not exist: text = integerPick one of these depending on what you need:
-- Compare as a number
SELECT * FROM events WHERE (payload ->> 'amount')::numeric > 100;
-- Compare as JSON (no cast of the stored value; can use a GIN index with @>)
SELECT * FROM events WHERE payload @> '{"status": "paid"}';
-- Compare a jsonb value with a jsonb literal
SELECT * FROM events WHERE payload -> 'a' = '1'::jsonb;@> containment is usually the best choice for filters, because a GIN index on the jsonb column can serve it.
Plain json (not jsonb) has no equality operator at all:
SELECT '{"a":1}'::json = '{"a":1}'::json;
-- ERROR: operator does not exist: json = jsonCast to jsonb to compare, or better, store the column as jsonb.
integer ~~ unknown: LIKE on a number
LIKE is the ~~ operator, and it only exists for text types:
SELECT * FROM customers WHERE id LIKE '1%';
-- ERROR: operator does not exist: integer ~~ unknownCast the column to text:
SELECT * FROM customers WHERE id::text LIKE '1%';If you run this often on a large table, a plain index on id will not help. Add an expression index that matches the cast:
CREATE INDEX customers_id_text_idx ON customers ((id::text) text_pattern_ops);Very often, though, a prefix search on a number is really a range query in disguise. id BETWEEN 1000 AND 1999 is faster and uses the normal index.
Array operators: character varying[] @> text[]
Array literals default to text[], which does not match a varchar[] column:
CREATE TABLE ev (id int, tags varchar[]);
SELECT * FROM ev WHERE tags @> ARRAY['x'];
-- ERROR: operator does not exist: character varying[] @> text[]Cast the literal to the column's type:
SELECT * FROM ev WHERE tags @> ARRAY['x']::varchar[];Or switch the column to text[]. In Postgres, text and varchar perform the same, and text[] avoids this friction.
The function variant: function round(double precision, integer) does not exist
The same 42883 code appears when a function exists but not for your argument types:
SELECT round(1.2345::double precision, 2);
-- ERROR: function round(double precision, integer) does not existround(value, digits) is defined only for numeric. Cast first:
SELECT round(1.2345::numeric, 2); -- 1.23
SELECT round(avg(price)::numeric, 2) FROM products;Because avg() of an integer column returns numeric but avg() of a double precision column returns double precision, this error often appears only after someone changes a column type.
Find out what types you actually have
When the message names a type you did not expect, check it with pg_typeof:
SELECT pg_typeof(payload ->> 'amount') FROM events LIMIT 1; -- text
SELECT pg_typeof(avg(price)) FROM products; -- double precisionAnd list which operators exist for a given name and types:
SELECT oprname,
oprleft::regtype AS left_type,
oprright::regtype AS right_type,
oprresult::regtype AS result_type
FROM pg_operator
WHERE oprname = '@>'
ORDER BY 2, 3;Should you create a custom operator or implicit cast?
You can, with CREATE OPERATOR or CREATE CAST ... AS IMPLICIT, but it is rarely a good idea. Extra implicit casts make operator resolution ambiguous elsewhere (you start seeing operator is not unique errors), and they hide real data-type bugs. Fix the column type or the parameter type instead.
Summary
| Error | Fix |
|---|---|
integer = text / integer = character varying | Cast the non-indexed side, or change the column type |
uuid = character varying | Bind a UUID type, or use stringtype=unspecified, or ?::uuid |
jsonb = integer / text = integer | (col ->> 'k')::numeric, or @> with a jsonb literal |
json = json | Cast to jsonb or store as jsonb |
integer ~~ unknown | col::text LIKE ..., or use a range |
character varying[] @> text[] | ARRAY[...]::varchar[] or use text[] |
function round(double precision, integer) | Cast to numeric |
Type errors are easier to avoid when you can see column types while you write the query. In Chat2DB (opens in a new tab) the schema panel and autocomplete show each column's type, and the AI assistant can take a failing query plus this error message and rewrite it with the correct casts for your tables.
