In a data processing workflow, you can use a for-each node to execute the same subtask for each item in a list, such as a list of filenames or partitions. The node automatically iterates over the result set from an upstream node, which is typically an assignment node, and repeats its internal loop body for each element. This approach automates and streamlines your workflows by eliminating the repetitive manual work of creating individual tasks.
Use cases
In daily data development, a for-each node enables parameterized execution when you need to apply the same analysis or processing logic to different business units, product lines, or configuration items. For example, if your company has multiple product lines and you need to generate a separate daily report for each, the processing logic is identical; only the target data differs.
Similar to a for loop in a programming language, a for-each node automatically iterates over a list, such as table names, partition names, or filenames. It executes a predefined sub-workflow for each item in the list, significantly enhancing the automation and flexibility of your workflow.
Prerequisites
-
Edition requirements: This feature is available only in DataWorks Standard Edition and later.
-
Permission requirements: Your RAM account must be added to the corresponding workspace and granted the Development or Workspace Manager role. For more information, see Add members to a workspace.
How it works
The for-each node acts as a container that encapsulates a customizable sub-workflow, known as the loop body. Its mechanism is as follows:
-
Data input: The for-each node depends on an upstream assignment node or other assignable node (such as an EMR Hive node). It retrieves the array-formatted result set by binding to the
loopDataArrayparameter. -
Loop execution: When the node starts, it iterates through each element in the result set in order. For each element, it fully executes the inner loop body once, from the
Startnode to theEndnode.NoteThe Start and End nodes are not editable. They only mark the beginning and end of the loop body.
-
Data passing: During each iteration, the value of the current element is passed to the nodes inside the loop body via built-in variables. The internal business nodes use
${dag.foreach.current}to access the data item being processed.
Built-in parameters
Variables in the ${...} format are a template syntax specific to DataWorks. DataWorks directly parses these parameters and replaces them with their values before execution.
Nodes within the for-each loop body can use the following built-in variables to access the loop status and data:
|
Built-in parameter |
Description |
For loop analogy |
|
|
The complete result set passed from the upstream assignment node. |
Consider the following for loop code:
|
|
|
The data item being processed in the current iteration. |
|
|
|
The current loop offset (0-indexed). |
|
|
|
The current loop count (1-indexed). |
If the upstream output is a two-dimensional array, such as a SQL query result, you can also use the following syntax to access specific values:
|
Other parameters |
Description |
|
|
Gets a string by separating the elements of the current data row (a one-dimensional array) with a comma |
|
|
The |
|
|
The data from the The for-each node does not currently support nested loops. This example is for value retrieval demonstration only. |
Limitations
-
Execution mechanism: The loop supports both serial execution and parallel execution. You can choose parallel execution when the iterations are independent of each other.
-
Loop limit: The default maximum number of loops is 128, which can be adjusted up to 1024.
-
Debugging constraints: You cannot run a for-each node directly in Data Studio. You must deploy the task and then test it in Operation Center by using the smoke testing feature.
-
Execution constraints: A for-each node cannot be run in isolation. This includes smoke testing, backfill, and manual runs.
-
Flow control in the loop body: If you use a branch node inside a for-each loop body, you must ensure all branches eventually converge at a single merge node before connecting to the
Endnode. This guarantees the logical integrity of the loop body. -
Rerun constraints: After a node is deployed, an automatic rerun on failure resumes from the point of failure. However, a manual rerun triggers a complete rerun of the entire for-each node.
Procedure
This procedure uses an assignment node as the upstream node and a Shell node inside the loop body to print the results. This section guides you through configuring a complete for-each task:
-
Prepare the upstream data (configure an assignment node)
Create and configure an assignment node to provide an iterable result set for the downstream for-each node.
-
In the workflow, create an assignment node (for example,
assign) and place it upstream of the for-each node. -
Double-click the assignment node and select a Python 2 environment. For example, use
Python 2to output an array with four elements:The node outputs [10,20,30,40] to downstream nodes by automatically splitting the last output line into an array at each comma.
print "10,20,30,40" -
The assignment node automatically generates an output parameter named
outputs, which represents its result set. -
Save the assignment node.
-
-
Configure the for-each node to consume data
Configure the for-each node to receive the upstream data and use it within its loop body.
-
Double-click the for-each node to open its internal canvas.
-
In the Scheduling panel on the right, find the
loopDataArrayparameter under Scheduling Parameters and click Bind.Select the outputs parameter of the assign node to create the binding. After the binding is complete, the value of the loopDataArray parameter reflects its bound status.
-
In the dialog box that appears, set the Value Source to the upstream assignment node (
assign) and select itsoutputsparameter. This action automatically creates a dependency between the two nodes. -
In the for-each loop body, click Create Internal Node and create a
Shellnode.In a real-world scenario, you can configure any type of node.
-
Double-click the new Shell node and use built-in variables in the code to retrieve and print information about the loop:
#!/bin/bash # Use ${dag.loopTimes} to get the current loop count echo "Current loop number is: ${dag.loopTimes}" # Use ${dag.foreach.current} to get the data item for the current iteration echo "Current item is: ${dag.foreach.current}" -
(Optional) In the Scheduling Settings panel on the right, configure properties under Scheduling Policy.
-
Maximum Number of Loops: The default is 128, and the maximum is 1024.
ImportantThis parameter determines the maximum number of iterations for the loop body. If the number of upstream data items is large, increase this value to ensure all items are processed.
-
Execute Policy: Select Serial for this example.
-
Serial: Runs iterations sequentially.
-
Parallel: Runs loop iterations concurrently to improve task efficiency. In Parallel mode, if one iteration fails, it does not affect other iterations. The scheduler attempts to run all iterations to completion. The default concurrency is 5, and the maximum is 20.
-
-
-
Save the Shell node.
-
-
Deploy, run, and verify
Deploy the workflow to Operation Center for execution and verify the results of the for-each node.
-
Return to the main workflow canvas and click the Deploy button on the toolbar to publish the entire workflow.
-
Go to and perform a smoke test on the target workflow.
ImportantDo not perform a smoke test on the for-each node individually. Because the for-each node depends on the output of the upstream assignment node, you must start the test from the assignment node to ensure the data lineage is complete.
-
After the test instance runs successfully, find the for-each node instance in the list, open it, and right-click to select View Internal Nodes.
-
In the internal node view, check the Shell node instances generated by each loop. Open the running log of any instance to view the output for that iteration and verify that the output is correct.
The left panel shows that all four loop iterations are complete. The running log for the fourth iteration outputs
Current loop number is: 4andCurrent item is: 40, and the Shell command exits with code 0, indicating successful execution.
-
Use case: Process different data formats
Scenario 1: Process a one-dimensional array
-
assignment node output: 2025-11-01,2025-11-02,2025-11-03
-
Iteration count: 3
-
During the second iteration:
-
The value of
${dag.foreach.current}is2025-11-02. -
The value of
${dag.loopTimes}is2.
-
Scenario 2: Process a two-dimensional array
-
assignment node (MaxCompute SQL) output:
+-----+----------+ | id | city | +-----+----------+ | 101 | beijing | | 102 | shanghai | +-----+----------+ -
Iteration count: 2
-
During the second iteration:
-
The value of
${dag.foreach.current}is102,shanghai. -
The value of
${dag.loopTimes}is2. -
The value of
${dag.foreach.current[0]}is102. -
The value of
${dag.foreach.current[1]}isshanghai.
-
Use case: Batch processing for multiple business lines
This example shows how to use an assignment node and a for-each node to batch process user behavior data for multiple lines of business. This enables automated data processing where a single set of logic serves multiple product lines.
Background
Assume you are a data engineer at a large company responsible for processing data from three core lines of business: e-commerce (ecom), finance (finance), and logistics (logistics), with the possibility of adding more in the future. You need to apply the same aggregation logic to the user behavior logs of these lines of business every day to calculate the daily page views (PV) for each user and store the results in a unified summary table.
-
Upstream source tables (DWD layer):
-
dwd_user_behavior_ecom_d: E-commerce user behavior table. -
dwd_user_behavior_finance_d: Finance user behavior table. -
dwd_user_behavior_logistics_d: Logistics user behavior table. -
dwd_user_behavior_${line_of_business}_d: User behavior tables for other potential lines of business. -
These tables have the same schema and are partitioned by day (
dt).
-
-
Downstream destination table (DWS layer):
-
dws_user_summary_d: User summary table. -
This table is partitioned by line of business (
biz_line) and day (dt) to store the aggregated results from all lines of business.
-
Using a for-each node, you only need to maintain one set of processing logic, and the system automatically iterates through all lines of business to complete the calculations.
Data preparation
First, create the example tables and insert test data. This example uses the business date 20251010.
-
Associate a MaxCompute compute resource with the workspace.
-
Go to Data Studio and create a MaxCompute SQL node.
-
Create the source tables (DWD layer). In the MaxCompute SQL node, add the following code, select it, and run it.
-- E-commerce user behavior table CREATE TABLE IF NOT EXISTS dwd_user_behavior_ecom_d ( user_id STRING COMMENT 'User ID', action_type STRING COMMENT 'Behavior type', event_time BIGINT COMMENT 'Event time as a millisecond-level Unix timestamp' ) COMMENT 'E-commerce user behavior log detail table' PARTITIONED BY (dt STRING COMMENT 'Date partition in yyyymmdd format'); INSERT OVERWRITE TABLE dwd_user_behavior_ecom_d PARTITION (dt='20251010') VALUES ('user001', 'click', 1760004060000), -- 2025-10-10 10:01:00.000 ('user002', 'browse', 1760004150000), -- 2025-10-10 10:02:30.000 ('user001', 'add_to_cart', 1760004300000); -- 2025-10-10 10:05:00.000 -- Verify that the e-commerce user behavior table is created. SELECT * FROM dwd_user_behavior_ecom_d where dt='20251010'; -- Finance user behavior table CREATE TABLE IF NOT EXISTS dwd_user_behavior_finance_d ( user_id STRING COMMENT 'User ID', action_type STRING COMMENT 'Behavior type', event_time BIGINT COMMENT 'Event time as a millisecond-level Unix timestamp' ) COMMENT 'Finance user behavior log detail table' PARTITIONED BY (dt STRING COMMENT 'Date partition in yyyymmdd format'); INSERT OVERWRITE TABLE dwd_user_behavior_finance_d PARTITION (dt='20251010') VALUES ('user003', 'open_app', 1760020200000), -- 2025-10-10 14:30:00.000 ('user003', 'transfer', 1760020215000), -- 2025-10-10 14:30:15.000 ('user003', 'check_balance', 1760020245000), -- 2025-10-10 14:30:45.000 ('user004', 'open_app', 1760020300000); -- 2025-10-10 14:31:40.000 -- Verify that the finance user behavior table is created. SELECT * FROM dwd_user_behavior_finance_d where dt='20251010'; -- Logistics user behavior table CREATE TABLE IF NOT EXISTS dwd_user_behavior_logistics_d ( user_id STRING COMMENT 'User ID', action_type STRING COMMENT 'Behavior type', event_time BIGINT COMMENT 'Event time as a millisecond-level Unix timestamp' ) COMMENT 'Logistics user behavior log detail table' PARTITIONED BY (dt STRING COMMENT 'Date partition in yyyymmdd format'); INSERT OVERWRITE TABLE dwd_user_behavior_logistics_d PARTITION (dt='20251010') VALUES ('user001', 'check_status', 1760032800000), -- 2025-10-10 18:00:00.000 ('user005', 'schedule_pickup', 1760032920000); -- 2025-10-10 18:02:00.000 -- Verify that the logistics user behavior table is created. SELECT * FROM dwd_user_behavior_logistics_d where dt='20251010'; -
Create the destination table (DWS layer). In the MaxCompute SQL node, add the following code, select it, and run it.
CREATE TABLE IF NOT EXISTS dws_user_summary_d ( user_id STRING COMMENT 'User ID', pv BIGINT COMMENT 'Daily page views' ) COMMENT 'User daily page views summary table' PARTITIONED BY ( dt STRING COMMENT 'Date partition in yyyymmdd format', biz_line STRING COMMENT 'Line of business partition, such as ecom, finance, or logistics' );ImportantIf your workspace uses the standard development environment, you must deploy this node to the production environment and perform a data backfill.
Workflow implementation
-
Create a workflow. In the Scheduling Parameters panel on the right, set the scheduling parameter bizdate to the previous day:
$[yyyymmdd-1]. -
In the workflow, create an assignment node named get_biz_list. Use the MaxCompute SQL language to write the following code. This node outputs a list of the lines of business to be processed.
-- Output all lines of business to be processed. SELECT 'ecom' AS biz_line UNION ALL SELECT 'finance' AS biz_line UNION ALL SELECT 'logistics' AS biz_line; -
Configure the for-each node
-
Return to the workflow canvas and create a downstream for-each node for the get_biz_list assignment node.
-
Go to the for-each node settings panel. In the Scheduling panel on the right, under the tab, bind the loopDataArray parameter to the outputs of the get_biz_list node.
-
In the for-each node loop body, click Create Internal Node to create a MaxCompute SQL node, and then write the processing logic for the loop body.
Note-
This script is driven by the for-each node and runs once for each line of business.
-
At runtime, the system dynamically replaces the built-in variable ${dag.foreach.current} with the current line of business name. The expected iteration values are 'ecom', 'finance', and 'logistics'.
SET odps.sql.allow.dynamic.partition=true; INSERT OVERWRITE TABLE dws_user_summary_d PARTITION (dt='${bizdate}', biz_line) SELECT user_id, COUNT(*) AS pv, '${dag.foreach.current}' AS biz_line FROM dwd_user_behavior_${dag.foreach.current}_d WHERE dt = '${bizdate}' GROUP BY user_id; -
-
-
Add a verification node
Return to the workflow canvas. For the for-each node, click Create Downstream to create a MaxCompute SQL node, and then add the following code.
SELECT * FROM dws_user_summary_d WHERE dt='20251010' ORDER BY biz_line, user_id;
Deployment and results
Deploy the workflow to the production environment. Go to , find the workflow, and run a smoke test. Select '20251010' as the business date.
After the run completes, the final node produces the following output:
|
user_id |
pv |
dt |
biz_line |
|
user001 |
2 |
20251010 |
ecom |
|
user002 |
1 |
20251010 |
ecom |
|
user003 |
3 |
20251010 |
finance |
|
user004 |
1 |
20251010 |
finance |
|
user001 |
1 |
20251010 |
logistics |
|
user005 |
1 |
20251010 |
logistics |
Benefits
-
High scalability: To add a new line of business, you only need to add one line of SQL to the assignment node without modifying the processing logic.
-
Easy maintenance: All lines of business share the same processing logic. A change in one place applies to all.
FAQ
-
Q: Why can't I run a for-each node directly in Data Studio to test it?
A: This is by design. The node requires a full scheduling environment to resolve the node context and its dependencies, so it does not support direct execution in Data Studio. You must deploy the task to Operation Center and test it by using backfill or triggering a scheduled run.
-
Q: Why does a smoke test on an individual for-each node fail or do nothing?
A: The loop data for a for-each node comes from its
loopDataArrayinput parameter, which must be bound to theoutputsparameter of an upstream assignment node. If you run the for-each node by itself, it will either fail or be skipped because it cannot receive an input result set. -
Q: Why does my loop run only once?
A: This usually happens because the output from the upstream assignment node is parsed as a single element. Check your output:
-
1. Is it a single string without a delimiter?
-
2. If you expect to iterate over multiple items, ensure they are separated by commas (
,). For example,'item1,item2,item3'results in three loops, whereas'item1 item2 item3'results in only one.
-