Schema unnesting
When integrating with analytics services through Zero-ETL, you can choose how nested structures are represented in the target tables. AWS Glue Zero-ETL provides schema unnesting options to flatten complex data structures into more analytics-friendly formats.
Unnesting options
When creating a Zero-ETL integration with a source, you can choose from the following unnesting options. These options correspond to specific enumeration values that you'll use when calling the CreateIntegrationTableProperty API. For all unnest options, we would traverse to most inner layer and map DDB type to target spark/iceberg primitive type with best efforts. The type mapping between source DDB and target table is as below:
| DDB source data type | Target table data type |
|---|---|
"S" | StringType |
"B" | BinaryType |
"N" | DoubleType |
"BOOL" | BooleanType |
"SS" | ArrayType(StringType) |
"NS" | ArrayType(DoubleType) |
"BS" | ArrayType(BinaryType) |
"L" | ArrayType(StringType) |
"NULL" | Ignore |
"M" | StructType (TOP/NOUNNEST) |
- No unnesting - NO_UNNEST
-
API value:
NO_UNNESTPreserves the original nested structure of Amazon DynamoDB items. Maps and lists are stored as structured columns in the target.
Best for: Preserving the exact structure of your Amazon DynamoDB data when your analytics tools can work with nested data.
- Top level - TOP_LEVEL
-
API value:
TOP_LEVELFlattens the top level of nested maps into individual columns. List structures remain nested.
Preserves the exact structure of your Amazon DynamoDB data when your analytics tools can work with nested data with all DDB type information removed.
Best for: Balancing between data structure preservation and query simplicity when your Amazon DynamoDB table items have a consistent schema.
- Unnest all levels - FULL (default)
-
API value:
FULLRecursively flattens all nested structures (maps and lists) into individual columns with dot notation for naming.
Best for: Maximizing query simplicity when working with deeply nested structures and analytics tools that prefer flat schemas.
Note
Full unnesting can lead to very wide tables with many columns if your DynamoDB data has variable or deeply nested structures.
Example Using unnesting options in the API
When configuring schema unnesting through the CreateIntegrationTableProperty API, specify the unnesting option in the UnnestSpec parameter:
aws glue create-integration-table-property --resource-arn "arn:aws:glue:us-east-1:123456789012:database/my_db" --table-name "my-table" --cli-input-json '{ "TargetTableConfig": { "UnnestSpec": "FULL", "TargetTableName": "my-target-table", } }'
Unnesting examples
Consider a DynamoDB item with the following structure:
// Input DynamoDB Record { "Item": { "col_1": { "S": "value_1" }, "col_2": { "M": { "col_3": { "M": { "id": { "S": "value_3" } } }, "col_4": { "BOOL": true } } } } }
NO_UNNEST example
With NO_UNNEST, the entire row is stored within one column plus the primary key. DynamoDB type information is preserved. This maintains compatibility with Redshift querying patterns.
Resulting Iceberg table (assuming col_1 is primary key):
| col_1 (string) | value (struct) |
|---|---|
| value_1 |
|
Queries would need to use struct and array access patterns:
SELECT value.col_1, value.col_2.M.col_3.M.id.S, value.col_2.M.col_4.BOOL FROM product_table;
TOP_LEVEL example
With TOP_LEVEL, only the top-level fields are unnested while keeping nested fields intact as structs. DynamoDB type information is removed and typing is maintained. Converts to string type when schema conflicts occur.
Resulting Glue table after replication:
| col_1 (string) | col_2 (struct) |
|---|---|
| value_1 |
|
Queries would be simplified for the first level:
SELECT col_1, col_2.col_3.id, col_2.col_4 FROM product_table;
FULL example
With FULL unnesting, both top-level fields and nested struct/map fields are flattened. Dot notation is used for nested fields (e.g., "col_2.col_3.id"). Array elements remain unnested. Each leaf node becomes a top-level column.
Resulting Glue table after replication:
| col_1 (string) | col_2.col_3.id (string) | col_2.col_4 (boolean) |
|---|---|---|
| value_1 | value_3 | TRUE |
Queries would be fully flattened:
SELECT col_1, "col_2.col_3.id", "col_2.col_4" FROM product_table;