Query the database with queryDef query-database-api
Adobe Campaign provides powerful JavaScript methods to interact with the database using queryDef and the NLWS object. These SOAP-based methods allow you to load, create, update, and query data using JSON, XML, or SQL.
What is NLWS? what-is-nlws
NLWS (Neolane Web Services) is the global JavaScript object used to access Adobe Campaign’s SOAP-based API methods. Schemas are properties of the NLWS object, allowing you to interact with Campaign entities programmatically.
According to the Campaign JSAPI documentation, “schemas are ‘NLWS’ global objects.” The syntax to access schema methods follows this pattern:
NLWS.<namespace><SchemaName>.<method>()
Examples:
NLWS.nmsRecipient- Access methods for the recipient schema (nms:recipient)NLWS.nmsDelivery- Access methods for the delivery schema (nms:delivery)NLWS.xtkQueryDef- Access queryDef methods for querying the database
Common API methods include:
load(id)- Load an entity by its ID. Learn morecreate(data)- Create a new entitysave()- Save changes to an entity
Example from official documentation:
var delivery = NLWS.nmsDelivery.load("12435")
nms.recipient.create(), xtk.queryDef.create()). Both syntaxes work, but NLWS is the standard documented in the official Campaign JSAPI reference.Prerequisites prerequisites
Before using queryDef and NLWS methods, you should be familiar with:
- JavaScript
- Adobe Campaign data model and schemas
- XPath expressions for navigating schema elements
Understanding the Campaign data model:
Adobe Campaign comes with a pre-defined data model consisting of tables linked together in a Cloud database. The basic structure includes:
-
Recipient table (
nmsRecipient) - Main table storing marketing profiles -
Delivery table (
nmsDelivery) - Stores delivery actions and templates with parameters for performing deliveries -
Logs tables - Store execution logs:
nmsBroadLogRcp- Delivery logs for all messages sent to recipientsnmsTrackingLogRcp- Tracking logs for recipient reactions (opens, clicks)
-
Technical tables - Store system data like operators (
xtkGroup), sessions (xtkSessionInfo), workflows (xtkWorkflow)
To access schema descriptions in the Campaign interface, browse to Administration > Configuration > Data schemas, select a resource, and click the Documentation tab.
Entity Schema methods entity-schema-methods
Each schema in Adobe Campaign (e.g., nms:recipient, nms:delivery) comes with methods accessible through the NLWS object. These methods provide a convenient way to interact with database entities.
Static methods static-methods
Static SOAP methods are accessed by invoking a method on the object representing the schema. For example, NLWS.xtkWorkflow.PostEvent() invokes a static method.
Non-static methods non-static-methods
To use non-static SOAP methods, you must first retrieve an entity using the load or create methods on the corresponding schemas. Learn more in the Campaign JSAPI documentation.
Load, save, and create entities load-save-create
Load an entity by ID and update it:
// Load a delivery by @id and save
var delivery = NLWS.nmsDelivery.load("12435");
delivery.label = "New label";
delivery.save();
Create a recipient using JSON:
// Create via JSON, edit via JS and save
var recipient = NLWS.nmsRecipient.create({
x: { // the key 'x' doesn't matter
email: 'john.doe@example.com',
}
});
recipient.folder_id = 1183;
recipient.firstName = 'John';
recipient.lastName = 'Doe';
recipient.save();
Create a recipient using XML:
// Create via XML and save
var recipient = NLWS.nmsRecipient.create(
<recipient
email="support@adobe.com"
lastName="Adobe"
firstName="Support"
/>
);
recipient.save();
QueryDef overview querydef-overview
The xtk:queryDef schema provides methods to build and execute database queries. You can use NLWS.xtkQueryDef.create() to build queries with JSON or XML syntax.
Available operations:
select- Retrieve multiple recordsget- Retrieve a single record (SQLLIMIT 1)getIfExists- Retrieve a single record, return null if not foundcount- Count records matching criteria
Learn more about queryDef methods in the Campaign JSAPI documentation.
Query with JSON query-json
Use NLWS.xtkQueryDef.create() to build queries with JSON syntax. The get operation retrieves a single record (SQL LIMIT 1), while select retrieves multiple records.
Get a single recipient:
var email = "contact@example.com";
var query = NLWS.xtkQueryDef.create({
queryDef: {
schema: "nms:recipient",
operation: "get", // "get" does a SQL "LIMIT 1"
select: {
node: [{expr: "@id"}, {expr: "@email"}, {expr: "@firstName"}]
},
where: {
condition: [
{expr: "@email = '" + email + "'"}, // filter by email
],
}
}
});
var res = query.ExecuteQuery();
// res is an XML object such as <recipient id="1234" email="contact@example.com" firstName="John"/>
var recipient = NLWS.nmsRecipient.load(res.$id); // conversion to a JavaScript object
recipient.email = "newemail@example.com";
recipient.save();
Use getIfExists to avoid exceptions:
If the record might not exist, use operation: "getIfExists" instead of get to avoid exceptions:
var query = NLWS.xtkQueryDef.create({
queryDef: {
schema: "nms:recipient",
operation: "getIfExists",
select: { node: [{expr: "@id"}] },
where: {
condition: [{expr: "@email = 'nonexistent@example.com'"}]
}
}
});
var res = query.ExecuteQuery();
if (res) {
logInfo("Recipient found: " + res.$id);
} else {
logInfo("Recipient not found");
}
Select multiple records select-multiple
Use the select operation to retrieve multiple records. You can add conditions, ordering, and limits.
Query workflows with filters and ordering:
var query = NLWS.xtkQueryDef.create({
queryDef: {
schema: "xtk:workflow",
operation: "select",
select: {
node: [
{expr: "@id"},
{expr: "@label"},
{expr: "@internalName"}
]
},
where: {
condition: [
{expr: "[folder/@name]='nmsTechnicalWorkflow'"},
{expr: "@production = 1"}
]
},
orderBy: {
node: {expr: "@internalName", sortDesc: "false"}
}
}
});
var res = query.ExecuteQuery();
var workflows = res.getElementsByTagName("workflow");
for each (var w in workflows) {
logInfo(w.getAttribute("internalName"));
}
Query deliveries with XML syntax:
var q = NLWS.xtkQueryDef.create(
<queryDef schema="nms:delivery" operation="select" lineCount="3">
<select>
<node expr="@id"/>
<node expr="@label"/>
<node expr="@created"/>
</select>
<where>
<condition expr="@label NOT LIKE '%Proof%'" bool-operator="AND"/>
<condition expr="@created <= '2024-12-01'" bool-operator="AND"/>
</where>
<orderBy>
<node expr="@lastModified" sortDesc="true"/>
</orderBy>
</queryDef>
);
var deliveries = q.ExecuteQuery();
for each(var delivery in deliveries.delivery) {
logInfo(delivery.@id + ": " + delivery.@label);
}
- Default limit varies by context (typically 200-10,000 records)
- Use
lineCountto explicitly set the maximum number of results - For large datasets (>1000 records), use workflows instead of queryDef. Workflows are designed to process millions of rows efficiently.
Learn more about ExecuteQuery and query best practices.
Query workflow transition data workflow-transition-data
When working with JavaScript activities in workflows, you can query data from incoming transitions using vars.targetSchema and vars.tableName.
Query recipient data from a workflow transition:
// Query data from the incoming transition
var query = NLWS.xtkQueryDef.create({
queryDef: {
schema: vars.targetSchema, // The schema from the previous activity
operation: 'select',
lineCount: 999999999, // Override default 10,000 limit
select: {
node: [
{expr: '@id'},
{expr: '@email'},
{expr: '@firstName'},
{expr: '@lastName'}
]
},
}
});
var records = query.ExecuteQuery(); // Returns a DOMElement
for each(var record in records.getElements()) {
logInfo("Processing: " + record.$id + " - " + record.$email);
// Clean email address
var cleanedEmail = record.$email.replace(/\s+/g, '').toLowerCase();
// Update using parameterized query to prevent SQL injection
sqlExec(
"UPDATE " + vars.tableName + " SET sEmail=$(sz) WHERE iId=$(l)",
cleanedEmail,
record.$id
);
}
$(sz) for strings and $(l) for integers to prevent SQL injection vulnerabilities. Learn more in the Campaign JSAPI documentation.Count records count-records
Use Count(@id) with an alias to count records.
Count running hypotheses:
var jobCount = NLWS.xtkQueryDef.create(
<queryDef schema="nms:remaHypothesis" operation="get">
<select>
<node expr="Count(@id)" alias="@count"/>
</select>
<where>
<condition expr={"@status=" + HYPOTHESIS_STATUS_RUNNING}/>
</where>
</queryDef>
);
var iJobCount = parseInt(jobCount.ExecuteQuery().@count);
logInfo("Running jobs: " + iJobCount);
Count with multiple conditions:
var xmlQuery = <queryDef schema="nms:trackingLogRcp" operation="select">
<select>
<node expr="DateOnly(@logDate)" groupBy="1"/>
<node expr="count(@id)" alias="@count"/>
<node expr="countDistinct([@broadLog-id])" alias="@distinctCount"/>
</select>
<where>
<condition expr={"@logDate IS NOT NULL AND @logDate < #" + today + "# AND [@url-id] <> 1"}/>
</where>
</queryDef>;
var result = NLWS.xtkQueryDef.create(xmlQuery).ExecuteQuery();
Distribution of values distribution-values
Get the distribution of values for a specific field, useful for analyzing data patterns.
Distribution of country codes:
/**
* @class DistributionOfValues
* @param {string} schema - The schema name (e.g., 'nms:recipient')
* @param {string} field - The field to analyze (e.g., '@country')
*/
function DistributionOfValues(schema, field) {
this.queryDef = {
operation: 'select',
lineCount: 200,
schema: schema,
select: {
node: [
{alias: '@expr', expr: field, groupBy: 'true', noSqlBind: 'true'},
{alias: '@count', expr: 'COUNT()', label: 'Count'},
]
},
orderBy: {
node: [{expr: 'COUNT()', sortDesc: 'true'}]
},
};
/**
* Execute the query and return results
* @return {Array} XML list of results
*/
this.get = function() {
this.results = NLWS.xtkQueryDef.create({queryDef: this.queryDef}).ExecuteQuery();
return this.results.getElements();
};
}
// Usage example
var d = new DistributionOfValues('nms:recipient', '@country');
// Optional: Add additional filters
d.queryDef.where = {
condition: [{expr: 'DateOnly(@created) = #2024-12-01#'}]
};
// Execute and display results
for each(var result in d.get()) {
logInfo(result.$expr + ': ' + result.$count);
}
Query enumerations with analyze analyze-enumerations
The analyze option returns user-friendly names for enumeration values. Instead of just numeric values, Campaign will also return the string value and label using “Name” and “Label” suffixes.
Query delivery mapping with enumeration analysis:
var query = NLWS.xtkQueryDef.create({
queryDef: {
schema: "nms:deliveryMapping",
operation: "get",
select: {
node: [
{expr: "@id"},
{expr: "@name"},
{expr: "[storage/@exclusionType]", analyze: true} // Analyze enumeration
]
},
where: {
condition: [{expr: "@name='mapRecipient'"}]
}
}
});
var mapping = query.ExecuteQuery();
// Result includes:
// - exclusionType: 2 (numeric value)
// - exclusionTypeName: "excludeRecipient" (string value)
// - exclusionTypeLabel: "Exclude recipient" (display label)
logInfo("Type: " + mapping.$exclusionType);
logInfo("Name: " + mapping.$exclusionTypeName);
logInfo("Label: " + mapping.$exclusionTypeLabel);
Learn more about the analyze option.
Pagination pagination
Use lineCount and startLine to paginate through large result sets.
Retrieve records in pages:
// Get records 3 and 4 (skip first 2)
var query = NLWS.xtkQueryDef.create({
queryDef: {
schema: "nms:recipient",
operation: "select",
lineCount: 2, // Number of records per page
startLine: 2, // Starting position (0-indexed)
select: {
node: [
{expr: "@id"},
{expr: "@email"}
]
},
orderBy: {
node: [{expr: "@id"}] // Critical: Always use orderBy for pagination
}
}
});
var recipients = query.ExecuteQuery();
orderBy clause, query results are not guaranteed to be in a consistent order. Subsequent calls may return different pages or duplicate records. Always include an orderBy when using pagination.Learn more about pagination.
Dynamic query construction dynamic-queries
Build queries dynamically by appending conditions programmatically.
Append conditions to an existing query:
var xmlQuery = <queryDef schema="nms:delivery" operation="select">
<select>
<node expr="@id"/>
<node expr="@label"/>
</select>
<where/>
</queryDef>;
// Dynamically add conditions
if (includeProofs) {
xmlQuery.where.appendChild(
<condition expr="@label LIKE '%Proof%'"/>
);
}
if (startDate) {
xmlQuery.where.appendChild(
<condition expr={"@created >= #" + Format.toISO8601(startDate) + "#"}/>
);
}
var result = NLWS.xtkQueryDef.create(xmlQuery).ExecuteQuery();
Build select and where clauses in loops:
// Build select dynamically
var select = <select/>;
var fields = ["@id", "@label", "@created"];
for each(var field in fields) {
select.appendChild(<node expr={field}/>);
}
// Build where dynamically
var where = <where/>;
var conditions = [
"@status = 1",
"@type = 'email'"
];
for each(var condition in conditions) {
where.appendChild(<condition expr={condition}/>);
}
// Create complete query
var xmlQuery = <queryDef operation="select" schema="nms:delivery"/>;
xmlQuery.appendChild(select);
xmlQuery.appendChild(where);
var result = NLWS.xtkQueryDef.create(xmlQuery).ExecuteQuery();
Advanced queryDef methods advanced-methods
Beyond ExecuteQuery(), queryDef provides several specialized methods for advanced use cases.
BuildQuery - Generate SQL without executing build-query
Use BuildQuery() to generate the SQL statement without executing it. This is useful for debugging, logging, or passing queries to external systems.
var query = NLWS.xtkQueryDef.create(
<queryDef schema="nms:recipient" operation="select">
<select>
<node expr="@id"/>
<node expr="@email"/>
</select>
<where>
<condition expr="@email IS NOT NULL"/>
</where>
</queryDef>
);
// Get the generated SQL
var sql = query.BuildQuery();
logInfo("Generated SQL: " + sql);
// Output: "SELECT iRecipientId, sEmail FROM NmsRecipient WHERE sEmail IS NOT NULL"
Learn more about BuildQuery.
BuildQueryEx - Get SQL with format string build-query-ex
BuildQueryEx() returns both the SQL query and a format string compatible with the sqlSelect() function.
var query = NLWS.xtkQueryDef.create(
<queryDef schema="nms:recipient" operation="select">
<select>
<node expr="@id"/>
<node expr="@email"/>
<node expr="@firstName"/>
</select>
</queryDef>
);
var [sql, format] = query.BuildQueryEx();
logInfo("SQL: " + sql);
logInfo("Format: " + format);
// Use with sqlSelect
var results = sqlSelect(format, sql);
Learn more about BuildQueryEx.
SelectAll - Add all fields to select select-all
The SelectAll() method automatically adds all fields from the schema to the select clause, saving you from listing each field manually.
var query = NLWS.xtkQueryDef.create(
<queryDef schema="nms:recipient" operation="select">
<select/>
<where>
<condition expr="@id = 12345"/>
</where>
</queryDef>
);
// Add all fields to the select
query.SelectAll(false);
var result = query.ExecuteQuery();
// Result contains all recipient fields
Learn more about SelectAll.
Update - Mass update records mass-update
The Update() method allows you to perform mass updates on records matching your query criteria without loading each record individually.
// Mass update example: set a field value for all matching records
var updateQuery = NLWS.xtkQueryDef.create({
queryDef: {
schema: "nms:recipient",
operation: "update",
where: {
condition: [{expr: "@country = 'US'"}]
},
set: {
node: [{expr: "@blackList", value: "0"}]
}
}
});
// Execute mass update
updateQuery.Update();
logInfo("Mass update completed");
Learn more about Update.
GetInstanceFromModel - Query template instances get-instance-from-model
Use GetInstanceFromModel() to retrieve data from instances created from templates.
var query = NLWS.xtkQueryDef.create(
<queryDef schema="nms:delivery" operation="select">
<select>
<node expr="@id"/>
<node expr="@label"/>
</select>
<where>
<condition expr="@isModel = 1"/>
</where>
</queryDef>
);
// Get instance data from template
var instance = query.GetInstanceFromModel("nms:delivery");
Learn more about GetInstanceFromModel.
Batch operations batch-operations
Process multiple records in batch to improve performance.
Batch update delivery labels:
// Query all deliveries to update
var query = NLWS.xtkQueryDef.create({
queryDef: {
schema: vars.targetSchema,
operation: 'select',
lineCount: 999999999,
select: {
node: [{expr: '@id'}]
},
where: {
condition: [{expr: "@label LIKE '%OLD%'"}]
}
}
});
var records = query.ExecuteQuery();
// Process each record
for each(var record in records.getElements()) {
var delivery = NLWS.nmsDelivery.load(record.$id);
var oldLabel = delivery.label.toString();
var newLabel = oldLabel.replace(/OLD/g, 'NEW');
logInfo("Updating: " + oldLabel + " => " + newLabel);
delivery.label = newLabel;
delivery.save();
}
logInfo("Updated " + records.getElements().length + " deliveries");
Raw SQL execution raw-sql
For complex operations, you can execute raw SQL directly.
Execute parameterized SQL:
var dbEngine = instance.engine;
// Using parameterized query (recommended)
dbEngine.exec(
"UPDATE NmsUserAgentStats SET iVisitorsOfTheDay=$(l) WHERE tsDate=$(dt)",
visitorCount,
Format.parseDateTimeInter(dateString)
);
Query with sqlSelect:
// Execute SELECT query and parse results
var xml = sqlSelect(
"collection,@id,@email",
"SELECT iId as id, sEmail as email FROM " + vars.tableName + " WHERE iStatus = 1"
);
logInfo(xml.toXMLString()); // "<select><collection id="1" email="..."/></select>"
for each(var record in xml.collection) {
logInfo('ID: ' + record.@id + ', Email: ' + record.@email);
// Load full object if needed
if (vars.targetSchema == "nms:recipient") {
var recipient = NLWS.nmsRecipient.load(record.@id);
recipient.lastName = recipient.lastName.toUpperCase();
recipient.save();
}
}
- Always validate and sanitize user inputs
- Use parameterized queries with
$(sz),$(l),$(dt)etc. - Be aware of the differences between the local and cloud databases in FFDA deployments
Best practices best-practices
When working with queryDef and NLWS methods:
- Use workflows for large datasets - QueryDef is not designed for high-volume data processing. For datasets with more than 1,000 records, use workflows which can handle millions of rows efficiently. Learn more in the Campaign SDK documentation
- Use parameterized queries - Always use bound parameters (
$(sz),$(l)) withsqlExecto prevent SQL injection - Set explicit limits - Use
lineCountto control result size. Campaign’s default limits vary by context (200-10,000 records) - Use orderBy with pagination - Always include an
orderByclause when usingstartLineandlineCountto ensure consistent pagination - Use getIfExists - Use
operation: "getIfExists"when records might not exist to avoid exceptions - Use analyze for enumerations - Add
analyze: trueto select nodes to get user-friendly enumeration names and labels - Optimize queries - Add appropriate
whereconditions to limit result sets - Batch processing - Process multiple records in batches to avoid memory issues and timeouts
- FFDA awareness - In Enterprise (FFDA) deployments, be aware that Campaign works with two databases
Practical use cases use-cases
Debug and log queries debug-queries
Use BuildQuery() to inspect the generated SQL before execution:
var query = NLWS.xtkQueryDef.create({
queryDef: {
schema: "nms:recipient",
operation: "select",
select: { node: [{expr: "@id"}, {expr: "@email"}] },
where: { condition: [{expr: "@blackList = 0"}] }
}
});
// Log the SQL for debugging
var sql = query.BuildQuery();
logInfo("About to execute: " + sql);
// Now execute
var results = query.ExecuteQuery();
Duplicate a record with SelectAll duplicate-record
Use SelectAll() to copy all fields when duplicating records:
// Query the original record
var query = NLWS.xtkQueryDef.create(
<queryDef schema="nms:delivery" operation="get">
<select/>
<where>
<condition expr="@id = 12345"/>
</where>
</queryDef>
);
// Select all fields for duplication
query.SelectAll(true); // true indicates duplication mode
var original = query.ExecuteQuery();
// Create a new delivery from the original
var newDelivery = NLWS.nmsDelivery.create(original);
newDelivery.label = original.@label + " (Copy)";
newDelivery.save();
Validate before mass update validate-mass-update
Always preview affected records before performing mass updates:
// Step 1: Preview what will be updated
var previewQuery = NLWS.xtkQueryDef.create({
queryDef: {
schema: "nms:recipient",
operation: "select",
select: { node: [{expr: "@id"}, {expr: "@email"}] },
where: { condition: [{expr: "@country = 'US' AND @blackList = 1"}] }
}
});
var preview = previewQuery.ExecuteQuery();
var count = preview.getElementsByTagName("recipient").length;
logInfo("About to update " + count + " recipients");
// Step 2: If count looks correct, proceed with mass update
if (count > 0 && count < 10000) {
sqlExec("UPDATE NmsRecipient SET iBlackList = 0 WHERE sCountryCode = 'US' AND iBlackList = 1");
logInfo("Mass update completed for " + count + " recipients");
} else {
logWarning("Update cancelled: count is " + count);
}
Query definition syntax reference querydef-reference
Complete structure of the queryDef object:
{
queryDef: {
schema: 'nms:recipient', // Required: target schema
operation: 'select', // select|get|getIfExists|count
lineCount: 100, // Maximum records to return
startLine: 0, // Offset for pagination
select: {
node: [
{
expr: '@id', // XPath expression
alias: '@myAlias', // Optional alias
label: 'ID', // Optional label
groupBy: 'true', // Group by this field
noSqlBind: 'true' // No SQL binding on constants
}
]
},
where: {
condition: [
{
expr: '@email IS NOT NULL', // Condition expression
boolOperator: 'AND', // AND|OR
setOperator: 'EXISTS', // EXISTS|NOT EXISTS|IN|NOT IN
enabledIf: '', // Enabling condition
ignore: false, // Ignore this condition
sql: '', // Native SQL expression
'filter-name': '' // Predefined filter name
}
]
},
orderBy: {
node: [
{
expr: '@lastModified', // Field to sort by
sortDesc: 'true' // true for DESC, false for ASC
}
]
},
groupBy: {
node: [
{expr: '@country'} // Group by field
]
}
}
}