Database Destinations
Write Data Flow results to Snowflake, BigQuery, PostgreSQL, Supabase, MySQL, or SQL Server. Covers network access, required permissions, the tables Better Analyst manages, column types, and per-engine notes.
A database destination writes each run's result into a table you choose. This page covers what your database administrator needs to know before you point a production database at a flow.
Database destinations are available on Max and Enterprise plans for Snowflake, BigQuery, PostgreSQL, Supabase, MySQL, and SQL Server.
Before You Connect
You need four things:
- Network access from Better Analyst to your database (see below).
- A dedicated database user for Better Analyst, with write permission on one schema.
- A schema that already exists. Better Analyst creates tables, but not schemas. Create the target schema first, or use the Create schema action on the connector page.
- For BigQuery, a service-account connection. A BigQuery connection created with OAuth cannot be used as a destination.
Network Access
Better Analyst connects to your database from its own servers over the public internet, so the database must accept inbound connections from those addresses.
Most managed databases block all external traffic by default. You will typically need to:
- Add Better Analyst's addresses to your allowlist — the security group, firewall rule, or IP allowlist that governs your database. In Amazon RDS this is the security group's inbound rules; in Google Cloud SQL it is Authorized networks; in Azure SQL it is Firewall rules; in Supabase it is Network Restrictions.
- Enable SSL/TLS. Connections are encrypted in transit. Snowflake and BigQuery require it; for PostgreSQL, MySQL, and SQL Server, enable it on the server and select SSL in the connector.
- Open the database's port to those addresses — 5432 for PostgreSQL and Supabase, 3306 for MySQL, 1433 for SQL Server. Snowflake and BigQuery are reached over HTTPS and need no port changes.
Better Analyst connects from these address ranges. Allowlist both:
74.220.50.0/24
74.220.58.0/24
These ranges are stable, so this is a one-time configuration step. If your security policy requires individual addresses rather than CIDR ranges, contact support.
A database that is only reachable inside a private network — a VPC with no public endpoint, or a host on localhost — cannot be used as a Data Flow destination. Expose it through a public endpoint restricted to the allowlist, or use a bastion or tunnel that provides one.
Required Permissions
Create a dedicated user rather than reusing an application or admin account. Better Analyst needs, scoped to the one target schema:
| Permission | Why it is needed |
|---|---|
CREATE TABLE | Creates the destination table on the first run, plus temporary staging tables |
INSERT | Writes the rows |
UPDATE, DELETE | Applies Update or add rows and Replace with latest data |
SELECT | Reads back its own bookkeeping and confirms row counts |
DROP (own tables) | Cleans up its temporary staging tables after each run |
A PostgreSQL example:
CREATE USER formulabot_writer WITH PASSWORD 'your-secure-password';
GRANT USAGE, CREATE ON SCHEMA analytics TO formulabot_writer;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA analytics TO formulabot_writer;
ALTER DEFAULT PRIVILEGES IN SCHEMA analytics
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO formulabot_writer;
Grant these on a schema dedicated to Better Analyst deliveries — not on a schema holding tables your applications depend on. Replace with latest data deletes existing rows in its target table by design.
Better Analyst refuses to write to system schemas regardless of permissions, including information_schema, pg_catalog, mysql, sys, master, msdb, and tempdb.
Tables Better Analyst Creates
Alongside your destination table, Better Analyst maintains its own tables in the same schema. Every one begins with _fb_:
| Table | Purpose |
|---|---|
_fb_sync_meta | One row per destination table, recording the last run's ID, write mode, rows inserted and updated, and final row count |
_fb_stg_… | Short-lived staging tables used while a run is being applied |
_fb_write_probe_… | Created and removed when a destination is first tested, to confirm write access |
_fb_sync_meta is what makes a run safe to retry. If a run is interrupted midway — a network drop, a restart — the next attempt reads this table, recognizes the interrupted run, and finishes or rolls it back rather than writing the same rows twice.
Leave _fb_ tables in place. Deleting _fb_sync_meta does not break the destination table, but the next run loses its safety record and re-creates it from scratch.
Column Types
Better Analyst maps every delivered column to one of three general types:
| Delivered as | PostgreSQL / Supabase | MySQL | SQL Server | Snowflake | BigQuery |
|---|---|---|---|---|---|
| Text | text | TEXT | NVARCHAR(MAX) | STRING | STRING |
| Number | double precision | DOUBLE | FLOAT | DOUBLE | FLOAT64 |
| Boolean | boolean | TINYINT(1) | BIT | BOOLEAN | BOOL |
Two consequences worth planning around:
- Dates arrive as text. A
datecolumn lands as a string, soWHERE date >= '2026-08-01'compares text rather than dates. It works forYYYY-MM-DDvalues because they sort correctly as text, but it is not a date column. - Whole numbers arrive as floating point. A count of 3,788 may display as
3788.0.
If you need stricter types, create a view over the destination table that casts the columns, and point your reports at the view. Better Analyst only ever writes to the table it manages, so a view on top is never disturbed by a run.
A text column used as a merge key is created narrower so the database can index it — VARCHAR(255) in MySQL and NVARCHAR(255) in SQL Server, since neither engine can index an unbounded text column. Merge-key values longer than 255 characters fail the write rather than being truncated.
Existing columns are not retyped. If you pre-create the destination table with the types you want and the delivered data fits, Better Analyst writes into your types.
Table and Column Naming
Table, schema, and column names must start with a letter or underscore, contain only letters, numbers, and underscores, and be 64 characters or shorter. Names beginning with _fb_ are reserved.
If your source data has column names that do not fit — spaces, punctuation, or leading digits — rename them in the transform step before delivery.
Size Limits
| Limit | Value |
|---|---|
| Rows written per run | 500,000 |
| Columns per table | 512 |
| Total cells per run | 5,000,000 |
| Time for a single statement | 120 seconds |
In practice the source limits bind first: a run reads at most 100,000 rows from its source, so a delivery only approaches 500,000 rows if a transform multiplies rows — for example a join that fans out.
If a run exceeds the statement timeout, the usual cause is a destination table without an index on its merge-key columns. See below.
Write Modes on a Database
Database destinations support the same three write modes as Google Sheets:
| Mode | What it does to the table |
|---|---|
| Update or add rows | Rows whose merge keys match are updated in place; new keys are inserted; rows absent from this run are left alone |
| Replace with latest data | All existing rows are deleted, then this run's rows are inserted |
| Append every run | This run's rows are inserted; nothing existing is touched, and duplicates are possible |
Index your merge keys. For Update or add rows, add a unique index or constraint on the merge-key columns of the destination table. Without one, each run scans the whole table to find matches, which gets slower as the table grows and eventually hits the statement timeout:
CREATE UNIQUE INDEX idx_daily_sessions_key
ON analytics.daily_sessions (date, campaign_id);
Replace with latest data deletes every existing row in the destination table on every run, including rows an earlier run wrote and rows you added by hand. Use a table dedicated to this flow.
Per-Engine Notes
PostgreSQL
PostgreSQL 12 or later. The writer role needs USAGE and CREATE on the target schema, as in the example above.
Supabase
Supabase uses the PostgreSQL connection path with the Session pooler details from the Supabase Connect dialog, the same connection used for reading — see Connect Supabase. If the target schema has row-level security enabled, confirm the writer role's policies permit insert, update, and delete, or the run fails with a permission error even though the grants look correct.
MySQL
MySQL 5.7 or later, on port 3306.
SQL Server
SQL Server 2016 or later, and Azure SQL. The connection field is the server name; a named instance goes in the instance field rather than being appended to the server name.
Snowflake
Requires a warehouse, database, and schema. The role must have USAGE on the warehouse and CREATE TABLE on the schema. A suspended warehouse is fine if it is set to auto-resume; if it cannot resume, runs fail while it is suspended.
BigQuery
Requires a service-account connection — an OAuth-based BigQuery connection cannot be a destination. The service account needs BigQuery Data Editor on the target dataset and BigQuery Job User on the project. The dataset must already exist, and its location is fixed at creation.
Troubleshooting
"The database identity could not be verified"
Better Analyst checks that the database it is about to write to is the one the flow was configured against. This appears when the connector's host, port, or database name no longer resolves to the same server — usually after a database was moved, restored, or recreated. Reconnect the connector with the current details, then re-save the flow.
Connection timeouts
The allowlist is the usual cause. Confirm the current Better Analyst addresses are in the database's firewall rule, and that the rule covers the port your database listens on.
"Permission denied" on the first run
The first run creates the destination table, which needs CREATE TABLE on the schema — a broader permission than the INSERT needed by later runs. A user granted only INSERT connects and tests successfully, then fails on the first real delivery.
Runs slow down as the table grows
Add a unique index on the merge-key columns, as above.