Skip to main content

DoSqlQuery

Overview

DoSqlQuery executes any SQL query against the database for complex reporting, analytics, and multi-table operations. It provides full SQL syntax support including JOINs, aggregations, subqueries, and advanced SQL features.

When to Use

Use DoSqlQuery for:

  • Complex queries with JOINs across multiple tables
  • Aggregations (SUM, COUNT, AVG, MIN, MAX)
  • Reporting and analytics
  • Core database tables (Loans, Customers, Deposits)
  • Complex business logic requiring SQL

Don't use for:

  • Simple single-table queries (use GetFullTableQuery for BPM tables)
  • Standard entity queries (use RetrieveLoanListQuery, etc. when available)
  • INSERT/UPDATE/DELETE operations (use specific commands)

Syntax

doCmd() Usage

var result = doCmd('DoSqlQuery', {
Data: {
sql: 'SELECT * FROM TableName WHERE Condition = Value',
timeout: 30000
}
});

JSON Configuration

{
"commandName": "DoSqlQuery",
"parameters": {
"Data": {
"sql": "SELECT L.*, C.FirstName FROM Loans L INNER JOIN Customers C ON L.CustomerId = C.Id WHERE L.Status = 'ACTIVE'",
"timeout": 30000
}
}
}

Parameters

ParameterTypeRequiredDescriptionDefault
Data.sqlstringYesSQL query to execute-
Data.timeoutnumberNoQuery timeout in millisecondsNo timeout

SQL Capabilities

FeatureSupportedExample
SELECT✅ YesSELECT * FROM Loans
JOIN✅ YesINNER JOIN, LEFT JOIN, RIGHT JOIN
WHERE✅ YesWHERE Status = 'ACTIVE'
GROUP BY✅ YesGROUP BY CustomerId
ORDER BY✅ YesORDER BY Amount DESC
HAVING✅ YesHAVING COUNT(*) > 5
Subqueries✅ YesWHERE Id IN (SELECT ...)
Aggregations✅ YesSUM(), COUNT(), AVG(), MIN(), MAX()
CASE✅ YesCASE WHEN ... THEN ... END
Date Functions✅ YesDATEADD(), DATEDIFF(), GETDATE()
String Functions✅ YesCONCAT(), SUBSTRING(), UPPER()
INSERT❌ NoUse specific commands
UPDATE❌ NoUse specific commands
DELETE❌ NoUse specific commands

Return Value

Success Response

{
"isSuccessful": true,
"message": "SQL query executed successfully.",
"data": [
{
"column1": "value1",
"column2": "value2",
"column3": "value3"
},
{
"column1": "value4",
"column2": "value5",
"column3": "value6"
}
]
}

Error Response

{
"isSuccessful": false,
"message": "SQL error: Invalid column name 'ColumnName'."
}

Examples

Example 1: Customer Loan Summary

var customerId = context.customerId;

var result = doCmd('DoSqlQuery', {
Data: {
sql: 'SELECT C.Id, C.FirstName, C.LastName, C.CreditScore, ' +
'COUNT(L.Id) AS LoanCount, ' +
'SUM(L.PrincipalAmount) AS TotalLoanAmount, ' +
'SUM(L.Balance) AS TotalBalance ' +
'FROM Customers C ' +
'LEFT JOIN Loans L ON C.Id = L.CustomerId AND L.Status = \'ACTIVE\' ' +
'WHERE C.Id = ' + customerId + ' ' +
'GROUP BY C.Id, C.FirstName, C.LastName, C.CreditScore',
timeout: 30000
}
});

if (result.isSuccessful && result.data.length > 0) {
var summary = result.data[0];

context.customerLoanCount = summary.loanCount;
context.customerTotalDebt = summary.totalBalance;
context.customerCreditScore = summary.creditScore;

return {
success: true,
loanCount: summary.loanCount,
totalDebt: summary.totalBalance
};
}

Example 2: Overdue Loans Report

var reportResult = doCmd('DoSqlQuery', {
Data: {
sql: 'SELECT L.LoanAccountId, ' +
'C.FirstName + \' \' + C.LastName AS CustomerName, ' +
'C.MobileNumber, ' +
'L.NextPaymentDate, ' +
'L.NextPaymentAmount, ' +
'DATEDIFF(day, L.NextPaymentDate, GETDATE()) AS DaysOverdue ' +
'FROM Loans L ' +
'INNER JOIN Customers C ON L.CustomerId = C.Id ' +
'WHERE L.Status = \'ACTIVE\' ' +
'AND L.NextPaymentDate < GETDATE() ' +
'ORDER BY DaysOverdue DESC',
timeout: 45000
}
});

