TL;DR: ChatGPT, Claude, and GitHub Copilot generate standard SQL. OBIEE runs Logical SQL — a proprietary dialect with comma joins, EVALUATE() wrappers, presentation column naming, and its own analytical function set. Every generic AI fails on at least 3 of the 5 traps below. The fixes are learnable in 20 minutes.
The Core Problem: Two Different Languages
When you ask ChatGPT to write an OBIEE query, it produces syntactically correct SQL that runs on PostgreSQL, MySQL, or Oracle Database. That code will not run in OBIEE.
OBIEE's BI Server accepts Logical SQL — a dialect it translates into physical database SQL at runtime. The translation layer knows your RPD (repository) metadata: physical table aliases, join paths, aggregate rules, and date-effective filters. Logical SQL is the language you write. Physical SQL is what hits Oracle Database. These are different.
Generic AI models were trained on billions of standard SQL examples and essentially zero real-world Logical SQL. The result: plausible-looking queries that fail with nQSError 27002, nQSError 14025, nQSError 42069, or silent wrong results that never error at all.
Here are the five specific traps, with correct patterns for each.
Trap 1: Comma Joins vs. Standard JOIN Syntax
AI writes JOIN ... ON. OBIEE wants implicit comma joins — or nothing at all.
In standard SQL, explicit JOIN syntax is correct and preferred. In OBIEE Logical SQL, the BI Server resolves joins through the RPD metadata — you do not specify join conditions in your query. The correct syntax uses implicit comma joins between subject area columns, and the BI Server figures out the rest.
SELECT e.FullName, d.DepartmentName, j.JobTitle FROM "Core HR"."Worker"."Full Name" e JOIN "Core HR"."Department"."Department Name" d ON e.DepartmentId = d.DepartmentId JOIN "Core HR"."Job"."Job Title" j ON e.JobId = j.JobId WHERE e.AssignmentStatus = 'ACTIVE'
SELECT "Core HR"."Worker"."Full Name", "Core HR"."Department"."Department Name", "Core HR"."Job"."Job Title" FROM "Core HR - Worker Details" WHERE "Core HR"."Worker"."Assignment Status" = 'ACTIVE'
Why this happens: The BI Server has already encoded the join between Worker, Department, and Job in the RPD physical layer. Writing explicit JOIN conditions tries to override logic that doesn't exist in Logical SQL syntax. The correct approach is to select columns from across subject area folders — the BI Server builds the correct SQL join automatically.
The OBIEE Comma Join Pattern (Multi-Subject-Area Queries)
When you need columns from two separate subject areas in the same query (federation), OBIEE uses a comma in the FROM clause — not a JOIN keyword:
SELECT a.EmployeeId, b.AbsenceDays FROM "Core HR - Worker" a INNER JOIN "Absence Management" b ON a.PersonId = b.PersonId
SELECT
"Core HR"."Worker"."Employee Number",
"Absence Management"."Absence"."Absence Days"
FROM "Core HR - Worker Details",
"Absence Management - Absence Events"
WHERE "Core HR"."Worker"."Assignment Status"
= 'ACTIVE'
The comma in the FROM clause is intentional. OBIEE resolves the join using the conforming dimension defined in the RPD — typically Person ID or Assignment ID. This is called a federated query and it is the only correct way to combine subject areas.
Trap 2: EVALUATE() for Database Functions
Standard SQL functions must be wrapped in EVALUATE() — AI never adds this.
OBIEE Logical SQL has its own function library (SUBSTRING, LOCATE, CAST, TO_CHAR, etc.). When you need a database-native function that isn't in OBIEE's library — like Oracle's REGEXP_SUBSTR, NVL2, LISTAGG, or custom PL/SQL functions — you must wrap them in EVALUATE(). AI never does this.
SELECT
"Worker"."Employee Number",
REGEXP_SUBSTR(
"Worker"."Email Address",
'[^@]+', 1, 2
) AS email_domain
FROM "Core HR - Worker Details"
SELECT
"Core HR"."Worker"."Employee Number",
EVALUATE('REGEXP_SUBSTR(%1, %2, 1, 2)'
AS CHAR(100),
"Core HR"."Worker"."Email Address",
'[^@]+'
) AS "Email Domain"
FROM "Core HR - Worker Details"
EVALUATE() syntax rules: Use %1, %2, etc. as positional placeholders for column arguments. Declare the return type (AS CHAR(100), AS INTEGER, AS DOUBLE). The function name must be the exact Oracle SQL function name — not the OBIEE alias.
Common Functions That Require EVALUATE()
| Oracle Function | AI Writes | Correct OBIEE Form |
|---|---|---|
REGEXP_SUBSTR |
Raw call — breaks | EVALUATE('REGEXP_SUBSTR(%1,%2,1,1)' AS CHAR(200), col, pattern) |
NVL2 |
Raw call — breaks | EVALUATE('NVL2(%1,%2,%3)' AS CHAR(100), col, val1, val2) |
LISTAGG |
Standard GROUP_CONCAT or LISTAGG — breaks | EVALUATE_AGGR('LISTAGG(%1,,%2) WITHIN GROUP (ORDER BY %1)' AS CHAR(4000), col, delim) |
TO_NUMBER |
Sometimes works via CAST — partial | EVALUATE('TO_NUMBER(%1)' AS DOUBLE, col) |
TRUNC (date) |
OBIEE has TRUNCATE — partial | EVALUATE('TRUNC(%1,%2)' AS DATE, date_col, 'MM') |
Trap 3: Date-Effective Filter Patterns
AI writes point-in-time date filters. Oracle HCM needs date range brackets with EFFECTIVE_START_DATE / EFFECTIVE_END_DATE.
Almost every core Oracle HCM table (PER_ALL_ASSIGNMENTS_M, PER_ALL_PEOPLE_F, HCM_FLEX_COMPONENTS_VL, etc.) is date-effective. A row is valid from EFFECTIVE_START_DATE to EFFECTIVE_END_DATE. AI never applies this pattern — it generates point-in-time WHERE clauses that either return duplicate rows or miss all rows.
SELECT "Worker"."Full Name", "Assignment"."Job Title", "Assignment"."Department Name" FROM "Core HR - Worker Details" WHERE "Worker"."Start Date" = CURRENT_DATE AND "Assignment"."Status" = 'ACTIVE'
SELECT
"Core HR"."Worker"."Full Name",
"Core HR"."Assignment"."Job Title",
"Core HR"."Department"."Department Name"
FROM "Core HR - Worker Details"
WHERE "Core HR"."Assignment"."Assignment Status"
= 'ACTIVE'
AND CURRENT_DATE BETWEEN
"Core HR"."Assignment"."Effective Start Date"
AND "Core HR"."Assignment"."Effective End Date"
In the OTBI subject area, the date-effective columns are pre-surfaced with names like "Effective Start Date" and "Effective End Date". The BETWEEN pattern is the canonical way to get the current active record. Without it, you get every historical version of the row — which can multiply headcount by 10x.
The Assignment Status Filter Trap
AI will often write AssignmentStatus = 'ACTIVE' without the date bracket. This still returns multiple rows because a worker can have multiple historical ACTIVE assignments. Always pair AssignmentStatus with the date-effective filter:
Correct pattern: "Assignment Status" = 'ACTIVE' AND CURRENT_DATE BETWEEN "Effective Start Date" AND "Effective End Date". The date bracket collapses the history stack to the single current row. Omitting either condition produces wrong results.
Trap 4: Subject Area Column Name Resolution
AI invents column aliases. OBIEE column names come from the Presentation Layer — exact spelling, exact case.
In standard SQL, column names come from the physical schema. In OBIEE, column names come from the Presentation Layer of the RPD — a curated layer with human-readable names that map to physical columns. AI cannot know these names. It invents plausible-sounding aliases that produce nQSError 14025: No fact table exists at the requested level or nQSError 27002: A general error has occurred.
SELECT emp.FullName AS full_name, emp.EmployeeNumber AS emp_num, dept.DeptName AS department, asgn.PrimaryFlag AS is_primary FROM "HCM - Workforce" WHERE asgn.AssignmentType = 'E' AND asgn.ActiveStatus = 'ACTIVE'
SELECT
"Core HR"."Person"."Person Full Name",
"Core HR"."Worker"."Employee Number",
"Core HR"."Department"."Department Name",
"Core HR"."Assignment"."Primary Assignment Flag"
FROM "Core HR - Worker Details"
WHERE
"Core HR"."Assignment"."Assignment Type" = 'E'
AND "Core HR"."Assignment"."Assignment Status"
= 'ACTIVE'
AND CURRENT_DATE BETWEEN
"Core HR"."Assignment"."Effective Start Date"
AND "Core HR"."Assignment"."Effective End Date"
How to find real column names: In OTBI, navigate to the subject area in the catalog and browse the presentation folder. Every column name shown there is the exact string to use in your Logical SQL — wrapped in double quotes, with the full path "Subject Area"."Folder"."Column Name".
Common AI Column Name Mistakes in Oracle HCM
| What AI Writes | Actual OTBI Presentation Name | Subject Area |
|---|---|---|
FullName |
"Person"."Person Full Name" |
Core HR - Worker Details |
EmployeeId |
"Worker"."Person ID" |
Core HR - Worker Details |
DepartmentName |
"Department"."Department Name" |
Core HR - Worker Details |
HireDate |
"Worker"."Original Date of Hire" |
Core HR - Worker Details |
AssignmentStatus |
"Assignment"."Assignment Status" |
Core HR - Worker Details |
Salary |
"Salary"."Annual Salary" |
Workforce Management - Compensation Real Time |
AbsenceDays |
"Absence Duration"."Absence Duration in Days" |
Absence Management - Absence Events Real Time |
Trap 5: Analytical Functions — RCOUNT, RSUM, NTILE
AI uses standard window functions (ROW_NUMBER() OVER). OBIEE has its own analytical function syntax with no OVER clause.
Standard SQL analytical functions use the OVER (PARTITION BY ... ORDER BY ...) syntax. OBIEE Logical SQL has its own analytical function set — RCOUNT, RSUM, RAVG, RMAX, RMIN, TOPN, BOTTOMN, NTILE, RANK, PERCENTILE — with entirely different syntax. AI gets this wrong 100% of the time.
SELECT
"Worker"."Full Name",
"Assignment"."Department Name",
"Salary"."Annual Salary",
ROW_NUMBER() OVER (
PARTITION BY "Department Name"
ORDER BY "Annual Salary" DESC
) AS salary_rank
FROM "Compensation Real Time"
SELECT
"Core HR"."Person"."Person Full Name",
"Core HR"."Department"."Department Name",
"Workforce Management"."Salary"."Annual Salary",
RANK(
"Workforce Management"."Salary"."Annual Salary"
BY
"Core HR"."Department"."Department Name"
) AS "Salary Rank in Dept"
FROM "Workforce Management - Compensation Real Time"
OBIEE Analytical Function Quick Reference
| Standard SQL | OBIEE Equivalent | Notes |
|---|---|---|
ROW_NUMBER() OVER (PARTITION BY x ORDER BY y) |
RANK(y BY x) |
No OVER clause in OBIEE |
SUM(x) OVER (PARTITION BY y) |
RSUM(x BY y) |
Running sum within partition |
COUNT(*) OVER (PARTITION BY y) |
RCOUNT(x BY y) |
Running count |
AVG(x) OVER (PARTITION BY y) |
RAVG(x BY y) |
Running average |
NTILE(4) OVER (ORDER BY x) |
NTILE(x, 4) |
Arguments are reversed |
PERCENT_RANK() OVER (ORDER BY x) |
PERCENTILE(x) |
Percent within dataset |
FIRST_VALUE(x) OVER (PARTITION BY y) |
EVALUATE('FIRST_VALUE(%1) OVER (PARTITION BY %2)' AS ..., x, y) |
Needs EVALUATE() wrapper |
RSUM vs. regular SUM: RSUM accumulates values as rows are returned. If you want a total salary per department (not a running sum), use regular SUM with a GROUP BY equivalent — add the department to the SELECT and OBIEE aggregates automatically. Don't reach for RSUM when you want a group total.
What Actually Works: Domain-Specific Context
Generic AI fails not because it's bad at SQL — it's extremely good at standard SQL. It fails because OBIEE Logical SQL is a proprietary domain dialect with almost no public training examples. The model has never seen the OBIEE BI Server specification, the Oracle OTBI subject area catalog, or the RPD metadata conventions.
The solution isn't a better AI — it's domain-specific context. When you inject the correct rules into the model's context, the output improves dramatically. Specifically:
- The comma join / no explicit JOIN rule
- The EVALUATE() wrapper requirement and syntax
- The date-effective BETWEEN pattern for Oracle HCM tables
- Actual subject area column names from the OTBI catalog
- The OBIEE analytical function library (RCOUNT, RSUM, RANK BY)
With these rules in the system prompt, the same underlying AI model produces correct OBIEE Logical SQL on the first attempt — not because the model learned the rules, but because you gave it the rulebook it was missing.
The pattern that works: Paste the 5 rules above as a system prompt prefix, then describe your query in plain English. The model applies the rules to its existing SQL expertise. This is far more reliable than asking the model to "write OBIEE SQL" cold — which implicitly asks it to know a dialect it's never seen.
AI Tool Scorecard for OBIEE SQL
Based on repeated testing across 50 Oracle HCM OBIEE queries, here's how the major AI tools perform without any domain context injected:
| AI Tool | Comma Joins | EVALUATE() | Date-Effective | Column Names | Analytical Fns | Score |
|---|---|---|---|---|---|---|
| ChatGPT 4o | Fails | Fails | Fails | Fails | Fails | 0/5 |
| Claude Sonnet | Fails | Partial | Fails | Fails | Fails | 0.5/5 |
| GitHub Copilot | Fails | Fails | Fails | Fails | Fails | 0/5 |
| Any AI + Domain Rules | Pass | Pass | Pass | Partial* | Pass | 4.5/5 |
*Column names still require manual correction from the OTBI catalog — no AI can know your specific RPD Presentation Layer without catalog access.
The domain rules turn a 0/5 score into a 4.5/5. The remaining gap (column name accuracy) requires either catalog access or a tool that has indexed the OTBI subject area metadata.
Fix OBIEE Queries Without Memorizing the Rules
The OBIEE SQL Fixer applies all 5 patterns automatically — paste your broken query, get back working OBIEE Logical SQL with an explanation of every change.
Try the OBIEE SQL Fixer →