Chapter 04 · Schemas, Data Types, Keys, Constraints, and SQL Modes

SQL_MODE, Strictness, Implicit Conversion, Oracle Mode Awareness, and Data Quality

Make SQL_MODE a visible data-quality contract by testing strictness, coercion, date policy, grouping semantics and Oracle compatibility in isolated MariaDB sessions.

Intermediate105–135 minutesSQL_MODE + data-quality labMariaDB 12.3.2Session-scoped mode experimentsLast reviewed: August 2026

Learning outcomes

ServiceHub passes schema review, yet the same INSERT behaves differently in two environments: development rejects an oversized/invalid value, while an old import script coerces it and continues with warnings. A reporting query is accepted on one session and rejected on another. An Oracle-migration proof of concept turns on sql_mode=ORACLE and accidentally changes parsing expectations for unrelated code. The schema did not change; the SQL mode contract did.

SQL_MODE is a comma-separated set of behavioral flags that can change parsing, validation, conversions and compatibility semantics. It has GLOBAL and SESSION scope: changing GLOBAL affects new sessions, not magically every already-connected client. MariaDB’s current default includes strict behavior such as STRICT_TRANS_TABLES, but applications should inspect and test the exact mode instead of assuming defaults from another MariaDB/MySQL version.

01

Inspect GLOBAL and SESSION SQL_MODE and explain inheritance to new connections.

02

Demonstrate strict versus permissive conversion behavior with warnings/errors.

03

Test implicit numeric/string conversion and zero/invalid-date modes explicitly.

04

Explain ONLY_FULL_GROUP_BY and ORACLE mode as opt-in semantic changes, not universal defaults.

05

Build a data-quality acceptance test that records mode, charset/collation and representative failing writes.

Lab safety

Every mode change in the mandatory lab is SESSION-scoped and is restored before the session ends. Do not change GLOBAL SQL_MODE on a shared server merely to reproduce an example. Chapter 03 already established the runtime-versus-persistence distinction.

1. SQL_MODE is part of the application runtime contract

MariaDB exposes SQL mode through the sql_mode system variable. A session inherits the GLOBAL value when it connects, then can change its own SESSION value. Stored programs can also retain mode-sensitive creation semantics. Therefore a migration test must record the mode under which DDL and queries run; “the server is MariaDB 12.3” is not enough.

sql · capture the mode before every behavior test
SELECT @@GLOBAL.sql_mode AS global_mode,       @@SESSION.sql_mode AS session_mode,       @@version AS server_version,       @@character_set_connection,       @@collation_connection;SELECT REPLACE(@@SESSION.sql_mode, ',', '\n') AS enabled_modes;

Current MariaDB documentation lists a default that includes STRICT_TRANS_TABLES, ERROR_FOR_DIVISION_BY_ZERO, NO_AUTO_CREATE_USER, and NO_ENGINE_SUBSTITUTION for modern releases. But deployments can override that globally or per session. Always test the effective value rather than memorizing the vendor default.

2. Strictness decides whether bad input becomes an error or coerced data

STRICT_TRANS_TABLES makes invalid or missing values in transactional tables such as InnoDB produce errors in many cases where a permissive mode might coerce a value and emit a warning. “The INSERT returned success” is therefore weak evidence unless the application also handles warnings and the expected SQL mode is pinned.

sql · strict versus permissive in one isolated session
CREATE TABLE servicehub_sandbox.mode_probe (  id INT PRIMARY KEY,  small_code VARCHAR(5) NOT NULL,  quantity TINYINT UNSIGNED NOT NULL) ENGINE=InnoDB;SET @saved_mode := @@SESSION.sql_mode;SET SESSION sql_mode = 'STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION';-- Expected error: value too long.INSERT INTO servicehub_sandbox.mode_probe VALUES (1,'TOO-LONG',10);SET SESSION sql_mode = 'NO_ENGINE_SUBSTITUTION';-- Depending on exact statement/type, permissive mode can coerce/truncate and warn.INSERT INTO servicehub_sandbox.mode_probe VALUES (2,'TOO-LONG',10);SHOW WARNINGS;SELECT * FROM servicehub_sandbox.mode_probe WHERE id=2;SET SESSION sql_mode = @saved_mode;

The lab is deliberately evidence-based: inspect both the result row and SHOW WARNINGS. Do not teach a single warning text as immutable because wording can change. The mechanism is what matters: strictness changes whether unsafe conversion is rejected or tolerated/coerced.

3. Implicit conversion can make syntactically valid SQL semantically dangerous