if (reportResult.isSuccessful) {
context.overdueLoans = reportResult.data;
return {
success: true,
overdueCount: reportResult.data.length,
loans: reportResult.data
};
}

Example 3: Branch Performance Dashboard

var performanceResult = doCmd('DoSqlQuery', {
Data: {
sql: 'SELECT B.BranchName, ' +
'COUNT(DISTINCT L.Id) AS TotalLoans, ' +
'SUM(L.PrincipalAmount) AS TotalDisbursed, ' +
'COUNT(DISTINCT L.CustomerId) AS UniqueCustomers, ' +
'AVG(L.InterestRate) AS AvgRate ' +
'FROM Loans L ' +
'INNER JOIN Branches B ON L.BranchId = B.Id ' +
'WHERE L.DisbursementDate >= DATEADD(month, -1, GETDATE()) ' +
'GROUP BY B.BranchName ' +
'ORDER BY TotalDisbursed DESC',
timeout: 60000
}
});

if (performanceResult.isSuccessful) {
context.branchPerformance = performanceResult.data;
return performanceResult.data;
}

Example 4: Credit Score Distribution

var distributionResult = doCmd('DoSqlQuery', {
Data: {
sql: 'SELECT ' +
'CASE ' +
' WHEN CreditScore >= 800 THEN \'Excellent\' ' +
' WHEN CreditScore >= 700 THEN \'Good\' ' +
' WHEN CreditScore >= 600 THEN \'Fair\' ' +
' ELSE \'Poor\' ' +
'END AS CreditCategory, ' +
'COUNT(*) AS CustomerCount, ' +
'AVG(CreditScore) AS AvgScore ' +
'FROM Customers ' +
'WHERE Status = \'ACTIVE\' ' +
'GROUP BY CASE ' +
' WHEN CreditScore >= 800 THEN \'Excellent\' ' +
' WHEN CreditScore >= 700 THEN \'Good\' ' +
' WHEN CreditScore >= 600 THEN \'Fair\' ' +
' ELSE \'Poor\' ' +
'END',
timeout: 30000
}
});

if (distributionResult.isSuccessful) {
context.creditDistribution = distributionResult.data;
return distributionResult.data;
}

Example 5: Transaction History with Subquery

var customerId = context.customerId;

var transactionResult = doCmd('DoSqlQuery', {
Data: {
sql: 'SELECT LT.*, L.LoanAccountId, ' +
'(SELECT SUM(Amount) ' +
' FROM LoanTransactions LT2 ' +
' WHERE LT2.LoanId = LT.LoanId ' +
' AND LT2.TransactionDate <= LT.TransactionDate) AS RunningBalance ' +
'FROM LoanTransactions LT ' +
'INNER JOIN Loans L ON LT.LoanId = L.Id ' +
'WHERE L.CustomerId = ' + customerId + ' ' +
'ORDER BY LT.TransactionDate DESC',
timeout: 30000
}
});

if (transactionResult.isSuccessful) {
context.transactionHistory = transactionResult.data;
return transactionResult.data;
}

Use Cases

1. Customer Risk Assessment

var riskResult = doCmd('DoSqlQuery', {
Data: {
sql: 'SELECT C.Id, C.CreditScore, ' +
'COUNT(L.Id) AS ActiveLoans, ' +
'SUM(L.Balance) AS TotalDebt, ' +
'CASE ' +
' WHEN C.CreditScore >= 700 AND COUNT(L.Id) < 3 THEN \'LOW\' ' +
' WHEN C.CreditScore >= 600 THEN \'MEDIUM\' ' +
' ELSE \'HIGH\' ' +
'END AS RiskCategory ' +
'FROM Customers C ' +
'LEFT JOIN Loans L ON C.Id = L.CustomerId AND L.Status = \'ACTIVE\' ' +
'WHERE C.Id = ' + context.customerId + ' ' +
'GROUP BY C.Id, C.CreditScore',
timeout: 30000
}
});

2. Portfolio Analysis

