AI & Cubes

Why pointing Athena at a cube is safer, more accurate, and more privacy-preserving than letting AI write raw SQL against your database.


Table of Contents

Why Cubes Make AI Better

When you ask an AI assistant "what was our revenue last quarter?" against a raw database, the model has to:

  1. Guess which table holds revenue (orders? invoices? line_items? payments?)
  2. Guess which column is the amount (amount? total? gross_total? net_total?)
  3. Guess whether to include refunds, taxes, currency conversion
  4. Hope all those guesses match how your business actually defines revenue

Even with a perfect schema, this gets wrong answers more often than people admit. And you don't see that it's wrong — you just see a number.

A cube fixes this by giving the AI a vocabulary that's already correct. When revenue is a named measure with one definition, the AI doesn't guess — it picks revenue from the cube and the SQL is generated from your reviewed, named, versioned definition. Same number every time, same number across every consumer.

Privacy: What the AI Sees

DataPallas's data-privacy stance is that the AI never sees your data — only your schema and metadata. Cubes strengthen this:

What the AI seesWhen raw SQLWhen using a cube
Full database schema (every table, column, type)YesNo — only the cube's declared dimensions and measures
Joined-table column listsYesNo — only the cube's joined-table aliases
Free-form column names that may hint at PIIYesNo — the cube's title and description fields are what the AI reads
Row dataNoNo

A cube is effectively a redacted view of your schema specifically for AI. You decide what's in it — exclude sensitive tables, rename columns to user-friendly titles, write descriptions in business language — and the AI works only with that surface.

Asking Questions Through a Cube

In the Chat2DB AI panel, after you've created cubes, you'll see them listed alongside raw database connections. Pick a cube as the context and ask questions in plain English:

"Show monthly revenue trend for the last 6 months, broken down by country."

The AI translates this into a cube selection — dimensions: order_date, country, measures: revenue, segments: recent_6_months — and the cube generates the SQL. You see both the question, the cube selection, and the resulting chart side by side. If the answer surprises you, you can drill into the cube selection itself rather than re-reading 40 lines of SQL.

📷 Screenshot placeholder: 200_40_cube-in-chat.png — Athena chat answering a natural-language question by picking fields on a cube, with the result chart and the cube selection visible.

Writing Cube DSL With AI

Cubes are not only consumed by AI — they're also created with AI. Inside the Cube editor, the Hey AI, Help Me… button:

  1. Reads the live database schema for the selected connection
  2. Lets you describe the cube you want in natural language ("a cube on orders with revenue by region and time-based segments")
  3. Drafts the Groovy DSL for you, ready to review and tweak

This is by far the fastest way to get the first draft of a cube right. Review the generated DSL against the DSL Reference, adjust titles and descriptions to match your business language, and save.

📷 Screenshot placeholder: 200_45_cube-ai-help.png — The "Hey AI, Help Me…" dialog showing a natural-language prompt and the generated cube DSL.

What Your Database Decides

DataPallas writes standard SQL and sends it to your database unchanged. A few answers are therefore your database's own, not DataPallas's: the same cube, the same filter and the same dashboard can answer a little differently on two vendors. These are the ones worth knowing about.

