PULL
PULL copies a query's current results into one of three families of destination, all under one unified syntax:
- a table of a local H2 database (
AS H2); - a tab of an Excel or OpenDocument spreadsheet file (
AS XLSX/AS ODS); - a whole flat file (CSV, plain text, JSON, Markdown, or HTML) (
AS CSV/AS TXT/AS JSON/AS MD/AS HTML).
None of the three need to exist beforehand: a table's columns and types, or a spreadsheet's headers, are derived from the query itself.
Every example on this page uses the sample WORLD database's COUNTRY and CITY tables, which ship with BroadSQL for exactly this purpose.
COUNTRY (ACTIVITY_STATUS_ID, CAPITAL, CODE, CODE2, CONTINENT, CURRENCY_CODE, FIPS, GEONAMEID, GNP,
GNPOLD, GOVERNMENTFORM, HEADOFSTATE, INDEPYEAR, INSERTION_DATE, ISO_NUMERIC, LAST_UPDATE,
LIFEEXPECTANCY, LOCALNAME, NAME, NEIGHBOURS, PHONE, POPULATION, REGION, SURFACEAREA, TLD)
CITY (ACTIVITY_STATUS_ID, COUNTRYCODE, DISTRICT, ID, INSERTION_DATE, LAST_UPDATE, NAME, POPULATION)
Recommended alternative to EXPORT/DUMP for a one-shot export: PULL lets you name the destination format and file explicitly, in one command, instead of EXPORT's mode-toggle workflow or DUMP's automatic row-count-based format choice; and it's the only way to get JSON, Markdown, or HTML output at all, since EXPORT/DUMP don't have those. EXPORT/DUMP still work exactly as before and aren't going anywhere (see Export & Dump), but for a new script, PULL is the more explicit, more predictable choice. Two differences worth knowing before you switch: PULL's CSV/TXT output doesn't honor SET SEP (see "Writing to a text file" below), and PULL rejects a column it can't map (large objects, binary, structural types) with a clear error rather than writing whatever the driver happens to return for it; true for every format, including the spreadsheet and H2 destinations.
Syntax
PULL <source> TO <name>.<table> AS H2 [MODE OVERWRITE | MODE APPEND KEY(<column>)] ;
PULL <source> TO <name>.<tab> AS XLSX | ODS ;
PULL <source> TO <name> AS CSV | TXT | JSON | MD | HTML ;
<source> ::= <table or view name>
| /
| ( <query> )
<source>: what to copy.- A bare table/view name: shortcut for
SELECT * FROM <name>. /: the last query you ran.- A parenthesized query: parentheses are always mandatory here.
- A bare table/view name: shortcut for
- The destination's shape depends on the format.
AS H2/AS XLSX/AS ODStake<name>.<table-or-tab>: a dot is required.AS CSV/AS TXT/AS JSON/AS MD/AS HTMLtake a bare<name>: a dot is a syntax error, since a flat file has no tab/table part to address after one.
MODEis only meaningful forAS H2(see "MODE OVERWRITE"/"MODE APPEND KEY" below). Every other format doesn't accept aMODEclause at all: each always fully (re)writes its destination (the named tab for XLSX/ODS, the whole file for every flat-file format).
Writing to a local H2 database (AS H2)
<name> (before the dot): the target H2 database. If it already names a connection, that connection is reused (it must be H2: PULL refuses to touch a connection of any other type). Otherwise a new H2 database is created automatically and registered under that name. <table> (after the dot): the table to write into that database.
Source form
| Case | Example |
|---|---|
| Bare table name | PULL COUNTRY TO WORKCOPY.COUNTRY AS H2; |
Last query (/) | PULL / TO WORKCOPY.COUNTRY AS H2; |
| Parenthesized query | PULL (SELECT * FROM COUNTRY WHERE CONTINENT = 'Europe') TO WORKCOPY.EUROPE AS H2; |
Destination
| Case | Example |
|---|---|
New H2 database (no connection named WORKCOPY exists yet) | PULL COUNTRY TO WORKCOPY.COUNTRY AS H2; |
Existing H2 connection reused (same syntax; WORKCOPY already registered and is H2) | PULL COUNTRY TO WORKCOPY.COUNTRY AS H2; |
Table name containing a dot (split on the first dot only; PUBLIC.COUNTRY becomes the table name) | PULL COUNTRY TO WORKCOPY.PUBLIC.COUNTRY AS H2; |
MODE OVERWRITE
| Case | Example |
|---|---|
| Default (omitted) | PULL COUNTRY TO WORKCOPY.COUNTRY AS H2; |
| Explicit | PULL COUNTRY TO WORKCOPY.COUNTRY AS H2 MODE OVERWRITE; |
MODE APPEND KEY(<column>)
| Case | Example |
|---|---|
| Integer key | PULL CITY TO WORKCOPY.CITY AS H2 MODE APPEND KEY(ID); |
| Integer key, second table | PULL COUNTRY TO WORKCOPY.COUNTRY AS H2 MODE APPEND KEY(GEONAMEID); |
| Date/time key | PULL COUNTRY TO WORKCOPY.COUNTRY AS H2 MODE APPEND KEY(LAST_UPDATE); |
| Date/time key, second table | PULL CITY TO WORKCOPY.CITY AS H2 MODE APPEND KEY(INSERTION_DATE); |
Space before ( is fine | PULL CITY TO WORKCOPY.CITY AS H2 MODE APPEND KEY (ID); |
| Parenthesized single-table query as source | PULL (SELECT ID, NAME, COUNTRYCODE, POPULATION, INSERTION_DATE FROM CITY WHERE COUNTRYCODE = 'FRA') TO WORKCOPY.CITY_FRANCE AS H2 MODE APPEND KEY(ID); |
/ as source | PULL / TO WORKCOPY.LASTRESULT AS H2 MODE APPEND KEY(ID); |
A subquery inside WHERE is fine (it's not a join or a second FROM table) | PULL (SELECT * FROM CITY WHERE ID IN (SELECT ID FROM CITY WHERE POPULATION > 1000000)) TO WORKCOPY.BIGCITY AS H2 MODE APPEND KEY(ID); |
How MODE APPEND KEY(<column>) behaves
- First run (the target table doesn't exist yet): behaves like
OVERWRITE's table creation, minus the drop: every row the query returns is loaded. - Later runs: BroadSQL reads the current maximum value of
<column>already in the target table, then only fetches and inserts source rows whose<column>is strictly greater. No row already in the target is ever updated or deleted. - Re-running with no new source rows inserts zero rows: safe to run on a schedule.
<column>must be a single column of a numeric or date/time type (composite keys are not supported byAPPEND).- The source query must be a simple, single-table query: see "Not supported" below for the exact boundary.
Typical use: mirror a table that gets old rows purged at the source (e.g. every 3 months) into a local H2 database with a much longer retention window, refreshed on a schedule with no manual bookkeeping:
PULL CITY TO ARCHIVE.CITY AS H2 MODE APPEND KEY(ID);
Run again next week, next month, whenever; only the rows added at the source since the last run are added locally; everything already pulled stays exactly as first captured.
Writing to a spreadsheet (AS XLSX / AS ODS)
<name> (before the dot): a plain file name, resolved to <default export folder>/<name>.xlsx or .ods. Unlike AS H2, there is no connection lookup: it's just a file. <tab> (after the dot): the tab to write. Writing a tab that already exists in the file erases and replaces just that tab: every other tab is left completely untouched, whether the file was built by an earlier PULL or by something else entirely (a hand-built spreadsheet, or one produced by EXPORT/DUMP). This is the main advantage over EXPORT/DUMP: several PULL calls, each naming a different tab, build up one multi-tab file over time.
| Case | Example |
|---|---|
| New file, one tab | PULL COUNTRY TO REPORT.COUNTRIES AS XLSX; |
Second PULL into the same file, different tab: both tabs now coexist | PULL CITY TO REPORT.CITIES AS XLSX; |
Re-running the first PULL: only COUNTRIES is rebuilt, CITIES is untouched | PULL COUNTRY TO REPORT.COUNTRIES AS XLSX; |
| ODS instead of Excel: same syntax, different extension | PULL COUNTRY TO REPORT.COUNTRIES AS ODS; |
Every file also gets a QUERIES tab, maintained automatically: one row per data tab, recording that tab's query, the date it was last pulled, the source connection, and how many rows it holds. Re-pulling a tab updates its row in place rather than adding a new one. QUERIES is a reserved tab name: you can't PULL a tab called QUERIES yourself.
Writing to a flat file (AS CSV / AS TXT / AS JSON / AS MD / AS HTML)
<name>: a plain file name with no dot at all (a flat file has no tab/table part to address after one), resolved to <default export folder>/<name>.<extension>. Always a full, clean overwrite of the whole file: there's no equivalent of AS XLSX/AS ODS's "just this tab" for a single flat file, and no per-file metadata (each file stays plain and self-contained: nothing is prepended to it).
| Case | Example |
|---|---|
| New CSV file | PULL COUNTRY TO COUNTRIES AS CSV; |
| New TXT file, tab-separated | PULL COUNTRY TO COUNTRIES AS TXT; |
| New JSON file: an array of objects, one per row | PULL COUNTRY TO COUNTRIES AS JSON; |
| New Markdown file: a GitHub-Flavored-Markdown table | PULL COUNTRY TO COUNTRIES AS MD; |
New HTML file: a bare <table> fragment, ready to paste into an email/wiki page | PULL COUNTRY TO COUNTRIES AS HTML; |
Re-running the same PULL: the file is fully rewritten, not appended to (every format) | PULL COUNTRY TO COUNTRIES AS CSV; |
CSV / TXT
The CSV field separator is not SET SEP. AS CSV uses a dedicated setting, CsvSeparator in BroadSQL.ini (a single character, e.g. ; or ,, defaults to ; if the setting is absent). AS TXT always uses a tab, regardless of any setting. Neither is affected by SET SEP, and there is no option to pick a different separator (e.g. a pipe) for a PULL-generated CSV/TXT file: a universal, well-formed file is preferred over a customizable one here. If you need a specific, non-standard separator, use EXPORT/DUMP with SET SEP instead (see Export & Dump).
Both formats quote a field that contains the separator, a double quote, or a line break (doubling any embedded quote), so a value like O'Brien, Jr. never corrupts the file even with a comma separator.
JSON
A single JSON array, one object per row, keyed by column name: the shape most tools expect when asked for "the data as JSON". A number is written as a real JSON number when it's exact; a DECIMAL too precise to survive as a JSON number (which most parsers treat as a double) falls back to a quoted string holding its exact value, the same rule already used for Excel. Dates/times are ISO-8601 (2024-01-15, 14:30:00, 2024-01-15T14:30:00), the convention most JSON-consuming tools expect. A NULL is a literal, unquoted null.
Markdown
A GitHub-Flavored-Markdown table (header row, separator row, data rows): pastes cleanly into GitHub/ GitLab issues and PRs, Confluence, Notion, and Slack. A literal | in a value is escaped as \|; an embedded line break becomes <br> (rendered as a line break by every major GFM tool, since a raw newline would otherwise split one row into two broken ones). A NULL is a blank cell.
HTML
A bare <table>...</table> fragment: deliberately not a full, standalone web page (no <html>/ <head>/<body>), so it pastes cleanly into an existing email or wiki page rather than fighting with a wrapper document. The header row is lightly styled inline (bold, light background) so the styling survives the paste. &, <, > are escaped; a NULL is an empty cell.
Not supported
| Attempt | Why |
|---|---|
PULL COUNTRY; | A destination and a format are always required. |
PULL SELECT * FROM COUNTRY TO WORKCOPY.COUNTRY AS H2; | A bare, unparenthesized query is never accepted; wrap it: PULL (SELECT * FROM COUNTRY) TO WORKCOPY.COUNTRY AS H2;. |
PULL COUNTRY TO WORKCOPY.COUNTRY AS DB; | AS DB was retired; use AS H2. |
PULL COUNTRY TO ORAPROD.COUNTRY AS H2; (ORAPROD already registered as a non-H2 connection) | PULL refuses to touch a connection that isn't H2. |
PULL COUNTRY TO C:\FOLDER\WORKCOPY.COUNTRY AS H2; | The database name can't be a path; register the connection once, then address it by name. |
PULL COUNTRY TO NUL.COUNTRY AS H2; | NUL is a reserved Windows device name. |
PULL COUNTRY TO PRODUCTIONCOPY2026.COUNTRY AS H2; | The database name is limited to 15 characters. |
PULL COUNTRY TO WORKCOPY.COUNTRY AS H2 MODE APPEND; | APPEND always requires KEY(<column>); there is no keyless variant. |
PULL CITY TO WORKCOPY.CITY AS H2 MODE APPEND KEY(ID, COUNTRYCODE); | APPEND supports a single key column only; composite keys are not supported. |
PULL CITY TO WORKCOPY.CITY AS H2 MODE APPEND KEY(); | KEY(...) needs a column name. |
PULL COUNTRY TO WORKCOPY.COUNTRY AS H2 MODE APPEND KEY(NAME); | NAME is a text column; APPEND KEY requires a numeric or date/time column. |
PULL (SELECT NAME, CONTINENT FROM COUNTRY) TO WORKCOPY.COUNTRY AS H2 MODE APPEND KEY(GEONAMEID); | The key column must be part of the query's own result columns. |
PULL (SELECT C.NAME, CI.NAME FROM COUNTRY C JOIN CITY CI ON CI.COUNTRYCODE = C.CODE) TO WORKCOPY.T AS H2 MODE APPEND KEY(ID); | APPEND KEY doesn't support a join as the source. |
PULL (SELECT * FROM COUNTRY, CITY) TO WORKCOPY.T AS H2 MODE APPEND KEY(ID); | Nor a comma-joined FROM (two tables). |
PULL (SELECT * FROM (SELECT * FROM CITY) T) TO WORKCOPY.CITY AS H2 MODE APPEND KEY(ID); | Nor a derived table (subquery) in FROM. |
PULL (SELECT CONTINENT, COUNT(*) FROM COUNTRY GROUP BY CONTINENT) TO WORKCOPY.T AS H2 MODE APPEND KEY(CONTINENT); | Nor GROUP BY. |
PULL (SELECT * FROM CITY ORDER BY POPULATION DESC) TO WORKCOPY.CITY AS H2 MODE APPEND KEY(ID); | Nor ORDER BY. |
PULL (SELECT * FROM CITY LIMIT 10) TO WORKCOPY.CITY AS H2 MODE APPEND KEY(ID); | Nor LIMIT/OFFSET/FETCH. |
PULL (SELECT ID, NAME FROM CITY WHERE COUNTRYCODE = 'FRA') TO ARCHIVE.CITY AS H2 MODE APPEND KEY(ID); (where ARCHIVE.CITY already has a POPULATION column too) | APPEND's columns must match the existing target table's columns exactly; no partial-column update. |
PULL COUNTRY TO WORKCOPY.COUNTRY AS H2 MODE MERGE; | Not implemented yet. |
PULL COUNTRY TO REPORT.QUERIES AS XLSX; | QUERIES is reserved for the automatic per-file query log. |
PULL COUNTRY TO REPORT.COUNTRIES AS XLSX MODE OVERWRITE; | AS XLSX/AS ODS don't accept a MODE clause; a tab is always erased and replaced. |
PULL (SELECT ID, LOCALNAME FROM COUNTRY) TO REPORT.PHOTOS AS XLSX; (if a column were a BLOB/CLOB) | Large objects, binary data, and structural/vendor-specific column types are rejected before the file is touched. |
PULL COUNTRY TO REPORT.COUNTRIES AS CSV; | Every flat-file format (AS CSV/AS TXT/AS JSON/AS MD/AS HTML) takes a bare file name, no dot; there's no tab/table to address in a flat file. |
PULL COUNTRY TO COUNTRIES AS JSON MODE OVERWRITE; | No flat-file format accepts a MODE clause; the whole file is always rewritten. |
PULL (SELECT ID, LOCALNAME FROM COUNTRY) TO PHOTOS AS MD; (if a column were a BLOB/CLOB) | Same rejection as AS XLSX above; applies identically to every flat-file format. |
Related pages
- Command reference: arguments, generated from the command's own code, always up to date.
- Export & Dump: the original way to write query results and whole tables to a file; still fully supported.
BroadSQL