MariaDB can convert strings to numbers and numbers to strings in expressions. That convenience can surprise indexing, comparison and data-quality logic. For example, comparing a numeric column to a string literal that looks numeric may coerce the string; comparing mixed formats can produce warnings or unexpected matches. Production code should bind parameters with the intended types and avoid using implicit conversion as validation.

sql · make conversion visible
SELECT '10' + 5 AS numeric_conversion;SELECT CAST('10' AS UNSIGNED) + 5 AS explicit_conversion;-- Deliberately suspicious input: inspect value and warnings.SELECT CAST('10x' AS UNSIGNED) AS coerced_value;SHOW WARNINGS;

Explicit CAST documents intent but does not make invalid source data magically correct; it gives you a controlled place to detect failure/warnings. In ingestion pipelines, stage raw text first when necessary, validate it, then convert into typed columns under a known strict mode.

4. Zero and invalid dates are mode-sensitive legacy boundaries

MariaDB has modes such as NO_ZERO_DATE, NO_ZERO_IN_DATE and ALLOW_INVALID_DATES that alter acceptance of unusual date values. Current documentation notes that NO_ZERO_DATE and NO_ZERO_IN_DATE produce errors in strict mode and warnings/coercion in less strict paths. The correct modern application design is not “which zero date should we allow?” but “does the domain have an unknown date, and if so should that be NULL or a separate status?”

sql · test date policy without changing global state
CREATE TABLE servicehub_sandbox.date_mode_probe (  id INT PRIMARY KEY,  scheduled_on DATE NULL) ENGINE=InnoDB;SET @saved_mode := @@SESSION.sql_mode;SET SESSION sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE';-- Expected to be rejected under this strict policy.INSERT INTO servicehub_sandbox.date_mode_probe VALUES (1,'0000-00-00');INSERT INTO servicehub_sandbox.date_mode_probe VALUES (2,'2026-00-15');SET SESSION sql_mode = @saved_mode;

If a legacy migration contains zero dates, do not silently relax production mode and hope. Inventory the values, decide their business meaning, transform them in a controlled staging process, and document any intentional exception.

5. ONLY_FULL_GROUP_BY changes which reporting queries are accepted

ONLY_FULL_GROUP_BY rejects SELECT-list columns that are neither grouped nor aggregated according to MariaDB’s rules. It is not part of MariaDB’s long-standing default mode set in the way many MySQL users expect, so portable applications should test reports explicitly instead of assuming one vendor’s defaults.

sql · ambiguous grouping made explicit
CREATE TABLE servicehub_sandbox.group_probe (  team_id INT NOT NULL,  technician_name VARCHAR(80) NOT NULL,  completed_jobs INT NOT NULL) ENGINE=InnoDB;INSERT INTO servicehub_sandbox.group_probe VALUES(1,'Ava',5),(1,'Noah',7),(2,'Mina',4);SET @saved_mode := @@SESSION.sql_mode;SET SESSION sql_mode = CONCAT_WS(',', NULLIF(@@SESSION.sql_mode,''), 'ONLY_FULL_GROUP_BY');-- Intentionally invalid/ambiguous under ONLY_FULL_GROUP_BY:SELECT team_id, technician_name, SUM(completed_jobs)FROM servicehub_sandbox.group_probeGROUP BY team_id;-- Repair: choose an explicit grouped or aggregated result.SELECT team_id, SUM(completed_jobs) AS total_jobsFROM servicehub_sandbox.group_probeGROUP BY team_id;SET SESSION sql_mode = @saved_mode;

The repair is not “turn the mode off.” The repair is to decide what result the query actually means. If you need a technician associated with a maximum, use a deterministic window/subquery pattern in Chapter 05 rather than selecting an arbitrary ungrouped value.

6. ORACLE mode is an explicit compatibility mode—not full Oracle Database

MariaDB supports SQL_MODE=ORACLE. Current documentation explains that from MariaDB 10.3 it changes more than a few parser flags: it enables a large subset of Oracle-style PL/SQL syntax for stored programs and bundles other compatibility behaviors. That is powerful for migrations, but “Oracle mode” does not mean complete Oracle Database feature, optimizer, package, datatype, transaction or operational equivalence.

sql · inspect ORACLE mode in a disposable session
SET @saved_mode := @@SESSION.sql_mode;SET SESSION sql_mode = 'ORACLE';SELECT @@SESSION.sql_mode;-- Observe that mode changes parser/compatibility behavior.-- Do not deploy unrelated application sessions under ORACLE mode by accident.SET SESSION sql_mode = @saved_mode;
Migration rule

