相同的SQL,不同的结果:一个微妙的Oracle与PostgreSQL迁移错误
Same SQL, Different Results: A Subtle Oracle vs. PostgreSQL Migration Bug

原始链接: https://databaserookies.wordpress.com/2026/01/30/same-sql-different-results-a-subtle-oracle-vs-postgresql-migration-bug/

## 数据库迁移陷阱:一个微妙的错误 即使使用语法正确的SQL,数据库迁移也可能导致Oracle和PostgreSQL等系统之间出现意外的计算差异。 这不是语法错误,而是数据库*解释*逻辑方式的差异。 这个问题通常源于运算符优先级和隐式类型转换。 Oracle会积极地通过隐式转换数据类型和重新排序操作(例如在算术运算前进行连接)来“帮助”你,而PostgreSQL则严格遵守定义的运算符优先级并要求显式转换。 一个看似简单的计算——提取分钟数、添加值和连接——可能会产生截然不同的结果。 Oracle可能会在执行算术运算之前静默地转换和连接字符串,而PostgreSQL会先执行算术运算。 这些错误的危险在于它们是隐性的,能够通过初始测试,并且只在生产环境中出现,导致财务错误计算或数据不一致。 解决方案? **务必明确。** 始终使用显式转换和括号来清楚地定义操作顺序,确保你的SQL表达了你*真正*的意图,而与数据库系统无关。 迁移不仅仅是翻译; 而是仔细的解释。

黑客新闻 新 | 过去 | 评论 | 提问 | 展示 | 招聘 | 提交 登录 相同的SQL,不同的结果:一个微妙的Oracle与PostgreSQL迁移错误 (databaserookies.wordpress.com) 11点 由 tanelpoder 2小时前 | 隐藏 | 过去 | 收藏 | 1评论 harperlee 27分钟前 [–] 显然是AI写的。回复 指南 | 常见问题 | 列表 | API | 安全 | 法律 | 申请YC | 联系 搜索:
相关文章

原文

Read time: ~6 minutes

A real-world deep dive into operator precedence, implicit casting, and why database engines “don’t think the same way”.

The Database Migration Mystery That Started It All

You migrate a perfectly stable Oracle application to PostgreSQL.

  • The SQL runs
  • The tests pass
  • The syntax looks correct
  • Nothing crashes

And yet… the numbers or query calculations are wrong.

Not obviously wrong. Not broken. Just different.
Those are the worst bugs the ones that quietly ship to production. This is a story about one such bug, hiding behind familiar operators, clean-looking conversions, and false confidence.

The Original Business Logic (Oracle)

Here’s a simplified piece of real production logic used to compute a varhour value from timestamp data:

At first glance, this feels routine:

  • Extract minutes
  • Perform arithmetic
  • Concatenate values

Nothing here screams “migration risk”.

The Migration Illusion: “Looks Correct, Right?”

During migration, teams don’t blindly copy Oracle SQL. They do the right thing make types explicit and clean up the logic.

Here’s the PostgreSQL converted version, already “fixed” with necessary casts:

No syntax errors. Explicit casting. Clean and readable. At this point, most migrations move on.

Side-by-Side: Oracle vs PostgreSQL (At First Glance)

Let’s compare the two versions:

AspectOraclePostgreSQL
Concatenation operator||||
Arithmetic operators+, -+, -
Minute extractionTO_CHAR(varmonth,'MI')TO_CHAR(varmonth,'MI')::integer
Explicit casting❌ Implicit✅ Explicit
Query runs successfully
Logic looks identical

Everything appears aligned.
Same operators. Same order. Same intent. So naturally, we expect the same result.

Let’s test with a real value:

Output:

Databasevarhour
Oracle1500
PostgreSQL14100

Same logic. Same data. Different result. Now the real question appears:

How can two “explicit” queries still behave differently?

What Your Brain Thinks Is Happening

When most of us read this expression:

Our brain assumes:

  1. Arithmetic happens first (+, -)
  2. Concatenation happens last (||)

That assumption is correct in PostgreSQL. It is not correct in Oracle.

Oracle’s Behavior: “Let Me Help You”

Oracle aggressively applies implicit type conversion. Internally, Oracle rewrites the expression to something closer to:

Concatenation happens before arithmetic.

Step by step:

  1. varhr - 114
  2. TO_CHAR(14)'14'
  3. TO_CHAR(varmonth,'MI')'59'
  4. '14' || '59''1459'
  5. TO_NUMBER('1459')1459
  6. 1459 + 1 + 401500

Oracle silently guessed your intent.

PostgreSQL’s Behavior: “Be Explicit”

PostgreSQL does no guessing. It follows strict operator precedence:

  1. TO_CHAR(loadmonth,'MI')::integer59
  2. 59 + 1 + 40100
  3. (end_hr - 1)::text'14'
  4. '14' || '100'14100

Different grouping. Different result. No error.

Proof: Oracle’s Execution Plan

Oracle doesn’t hide this, it just doesn’t advertise it.

The projection shows:

That TO_NUMBER() wrapping the concatenation is the smoking gun.

Why This Bug Is So Hard to Catch

  1. It never throws an error
  2. The SQL looks correct
  3. Early test data rarely hits edge cases
  4. Automated migration tools miss it
  5. The behavior difference is undocumented in most migration guides

This is not a syntax problem. It’s a behavioral difference.

The Real Issue Isn’t concat operator(||) or implicit casting

This comes down to philosophy:

AspectOraclePostgreSQL
Type handlingImplicit type coercionExplicit casting
Operator behaviorFlexible, context-drivenStrict and deterministic
Operator precedenceMay group expressions implicitlyFixed, well-defined precedence
Developer experienceConvenience-orientedPrecision-oriented
Error toleranceTries to “make it work”Forces you to be explicit
Core philosophy“Make it work”“Say what you mean”

Neither is wrong. But assuming they behave the same is dangerous.

The Fix: Make Intent Explicit

Output:

This version:

  • Produces identical results
  • Documents intent
  • Survives migrations
  • Prevents silent data corruption

Real-World Impact

I’ve seen this exact pattern cause:

  • Financial miscalculations
  • Audit timestamp mismatches
  • Reconciliation failures weeks after go-live
  • “The numbers don’t add up” production escalations

The worst part? These bugs surface after trust is already established.

Key Takeaways

  • Execution plans reveal truth, not source code
  • || mixed with + is a migration red flag
  • Explicit casting doesn’t guarantee identical behavior
  • Migration is about semantics, not syntax

The Bottom Line

Database migration isn’t translation. It’s interpretation.

When Oracle silently rewrites logic and PostgreSQL refuses to guess, you must be explicit. And once you start writing SQL that works the same everywhere, you don’t just migrate safely you migrate confidently.

Try it Yourself

About Deepak Mahto

Database Guy with expertise in database migration,performance and Cloud Adoption.
联系我们 contact @ memedata.com