SQL Practice Logo

SQLPractice Online

ANSI SQL Standards & Database Differences: Mistakes

Module: Foundational Concepts

SELECT first_name || ' ' || last_name FROM users; -- In MySQL

SELECT CONCAT(first_name, ' ', last_name) FROM users;

MySQL treats || as logical OR by default (returning 0 or 1). It requires CONCAT() for string concatenation. PostgreSQL and Oracle support || as the ANSI concatenation operator. SQL Server uses + instead. CONCAT() is the only function that works on all four.

Always use CONCAT() for string concatenation in any code that might run on MySQL.

High

In MySQL, || is logical OR. This returns 0, not the concatenated string.

SELECT * FROM orders ORDER BY created_at LIMIT 10; -- In SQL Server

SELECT * FROM orders ORDER BY created_at OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY;

LIMIT is MySQL and PostgreSQL syntax. SQL Server uses TOP (no offset) or OFFSET/FETCH NEXT. Note that SQL Server uses FETCH NEXT while ANSI SQL uses FETCH FIRST — a subtle difference.

Use FETCH FIRST/NEXT for portability on modern databases. Use an ORM for true cross-database pagination.

High

Incorrect syntax near 'LIMIT'. SQL Server does not support LIMIT.

SELECT * FROM users WHERE email = NULL;

SELECT * FROM users WHERE email IS NULL;

NULL comparisons using = always return NULL (unknown), which is treated as false in WHERE clauses. This is SQL-92 standard behavior on every database. Always use IS NULL or IS NOT NULL. The same applies to != NULL — use IS NOT NULL instead.

If you ever get a WHERE clause returning unexpected empty results, check for = NULL comparisons.

Critical

Returns 0 rows on every database. NULL = NULL evaluates to NULL, not TRUE.

user_id INT AUTO_INCREMENT -- Schema targeting PostgreSQL

user_id BIGINT GENERATED ALWAYS AS IDENTITY -- or use BIGSERIAL

AUTO_INCREMENT is MySQL-only. PostgreSQL uses SERIAL/BIGSERIAL (legacy) or GENERATED AS IDENTITY (SQL:2003 standard, preferred since PostgreSQL 10). SQL Server uses IDENTITY(1,1). Oracle 12c+ uses GENERATED AS IDENTITY.

Use GENERATED AS IDENTITY for new schemas on PostgreSQL 10+, SQL Server 2012+, and Oracle 12c+. Fall back to SERIAL for older PostgreSQL.

High

syntax error at or near 'AUTO_INCREMENT'

REPLACE INTO user_profiles (email, name) VALUES (?, ?);

INSERT INTO user_profiles (email, name) VALUES (?, ?) ON DUPLICATE KEY UPDATE name = VALUES(name);

REPLACE INTO is not an upsert. It DELETEs the conflicting row and INSERTs a new one. This fires DELETE triggers, cascades to foreign key children (potentially deleting them), advances AUTO_INCREMENT sequences, and can cause race conditions. Use ON DUPLICATE KEY UPDATE in MySQL or ON CONFLICT in PostgreSQL.

Treat REPLACE INTO as deprecated. It is never the right tool for upsert semantics.

Critical

REPLACE INTO silently deletes and reinserts rows, cascading DELETE to child tables.

SELECT * FROM feature_flags WHERE is_enabled = TRUE; -- In SQL Server

SELECT * FROM feature_flags WHERE is_enabled = 1;

SQL Server uses the BIT data type (values 1 and 0) with no BOOLEAN type and no TRUE/FALSE literals. PostgreSQL has a real BOOLEAN type with TRUE/FALSE. MySQL has BOOLEAN as an alias for TINYINT(1) and accepts TRUE/FALSE as 1/0. Always use 1/0 for boolean columns in cross-database schemas.

Use 1/0 for boolean values in any SQL that might run on SQL Server.

Medium

Must declare the scalar variable '@TRUE' or similar SQL Server parsing error.