Treat ORACLE mode like any other compatibility layer: scope it intentionally, inventory the syntax/features it changes, run the real stored-program/SQL corpus, and document unsupported Oracle behaviors. It is a migration aid, not a promise of full product equivalence.

7. Data-quality baseline: mode + charset + collation + time zone

SQL mode does not act alone. Chapter 03 established that time zone, character set and collation also change how input is interpreted and compared. A reproducible application contract should capture these settings together and test representative edge cases whenever a connector, ORM, server series or migration target changes.

sql · ServiceHub session contract snapshot
SELECT @@version AS server_version,       @@SESSION.sql_mode AS sql_mode,       @@SESSION.time_zone AS time_zone,       @@character_set_client AS character_set_client,       @@character_set_connection AS character_set_connection,       @@collation_connection AS collation_connection,       @@character_set_results AS character_set_results;
Acceptance test Expected evidence
Oversized VARCHAR under strict mode Statement fails; no silently truncated row.
Invalid/zero date policy Behavior matches documented domain decision.
Mixed-type conversion Warnings/errors are detected, not ignored.
Grouping query Deterministic query works with intended ONLY_FULL_GROUP_BY policy.
Oracle compatibility test Only scoped migration sessions use ORACLE mode.
New connection Inherits the documented GLOBAL mode unless application overrides SESSION.

8. Deliberately wrong approach: make production permissive so imports “work”

Turning off strict modes globally to make a legacy import complete is attractive because the migration stops throwing errors. It also converts unknown data-quality problems into rows you now have to discover later. The safer workflow is to keep the production contract strict, load questionable legacy data into a staging table or disposable instance, capture warnings/rejects, transform invalid values explicitly, then load validated typed rows.

Production judgment

If a legacy application genuinely depends on a permissive mode, document that dependency and isolate it with session/account/application configuration while you plan remediation. A hidden global relaxation affects every new client and can change behavior far beyond the one import that motivated it.

9. Chapter checkpoint lab and cleanup

Run the following acceptance sequence entirely in servicehub_sandbox. Save your original SESSION mode and restore it after each experiment.

  1. Record GLOBAL/SESSION SQL_MODE plus charset/collation/time-zone settings.
  2. Run the strict-versus-permissive oversized VARCHAR test and inspect SHOW WARNINGS.
  3. Run explicit string-to-number conversion tests and record warnings.
  4. Test zero/invalid dates under a strict date policy.
  5. Enable ONLY_FULL_GROUP_BY only for the current session, run the intentionally ambiguous query, then repair it.
  6. Enter ORACLE mode only long enough to observe the session setting, then restore the saved mode.
  7. Open a fresh connection and verify which GLOBAL mode it inherits.
  8. Drop only the Chapter 04 sandbox objects after checking DATABASE() and server identity.

Check your understanding

  1. What is the difference between GLOBAL and SESSION SQL_MODE?
  2. Why must an application inspect warnings in permissive conversion paths?
  3. What is the safer representation of an unknown business date than a zero date?
  4. Why is ONLY_FULL_GROUP_BY useful even when it is not your server default?
  5. Does SQL_MODE=ORACLE make MariaDB a complete Oracle Database replacement?
Review the answers

GLOBAL SQL_MODE is inherited by new sessions; an existing connection keeps its SESSION value unless it changes it. Permissive conversions can succeed while changing/truncating data, so warnings are part of the evidence. Unknown dates should normally be modeled explicitly—often as NULL plus any needed status—rather than a magic zero date. ONLY_FULL_GROUP_BY exposes ambiguous reporting queries and improves portability discipline. ORACLE mode enables important syntax/compatibility behavior but is not full Oracle Database equivalence.

10. Chapter 04 summary and bridge

Chapter 04 treated data definition as a correctness contract. You learned that MariaDB database/schema naming is one namespace; case policy and temporary shadowing can change object resolution; types encode precision, comparison and time/JSON/UUID/vector semantics; constraints protect invariants; identifier allocators do not promise gaplessness; and SQL_MODE can change whether the same statement is rejected, coerced or parsed differently.

Chapter 05 now assumes this contract is in place and turns to query semantics: NULL, joins, subqueries, common table expressions, recursive traversal, window functions and reporting. The recurring discipline stays the same—write a query, observe its actual result, and explain why MariaDB produced that result rather than relying on syntax familiarity.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only assets compatible with the Ethereum/ERC-20 network. Do not send TRC-20/TRON assets.