CRUD Workflow Patterns
The four standard workflow patterns for creating, reading, updating, and deleting Data Ocean SQL records — each workflow handles one operation, triggered by REST or a form action, executed by SQL nodes.
Overview: One Workflow Per Operation
The Application Data pattern uses four dedicated workflows — one per CRUD operation. This separation of concerns means each workflow is small, focused, and independently modifiable. Shared validation logic is extracted into a sub-workflow referenced by the create and update workflows.
| Workflow | Trigger | SQL Node | Returns |
|---|---|---|---|
lead-create | REST POST | SqlUpdateNode (INSERT) | JSON: { leadId, success } |
lead-read | REST GET | SqlQueryNode (SELECT) | JSON: lead object or list |
lead-update | REST PUT | SqlUpdateNode (UPDATE) | JSON: { success, rowsAffected } |
lead-delete | REST DELETE | SqlUpdateNode (soft DELETE) | JSON: { success } |
Create Workflow Pattern
REST Trigger (POST)
Trigger: POST /api/workflows/lead-create/trigger. Input body contains the form data (fullName, email, etc.).
Input Validation (Sub-workflow)
Call lead-validate sub-workflow. Checks required fields, email format, value ranges. Returns validation errors or passes through.
SqlUpdateNode (INSERT)
Execute parameterized INSERT into Lead table. Node binds all input variables to SQL parameters. Returns rowsAffected = 1.
AI Enrichment Trigger (Optional)
Optionally trigger lead-enrich workflow asynchronously. Does not block the response — enrichment runs in background.
Return Response
Return { "leadId": "...", "success": true } to the caller (form or API consumer).
Create Workflow — SQL Node Config
{
"nodeType": "SqlUpdateNode",
"nodeId": "insert-lead",
"datasourceId": "sales-data-db",
"command": "INSERT INTO Lead (LeadId, TenantId, FullName, CompanyName, Email, Phone, Source, Status, AssignedToUserId, EstimatedValue, Notes, FollowUpDate, CreatedBy, CreatedAt, UpdatedAt) VALUES (NEWID(), @tenantId, @fullName, @companyName, @email, @phone, @source, @status, @assignedToUserId, @estimatedValue, @notes, @followUpDate, @createdBy, GETUTCDATE(), GETUTCDATE())",
"parameters": {
"tenantId": "{{workflow.tenantId}}",
"fullName": "{{input.fullName}}",
"companyName": "{{input.companyName}}",
"email": "{{input.email}}",
"phone": "{{input.phone}}",
"source": "{{input.source}}",
"status": "{{input.status}}",
"assignedToUserId": "{{input.assignedToUserId}}",
"estimatedValue": "{{input.estimatedValue}}",
"notes": "{{input.notes}}",
"followUpDate": "{{input.followUpDate}}",
"createdBy": "{{workflow.userId}}"
},
"outputVariable": "insertResult"
}
Read Workflow Patterns
The Read operation has two variants — single record and list:
Single Record Read
{
"nodeType": "SqlQueryNode",
"nodeId": "read-lead",
"datasourceId": "sales-data-db",
"query": "SELECT LeadId, FullName, CompanyName, Email, Phone, Source, Status, AssignedToUserId, EstimatedValue, Notes, FollowUpDate, CreatedAt, UpdatedAt, ClassificationLabel FROM Lead WHERE TenantId = @tenantId AND LeadId = @leadId AND IsDeleted = 0",
"parameters": {
"tenantId": "{{workflow.tenantId}}",
"leadId": "{{input.leadId}}"
},
"firstRowOnly": true,
"outputVariable": "lead"
}
Paginated List Read
{
"nodeType": "SqlQueryNode",
"nodeId": "list-leads",
"datasourceId": "sales-data-db",
"query": "SELECT LeadId, FullName, CompanyName, Email, Status, AssignedToUserId, EstimatedValue, CreatedAt FROM Lead WHERE TenantId = @tenantId AND (@status IS NULL OR Status = @status) AND (@assignedTo IS NULL OR AssignedToUserId = @assignedTo) AND IsDeleted = 0 ORDER BY UpdatedAt DESC OFFSET @offset ROWS FETCH NEXT @pageSize ROWS ONLY",
"parameters": {
"tenantId": "{{workflow.tenantId}}",
"status": "{{input.statusFilter}}",
"assignedTo": "{{input.assignedToFilter}}",
"offset": "{{input.offset}}",
"pageSize": "{{input.pageSize}}"
},
"outputVariable": "leads"
}
Update Workflow Pattern
{
"nodeType": "SqlUpdateNode",
"nodeId": "update-lead",
"datasourceId": "sales-data-db",
"command": "UPDATE Lead SET FullName = @fullName, CompanyName = @companyName, Email = @email, Phone = @phone, Source = @source, Status = @status, AssignedToUserId = @assignedToUserId, EstimatedValue = @estimatedValue, Notes = @notes, FollowUpDate = @followUpDate, UpdatedAt = GETUTCDATE() WHERE TenantId = @tenantId AND LeadId = @leadId AND IsDeleted = 0",
"parameters": {
"tenantId": "{{workflow.tenantId}}",
"leadId": "{{input.leadId}}",
"fullName": "{{input.fullName}}",
"email": "{{input.email}}",
"status": "{{input.status}}"
// ... other fields
},
"outputVariable": "updateResult"
}
Delete Workflow Pattern (Soft Delete)
{
"nodeType": "SqlUpdateNode",
"nodeId": "soft-delete-lead",
"datasourceId": "sales-data-db",
"command": "UPDATE Lead SET IsDeleted = 1, DeletedAt = GETUTCDATE(), UpdatedAt = GETUTCDATE() WHERE TenantId = @tenantId AND LeadId = @leadId AND IsDeleted = 0",
"parameters": {
"tenantId": "{{workflow.tenantId}}",
"leadId": "{{input.leadId}}"
},
"outputVariable": "deleteResult"
}
// Follow with a condition node:
// If deleteResult.rowsAffected == 0 → return 404 Not Found
// If deleteResult.rowsAffected == 1 → return 200 Success
If two users simultaneously update the same lead, the second UPDATE will overwrite the first without warning. For concurrency-sensitive data, add an RowVersion column (timestamp/rowversion type in SQL Server) and include WHERE RowVersion = @expectedVersion in the UPDATE command. If rowsAffected = 0, the record was modified by another user — return a 409 Conflict response.