writing-sql

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese
<!-- TIER:1 -->
<!-- TIER:1 -->

Writing SQL for RDBMS Integrations

为RDBMS集成编写SQL

RDBMS exports and imports use SQL queries to read from and write to relational databases. Queries are Handlebars templates -- the platform evaluates expressions like
{{{record.fieldName}}}
at runtime, then executes the resulting SQL against the connected database.
This skill covers what SQL to write and how to write it. For which adaptor type to use, connection setup, and queryType selection, see the related skills below.
RDBMS的导出和导入操作使用SQL查询来读写关系型数据库。查询采用Handlebars模板——平台会在运行时解析
{{{record.fieldName}}}
这类表达式,然后针对已连接的数据库执行生成的SQL语句。
本技能涵盖应编写何种SQL以及如何编写。关于适配器类型选择、连接配置和queryType选择,请参阅下方相关技能。

Quick Reference

快速参考

Context decision matrix

上下文决策矩阵

WhereFieldHandlebars prefixBracesExample
Export query
rdbms.query
record.
(parameterized) or none (standalone)
{{{ }}}
SELECT * FROM orders WHERE id = {{{record.id}}}
Export delta query
rdbms.query
platform-injected tokens
{{ }}
or
{{{ }}}
WHERE updated_at > '{{lastExportDateTime}}'
Export once mark-as-processed
rdbms.once.query
record.
{{{ }}}
UPDATE orders SET exported = true WHERE id = {{{record.id}}}
Import per_record query
rdbms.query[]
record.
{{{ }}}
INSERT INTO users (name) VALUES ('{{{record.name}}}')
Import per_page query
rdbms.query[]
batch_of_records
+
record.
{{{ }}}
{{#each batch_of_records}}...{{{record.field}}}...{{/each}}
Import bulk_load override
rdbms.bulkLoad.overrideMergeQuery
staging table ref
{{ }}
MERGE INTO target USING {{import.rdbms.bulkLoad.preMergeTemporaryTable}}
位置字段Handlebars前缀大括号示例
导出查询
rdbms.query
record.
(参数化)或无(独立查询)
{{{ }}}
SELECT * FROM orders WHERE id = {{{record.id}}}
增量导出查询
rdbms.query
平台注入的令牌
{{ }}
{{{ }}}
WHERE updated_at > '{{lastExportDateTime}}'
单次导出标记已处理
rdbms.once.query
record.
{{{ }}}
UPDATE orders SET exported = true WHERE id = {{{record.id}}}
单条记录导入查询
rdbms.query[]
record.
{{{ }}}
INSERT INTO users (name) VALUES ('{{{record.name}}}')
分页导入查询
rdbms.query[]
batch_of_records
+
record.
{{{ }}}
{{#each batch_of_records}}...{{{record.field}}}...{{/each}}
批量加载覆盖查询
rdbms.bulkLoad.overrideMergeQuery
临时表引用
{{ }}
MERGE INTO target USING {{import.rdbms.bulkLoad.preMergeTemporaryTable}}

Key syntax rules

核心语法规则

  1. Always use
    record.
    prefix
    (AFE 2.0). Never bare
    fieldName
    or
    data.fieldName
    .
  2. Prefer triple braces
    {{{ }}}
    for values in SQL. Double braces
    {{ }}
    auto-wrap values in single quotes in RDBMS context -- this silently corrupts numeric values and breaks SQL syntax.
  3. Add your own quotes for strings:
    '{{{record.name}}}'
    . Triple braces give you full control.
  4. No quotes for numbers:
    {{{record.quantity}}}
    .
  5. Import
    query
    is an array of strings
    , not a single string:
    ["INSERT INTO ..."]
    .
  6. Nested fields use dot notation:
    {{{record.address.city}}}
    .
  1. 始终使用
    record.
    前缀
    (AFE 2.0)。切勿直接使用
    fieldName
    data.fieldName
  2. **优先使用三大括号
    {{{ }}}
    **处理SQL中的值。双大括号
    {{ }}
    会在RDBMS上下文自动为值添加单引号——这会悄悄损坏数值类型并破坏SQL语法。
  3. 为字符串手动添加引号
    '{{{record.name}}}'
    。三大括号让你拥有完全控制权。
  4. 数值无需添加引号
    {{{record.quantity}}}
  5. 导入
    query
    是字符串数组
    ,而非单个字符串:
    ["INSERT INTO ..."]
  6. 嵌套字段使用点标记法
    {{{record.address.city}}}

Related skills

相关技能

  • configuring-exports > RDBMS -- adaptorType, delta/once mode, export-level config
  • configuring-imports > RDBMS -- queryType decision tree, bulkInsert/bulkLoad config
  • writing-handlebars -- full Handlebars syntax, helpers, block expressions
  • configuring-connections > RDBMS -- database connection setup
  • 配置导出 > RDBMS -- 适配器类型、增量/单次模式、导出级配置
  • 配置导入 > RDBMS -- queryType决策树、bulkInsert/bulkLoad配置
  • 编写Handlebars -- 完整Handlebars语法、辅助函数、块表达式
  • 配置连接 > RDBMS -- 数据库连接设置

Schema reference

架构参考

  • rdbms-export.yml -- export query and once fields
  • rdbms-import.yml -- import query, queryType, bulkInsert, bulkLoad fields
<!-- TIER:2 -->
  • rdbms-export.yml -- 导出查询及单次操作字段
  • rdbms-import.yml -- 导入查询、queryType、bulkInsert、bulkLoad字段
<!-- TIER:2 -->

How to Write a SQL Query

如何编写SQL查询

1. Discover the schema

1. 发现数据库架构

Query the live database to find tables and columns before writing SQL:
bash
celigo metadata types <connectionId>                    # List tables
celigo metadata fields <connectionId> <tableName>       # List columns and types
For Snowflake, use fully qualified names:
database.schema.table
.
在编写SQL前,先查询实时数据库以获取表和列信息:
bash
celigo metadata types <connectionId>                    # 列出表
celigo metadata fields <connectionId> <tableName>       # 列出列及类型
对于Snowflake,请使用完全限定名称:
database.schema.table

2. Determine the query pattern

2. 确定查询模式

Exports -- what kind of data fetch?
PatternExport typeQuery template
Full fetch
type: null
SELECT columns FROM table WHERE conditions
Delta/incremental
type: "delta"
SELECT ... WHERE updated_at > '{{lastExportDateTime}}'
Once (mark-as-processed)
type: "once"
rdbms.query
= SELECT unprocessed;
rdbms.once.query
= UPDATE to mark processed
Imports -- what kind of write operation?
PatternqueryTypeWhat to configure
INSERT with duplicate check
["per_record"]
rdbms.query
with INSERT ... ON CONFLICT / ON DUPLICATE KEY
UPDATE
["per_record"]
rdbms.query
with UPDATE ... WHERE
UPSERT / MERGE
["per_record"]
or
["bulk_load"]
per_record:
rdbms.query
with MERGE; bulk_load:
bulkLoad.primaryKeys
Pure INSERT (no checking)
["bulk_insert"]
rdbms.bulkInsert.tableName
and
batchSize
-- no SQL needed
Bulk load with upsert
["bulk_load"]
rdbms.bulkLoad.tableName
and
primaryKeys
-- auto-generates MERGE
Custom bulk merge
["bulk_load"]
bulkLoad.overrideMergeQuery: true
+ custom SQL referencing staging table
导出 -- 需要获取何种数据?
模式导出类型查询模板
全量获取
type: null
SELECT columns FROM table WHERE conditions
增量获取
type: "delta"
SELECT ... WHERE updated_at > '{{lastExportDateTime}}'
单次获取(标记已处理)
type: "once"
rdbms.query
= 查询未处理记录;
rdbms.once.query
= 更新标记为已处理
导入 -- 需要执行何种写入操作?
模式queryType需配置内容
带重复检查的INSERT
["per_record"]
rdbms.query
包含INSERT ... ON CONFLICT / ON DUPLICATE KEY
UPDATE
["per_record"]
rdbms.query
包含UPDATE ... WHERE
UPSERT / MERGE
["per_record"]
["bulk_load"]
per_record:
rdbms.query
包含MERGE;bulk_load:
bulkLoad.primaryKeys
纯INSERT(无检查)
["bulk_insert"]
rdbms.bulkInsert.tableName
batchSize
-- 无需编写SQL
带UPSERT的批量加载
["bulk_load"]
rdbms.bulkLoad.tableName
primaryKeys
-- 自动生成MERGE语句
自定义批量MERGE
["bulk_load"]
bulkLoad.overrideMergeQuery: true
+ 引用临时表的自定义SQL

3. Write the SQL

3. 编写SQL

Follow the database-specific dialect patterns below. Use
{{{record.fieldName}}}
for runtime values.
遵循下方的数据库方言模式。使用
{{{record.fieldName}}}
注入运行时值。

4. Test the query

4. 测试查询

bash
undefined
bash
undefined

Test an export query -- invoke returns real data

测试导出查询 -- 调用会返回真实数据

celigo exports invoke <exportId>
celigo exports invoke <exportId>

Test an import -- submit test records

测试导入 -- 提交测试记录

echo '[{"name":"test","email":"test@example.com"}]' | celigo imports invoke <importId>
undefined
echo '[{"name":"test","email":"test@example.com"}]' | celigo imports invoke <importId>
undefined

Export Query Patterns

导出查询模式

Standard SELECT

标准SELECT

sql
SELECT id, name, email, status
FROM customers
WHERE status = 'ACTIVE'
ORDER BY id
sql
SELECT id, name, email, status
FROM customers
WHERE status = 'ACTIVE'
ORDER BY id

Delta export (incremental)

增量导出(incremental)

Use
{{lastExportDateTime}}
(platform-injected, not from a record). The token resolves to the timestamp of the last successful export run.
sql
SELECT id, name, email, updated_at
FROM customers
WHERE updated_at > '{{lastExportDateTime}}'
ORDER BY updated_at ASC
For databases that need specific timestamp formats:
sql
-- Snowflake
WHERE updated_at > TO_TIMESTAMP('{{lastExportDateTime}}', 'YYYY-MM-DD HH24:MI:SS')

-- MySQL
WHERE updated_at > STR_TO_DATE('{{lastExportDateTime}}', '%Y-%m-%d %H:%i:%s')

-- PostgreSQL
WHERE updated_at > '{{lastExportDateTime}}'::timestamp
使用
{{lastExportDateTime}}
(平台注入,而非来自记录)。该令牌会解析为上次成功导出运行的时间戳。
sql
SELECT id, name, email, updated_at
FROM customers
WHERE updated_at > '{{lastExportDateTime}}'
ORDER BY updated_at ASC
对于需要特定时间戳格式的数据库:
sql
-- Snowflake
WHERE updated_at > TO_TIMESTAMP('{{lastExportDateTime}}', 'YYYY-MM-DD HH24:MI:SS')

-- MySQL
WHERE updated_at > STR_TO_DATE('{{lastExportDateTime}}', '%Y-%m-%d %H:%i:%s')

-- PostgreSQL
WHERE updated_at > '{{lastExportDateTime}}'::timestamp

Once export (mark-as-processed)

单次导出(标记已处理)

Two queries work together. The export
rdbms.query
fetches unprocessed records;
rdbms.once.query
marks each one after successful export.
json
{
  "type": "once",
  "rdbms": {
    "query": "SELECT id, name, email FROM orders WHERE exported = false",
    "once": {
      "query": "UPDATE orders SET exported = true WHERE id = {{{record.id}}}"
    }
  }
}
需要两个查询配合工作。导出的
rdbms.query
获取未处理记录;
rdbms.once.query
在成功导出后标记每条记录。
json
{
  "type": "once",
  "rdbms": {
    "query": "SELECT id, name, email FROM orders WHERE exported = false",
    "once": {
      "query": "UPDATE orders SET exported = true WHERE id = {{{record.id}}}"
    }
  }
}

JOINs

JOIN查询

sql
SELECT o.id, o.order_date, c.name AS customer_name, c.email,
       p.product_name, oi.quantity, oi.unit_price
FROM orders o
JOIN customers c ON o.customer_id = c.id
JOIN order_items oi ON o.id = oi.order_id
JOIN products p ON oi.product_id = p.id
WHERE o.status = 'SHIPPED'
  AND o.order_date > '{{lastExportDateTime}}'
sql
SELECT o.id, o.order_date, c.name AS customer_name, c.email,
       p.product_name, oi.quantity, oi.unit_price
FROM orders o
JOIN customers c ON o.customer_id = c.id
JOIN order_items oi ON o.id = oi.order_id
JOIN products p ON oi.product_id = p.id
WHERE o.status = 'SHIPPED'
  AND o.order_date > '{{lastExportDateTime}}'

Aggregations

聚合查询

sql
SELECT customer_id, COUNT(*) AS order_count, SUM(total) AS lifetime_value
FROM orders
WHERE status IN ('COMPLETED', 'SHIPPED')
GROUP BY customer_id
HAVING SUM(total) > 1000
sql
SELECT customer_id, COUNT(*) AS order_count, SUM(total) AS lifetime_value
FROM orders
WHERE status IN ('COMPLETED', 'SHIPPED')
GROUP BY customer_id
HAVING SUM(total) > 1000

Import Query Patterns

导入查询模式

All import queries use
{{{record.fieldName}}}
to inject values from incoming records. The
query
field is an array of strings.
所有导入查询均使用
{{{record.fieldName}}}
注入传入记录中的值。
query
字段是字符串数组

INSERT (per_record)

INSERT(单条记录)

json
{
  "rdbms": {
    "queryType": ["per_record"],
    "query": ["INSERT INTO customers (name, email, phone) VALUES ('{{{record.name}}}', '{{{record.email}}}', '{{{record.phone}}}')"]
  }
}
json
{
  "rdbms": {
    "queryType": ["per_record"],
    "query": ["INSERT INTO customers (name, email, phone) VALUES ('{{{record.name}}}', '{{{record.email}}}', '{{{record.phone}}}')"]
  }
}

UPDATE (per_record)

UPDATE(单条记录)

json
{
  "rdbms": {
    "queryType": ["per_record"],
    "query": ["UPDATE customers SET name = '{{{record.name}}}', email = '{{{record.email}}}' WHERE id = {{{record.id}}}"]
  }
}
json
{
  "rdbms": {
    "queryType": ["per_record"],
    "query": ["UPDATE customers SET name = '{{{record.name}}}', email = '{{{record.email}}}' WHERE id = {{{record.id}}}"]
  }
}

UPSERT -- database-specific

UPSERT -- 数据库方言特定

MySQL (ON DUPLICATE KEY UPDATE):
json
{
  "rdbms": {
    "queryType": ["per_record"],
    "query": ["INSERT INTO customers (id, name, email) VALUES ({{{record.id}}}, '{{{record.name}}}', '{{{record.email}}}') ON DUPLICATE KEY UPDATE name = '{{{record.name}}}', email = '{{{record.email}}}'"]
  }
}
PostgreSQL (ON CONFLICT):
json
{
  "rdbms": {
    "queryType": ["per_record"],
    "query": ["INSERT INTO customers (id, name, email) VALUES ({{{record.id}}}, '{{{record.name}}}', '{{{record.email}}}') ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, email = EXCLUDED.email"]
  }
}
Snowflake (MERGE):
json
{
  "rdbms": {
    "queryType": ["per_record"],
    "query": ["MERGE INTO customers AS t USING (SELECT {{{record.id}}} AS id, '{{{record.name}}}' AS name, '{{{record.email}}}' AS email) AS s ON t.id = s.id WHEN MATCHED THEN UPDATE SET t.name = s.name, t.email = s.email WHEN NOT MATCHED THEN INSERT (id, name, email) VALUES (s.id, s.name, s.email)"]
  }
}
SQL Server (MERGE):
json
{
  "rdbms": {
    "queryType": ["per_record"],
    "query": ["MERGE INTO customers AS t USING (SELECT {{{record.id}}} AS id, '{{{record.name}}}' AS name, '{{{record.email}}}' AS email) AS s ON t.id = s.id WHEN MATCHED THEN UPDATE SET t.name = s.name, t.email = s.email WHEN NOT MATCHED THEN INSERT (id, name, email) VALUES (s.id, s.name, s.email);"]
  }
}
MySQL(ON DUPLICATE KEY UPDATE):
json
{
  "rdbms": {
    "queryType": ["per_record"],
    "query": ["INSERT INTO customers (id, name, email) VALUES ({{{record.id}}}, '{{{record.name}}}', '{{{record.email}}}') ON DUPLICATE KEY UPDATE name = '{{{record.name}}}', email = '{{{record.email}}}'"]
  }
}
PostgreSQL(ON CONFLICT):
json
{
  "rdbms": {
    "queryType": ["per_record"],
    "query": ["INSERT INTO customers (id, name, email) VALUES ({{{record.id}}}, '{{{record.name}}}', '{{{record.email}}}') ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, email = EXCLUDED.email"]
  }
}
Snowflake(MERGE):
json
{
  "rdbms": {
    "queryType": ["per_record"],
    "query": ["MERGE INTO customers AS t USING (SELECT {{{record.id}}} AS id, '{{{record.name}}}' AS name, '{{{record.email}}}' AS email) AS s ON t.id = s.id WHEN MATCHED THEN UPDATE SET t.name = s.name, t.email = s.email WHEN NOT MATCHED THEN INSERT (id, name, email) VALUES (s.id, s.name, s.email)"]
  }
}
SQL Server(MERGE):
json
{
  "rdbms": {
    "queryType": ["per_record"],
    "query": ["MERGE INTO customers AS t USING (SELECT {{{record.id}}} AS id, '{{{record.name}}}' AS name, '{{{record.email}}}' AS email) AS s ON t.id = s.id WHEN MATCHED THEN UPDATE SET t.name = s.name, t.email = s.email WHEN NOT MATCHED THEN INSERT (id, name, email) VALUES (s.id, s.name, s.email);"]
  }
}

Multiple statements (per_record)

多语句(单条记录)

When you need to run multiple SQL statements per record, add them as separate array elements:
json
{
  "rdbms": {
    "queryType": ["per_record"],
    "query": [
      "INSERT INTO orders (id, customer_id, total) VALUES ({{{record.orderId}}}, {{{record.customerId}}}, {{{record.total}}})",
      "UPDATE customers SET last_order_date = CURRENT_TIMESTAMP WHERE id = {{{record.customerId}}}"
    ]
  }
}
当需要为每条记录运行多条SQL语句时,将它们作为单独的数组元素添加:
json
{
  "rdbms": {
    "queryType": ["per_record"],
    "query": [
      "INSERT INTO orders (id, customer_id, total) VALUES ({{{record.orderId}}}, {{{record.customerId}}}, {{{record.total}}})",
      "UPDATE customers SET last_order_date = CURRENT_TIMESTAMP WHERE id = {{{record.customerId}}}"
    ]
  }
}

Bulk insert (no SQL needed)

批量插入(无需编写SQL)

For pure INSERTs with no duplicate checking, use
bulkInsert
-- the platform generates the SQL:
json
{
  "rdbms": {
    "queryType": ["bulk_insert"],
    "bulkInsert": {
      "tableName": "customers",
      "batchSize": "1000"
    }
  }
}
Column mapping comes from
mappings[]
on the import (Mapper 2.0). Each mapping's
generate
value must match a column name in the target table.
对于无需重复检查的纯INSERT操作,使用
bulkInsert
——平台会自动生成SQL:
json
{
  "rdbms": {
    "queryType": ["bulk_insert"],
    "bulkInsert": {
      "tableName": "customers",
      "batchSize": "1000"
    }
  }
}
列映射来自导入的
mappings[]
(Mapper 2.0)。每个映射的
generate
值必须与目标表中的列名匹配。

Bulk load with auto-generated MERGE

自动生成MERGE的批量加载

For high-volume upsert on Snowflake or Azure Synapse, use
bulkLoad
with
primaryKeys
:
json
{
  "rdbms": {
    "queryType": ["bulk_load"],
    "bulkLoad": {
      "tableName": "WAREHOUSE.PUBLIC.CUSTOMERS",
      "primaryKeys": "id"
    }
  }
}
The platform stages data into a temporary table, then auto-generates a MERGE using the primary keys. For composite keys:
"primaryKeys": "order_id,product_id"
.
对于Snowflake或Azure Synapse上的高吞吐量UPSERT操作,使用带
primaryKeys
bulkLoad
json
{
  "rdbms": {
    "queryType": ["bulk_load"],
    "bulkLoad": {
      "tableName": "WAREHOUSE.PUBLIC.CUSTOMERS",
      "primaryKeys": "id"
    }
  }
}
平台会将数据暂存到临时表,然后使用主键自动生成MERGE语句。对于复合键:
"primaryKeys": "order_id,product_id"

Bulk load with custom merge

自定义MERGE的批量加载

When the auto-generated MERGE isn't sufficient (conditional updates, ignore-existing, multi-table ops), override it:
json
{
  "rdbms": {
    "queryType": ["bulk_load"],
    "bulkLoad": {
      "tableName": "WAREHOUSE.PUBLIC.CUSTOMERS",
      "primaryKeys": "id",
      "overrideMergeQuery": true
    }
  }
}
The custom SQL goes in the
rdbms.query
field (yes, even with
bulkLoad
). Reference the staging table via
{{import.rdbms.bulkLoad.preMergeTemporaryTable}}
:
sql
MERGE INTO WAREHOUSE.PUBLIC.CUSTOMERS AS t
USING {{import.rdbms.bulkLoad.preMergeTemporaryTable}} AS s
ON t.id = s.id
WHEN MATCHED AND s.updated_at > t.updated_at THEN
  UPDATE SET t.name = s.name, t.email = s.email, t.updated_at = s.updated_at
WHEN NOT MATCHED THEN
  INSERT (id, name, email, updated_at) VALUES (s.id, s.name, s.email, s.updated_at)
当自动生成的MERGE无法满足需求(条件更新、忽略已存在记录、多表操作)时,可以覆盖默认逻辑:
json
{
  "rdbms": {
    "queryType": ["bulk_load"],
    "bulkLoad": {
      "tableName": "WAREHOUSE.PUBLIC.CUSTOMERS",
      "primaryKeys": "id",
      "overrideMergeQuery": true
    }
  }
}
自定义SQL需放在
rdbms.query
字段中(即使使用
bulkLoad
也需如此)。通过
{{import.rdbms.bulkLoad.preMergeTemporaryTable}}
引用临时表:
sql
MERGE INTO WAREHOUSE.PUBLIC.CUSTOMERS AS t
USING {{import.rdbms.bulkLoad.preMergeTemporaryTable}} AS s
ON t.id = s.id
WHEN MATCHED AND s.updated_at > t.updated_at THEN
  UPDATE SET t.name = s.name, t.email = s.email, t.updated_at = s.updated_at
WHEN NOT MATCHED THEN
  INSERT (id, name, email, updated_at) VALUES (s.id, s.name, s.email, s.updated_at)

per_page batch operations

分页批量操作

per_page
gives you the entire page of records as
batch_of_records
. Use
{{#each}}
to iterate:
json
{
  "rdbms": {
    "queryType": ["per_page"],
    "query": ["INSERT INTO customers (name, email) VALUES {{#each batch_of_records}}('{{{record.name}}}', '{{{record.email}}}'){{#unless @last}},{{/unless}}{{/each}}"]
  }
}
per_page
会将整页记录作为
batch_of_records
提供。使用
{{#each}}
进行遍历:
json
{
  "rdbms": {
    "queryType": ["per_page"],
    "query": ["INSERT INTO customers (name, email) VALUES {{#each batch_of_records}}('{{{record.name}}}', '{{{record.email}}}'){{#unless @last}},{{/unless}}{{/each}}"]
  }
}

Dialect Patterns

数据库方言模式

Snowflake

Snowflake

sql
-- Fully qualified table names (required unless connection sets default schema)
SELECT * FROM MY_DATABASE.MY_SCHEMA.MY_TABLE

-- FLATTEN for semi-structured data (VARIANT columns)
SELECT f.value:name::STRING AS name, f.value:email::STRING AS email
FROM MY_TABLE, LATERAL FLATTEN(input => MY_TABLE.json_column) f

-- QUALIFY for window function filtering
SELECT * FROM orders
QUALIFY ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_date DESC) = 1

-- Timestamp handling
WHERE updated_at > TO_TIMESTAMP('{{lastExportDateTime}}', 'YYYY-MM-DD"T"HH24:MI:SS')

-- Case: Snowflake uppercases unquoted identifiers. Use double quotes to preserve case
SELECT "camelCaseColumn" FROM "MixedCaseTable"
sql
-- 完全限定表名(除非连接设置了默认架构,否则为必填)
SELECT * FROM MY_DATABASE.MY_SCHEMA.MY_TABLE

-- 对半结构化数据(VARIANT列)使用FLATTEN
SELECT f.value:name::STRING AS name, f.value:email::STRING AS email
FROM MY_TABLE, LATERAL FLATTEN(input => MY_TABLE.json_column) f

-- 使用QUALIFY过滤窗口函数结果
SELECT * FROM orders
QUALIFY ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_date DESC) = 1

-- 时间戳处理
WHERE updated_at > TO_TIMESTAMP('{{lastExportDateTime}}', 'YYYY-MM-DD"T"HH24:MI:SS')

-- 注意:Snowflake会将未加引号的标识符转为大写。使用双引号保留大小写
SELECT "camelCaseColumn" FROM "MixedCaseTable"

PostgreSQL

PostgreSQL

sql
-- JSONB operators
SELECT data->>'name' AS name, data->'address'->>'city' AS city
FROM customers WHERE data @> '{"active": true}'

-- ILIKE for case-insensitive matching
SELECT * FROM products WHERE name ILIKE '%widget%'

-- ON CONFLICT for upsert
INSERT INTO customers (id, name, email) VALUES ({{{record.id}}}, '{{{record.name}}}', '{{{record.email}}}')
ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, email = EXCLUDED.email

-- Array operations
SELECT * FROM users WHERE 'admin' = ANY(roles)

-- CTEs
WITH recent_orders AS (
  SELECT * FROM orders WHERE order_date > '{{lastExportDateTime}}'
)
SELECT c.name, r.total FROM customers c JOIN recent_orders r ON c.id = r.customer_id
sql
-- JSONB操作符
SELECT data->>'name' AS name, data->'address'->>'city' AS city
FROM customers WHERE data @> '{"active": true}'

-- 使用ILIKE进行不区分大小写匹配
SELECT * FROM products WHERE name ILIKE '%widget%'

-- 使用ON CONFLICT实现UPSERT
INSERT INTO customers (id, name, email) VALUES ({{{record.id}}}, '{{{record.name}}}', '{{{record.email}}}')
ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, email = EXCLUDED.email

-- 数组操作
SELECT * FROM users WHERE 'admin' = ANY(roles)

-- CTE(公共表表达式)
WITH recent_orders AS (
  SELECT * FROM orders WHERE order_date > '{{lastExportDateTime}}'
)
SELECT c.name, r.total FROM customers c JOIN recent_orders r ON c.id = r.customer_id

MySQL / MariaDB

MySQL / MariaDB

sql
-- ON DUPLICATE KEY UPDATE for upsert
INSERT INTO customers (id, name, email)
VALUES ({{{record.id}}}, '{{{record.name}}}', '{{{record.email}}}')
ON DUPLICATE KEY UPDATE name = VALUES(name), email = VALUES(email)

-- JSON_EXTRACT for JSON columns
SELECT JSON_EXTRACT(data, '$.name') AS name FROM customers

-- GROUP_CONCAT for string aggregation
SELECT customer_id, GROUP_CONCAT(product_name SEPARATOR ', ') AS products
FROM order_items GROUP BY customer_id

-- IFNULL for null handling
SELECT IFNULL(middle_name, '') AS middle_name FROM users

-- Timestamp formatting
WHERE updated_at > STR_TO_DATE('{{lastExportDateTime}}', '%Y-%m-%d %H:%i:%s')
sql
-- 使用ON DUPLICATE KEY UPDATE实现UPSERT
INSERT INTO customers (id, name, email)
VALUES ({{{record.id}}}, '{{{record.name}}}', '{{{record.email}}}')
ON DUPLICATE KEY UPDATE name = VALUES(name), email = VALUES(email)

-- 对JSON列使用JSON_EXTRACT
SELECT JSON_EXTRACT(data, '$.name') AS name FROM customers

-- 使用GROUP_CONCAT进行字符串聚合
SELECT customer_id, GROUP_CONCAT(product_name SEPARATOR ', ') AS products
FROM order_items GROUP BY customer_id

-- 使用IFNULL处理空值
SELECT IFNULL(middle_name, '') AS middle_name FROM users

-- 时间戳格式化
WHERE updated_at > STR_TO_DATE('{{lastExportDateTime}}', '%Y-%m-%d %H:%i:%s')

SQL Server / Azure Synapse

SQL Server / Azure Synapse

sql
-- MERGE with required semicolon terminator
MERGE INTO customers AS t
USING (SELECT {{{record.id}}} AS id, '{{{record.name}}}' AS name) AS s
ON t.id = s.id
WHEN MATCHED THEN UPDATE SET t.name = s.name
WHEN NOT MATCHED THEN INSERT (id, name) VALUES (s.id, s.name);

-- TOP instead of LIMIT
SELECT TOP 100 * FROM orders ORDER BY order_date DESC

-- STRING_AGG for string aggregation (SQL Server 2017+)
SELECT customer_id, STRING_AGG(product_name, ', ') AS products
FROM order_items GROUP BY customer_id

-- CROSS APPLY for row-valued functions
SELECT c.name, o.total FROM customers c
CROSS APPLY (SELECT TOP 1 * FROM orders WHERE customer_id = c.id ORDER BY order_date DESC) o

-- Bracket identifiers for reserved words or special characters
SELECT [order], [name] FROM [my-table]
sql
-- MERGE语句必须以分号结尾
MERGE INTO customers AS t
USING (SELECT {{{record.id}}} AS id, '{{{record.name}}}' AS name) AS s
ON t.id = s.id
WHEN MATCHED THEN UPDATE SET t.name = s.name
WHEN NOT MATCHED THEN INSERT (id, name) VALUES (s.id, s.name);

-- 使用TOP替代LIMIT
SELECT TOP 100 * FROM orders ORDER BY order_date DESC

-- 使用STRING_AGG进行字符串聚合(SQL Server 2017+)
SELECT customer_id, STRING_AGG(product_name, ', ') AS products
FROM order_items GROUP BY customer_id

-- 使用CROSS APPLY处理行值函数
SELECT c.name, o.total FROM customers c
CROSS APPLY (SELECT TOP 1 * FROM orders WHERE customer_id = c.id ORDER BY order_date DESC) o

-- 对保留字或特殊字符使用方括号标识符
SELECT [order], [name] FROM [my-table]

Oracle

Oracle

sql
-- NVL for null handling
SELECT NVL(middle_name, '') AS middle_name FROM users

-- ROWNUM for limiting results (pre-12c)
SELECT * FROM (SELECT * FROM orders ORDER BY order_date DESC) WHERE ROWNUM <= 100

-- FETCH FIRST for limiting results (12c+)
SELECT * FROM orders ORDER BY order_date DESC FETCH FIRST 100 ROWS ONLY

-- LISTAGG for string aggregation
SELECT customer_id, LISTAGG(product_name, ', ') WITHIN GROUP (ORDER BY product_name)
FROM order_items GROUP BY customer_id

-- MERGE
MERGE INTO customers t USING (SELECT {{{record.id}}} AS id, '{{{record.name}}}' AS name FROM dual) s
ON (t.id = s.id)
WHEN MATCHED THEN UPDATE SET t.name = s.name
WHEN NOT MATCHED THEN INSERT (id, name) VALUES (s.id, s.name)
sql
-- 使用NVL处理空值
SELECT NVL(middle_name, '') AS middle_name FROM users

-- 使用ROWNUM限制结果(12c之前版本)
SELECT * FROM (SELECT * FROM orders ORDER BY order_date DESC) WHERE ROWNUM <= 100

-- 使用FETCH FIRST限制结果(12c及之后版本)
SELECT * FROM orders ORDER BY order_date DESC FETCH FIRST 100 ROWS ONLY

-- 使用LISTAGG进行字符串聚合
SELECT customer_id, LISTAGG(product_name, ', ') WITHIN GROUP (ORDER BY product_name)
FROM order_items GROUP BY customer_id

-- MERGE语句
MERGE INTO customers t USING (SELECT {{{record.id}}} AS id, '{{{record.name}}}' AS name FROM dual) s
ON (t.id = s.id)
WHEN MATCHED THEN UPDATE SET t.name = s.name
WHEN NOT MATCHED THEN INSERT (id, name) VALUES (s.id, s.name)

BigQuery

BigQuery

sql
-- Backtick identifiers
SELECT * FROM `project.dataset.table`

-- UNNEST for array columns
SELECT * FROM `orders`, UNNEST(items) AS item

-- STRUCT access
SELECT address.city, address.state FROM customers

-- SAFE_DIVIDE to avoid division by zero
SELECT SAFE_DIVIDE(revenue, orders) AS avg_order_value FROM metrics

-- Timestamp handling
WHERE updated_at > TIMESTAMP('{{lastExportDateTime}}')
sql
-- 使用反引号标识符
SELECT * FROM `project.dataset.table`

-- 对数组列使用UNNEST
SELECT * FROM `orders`, UNNEST(items) AS item

-- STRUCT访问
SELECT address.city, address.state FROM customers

-- 使用SAFE_DIVIDE避免除零错误
SELECT SAFE_DIVIDE(revenue, orders) AS avg_order_value FROM metrics

-- 时间戳处理
WHERE updated_at > TIMESTAMP('{{lastExportDateTime}}')

Redshift

Redshift

sql
-- PostgreSQL-based syntax
SELECT * FROM orders WHERE status = 'ACTIVE'

-- LISTAGG for string aggregation
SELECT customer_id, LISTAGG(product_name, ', ') WITHIN GROUP (ORDER BY product_name)
FROM order_items GROUP BY customer_id

-- COPY-oriented -- bulk_load uses Redshift COPY under the hood
-- For custom merge logic, use overrideMergeQuery with staging table reference
<!-- TIER:3 -->
sql
-- 基于PostgreSQL的语法
SELECT * FROM orders WHERE status = 'ACTIVE'

-- 使用LISTAGG进行字符串聚合
SELECT customer_id, LISTAGG(product_name, ', ') WITHIN GROUP (ORDER BY product_name)
FROM order_items GROUP BY customer_id

-- 基于COPY的机制——bulkLoad底层使用Redshift COPY
-- 如需自定义MERGE逻辑,使用overrideMergeQuery并引用临时表
<!-- TIER:3 -->

Pre-Submit Checklist

提交前检查清单

Export queries

导出查询

  • SELECT query is syntactically valid for the target database dialect
  • Delta uses
    {{lastExportDateTime}}
    in the query, NOT a
    delta
    object inside
    rdbms
  • Once export has both queries --
    rdbms.query
    for SELECT and
    rdbms.once.query
    for UPDATE
  • Table and column names exist -- verify with
    celigo metadata types/fields <connectionId>
  • Snowflake uses fully qualified names --
    database.schema.table
    unless the connection sets defaults
  • SELECT查询符合目标数据库方言的语法规范
  • 增量查询使用
    {{lastExportDateTime}}
    ,而非在
    rdbms
    内设置
    delta
    对象
  • 单次导出包含两个查询 --
    rdbms.query
    用于SELECT,
    rdbms.once.query
    用于UPDATE
  • 表和列名真实存在 -- 使用
    celigo metadata types/fields <connectionId>
    验证
  • Snowflake使用完全限定名称 --
    database.schema.table
    ,除非连接已设置默认架构

Import queries

导入查询

  • query
    is an array of strings
    --
    ["INSERT INTO ..."]
    , not
    "INSERT INTO ..."
  • queryType
    matches the operation
    --
    ["per_record"]
    for UPDATE/UPSERT,
    ["bulk_insert"]
    for pure INSERT,
    ["bulk_load"]
    for high-volume
  • All
    {{{record.fieldName}}}
    paths match incoming data
    -- invoke the upstream export to verify field names
  • Strings are quoted, numbers are not --
    '{{{record.name}}}'
    vs
    {{{record.id}}}
  • Uses
    record.
    prefix
    -- not bare
    fieldName
    or
    data.fieldName
  • Uses triple braces
    {{{ }}}
    -- double braces auto-wrap in quotes, breaking numeric values and SQL syntax
  • bulkInsert/bulkLoad not set alongside query -- these are mutually exclusive with the
    query
    field (except
    overrideMergeQuery
    )
  • query
    是字符串数组
    --
    ["INSERT INTO ..."]
    ,而非
    "INSERT INTO ..."
  • queryType
    与操作匹配
    --
    ["per_record"]
    用于UPDATE/UPSERT,
    ["bulk_insert"]
    用于纯INSERT,
    ["bulk_load"]
    用于高吞吐量操作
  • 所有
    {{{record.fieldName}}}
    路径与传入数据匹配
    -- 调用上游导出以验证字段名
  • 字符串添加引号,数值无需添加 --
    '{{{record.name}}}'
    对比
    {{{record.id}}}
  • 使用
    record.
    前缀
    -- 不直接使用
    fieldName
    data.fieldName
  • 使用三大括号
    {{{ }}}
    -- 双大括号会自动添加引号,破坏数值类型和SQL语法
  • bulkInsert/bulkLoad未与query同时设置 -- 这些与
    query
    字段互斥(
    overrideMergeQuery
    除外)

Cross-resource

跨资源检查

  • Connection type is RDBMS --
    type
    on the connection matches one of: mysql, mariadb, postgresql, mssql, azuresynapse, oracle, snowflake, bigquery, redshift
  • SQL dialect matches the database -- MERGE syntax differs between Snowflake, SQL Server, Oracle, PostgreSQL
  • 连接类型为RDBMS -- 连接的
    type
    为以下之一:mysql、mariadb、postgresql、mssql、azuresynapse、oracle、snowflake、bigquery、redshift
  • SQL方言与数据库匹配 -- Snowflake、SQL Server、Oracle、PostgreSQL的MERGE语法各不相同

Gotchas

常见陷阱

  1. Double braces auto-format in RDBMS.
    {{record.name}}
    outputs
    'value'
    (wrapped in quotes).
    {{{record.name}}}
    outputs
    value
    (raw). Use triple braces and add your own quotes for strings -- this gives you control and avoids double-quoting or broken numeric values.
  2. Import
    query
    must be an array.
    "query": "INSERT INTO ..."
    fails silently or throws a Cast error. Always use
    ["INSERT INTO ..."]
    .
  3. queryType
    values are specific.
    Use
    ["per_record"]
    ,
    ["bulk_insert"]
    ,
    ["per_page"]
    ,
    ["bulk_load"]
    . Do NOT use
    ["INSERT"]
    or
    ["UPDATE"]
    as standalone values.
  4. Snowflake rejects legacy
    queryType
    values on PUT.
    insert
    /
    update
    may work on POST but fail on PUT. Use
    per_record
    or
    bulk_insert
    from the start.
  5. Missing
    record.
    prefix produces empty values.
    {{{name}}}
    resolves to nothing. Always use
    {{{record.name}}}
    .
  6. Snowflake requires fully qualified table names.
    database.schema.table
    unless the connection sets a default schema. Unqualified names fail silently or hit the wrong table.
  7. per_page
    has a different Handlebars context.
    The context is
    batch_of_records
    , not a single record. Use
    {{#each batch_of_records}}...{{{record.fieldName}}}...{{/each}}
    .
  8. bulkLoad.overrideMergeQuery
    references a staging table.
    Use
    {{import.rdbms.bulkLoad.preMergeTemporaryTable}}
    -- not the target table name.
  9. SQL Server MERGE requires a semicolon terminator. Missing
    ;
    at the end causes syntax errors.
  10. once.query
    runs per record, not per batch.
    The
    {{record.id}}
    in the once query refers to the current exported record. Don't write batch UPDATE statements here.
  11. Don't put delta config inside
    rdbms
    .
    There's no
    rdbms.delta
    property. Delta is handled by embedding
    {{lastExportDateTime}}
    directly in the SQL query text.
  12. NULL handling varies by dialect. Use
    NVL
    (Oracle),
    IFNULL
    (MySQL),
    COALESCE
    (standard/Snowflake/PostgreSQL/SQL Server). Don't assume one works everywhere.
  1. RDBMS中双大括号会自动格式化
    {{record.name}}
    会输出
    'value'
    (带引号),
    {{{record.name}}}
    会输出
    value
    (原始值)。针对字符串请使用三大括号并自行添加引号——这样你可以完全控制格式,避免重复引号或破坏数值类型。
  2. 导入
    query
    必须是数组
    "query": "INSERT INTO ..."
    会静默失败或抛出类型转换错误。请始终使用
    ["INSERT INTO ..."]
  3. queryType
    值有特定要求
    。使用
    ["per_record"]
    ["bulk_insert"]
    ["per_page"]
    ["bulk_load"]
    。请勿使用
    ["INSERT"]
    ["UPDATE"]
    作为独立值。
  4. Snowflake在PUT请求时拒绝旧版
    queryType
    insert
    /
    update
    可能在POST请求中生效,但在PUT请求中失败。请从一开始就使用
    per_record
    bulk_insert
  5. 缺少
    record.
    前缀会导致值为空
    {{{name}}}
    会解析为空。请始终使用
    {{{record.name}}}
  6. Snowflake要求使用完全限定表名
    database.schema.table
    ,除非连接已设置默认架构。非限定名称会静默失败或访问错误的表。
  7. per_page
    的Handlebars上下文不同
    。上下文是
    batch_of_records
    ,而非单条记录。请使用
    {{#each batch_of_records}}...{{{record.fieldName}}}...{{/each}}
  8. bulkLoad.overrideMergeQuery
    引用临时表
    。使用
    {{import.rdbms.bulkLoad.preMergeTemporaryTable}}
    ——而非目标表名。
  9. SQL Server的MERGE语句必须以分号结尾。缺少末尾的
    ;
    会导致语法错误。
  10. once.query
    逐条记录运行,而非批量运行
    。once查询中的
    {{record.id}}
    指当前导出的记录。请勿在此处编写批量UPDATE语句。
  11. 请勿在
    rdbms
    内设置增量配置
    。不存在
    rdbms.delta
    属性。增量处理需通过在SQL查询文本中直接嵌入
    {{lastExportDateTime}}
    实现。
  12. 空值处理因方言而异。使用
    NVL
    (Oracle)、
    IFNULL
    (MySQL)、
    COALESCE
    (标准/Snowflake/PostgreSQL/SQL Server)。不要假设某一种函数在所有数据库中都生效。

Common Errors

常见错误

ErrorLikely CauseFix
Cast error
or
Invalid query format
query
is a string instead of an array
Change to
["SQL here"]
Empty values in SQL /
NULL
where data expected
Missing
record.
prefix in Handlebars
Use
{{{record.fieldName}}}
'42'
instead of
42
for numeric field
Double braces auto-quotingSwitch to triple braces
{{{ }}}
Compilation error: Object does not exist
(Snowflake)
Unqualified table nameUse
database.schema.table
Invalid value for queryType
on PUT
Legacy
insert
/
update
queryType
Use
per_record
or
bulk_insert
Merge statement must be terminated by ;
(SQL Server)
Missing semicolon at end of MERGEAdd
;
after the final clause
Ambiguous column reference
JOIN without table aliasPrefix columns with table aliases
Syntax error near ON DUPLICATE KEY
Using MySQL syntax on PostgreSQL/SnowflakeUse the correct dialect:
ON CONFLICT
(Postgres) or
MERGE
(Snowflake)
Column count doesn't match value count
Mismatch between INSERT columns and VALUESVerify column list matches the number of
{{{record.x}}}
values
Permission denied for table
Connection user lacks INSERT/UPDATE grantsCheck database permissions for the connection user
错误可能原因修复方案
Cast error
Invalid query format
query
是字符串而非数组
修改为
["SQL语句"]
SQL中出现空值 / 预期有数据但显示
NULL
Handlebars中缺少
record.
前缀
使用
{{{record.fieldName}}}
数值字段显示为
'42'
而非
42
双大括号自动添加引号切换为三大括号
{{{ }}}
Compilation error: Object does not exist
(Snowflake)
表名称非限定使用
database.schema.table
PUT请求时
Invalid value for queryType
使用旧版
insert
/
update
queryType
使用
per_record
bulk_insert
Merge statement must be terminated by ;
(SQL Server)
MERGE语句末尾缺少分号在最后一个子句后添加
;
Ambiguous column reference
JOIN未使用表别名为列添加表别名前缀
Syntax error near ON DUPLICATE KEY
在PostgreSQL/Snowflake上使用MySQL语法使用正确的方言:
ON CONFLICT
(Postgres)或
MERGE
(Snowflake)
Column count doesn't match value count
INSERT列与VALUES数量不匹配验证列列表与
{{{record.x}}}
值的数量一致
Permission denied for table
连接用户缺少INSERT/UPDATE权限检查连接用户的数据库权限