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. Oracle's TIMESTAMP WITH TIME ZONE and TIMESTAMP WITH LOCAL TIME ZONE columns are supported despite not being one of the standard JDBC date/time types: every format writes them as a plain timestamp, without the zone or offset.
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 again at any time.
<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, kept current by re-running the same command, 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