Column Naming Conventions for Multi-Warehouse Exports
Inconsistent column names break silently across warehouses during migrations.

A join fails without any error message. Or worse, it succeeds, and two dashboards report different numbers for the same metric because one table calls it CampaignID and another calls it campaign_id. Neither is wrong on its own terms. Together they cost someone an afternoon of debugging, or a board meeting's worth of credibility, and this happens daily in organizations that treat column naming as a developer's private habit rather than a decision with business consequences.
The stakes get concrete during due diligence. A scenario common in acquisitions: a buyer reviewing a target company's data finds the same vendor spelled three different ways across datasets that were never reconciled. The buyer doesn't conclude the data is messy. The buyer concludes the business itself is poorly run, and in some cases walks away from the deal. Naming inconsistency is a risk with a dollar figure attached at that point. It's a risk with a dollar figure attached.
Two forces have raised the stakes further. Teams have grown larger and more distributed, so the tribal knowledge that once lived in one engineer's head, the kind that let a small team silently agree on what q3_spend meant, stops working the moment five teams across three regions write against the same schema without ever meeting. And AI-powered analytics tools now read schemas directly, without a human translator in between. A column named rev_q3_adj_final_v2 tells an engineer who wrote it something specific. It tells a language model nothing, and the model either guesses at the meaning or returns an answer nobody can verify.
Multiplying that by warehouse count makes the problem compound instead of just repeat. A naming choice that works cleanly in one platform can break, silently, the moment the same schema lands in another. That's the failure mode this piece is built around, and it deserves more attention than it currently gets.
How widespread multi-warehouse deployments have become
Multiple data warehouses are now the norm. Industry figures show multi-cloud strategies in 83% of enterprises, and 78% run hybrid cloud environments. Any naming convention built around a single destination is already designed for a shrinking minority of real deployments.
The scale involved isn't abstract. SmarterX migrated more than 80 databases and thousands of tables, drawn from 21 separate data sources, from Snowflake to BigQuery, and did it in under a month. That scale, compressed into a single month, put the portability of every identifier in the schema to the test.
Travelpass Group moved its BI and SQL analytics workloads from Databricks back to Snowflake and cut compute costs for that workload class by 65%. That kind of migration doesn't just move data. That kind of migration forces a direct comparison between two identifier systems that had never previously had to coexist.
The pattern beneath both examples is that companies aren't choosing a warehouse once and settling in. They're migrating, adding new destinations, running two or three platforms at the same time for different teams or workloads. A naming decision made for one system gets reused, almost by default, against systems it was never built to survive. And the reason it fails isn't random. Each warehouse enforces its own identifier rules, rules that rarely get compared side by side until a migration forces the question.
The identifier rules each warehouse enforces
Snowflake, BigQuery, and Redshift are all broadly ANSI SQL-compatible, which makes the differences easy to miss. The divergence appears in how each platform handles case, which words it reserves for itself, how it treats quoting, and what it does with special characters.
Redshift folds ASCII letters in delimited identifiers to lowercase, without asking and without warning. A column exported as CustomerRevenue, or customerRevenue, lands in Redshift as customerrevenue. Nothing throws an error. The camelCase or PascalCase name just gets flattened, silently, and anyone relying on the original casing to disambiguate two similarly named fields finds out the hard way. Snake_case is the only style that survives Redshift's default behavior without requiring explicit quoting on every single reference.
Snowflake runs the opposite direction. Unquoted identifiers get uppercased internally, so a column written in lowercase is stored in uppercase internally, which can create mismatches when a downstream tool preserves the original lowercase casing from the source. Snowflake also reserves a list of words that trips up engineers who don't expect it: LEFT, OUTER, and JOIN can't be used as table names or aliases in a FROM clause, and TRUE, FALSE, and CASE can't appear as column references in a scalar expression. Use any of them unquoted, and the query fails at execution, not at design time.
BigQuery brings its own dialect entirely, standard SQL with backtick-quoted identifiers, native STRUCT and ARRAY types, and JSON_VALUE for pulling data out of semi-structured columns. Column names that bake in a type assumption, amount_float or created_timestamp, can behave inconsistently across these three platforms simply because each one's type system and casting rules diverge in ways the column name never accounted for.
The naming decisions that break silently across destinations
CamelCase and PascalCase columns look perfectly reasonable in the source system. They survive in Snowflake, so long as someone remembers to quote them. Then they arrive in Redshift lowercased into an unreadable run of characters, and this is the most common portability failure of all, precisely because nothing throws an error to flag it. The pipeline runs green. The data is just wrong.
Date-stamped columns cause a slower, quieter kind of damage. q3_spend, fy25_budget, primary_agency_2024, these all make sense the day someone names them. A year later, an AI tool reading the schema has no way to know whether q3_spend means Q3 of last year or Q3 of this one, and the column name has gone from useful to actively misleading without anyone touching it.
Opaque abbreviations carry the same problem in a different shape. cid, ord, rev meant something to the engineer who typed them in a hurry. They mean nothing to the next analyst, the next tool, or the model reading the schema six months later. Compare revenue_net, which at least hints at what it holds, against revenue2, which communicates nothing at all beyond "there was a revenue1."
And reserved words keep sneaking in because they're ordinary English before they're SQL keywords. Snowflake's reserved list includes CASE, TRUE, FALSE, LEFT, and JOIN, words common enough that an engineer reaches for one without registering that the database has already claimed it.
A warehouse-portable naming convention, rule by rule
Rule 1: use snake_case, exclusively, everywhere. It's the only casing style that survives Redshift's lowercase folding without needing quotes on every reference, and it reads clearly to both engineers and the business users who never touch SQL directly. customer_order_history is legible at a glance. customerorderhistory is readable only if you concentrate. CustomerOrderHistory, meanwhile, becomes customerorderhistory the moment it lands in Redshift, which defeats the purpose of writing it in PascalCase.
Rule 2: avoid reserved words, full stop, unless they're quoted. Check the published reserved-word list for every target platform before finalizing a schema, not after. Snowflake's list alone rules out CASE, LEFT, and TRUE as unquoted identifiers. Where a name is close to reserved but not quite, add a prefix: case_status instead of case, order_sequence instead of order. It costs six characters and saves a failed query.
Rule 3: no spaces, hyphens, or special characters, ever. Letters, numbers, and underscores only. Special characters cause outright syntax errors in some platforms and get silently rejected or mishandled in others, and the same discipline pays off outside the database entirely: column names built this way pass through Parquet files and CSV headers without needing escaping or quoting.
Rule 4: make the name do some of the documentation's job. Booleans should carry an is_ or has_ prefix (is_active, has_subscription). Timestamps take a _ts suffix (loaded_ts, created_ts). Dates get a _date suffix (report_date). Amounts should state their unit outright (revenue_usd, duration_seconds). None of this is decorative. It lets a downstream tool, including an AI agent reading the schema cold with no human to ask, infer type and meaning without cracking open a data dictionary.
Column naming and white-label data exports and embedded analytics
In warehouse-native embedded analytics, every query the BI layer generates points straight at column names living in the customer's own warehouse. There's no abstraction layer quietly translating between what the product shows and what the warehouse stores. The column name is the interface, and it's the interface for the dashboard, for any AI agent summarizing the data, and for every customer-built query running against that same table.
Three different numbers occur this way. A customer success team pulls a churn report and gets one number. The embedded dashboard, querying the same underlying event, shows customers a second number. A board deck built from a raw warehouse query produces a third number. Three systems, three separate definitions of churn baked into three different column references and three different calculation paths, and the result is that nobody in the room trusts any of the three figures, including the one that happens to be right.
White-label products carry a sharper version of this risk. When a SaaS vendor ships a fully branded data experience, the customer isn't looking at "a BI tool." The customer is looking at the vendor's product, full stop, and a naming inconsistency that would read as a data engineering hiccup in an internal tool reads as a product bug in a white-label one. That's a brand problem, not a backend problem, and it lands on whoever's logo is on the dashboard.
The shared-dataset multi-tenant pattern, one dataset filtered by tenant_id at query time, is a common architecture B2B SaaS companies reach for because it scales. But it also means column names function as a kind of contract. Every tenant, every embedded dashboard, and every downstream tool reads against the exact same set of names, so a naming decision made once, early, has to hold across every customer the product will ever have, not just the first ten it happened to be built for.