var portfolioResult = doCmd('DoSqlQuery', {
Data: {
sql: 'SELECT ProductType, ' +
'COUNT(*) AS TotalLoans, ' +
'SUM(PrincipalAmount) AS TotalDisbursed, ' +
'SUM(Balance) AS OutstandingBalance, ' +
'AVG(InterestRate) AS AvgRate, ' +
'SUM(CASE WHEN Status = \'OVERDUE\' THEN 1 ELSE 0 END) AS OverdueCount ' +
'FROM Loans ' +
'GROUP BY ProductType',
timeout: 45000
}
});

3. Payment Pattern Analysis

var patternResult = doCmd('DoSqlQuery', {
Data: {
sql: 'SELECT L.CustomerId, ' +
'COUNT(LT.Id) AS PaymentCount, ' +
'AVG(DATEDIFF(day, L.DueDate, LT.TransactionDate)) AS AvgDelayDays, ' +
'SUM(CASE WHEN LT.TransactionDate > L.DueDate THEN 1 ELSE 0 END) AS LatePayments ' +
'FROM Loans L ' +
'INNER JOIN LoanTransactions LT ON L.Id = LT.LoanId ' +
'WHERE LT.TransactionType = \'REPAYMENT\' ' +
'AND LT.TransactionDate >= DATEADD(month, -6, GETDATE()) ' +
'GROUP BY L.CustomerId ' +
'HAVING COUNT(LT.Id) >= 3',
timeout: 45000
}
});

Error Handling

try {
var result = doCmd('DoSqlQuery', {
Data: {
sql: 'SELECT * FROM Loans WHERE CustomerId = ' + context.customerId,
timeout: 30000
}
});

if (!result.isSuccessful) {
console.error('SQL query failed:', result.message);

// Check for specific errors
if (result.message.indexOf('Timeout') !== -1) {
throw new Error('Query timeout - consider optimizing SQL or increasing timeout');
} else if (result.message.indexOf('Invalid column') !== -1) {
throw new Error('SQL error - check column names');
} else if (result.message.indexOf('Invalid object name') !== -1) {
throw new Error('Table does not exist');
} else {
throw new Error('SQL execution failed: ' + result.message);
}
}

if (result.data.length === 0) {
console.warn('No data returned from query');
return { hasData: false };
}

// Process results
return { hasData: true, data: result.data };

} catch (error) {
console.error('Exception in DoSqlQuery:', error.message);
return { hasData: false, error: error.message };
}

Security Considerations

⚠️ SQL Injection Prevention

NEVER allow unsanitized user input directly in SQL:

// ❌ UNSAFE - SQL Injection Risk
var sql = 'SELECT * FROM Loans WHERE CustomerId = ' + userInput;

// ✅ SAFE - Validated input
var customerId = parseInt(context.customerId);
if (isNaN(customerId) || customerId <= 0) {
throw new Error('Invalid customer ID');
}
var sql = 'SELECT * FROM Loans WHERE CustomerId = ' + customerId;

// ✅ BETTER - Use specific Query commands when available
var result = doCmd('RetrieveLoanListQuery', {
Data: {
filters: {
customerId: context.customerId
}
}
});

Best Practices for Security

  1. Validate all inputs before building SQL
  2. Use specific commands when available (safer)
  3. Limit user permissions - restrict who can use DoSqlQuery
  4. Audit all usage - log all SQL executions
  5. Avoid dynamic SQL when possible

Performance Tips

✅ Do's

  • Add WHERE clauses to filter data early
  • Use TOP or LIMIT for large result sets
  • Set appropriate timeouts (30-60 seconds)
  • Test queries in SQL Server Management Studio first
  • Add indexes on frequently joined/filtered columns
  • Specify only needed columns (avoid SELECT *)
  • Use EXISTS instead of COUNT(*) > 0 for existence checks

❌ Don'ts

  • Don't run queries without timeout on production
  • Don't fetch entire tables without filters
  • Don't use in high-frequency loops
  • Don't ignore performance warnings
  • Don't use SELECT * when you only need specific columns
  • Don't execute without testing first

Optimization Examples

// ❌ BAD - No filter, no limit
var result = doCmd('DoSqlQuery', {
Data: {
sql: 'SELECT * FROM Loans'
}
});

// ✅ GOOD - Filtered, limited, specific columns
var result = doCmd('DoSqlQuery', {
Data: {
sql: 'SELECT TOP 100 LoanAccountId, PrincipalAmount, Status ' +
'FROM Loans ' +
'WHERE Status = \'ACTIVE\' ' +
'AND CreatedDate >= DATEADD(month, -1, GETDATE())',
timeout: 30000
}
});

