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 at runtime, then executes the resulting SQL against the connected database.
{{{record.fieldName}}}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模板——平台会在运行时解析这类表达式,然后针对已连接的数据库执行生成的SQL语句。
{{{record.fieldName}}}本技能涵盖应编写何种SQL以及如何编写。关于适配器类型选择、连接配置和queryType选择,请参阅下方相关技能。
Quick Reference
快速参考
Context decision matrix
上下文决策矩阵
| Where | Field | Handlebars prefix | Braces | Example |
|---|---|---|---|---|
| Export query | | | | |
| Export delta query | | platform-injected tokens | | |
| Export once mark-as-processed | | | | |
| Import per_record query | | | | |
| Import per_page query | | | | |
| Import bulk_load override | | staging table ref | | |
| 位置 | 字段 | Handlebars前缀 | 大括号 | 示例 |
|---|---|---|---|---|
| 导出查询 | | | | |
| 增量导出查询 | | 平台注入的令牌 | | |
| 单次导出标记已处理 | | | | |
| 单条记录导入查询 | | | | |
| 分页导入查询 | | | | |
| 批量加载覆盖查询 | | 临时表引用 | | |
Key syntax rules
核心语法规则
- Always use prefix (AFE 2.0). Never bare
record.orfieldName.data.fieldName - 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.{{ }} - Add your own quotes for strings: . Triple braces give you full control.
'{{{record.name}}}' - No quotes for numbers: .
{{{record.quantity}}} - Import is an array of strings, not a single string:
query.["INSERT INTO ..."] - Nested fields use dot notation: .
{{{record.address.city}}}
- 始终使用前缀(AFE 2.0)。切勿直接使用
record.或fieldName。data.fieldName - **优先使用三大括号**处理SQL中的值。双大括号
{{{ }}}会在RDBMS上下文自动为值添加单引号——这会悄悄损坏数值类型并破坏SQL语法。{{ }} - 为字符串手动添加引号:。三大括号让你拥有完全控制权。
'{{{record.name}}}' - 数值无需添加引号:。
{{{record.quantity}}} - 导入是字符串数组,而非单个字符串:
query。["INSERT INTO ..."] - 嵌套字段使用点标记法:。
{{{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
- rdbms-export.yml -- 导出查询及单次操作字段
- rdbms-import.yml -- 导入查询、queryType、bulkInsert、bulkLoad字段
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 typesFor Snowflake, use fully qualified names: .
database.schema.table在编写SQL前,先查询实时数据库以获取表和列信息:
bash
celigo metadata types <connectionId> # 列出表
celigo metadata fields <connectionId> <tableName> # 列出列及类型对于Snowflake,请使用完全限定名称:。
database.schema.table2. Determine the query pattern
2. 确定查询模式
Exports -- what kind of data fetch?
| Pattern | Export type | Query template |
|---|---|---|
| Full fetch | | |
| Delta/incremental | | |
| Once (mark-as-processed) | | |
Imports -- what kind of write operation?
| Pattern | queryType | What to configure |
|---|---|---|
| INSERT with duplicate check | | |
| UPDATE | | |
| UPSERT / MERGE | | per_record: |
| Pure INSERT (no checking) | | |
| Bulk load with upsert | | |
| Custom bulk merge | | |
导出 -- 需要获取何种数据?
| 模式 | 导出类型 | 查询模板 |
|---|---|---|
| 全量获取 | | |
| 增量获取 | | |
| 单次获取(标记已处理) | | |
导入 -- 需要执行何种写入操作?
| 模式 | queryType | 需配置内容 |
|---|---|---|
| 带重复检查的INSERT | | |
| UPDATE | | |
| UPSERT / MERGE | | per_record: |
| 纯INSERT(无检查) | | |
| 带UPSERT的批量加载 | | |
| 自定义批量MERGE | | |
3. Write the SQL
3. 编写SQL
Follow the database-specific dialect patterns below. Use for runtime values.
{{{record.fieldName}}}遵循下方的数据库方言模式。使用注入运行时值。
{{{record.fieldName}}}4. Test the query
4. 测试查询
bash
undefinedbash
undefinedTest 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>
undefinedecho '[{"name":"test","email":"test@example.com"}]' | celigo imports invoke <importId>
undefinedExport Query Patterns
导出查询模式
Standard SELECT
标准SELECT
sql
SELECT id, name, email, status
FROM customers
WHERE status = 'ACTIVE'
ORDER BY idsql
SELECT id, name, email, status
FROM customers
WHERE status = 'ACTIVE'
ORDER BY idDelta export (incremental)
增量导出(incremental)
Use (platform-injected, not from a record). The token resolves to the timestamp of the last successful export run.
{{lastExportDateTime}}sql
SELECT id, name, email, updated_at
FROM customers
WHERE updated_at > '{{lastExportDateTime}}'
ORDER BY updated_at ASCFor 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}}'::timestampOnce export (mark-as-processed)
单次导出(标记已处理)
Two queries work together. The export fetches unprocessed records; marks each one after successful export.
rdbms.queryrdbms.once.queryjson
{
"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.queryrdbms.once.queryjson
{
"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) > 1000sql
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) > 1000Import Query Patterns
导入查询模式
All import queries use to inject values from incoming records. The field is an array of strings.
{{{record.fieldName}}}query所有导入查询均使用注入传入记录中的值。字段是字符串数组。
{{{record.fieldName}}}queryINSERT (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 -- the platform generates the SQL:
bulkInsertjson
{
"rdbms": {
"queryType": ["bulk_insert"],
"bulkInsert": {
"tableName": "customers",
"batchSize": "1000"
}
}
}Column mapping comes from on the import (Mapper 2.0). Each mapping's value must match a column name in the target table.
mappings[]generate对于无需重复检查的纯INSERT操作,使用——平台会自动生成SQL:
bulkInsertjson
{
"rdbms": {
"queryType": ["bulk_insert"],
"bulkInsert": {
"tableName": "customers",
"batchSize": "1000"
}
}
}列映射来自导入的(Mapper 2.0)。每个映射的值必须与目标表中的列名匹配。
mappings[]generateBulk load with auto-generated MERGE
自动生成MERGE的批量加载
For high-volume upsert on Snowflake or Azure Synapse, use with :
bulkLoadprimaryKeysjson
{
"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操作,使用带的:
primaryKeysbulkLoadjson
{
"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 field (yes, even with ). Reference the staging table via :
rdbms.querybulkLoad{{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.querybulkLoad{{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_pagebatch_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}}"]
}
}per_pagebatch_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_idsql
-- 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_idMySQL / 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 referencesql
-- 基于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并引用临时表Pre-Submit Checklist
提交前检查清单
Export queries
导出查询
- SELECT query is syntactically valid for the target database dialect
- Delta uses in the query, NOT a
{{lastExportDateTime}}object insidedeltardbms - Once export has both queries -- for SELECT and
rdbms.queryfor UPDATErdbms.once.query - Table and column names exist -- verify with
celigo metadata types/fields <connectionId> - Snowflake uses fully qualified names -- unless the connection sets defaults
database.schema.table
- SELECT查询符合目标数据库方言的语法规范
- 增量查询使用,而非在
{{lastExportDateTime}}内设置rdbms对象delta - 单次导出包含两个查询 -- 用于SELECT,
rdbms.query用于UPDATErdbms.once.query - 表和列名真实存在 -- 使用验证
celigo metadata types/fields <connectionId> - Snowflake使用完全限定名称 -- ,除非连接已设置默认架构
database.schema.table
Import queries
导入查询
- is an array of strings --
query, not["INSERT INTO ..."]"INSERT INTO ..." - matches the operation --
queryTypefor UPDATE/UPSERT,["per_record"]for pure INSERT,["bulk_insert"]for high-volume["bulk_load"] - All paths match incoming data -- invoke the upstream export to verify field names
{{{record.fieldName}}} - Strings are quoted, numbers are not -- vs
'{{{record.name}}}'{{{record.id}}} - Uses prefix -- not bare
record.orfieldNamedata.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 field (except
query)overrideMergeQuery
- 是字符串数组 --
query,而非["INSERT INTO ..."]"INSERT INTO ..." - 与操作匹配 --
queryType用于UPDATE/UPSERT,["per_record"]用于纯INSERT,["bulk_insert"]用于高吞吐量操作["bulk_load"] - 所有路径与传入数据匹配 -- 调用上游导出以验证字段名
{{{record.fieldName}}} - 字符串添加引号,数值无需添加 -- 对比
'{{{record.name}}}'{{{record.id}}} - 使用前缀 -- 不直接使用
record.或fieldNamedata.fieldName - 使用三大括号-- 双大括号会自动添加引号,破坏数值类型和SQL语法
{{{ }}} - bulkInsert/bulkLoad未与query同时设置 -- 这些与字段互斥(
query除外)overrideMergeQuery
Cross-resource
跨资源检查
- Connection type is RDBMS -- on the connection matches one of: mysql, mariadb, postgresql, mssql, azuresynapse, oracle, snowflake, bigquery, redshift
type - SQL dialect matches the database -- MERGE syntax differs between Snowflake, SQL Server, Oracle, PostgreSQL
- 连接类型为RDBMS -- 连接的为以下之一:mysql、mariadb、postgresql、mssql、azuresynapse、oracle、snowflake、bigquery、redshift
type - SQL方言与数据库匹配 -- Snowflake、SQL Server、Oracle、PostgreSQL的MERGE语法各不相同
Gotchas
常见陷阱
- Double braces auto-format in RDBMS. outputs
{{record.name}}(wrapped in quotes).'value'outputs{{{record.name}}}(raw). Use triple braces and add your own quotes for strings -- this gives you control and avoids double-quoting or broken numeric values.value - Import must be an array.
queryfails silently or throws a Cast error. Always use"query": "INSERT INTO ...".["INSERT INTO ..."] - values are specific. Use
queryType,["per_record"],["bulk_insert"],["per_page"]. Do NOT use["bulk_load"]or["INSERT"]as standalone values.["UPDATE"] - Snowflake rejects legacy values on PUT.
queryType/insertmay work on POST but fail on PUT. Useupdateorper_recordfrom the start.bulk_insert - Missing prefix produces empty values.
record.resolves to nothing. Always use{{{name}}}.{{{record.name}}} - Snowflake requires fully qualified table names. unless the connection sets a default schema. Unqualified names fail silently or hit the wrong table.
database.schema.table - has a different Handlebars context. The context is
per_page, not a single record. Usebatch_of_records.{{#each batch_of_records}}...{{{record.fieldName}}}...{{/each}} - references a staging table. Use
bulkLoad.overrideMergeQuery-- not the target table name.{{import.rdbms.bulkLoad.preMergeTemporaryTable}} - SQL Server MERGE requires a semicolon terminator. Missing at the end causes syntax errors.
; - runs per record, not per batch. The
once.queryin the once query refers to the current exported record. Don't write batch UPDATE statements here.{{record.id}} - Don't put delta config inside . There's no
rdbmsproperty. Delta is handled by embeddingrdbms.deltadirectly in the SQL query text.{{lastExportDateTime}} - NULL handling varies by dialect. Use (Oracle),
NVL(MySQL),IFNULL(standard/Snowflake/PostgreSQL/SQL Server). Don't assume one works everywhere.COALESCE
- RDBMS中双大括号会自动格式化。会输出
{{record.name}}(带引号),'value'会输出{{{record.name}}}(原始值)。针对字符串请使用三大括号并自行添加引号——这样你可以完全控制格式,避免重复引号或破坏数值类型。value - 导入必须是数组。
query会静默失败或抛出类型转换错误。请始终使用"query": "INSERT INTO ..."。["INSERT INTO ..."] - 值有特定要求。使用
queryType、["per_record"]、["bulk_insert"]、["per_page"]。请勿使用["bulk_load"]或["INSERT"]作为独立值。["UPDATE"] - Snowflake在PUT请求时拒绝旧版值。
queryType/insert可能在POST请求中生效,但在PUT请求中失败。请从一开始就使用update或per_record。bulk_insert - 缺少前缀会导致值为空。
record.会解析为空。请始终使用{{{name}}}。{{{record.name}}} - Snowflake要求使用完全限定表名。,除非连接已设置默认架构。非限定名称会静默失败或访问错误的表。
database.schema.table - 的Handlebars上下文不同。上下文是
per_page,而非单条记录。请使用batch_of_records。{{#each batch_of_records}}...{{{record.fieldName}}}...{{/each}} - 引用临时表。使用
bulkLoad.overrideMergeQuery——而非目标表名。{{import.rdbms.bulkLoad.preMergeTemporaryTable}} - SQL Server的MERGE语句必须以分号结尾。缺少末尾的会导致语法错误。
; - 逐条记录运行,而非批量运行。once查询中的
once.query指当前导出的记录。请勿在此处编写批量UPDATE语句。{{record.id}} - 请勿在内设置增量配置。不存在
rdbms属性。增量处理需通过在SQL查询文本中直接嵌入rdbms.delta实现。{{lastExportDateTime}} - 空值处理因方言而异。使用(Oracle)、
NVL(MySQL)、IFNULL(标准/Snowflake/PostgreSQL/SQL Server)。不要假设某一种函数在所有数据库中都生效。COALESCE
Common Errors
常见错误
| Error | Likely Cause | Fix |
|---|---|---|
| | Change to |
Empty values in SQL / | Missing | Use |
| Double braces auto-quoting | Switch to triple braces |
| Unqualified table name | Use |
| Legacy | Use |
| Missing semicolon at end of MERGE | Add |
| JOIN without table alias | Prefix columns with table aliases |
| Using MySQL syntax on PostgreSQL/Snowflake | Use the correct dialect: |
| Mismatch between INSERT columns and VALUES | Verify column list matches the number of |
| Connection user lacks INSERT/UPDATE grants | Check database permissions for the connection user |
| 错误 | 可能原因 | 修复方案 |
|---|---|---|
| | 修改为 |
SQL中出现空值 / 预期有数据但显示 | Handlebars中缺少 | 使用 |
数值字段显示为 | 双大括号自动添加引号 | 切换为三大括号 |
| 表名称非限定 | 使用 |
PUT请求时 | 使用旧版 | 使用 |
| MERGE语句末尾缺少分号 | 在最后一个子句后添加 |
| JOIN未使用表别名 | 为列添加表别名前缀 |
| 在PostgreSQL/Snowflake上使用MySQL语法 | 使用正确的方言: |
| INSERT列与VALUES数量不匹配 | 验证列列表与 |
| 连接用户缺少INSERT/UPDATE权限 | 检查连接用户的数据库权限 |