DatabaseWhat it decides
OracleAn empty string is NULL, so a filter for = '' matches nothing and "is empty" also finds text you saved as ''. Oracle 12.2 is the minimum version: FETCH FIRST needs 12c, and a generated name such as gross_total_count_distinct needs the 128-byte identifiers of 12.2.
MySQL, MariaDB and SQL ServerText comparison, GROUP BY and DISTINCT follow the column's collation, which is case-insensitive on all three by default. MySQL and MariaDB are accent-insensitive too, so Otzi and Ötzi end up in one group and the same contains filter matches more rows there than on PostgreSQL; SQL Server's own default collation is accent-sensitive, so Ötzi stays apart. Trailing spaces are ignored by = on SQL Server, MariaDB and Db2, which pad the shorter value, but not by MySQL 8's default collation, where 'a' and 'a ' differ.
MySQL and MariaDBThe NO_BACKSLASH_ESCAPES SQL mode is not supported — a backslash in a filter value is doubled, as standard SQL asks — and neither is MariaDB's sql_mode=ORACLE. Zero dates (0000-00-00) are the driver's business: DataPallas does not set zeroDateTimeBehavior, so on MySQL the driver's own default applies and reading a zero date fails; add zeroDateTimeBehavior=CONVERT_TO_NULL to the connection URL yourself if your tables hold them. MariaDB's driver has no such setting, and a CAST('0000-00-00' AS DATE) is NULL on both servers. The MySQL URL DataPallas generates also pins the driver's time zone to UTC, which is how it converts dates it reads and writes; the server session keeps its own time_zone (SYSTEM).
PostgreSQLA timestamptz column is bucketed by day, week or month in the session time zone, which the driver sets to the time zone of the DataPallas server's JVM — not the viewer's.
SQLiteLOWER and LIKE fold ASCII only, so a contains filter is case-sensitive for Ü, Ł and every other non-ASCII letter. CAST(… AS DECIMAL(31,4)) does not round, so a total or an average can show binary-float digits. A number stored as text in a column with text affinity compares as text, where '9' is greater than '10'.
ClickHouseA ReplacingMergeTree or CollapsingMergeTree table counts the rows that have not been merged yet unless it is read with FINAL, which DataPallas does not add. count(DISTINCT …) follows the server's count_distinct_implementation setting, which is uniqExact — an exact count — unless your server changes it to one of the approximating uniq* functions.
IBM Db2Only Db2 for Linux, UNIX and Windows is supported. Db2 for i and Db2 for z/OS are not.

Two measure options ask the database for something older versions do not have: reading a number as a % of total, and reading it as a running total - each uses a window function (SUM(...) OVER (...)), so each needs MySQL 8.0, MariaDB 10.2 or SQLite 3.25 at the oldest. PostgreSQL, Oracle, SQL Server, Db2, ClickHouse and DuckDB have had it for far longer. On an older MySQL, MariaDB or SQLite the query is a syntax error; every other measure keeps working.

Two more are DataPallas's own and behave the same on every database:

  • An empty filter box, and an empty dashboard parameter, mean "no filter". The condition is left out of the query — an is one of filter writes a harmless 1=1 instead — rather than sent as a comparison with an empty value, which would match nothing while you are still typing.
  • A dashboard parameter is bound with its declared type, so a Date parameter compared with a timestamp column means midnight of that day. Use a date range when you mean the whole day.

When you write the SQL yourself — in the SQL editor, or by letting the AI write it and running what it wrote — DataPallas sends that text to the database exactly as you typed it: nothing is rewritten, and nothing is translated into another dialect. That also means a construct your database does not have stays an error. SQLite, for instance, rejects the standard date literal (DATE '2026-09-30'), the standard SUBSTRING(name FROM 1 FOR 3) and OFFSET … FETCH NEXT … ROWS ONLY, all of which PostgreSQL, Oracle, Db2, SQL Server and DuckDB accept; write SQLite's own date('2026-09-30'), substr(name, 1, 3) and LIMIT … OFFSET … instead. The AI is told which database it is writing for, so asking it for the SQL is usually the shortest way around a gap like this.

Best Practices

  • Write description fields like you're onboarding a new analyst. The AI reads them. "Total Revenue" is fine; "Sum of completed orders' net amount in USD, after refunds" is much better — and removes a class of wrong answers.
  • Hide what you don't want asked about. A cube is a curated surface. If a table contains PII or pre-launch product data, don't expose it as a dimension. The AI can only ask about what you put in the cube.
  • One cube per business domain, not per table. A good cube spans the fact table plus its commonly-joined dimension tables (orders + customers + products). Don't make one cube per database table — that's just a thin wrapper around the schema.
  • Start broad, narrow over time. Ship a cube with the obvious dimensions and measures, watch what users actually ask, then add segments and hierarchies for the patterns that emerge.
  • Cubes are versioned with your code. Each one is a folder under config/cubes/ holding <id>-cube-config.groovy (the DSL) and cube.xml (its name, description and connection). Commit them. Code-review them. Treat changes to a measure definition the same way you'd treat a change to a critical SQL view.