Common Error Codes

Error PatternCauseResolution
Timeout expiredQuery taking too longOptimize SQL, add indexes, increase timeout
Invalid column nameColumn doesn't existVerify column name in database
Invalid object nameTable doesn't existCheck table name spelling
Syntax errorInvalid SQLReview SQL syntax
Permission deniedInsufficient permissionsCheck database user permissions

API Endpoint

Method: POST
URL: /api/bpm/execute-command

Request Body:

{
"commandName": "DoSqlQuery",
"parameters": {
"Data": {
"sql": "SELECT * FROM Loans WHERE Status = 'ACTIVE'",
"timeout": 30000
}
},
"context": {}
}

Response:

{
"isSuccessful": true,
"message": "SQL query executed successfully.",
"data": [
{
"id": 1,
"loanAccountId": "LN-001",
"principalAmount": 50000
}
]
}

Best Practices

✅ Do

  • Validate all inputs before building SQL
  • Test queries in SQL tools first
  • Use appropriate timeouts
  • Add WHERE clauses to filter data
  • Use TOP/LIMIT for large datasets
  • Handle empty result sets
  • Log query execution for auditing

❌ Don't

  • Don't allow unsanitized user input in SQL
  • Don't execute INSERT/UPDATE/DELETE (use specific commands)
  • Don't run without timeout on production
  • Don't ignore error responses
  • Don't fetch entire tables
  • Don't use in tight loops

Complete Example: Multi-Step Analysis

// Complete workflow: Analyze customer eligibility with complex queries

// Step 1: Get customer summary
var customerSummaryResult = doCmd('DoSqlQuery', {
Data: {
sql: 'SELECT C.*, ' +
'COUNT(DISTINCT L.Id) AS TotalLoans, ' +
'SUM(CASE WHEN L.Status = \'ACTIVE\' THEN 1 ELSE 0 END) AS ActiveLoans, ' +
'SUM(L.Balance) AS TotalDebt ' +
'FROM Customers C ' +
'LEFT JOIN Loans L ON C.Id = L.CustomerId ' +
'WHERE C.Id = ' + context.customerId + ' ' +
'GROUP BY C.Id, C.FirstName, C.LastName, C.CreditScore, C.MonthlyIncome',
timeout: 30000
}
});

if (!customerSummaryResult.isSuccessful || customerSummaryResult.data.length === 0) {
throw new Error('Customer not found');
}

var customer = customerSummaryResult.data[0];

// Step 2: Check payment history
var paymentHistoryResult = doCmd('DoSqlQuery', {
Data: {
sql: 'SELECT ' +
'COUNT(*) AS TotalPayments, ' +
'SUM(CASE WHEN TransactionDate > DueDate THEN 1 ELSE 0 END) AS LatePayments, ' +
'AVG(DATEDIFF(day, DueDate, TransactionDate)) AS AvgDelayDays ' +
'FROM LoanTransactions LT ' +
'INNER JOIN Loans L ON LT.LoanId = L.Id ' +
'WHERE L.CustomerId = ' + context.customerId + ' ' +
'AND LT.TransactionType = \'REPAYMENT\' ' +
'AND LT.TransactionDate >= DATEADD(month, -12, GETDATE())',
timeout: 30000
}
});

var paymentHistory = paymentHistoryResult.data[0];

// Step 3: Calculate eligibility score
var eligibilityScore = 0;

if (customer.creditScore >= 700) eligibilityScore += 30;
if (customer.activeLoans < 3) eligibilityScore += 25;
if (customer.totalDebt < customer.monthlyIncome * 3) eligibilityScore += 25;
if (paymentHistory && paymentHistory.latePayments === 0) eligibilityScore += 20;

// Step 4: Return comprehensive result
return {
success: true,
eligible: eligibilityScore >= 70,
eligibilityScore: eligibilityScore,
customer: {
name: customer.firstName + ' ' + customer.lastName,
creditScore: customer.creditScore,
activeLoans: customer.activeLoans,
totalDebt: customer.totalDebt
},
paymentHistory: paymentHistory
};

Version History

  • v1.0: Initial release
  • v1.1: Added timeout parameter
  • v1.2: Improved error messages
  • v1.3: Enhanced security validation