Partial Updates (PATCH)
Overview
PATCH operations update specific fields without affecting others. This is the recommended method for most updates, as it's safer than PUT (which replaces the entire record).
Endpoint:
PATCH /api/v4/core/{dim}/{dim_id}
Content-Type: application/json
Authorization: Bearer {token}
Behavior:
- Updates ONLY specified fields
- Other fields remain unchanged
- Sets TREC='M' (Modified)
- Sets LDATA, LOWNER (last update audit fields)
- Validates COD_ON_OFF='M' permissions
- Executes UPDATE_CASCADE operations
- Publishes update event to outbox
✅ Recommended: Use PATCH for single/few field updates.
Basic PATCH Request
Single Field Update
Request:
PATCH /api/v4/core/PRD/123
Authorization: Bearer eyJhbGc...
Content-Type: application/json
{
"data": {
"XPRD02": 89.99 // Only update price
}
}
Response:
{
"status": "success",
"data": {
"PRD_ID": 123,
"XPRD01": "Widget Pro", // Unchanged
"XPRD02": 89.99, // Updated
"XPRD03": "PRD-2025-001", // Unchanged
"XPRD04": "Professional widget", // Unchanged
"XPRD05": "cat_electronics", // Unchanged
"XPRD06": true, // Unchanged
"XPRD09": 100, // Unchanged
"TREC": "M", // Changed to 'M'
"CDATA": "20251219153045", // Original creation time
"OWNER": "user@example.com", // Original creator
"LDATA": "20251219160000", // Last update timestamp
"LOWNER": "admin@example.com" // Last updater
}
}
Database SQL:
UPDATE TB_ANAG_PRD00
SET XPRD02 = 89.99,
TREC = 'M',
LDATA = '20251219160000',
LOWNER = 'admin@example.com'
WHERE PRD_ID = 123;
PATCH vs PUT
Safety Comparison
Scenario: Update only the price of a product.
❌ PUT (unsafe):
PUT /api/v4/core/PRD/123
{
"data": {
"XPRD01": "Widget Pro",
"XPRD02": 89.99, // Only this changed
"XPRD04": "Professional widget",
"XPRD05": "cat_electronics",
"XPRD06": true,
"XPRD09": 100
// Must include ALL fields
}
}
Problems:
- Must fetch current record first
- Must include ALL fields
- Risk of data loss if fields omitted
- Verbose request body
✅ PATCH (safe):
PATCH /api/v4/core/PRD/123
{
"data": {
"XPRD02": 89.99 // Only this changed
}
}
Benefits:
- No need to fetch current record
- Only include changed field
- No risk of data loss
- Minimal request body
Comparison Table
| Aspect | PUT | PATCH |
|---|---|---|
| Fields updated | ALL fields | ONLY specified fields |
| Other fields | Set to NULL/default if missing | Unchanged |
| Fetch required | ✅ Yes (to avoid data loss) | ❌ No |
| Request size | Large (all fields) | Small (only changed fields) |
| Safety | ⚠️ Risk of data loss | ✅ Safe |
| Use case | Full record replacement | Single/few field updates |
Multiple Field Updates
Update Several Fields
Request:
PATCH /api/v4/core/PRD/123
{
"data": {
"XPRD02": 89.99, // Update price
"XPRD06": false, // Deactivate
"XPRD09": 0 // Set stock to zero
}
}
Result:
- XPRD02 changed to 89.99
- XPRD06 changed to false
- XPRD09 changed to 0
- All other fields unchanged
Common PATCH Patterns
Pattern 1: Price Update
Update product price:
async function updateProductPrice(productId, newPrice) {
const response = await fetch(`${apiBase}/api/v4/core/PRD/${productId}`, {
method: 'PATCH',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
data: { XPRD02: newPrice }
})
});
return response.json();
}
// Usage
await updateProductPrice(123, 89.99);
Pattern 2: Toggle Active Status
Activate/deactivate product:
async function toggleProductActive(productId) {
// 1. Fetch current status
const response = await fetch(`${apiBase}/api/v4/core/PRD/${productId}`, {
headers: { 'Authorization': `Bearer ${token}` }
});
const current = await response.json();
// 2. Toggle
const newStatus = !current.data.XPRD06;
// 3. PATCH
const result = await fetch(`${apiBase}/api/v4/core/PRD/${productId}`, {
method: 'PATCH',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
data: { XPRD06: newStatus }
})
});
return result.json();
}
Pattern 3: Increment Counter
Increment stock quantity:
async function incrementStock(productId, quantity) {
// 1. Fetch current stock
const response = await fetch(`${apiBase}/api/v4/core/PRD/${productId}`, {
headers: { 'Authorization': `Bearer ${token}` }
});
const current = await response.json();
// 2. Calculate new stock
const newStock = current.data.XPRD09 + quantity;
// 3. PATCH
const result = await fetch(`${apiBase}/api/v4/core/PRD/${productId}`, {
method: 'PATCH',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
data: { XPRD09: newStock }
})
});
return result.json();
}
// Usage
await incrementStock(123, 50); // Add 50 units
Pattern 4: Bulk Field Update
Update same field across multiple records:
async function bulkUpdateCategory(productIds, newCategory) {
const updates = productIds.map(id =>
fetch(`${apiBase}/api/v4/core/PRD/${id}`, {
method: 'PATCH',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
data: { XPRD05: newCategory }
})
})
);
return Promise.all(updates);
}
// Usage
await bulkUpdateCategory([123, 124, 125], 'cat_new_electronics');
JavaScript Implementation
Product Service with PATCH
class ProductService {
constructor(apiBase, token) {
this.apiBase = apiBase;
this.token = token;
}
/**
* Partial update (PATCH)
*/
async patchProduct(productId, changes) {
const response = await fetch(`${this.apiBase}/api/v4/core/PRD/${productId}`, {
method: 'PATCH',
headers: {
'Authorization': `Bearer ${this.token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ data: changes })
});
if (!response.ok) {
const error = await response.json();
throw new ProductUpdateError(error);
}
return response.json();
}
/**
* Update price
*/
async updatePrice(productId, newPrice) {
return this.patchProduct(productId, { XPRD02: newPrice });
}
/**
* Update stock
*/
async updateStock(productId, newStock) {
return this.patchProduct(productId, { XPRD09: newStock });
}
/**
* Set active status
*/
async setActive(productId, active) {
return this.patchProduct(productId, { XPRD06: active });
}
/**
* Update category
*/
async updateCategory(productId, categoryId) {
return this.patchProduct(productId, { XPRD05: categoryId });
}
}
class ProductUpdateError extends Error {
constructor(errorResponse) {
super(errorResponse.message);
this.code = errorResponse.code;
this.field = errorResponse.field;
}
}
// Usage
const productService = new ProductService(apiBase, token);
// Update price
await productService.updatePrice(123, 89.99);
// Set inactive
await productService.setActive(123, false);
// Update category
await productService.updateCategory(123, 'cat_new_electronics');
React Hook for PATCH
import { useState } from 'react';
function useProductPatch(apiBase, token) {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
async function patchProduct(productId, changes) {
setLoading(true);
setError(null);
try {
const response = await fetch(`${apiBase}/api/v4/core/PRD/${productId}`, {
method: 'PATCH',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ data: changes })
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message);
}
const result = await response.json();
return result;
} catch (err) {
setError(err.message);
throw err;
} finally {
setLoading(false);
}
}
return { patchProduct, loading, error };
}
// Usage in component
function ProductPriceEditor({ productId }) {
const { patchProduct, loading, error } = useProductPatch(apiBase, token);
const [price, setPrice] = useState('');
async function handleSave() {
try {
await patchProduct(productId, { XPRD02: parseFloat(price) });
alert('Price updated!');
} catch (err) {
alert(`Error: ${err.message}`);
}
}
return (
<div>
<input
type="number"
value={price}
onChange={(e) => setPrice(e.target.value)}
disabled={loading}
/>
<button onClick={handleSave} disabled={loading}>
{loading ? 'Saving...' : 'Save'}
</button>
{error && <div className="error">{error}</div>}
</div>
);
}
COD_ON_OFF Validation
Modifiable Fields
TB_COST.COD_ON_OFF must include 'M' flag:
-- XPRD02 can be patched (M flag present)
INSERT INTO TB_COST (COD_DIM, NUM_COST, COD_VAR, COD_ON_OFF) VALUES
('PRD', 2, 'XPRD02', 'LDNMR');
-- XPRD03 cannot be patched (M flag missing)
INSERT INTO TB_COST (COD_DIM, NUM_COST, COD_VAR, COD_ON_OFF) VALUES
('PRD', 3, 'XPRD03', 'LDR'); -- Read-only counter
Validation Error:
Request:
PATCH /api/v4/core/PRD/123
{
"data": {
"XPRD03": "PRD-2025-999" // Read-only field
}
}
Response:
{
"error": "ValidationError",
"message": "Field not modifiable: XPRD03",
"code": "FIELD_NOT_MODIFIABLE",
"field": "XPRD03",
"dimension": "PRD"
}
PATCH with UPDATE_CASCADE
Cascading Updates
TB_COST.UPDATE_CASCADE triggers related updates:
-- When product name changes, update order items
INSERT INTO TB_COST (COD_DIM, NUM_COST, COD_VAR, UPDATE_CASCADE) VALUES
('PRD', 1, 'XPRD01', 'ORDITEM:XORDITEM_PRODUCT_NAME');
PATCH Triggers Cascade:
Request:
PATCH /api/v4/core/PRD/123
{
"data": {
"XPRD01": "Widget Pro V2" // Name changed
}
}
Result:
-- Updates product
UPDATE TB_ANAG_PRD00
SET XPRD01 = 'Widget Pro V2', TREC = 'M', LDATA = '20251219160000'
WHERE PRD_ID = 123;
-- Cascades to order items
UPDATE TB_ANAG_ORDITEM00
SET XORDITEM_PRODUCT_NAME = 'Widget Pro V2', TREC = 'M', LDATA = '20251219160000'
WHERE XORDITEM10 = 123;
Best Practices
✅ DO:
Use PATCH for single field updates:
// ✅ Good - update only price
PATCH /api/v4/core/PRD/123
{ "data": { "XPRD02": 89.99 } }
Use PATCH for multiple field updates:
// ✅ Good - update multiple fields
PATCH /api/v4/core/PRD/123
{
"data": {
"XPRD02": 89.99,
"XPRD06": false,
"XPRD09": 0
}
}
Handle validation errors:
// ✅ Good
try {
await patchProduct(123, { XPRD02: 89.99 });
} catch (error) {
if (error.code === 'FIELD_NOT_MODIFIABLE') {
alert(`Field ${error.field} is read-only`);
}
}
❌ DON'T:
Don't use PUT when PATCH is sufficient:
// ❌ Bad - PUT for single field
PUT /api/v4/core/PRD/123
{
"data": {
"XPRD01": "...",
"XPRD02": 89.99, // Only changing this
"XPRD04": "...",
// ... all other fields
}
}
// ✅ Good - PATCH
PATCH /api/v4/core/PRD/123
{ "data": { "XPRD02": 89.99 } }
Don't include unchanged fields:
// ❌ Bad - unnecessary fields
PATCH /api/v4/core/PRD/123
{
"data": {
"XPRD01": "Widget Pro", // Unchanged
"XPRD02": 89.99, // Changed
"XPRD04": "Description" // Unchanged
}
}
// ✅ Good - only changed field
PATCH /api/v4/core/PRD/123
{ "data": { "XPRD02": 89.99 } }
Don't try to PATCH read-only fields:
// ❌ Bad - counter is read-only
PATCH /api/v4/core/PRD/123
{ "data": { "XPRD03": "PRD-2025-999" } }
Performance Considerations
PATCH vs PUT Performance
PATCH advantages:
- Smaller request payload
- No need to fetch current record
- Fewer database columns updated
- Faster execution
Benchmark:
PUT with 20 fields: ~50ms (fetch + update)
PATCH with 1 field: ~10ms (update only)
5x faster with PATCH
Conditional Updates
Update Only If Condition Met
async function updatePriceIfActive(productId, newPrice) {
// 1. Fetch current
const response = await fetch(`${apiBase}/api/v4/core/PRD/${productId}`, {
headers: { 'Authorization': `Bearer ${token}` }
});
const current = await response.json();
// 2. Check condition
if (!current.data.XPRD06) {
throw new Error('Product is not active. Cannot update price.');
}
// 3. PATCH
const result = await fetch(`${apiBase}/api/v4/core/PRD/${productId}`, {
method: 'PATCH',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
data: { XPRD02: newPrice }
})
});
return result.json();
}
Summary
- ✅ PATCH updates ONLY specified fields
- ✅ Other fields remain unchanged (safer than PUT)
- ✅ No need to fetch record first (unlike PUT)
- ✅ Minimal request payload (better performance)
- ✅ Sets TREC='M' automatically
- ✅ COD_ON_OFF='M' flag required for modifiable fields
- ✅ UPDATE_CASCADE propagates changes to related records
Key Takeaways:
- Prefer PATCH over PUT for most updates
- Only include fields that are changing
- No need to fetch current record (unlike PUT)
- 5x faster than PUT for single field updates
- Safer - no risk of data loss from omitted fields
- TREC automatically transitions to 'M'
When to use PATCH vs PUT:
- PATCH: Single/few field updates (99% of cases)
- PUT: Full record replacement (rare - only when intentional)
Next: Delete Operations →
Related Concepts
- Update Operations - PUT operations
- Field Visibility - COD_ON_OFF flags
- Entity Lifecycle - TREC states