Skip to main content

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

AspectPUTPATCH
Fields updatedALL fieldsONLY specified fields
Other fieldsSet to NULL/default if missingUnchanged
Fetch required✅ Yes (to avoid data loss)❌ No
Request sizeLarge (all fields)Small (only changed fields)
Safety⚠️ Risk of data loss✅ Safe
Use caseFull record replacementSingle/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:

  1. Prefer PATCH over PUT for most updates
  2. Only include fields that are changing
  3. No need to fetch current record (unlike PUT)
  4. 5x faster than PUT for single field updates
  5. Safer - no risk of data loss from omitted fields
  6. 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 →