Introduction
Natural Language to SQL (NL2SQL) enables users to query databases using human language instead of complex SQL syntax. This technology combines NLP techniques with SQL generation to make data access more intuitive.
Architecture Overview
System Architecture
public class NL2SQLArchitecture {
/**
* NL2SQL Pipeline:
* 1. Text Preprocessing → 2. Intent Recognition →
* 3. Entity Extraction → 4. SQL Generation → 5. Query Execution
*/
private TextPreprocessor preprocessor;
private IntentRecognizer intentRecognizer;
private EntityExtractor entityExtractor;
private SQLGenerator sqlGenerator;
private QueryExecutor queryExecutor;
public NL2SQLArchitecture() {
this.preprocessor = new TextPreprocessor();
this.intentRecognizer = new IntentRecognizer();
this.entityExtractor = new EntityExtractor();
this.sqlGenerator = new SQLGenerator();
this.queryExecutor = new QueryExecutor();
}
public QueryResult processQuery(String naturalLanguageQuery) {
// Step 1: Preprocess text
ProcessedText processed = preprocessor.process(naturalLanguageQuery);
// Step 2: Recognize intent (SELECT, UPDATE, DELETE, etc.)
QueryIntent intent = intentRecognizer.recognize(processed);
// Step 3: Extract entities (tables, columns, conditions)
QueryEntities entities = entityExtractor.extract(processed);
// Step 4: Generate SQL
String sql = sqlGenerator.generate(intent, entities);
// Step 5: Execute query
return queryExecutor.execute(sql);
}
}
Core Components
Text Preprocessing
public class TextPreprocessor {
public ProcessedText process(String input) {
String cleaned = cleanText(input);
List<String> tokens = tokenize(cleaned);
List<String> normalized = normalize(tokens);
Map<String, Object> linguisticFeatures = extractLinguisticFeatures(normalized);
return new ProcessedText(input, cleaned, tokens, normalized, linguisticFeatures);
}
private String cleanText(String text) {
return text.toLowerCase()
.replaceAll("[^a-zA-Z0-9\\s]", "")
.replaceAll("\\s+", " ")
.trim();
}
private List<String> tokenize(String text) {
return Arrays.asList(text.split("\\s+"));
}
private List<String> normalize(List<String> tokens) {
return tokens.stream()
.map(this::lemmatize)
.map(this::removeStopWords)
.filter(token -> !token.isEmpty())
.collect(Collectors.toList());
}
private String lemmatize(String token) {
// Simple lemmatization rules
Map<String, String> lemmatizationRules = Map.of(
"customers", "customer",
"orders", "order",
"products", "product",
"employees", "employee"
);
return lemmatizationRules.getOrDefault(token, token);
}
private String removeStopWords(String token) {
Set<String> stopWords = Set.of("the", "a", "an", "and", "or", "but", "in", "on", "at", "to", "for", "of", "with", "by");
return stopWords.contains(token) ? "" : token;
}
private Map<String, Object> extractLinguisticFeatures(List<String> tokens) {
Map<String, Object> features = new HashMap<>();
features.put("token_count", tokens.size());
features.put("has_question_words", hasQuestionWords(tokens));
features.put("has_aggregation", hasAggregationWords(tokens));
features.put("has_comparison", hasComparisonWords(tokens));
return features;
}
private boolean hasQuestionWords(List<String> tokens) {
Set<String> questionWords = Set.of("what", "which", "who", "whom", "whose", "where", "when", "why", "how");
return tokens.stream().anyMatch(questionWords::contains);
}
private boolean hasAggregationWords(List<String> tokens) {
Set<String> aggregationWords = Set.of("count", "sum", "average", "avg", "maximum", "minimum", "max", "min", "total");
return tokens.stream().anyMatch(aggregationWords::contains);
}
private boolean hasComparisonWords(List<String> tokens) {
Set<String> comparisonWords = Set.of("greater", "less", "equal", "after", "before", "between", "than");
return tokens.stream().anyMatch(comparisonWords::contains);
}
}
// Data class for processed text
public class ProcessedText {
private final String original;
private final String cleaned;
private final List<String> tokens;
private final List<String> normalizedTokens;
private final Map<String, Object> linguisticFeatures;
public ProcessedText(String original, String cleaned, List<String> tokens,
List<String> normalizedTokens, Map<String, Object> linguisticFeatures) {
this.original = original;
this.cleaned = cleaned;
this.tokens = tokens;
this.normalizedTokens = normalizedTokens;
this.linguisticFeatures = linguisticFeatures;
}
// Getters
public List<String> getNormalizedTokens() { return normalizedTokens; }
public Map<String, Object> getLinguisticFeatures() { return linguisticFeatures; }
public String getCleaned() { return cleaned; }
}
Intent Recognition
public class IntentRecognizer {
public QueryIntent recognize(ProcessedText processedText) {
String text = processedText.getCleaned();
List<String> tokens = processedText.getNormalizedTokens();
// Rule-based intent recognition
if (isSelectIntent(text, tokens)) {
return QueryIntent.SELECT;
} else if (isUpdateIntent(text, tokens)) {
return QueryIntent.UPDATE;
} else if (isDeleteIntent(text, tokens)) {
return QueryIntent.DELETE;
} else if (isInsertIntent(text, tokens)) {
return QueryIntent.INSERT;
} else {
return QueryIntent.UNKNOWN;
}
}
private boolean isSelectIntent(String text, List<String> tokens) {
Set<String> selectIndicators = Set.of("show", "display", "list", "find", "get", "select", "what", "which", "who");
return selectIndicators.stream().anyMatch(text::contains) ||
tokens.stream().anyMatch(selectIndicators::contains);
}
private boolean isUpdateIntent(String text, List<String> tokens) {
Set<String> updateIndicators = Set.of("update", "change", "modify", "set", "edit");
return updateIndicators.stream().anyMatch(text::contains);
}
private boolean isDeleteIntent(String text, List<String> tokens) {
Set<String> deleteIndicators = Set.of("delete", "remove", "erase", "clear");
return deleteIndicators.stream().anyMatch(text::contains);
}
private boolean isInsertIntent(String text, List<String> tokens) {
Set<String> insertIndicators = Set.of("add", "create", "insert", "new");
return insertIndicators.stream().anyMatch(text::contains);
}
}
public enum QueryIntent {
SELECT, INSERT, UPDATE, DELETE, UNKNOWN
}
Entity Extraction
public class EntityExtractor {
private final DatabaseSchema schema;
private final Map<String, String> synonymMap;
public EntityExtractor(DatabaseSchema schema) {
this.schema = schema;
this.synonymMap = buildSynonymMap();
}
public QueryEntities extract(ProcessedText processedText) {
List<String> tokens = processedText.getNormalizedTokens();
String text = processedText.getCleaned();
QueryEntities entities = new QueryEntities();
// Extract tables
entities.setTables(extractTables(tokens));
// Extract columns
entities.setColumns(extractColumns(tokens));
// Extract conditions
entities.setConditions(extractConditions(text, tokens));
// Extract aggregation
entities.setAggregations(extractAggregations(tokens));
// Extract ordering
entities.setOrdering(extractOrdering(text));
return entities;
}
private List<String> extractTables(List<String> tokens) {
return schema.getTables().stream()
.filter(table -> tokens.contains(table.toLowerCase()))
.collect(Collectors.toList());
}
private List<String> extractColumns(List<String> tokens) {
List<String> columns = new ArrayList<>();
for (String table : schema.getTables()) {
List<String> tableColumns = schema.getColumns(table);
for (String column : tableColumns) {
String columnLower = column.toLowerCase();
if (tokens.contains(columnLower) ||
synonymMap.getOrDefault(columnLower, "").equals(columnLower)) {
columns.add(column);
}
}
}
return columns;
}
private List<QueryCondition> extractConditions(String text, List<String> tokens) {
List<QueryCondition> conditions = new ArrayList<>();
// Extract simple equality conditions
Pattern equalityPattern = Pattern.compile("(\\w+)\\s+(?:is|equals?|=\\s?)\\s*(\\w+)");
Matcher matcher = equalityPattern.matcher(text);
while (matcher.find()) {
String column = matcher.group(1);
String value = matcher.group(2);
conditions.add(new QueryCondition(column, "=", value));
}
// Extract comparison conditions
Pattern comparisonPattern = Pattern.compile("(\\w+)\\s+(greater|less|more|after|before)\\s+than\\s+(\\w+)");
matcher = comparisonPattern.matcher(text);
while (matcher.find()) {
String column = matcher.group(1);
String operator = mapComparisonToOperator(matcher.group(2));
String value = matcher.group(3);
conditions.add(new QueryCondition(column, operator, value));
}
return conditions;
}
private String mapComparisonToOperator(String comparison) {
switch (comparison.toLowerCase()) {
case "greater": case "more": case "after": return ">";
case "less": case "before": return "<";
default: return "=";
}
}
private List<Aggregation> extractAggregations(List<String> tokens) {
List<Aggregation> aggregations = new ArrayList<>();
Map<String, String> aggregationMap = Map.of(
"count", "COUNT",
"sum", "SUM",
"average", "AVG",
"avg", "AVG",
"maximum", "MAX",
"minimum", "MIN"
);
for (String token : tokens) {
if (aggregationMap.containsKey(token)) {
aggregations.add(new Aggregation(aggregationMap.get(token), ""));
}
}
return aggregations;
}
private Ordering extractOrdering(String text) {
if (text.contains("ascending") || text.contains("asc")) {
return new Ordering("", "ASC");
} else if (text.contains("descending") || text.contains("desc")) {
return new Ordering("", "DESC");
}
return null;
}
private Map<String, String> buildSynonymMap() {
return Map.of(
"client", "customer",
"purchase", "order",
"item", "product",
"worker", "employee",
"date", "order_date",
"name", "customer_name"
);
}
}
// Entity classes
public class QueryEntities {
private List<String> tables;
private List<String> columns;
private List<QueryCondition> conditions;
private List<Aggregation> aggregations;
private Ordering ordering;
private Integer limit;
// Constructors, getters, setters
}
public class QueryCondition {
private final String column;
private final String operator;
private final String value;
public QueryCondition(String column, String operator, String value) {
this.column = column;
this.operator = operator;
this.value = value;
}
// Getters
}
public class Aggregation {
private final String function;
private final String column;
public Aggregation(String function, String column) {
this.function = function;
this.column = column;
}
// Getters
}
public class Ordering {
private final String column;
private final String direction;
public Ordering(String column, String direction) {
this.column = column;
this.direction = direction;
}
// Getters
}
SQL Generation
public class SQLGenerator {
public String generate(QueryIntent intent, QueryEntities entities) {
switch (intent) {
case SELECT:
return generateSelectQuery(entities);
case INSERT:
return generateInsertQuery(entities);
case UPDATE:
return generateUpdateQuery(entities);
case DELETE:
return generateDeleteQuery(entities);
default:
throw new UnsupportedOperationException("Unsupported query intent: " + intent);
}
}
private String generateSelectQuery(QueryEntities entities) {
StringBuilder sql = new StringBuilder("SELECT ");
// Handle aggregations
if (!entities.getAggregations().isEmpty()) {
for (Aggregation agg : entities.getAggregations()) {
sql.append(agg.getFunction()).append("(").append(agg.getColumn()).append("), ");
}
sql.setLength(sql.length() - 2); // Remove trailing comma
} else if (!entities.getColumns().isEmpty()) {
// Add columns
for (String column : entities.getColumns()) {
sql.append(column).append(", ");
}
sql.setLength(sql.length() - 2); // Remove trailing comma
} else {
sql.append("*");
}
// Add FROM clause
if (!entities.getTables().isEmpty()) {
sql.append(" FROM ").append(String.join(", ", entities.getTables()));
} else {
throw new IllegalArgumentException("No tables specified in query");
}
// Add WHERE clause
if (!entities.getConditions().isEmpty()) {
sql.append(" WHERE ");
for (QueryCondition condition : entities.getConditions()) {
sql.append(condition.getColumn())
.append(" ")
.append(condition.getOperator())
.append(" ")
.append(quoteValue(condition.getValue()))
.append(" AND ");
}
sql.setLength(sql.length() - 5); // Remove trailing AND
}
// Add ORDER BY
if (entities.getOrdering() != null) {
sql.append(" ORDER BY ")
.append(entities.getOrdering().getColumn())
.append(" ")
.append(entities.getOrdering().getDirection());
}
// Add LIMIT
if (entities.getLimit() != null) {
sql.append(" LIMIT ").append(entities.getLimit());
}
return sql.toString();
}
private String generateInsertQuery(QueryEntities entities) {
// Implementation for INSERT queries
return "INSERT query generation not implemented";
}
private String generateUpdateQuery(QueryEntities entities) {
// Implementation for UPDATE queries
return "UPDATE query generation not implemented";
}
private String generateDeleteQuery(QueryEntities entities) {
// Implementation for DELETE queries
return "DELETE query generation not implemented";
}
private String quoteValue(String value) {
try {
Double.parseDouble(value);
return value; // It's a number, no quotes needed
} catch (NumberFormatException e) {
return "'" + value + "'"; // It's a string, add quotes
}
}
}
Database Schema Management
Schema Representation
public class DatabaseSchema {
private final Map<String, TableSchema> tables;
public DatabaseSchema() {
this.tables = new HashMap<>();
}
public void addTable(String tableName, List<String> columns) {
tables.put(tableName.toLowerCase(), new TableSchema(tableName, columns));
}
public List<String> getTables() {
return new ArrayList<>(tables.keySet());
}
public List<String> getColumns(String tableName) {
TableSchema table = tables.get(tableName.toLowerCase());
return table != null ? table.getColumns() : Collections.emptyList();
}
public boolean tableExists(String tableName) {
return tables.containsKey(tableName.toLowerCase());
}
public boolean columnExists(String tableName, String columnName) {
TableSchema table = tables.get(tableName.toLowerCase());
return table != null && table.getColumns().contains(columnName.toLowerCase());
}
}
public class TableSchema {
private final String name;
private final List<String> columns;
public TableSchema(String name, List<String> columns) {
this.name = name;
this.columns = columns.stream()
.map(String::toLowerCase)
.collect(Collectors.toList());
}
// Getters
public String getName() { return name; }
public List<String> getColumns() { return columns; }
}
Machine Learning Approach
Using Pre-trained Models
public class MLBasedNL2SQL {
private final TextPreprocessor preprocessor;
private final ModelExecutor modelExecutor;
private final SQLValidator sqlValidator;
public MLBasedNL2SQL() {
this.preprocessor = new TextPreprocessor();
this.modelExecutor = new ModelExecutor();
this.sqlValidator = new SQLValidator();
}
public String generateSQLWithModel(String naturalLanguageQuery, String schemaContext) {
try {
// Preprocess query
ProcessedText processed = preprocessor.process(naturalLanguageQuery);
// Prepare input for ML model
Map<String, Object> modelInput = prepareModelInput(processed, schemaContext);
// Execute model (could be local or remote)
String generatedSQL = modelExecutor.execute(modelInput);
// Validate and correct SQL if needed
return sqlValidator.validateAndCorrect(generatedSQL);
} catch (Exception e) {
throw new NL2SQLException("Failed to generate SQL using ML model", e);
}
}
private Map<String, Object> prepareModelInput(ProcessedText processed, String schemaContext) {
Map<String, Object> input = new HashMap<>();
input.put("question", processed.getCleaned());
input.put("tokens", processed.getNormalizedTokens());
input.put("schema", schemaContext);
input.put("features", processed.getLinguisticFeatures());
return input;
}
}
public class ModelExecutor {
public String execute(Map<String, Object> input) {
// This could integrate with various ML models:
// 1. Local TensorFlow/PyTorch model
// 2. Hugging Face transformers
// 3. Cloud-based NLP services
// For demonstration, using a simple rule-based fallback
return fallbackRuleBasedGeneration(input);
}
private String fallbackRuleBasedGeneration(Map<String, Object> input) {
String question = (String) input.get("question");
@SuppressWarnings("unchecked")
List<String> tokens = (List<String>) input.get("tokens");
// Simple pattern matching as fallback
if (question.contains("how many")) {
return "SELECT COUNT(*) FROM " + extractTable(tokens);
} else if (question.contains("list all")) {
return "SELECT * FROM " + extractTable(tokens);
}
return "SELECT * FROM " + extractTable(tokens) + " LIMIT 10";
}
private String extractTable(List<String> tokens) {
// Simple table extraction logic
Set<String> commonTables = Set.of("customers", "orders", "products", "employees");
return tokens.stream()
.filter(commonTables::contains)
.findFirst()
.orElse("customers");
}
}
Integration with Database
Query Execution and Result Processing
public class QueryExecutor {
private final DataSource dataSource;
public QueryExecutor(DataSource dataSource) {
this.dataSource = dataSource;
}
public QueryResult execute(String sql) {
try (Connection conn = dataSource.getConnection();
PreparedStatement stmt = conn.prepareStatement(sql)) {
boolean isResultSet = stmt.execute();
if (isResultSet) {
try (ResultSet rs = stmt.getResultSet()) {
return processResultSet(rs);
}
} else {
return new QueryResult(null, stmt.getUpdateCount());
}
} catch (SQLException e) {
throw new QueryExecutionException("Failed to execute query: " + sql, e);
}
}
private QueryResult processResultSet(ResultSet rs) throws SQLException {
ResultSetMetaData metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
// Get column names
List<String> columns = new ArrayList<>();
for (int i = 1; i <= columnCount; i++) {
columns.add(metaData.getColumnName(i));
}
// Get data
List<List<Object>> data = new ArrayList<>();
while (rs.next()) {
List<Object> row = new ArrayList<>();
for (int i = 1; i <= columnCount; i++) {
row.add(rs.getObject(i));
}
data.add(row);
}
return new QueryResult(new QueryResult.ResultSetData(columns, data), data.size());
}
}
public class QueryResult {
private final ResultSetData data;
private final int affectedRows;
private final String error;
public QueryResult(ResultSetData data, int affectedRows) {
this.data = data;
this.affectedRows = affectedRows;
this.error = null;
}
public QueryResult(String error) {
this.data = null;
this.affectedRows = 0;
this.error = error;
}
public static class ResultSetData {
private final List<String> columns;
private final List<List<Object>> rows;
public ResultSetData(List<String> columns, List<List<Object>> rows) {
this.columns = columns;
this.rows = rows;
}
// Getters
}
// Getters
}
Complete NL2SQL System
Main Controller Class
@Service
public class NL2SQLService {
private final NL2SQLArchitecture nl2sql;
private final DatabaseSchema schema;
private final QueryExecutor queryExecutor;
private final MLBasedNL2SQL mlGenerator;
public NL2SQLService(DataSource dataSource) {
this.schema = loadDatabaseSchema(dataSource);
this.nl2sql = new NL2SQLArchitecture();
this.queryExecutor = new QueryExecutor(dataSource);
this.mlGenerator = new MLBasedNL2SQL();
}
public NL2SQLResponse processNaturalLanguageQuery(String query, boolean useML) {
try {
String sql;
QueryResult result;
if (useML) {
// Use machine learning approach
sql = mlGenerator.generateSQLWithModel(query, schema.toString());
} else {
// Use rule-based approach
sql = nl2sql.processQuery(query).getSql();
}
// Execute the generated SQL
result = queryExecutor.execute(sql);
return new NL2SQLResponse(query, sql, result, null);
} catch (Exception e) {
return new NL2SQLResponse(query, null, null, e.getMessage());
}
}
private DatabaseSchema loadDatabaseSchema(DataSource dataSource) {
DatabaseSchema schema = new DatabaseSchema();
try (Connection conn = dataSource.getConnection()) {
// Load tables
ResultSet tables = conn.getMetaData().getTables(null, null, "%", new String[]{"TABLE"});
while (tables.next()) {
String tableName = tables.getString("TABLE_NAME");
List<String> columns = loadTableColumns(conn, tableName);
schema.addTable(tableName, columns);
}
} catch (SQLException e) {
throw new RuntimeException("Failed to load database schema", e);
}
return schema;
}
private List<String> loadTableColumns(Connection conn, String tableName) throws SQLException {
List<String> columns = new ArrayList<>();
ResultSet rs = conn.getMetaData().getColumns(null, null, tableName, null);
while (rs.next()) {
columns.add(rs.getString("COLUMN_NAME"));
}
return columns;
}
}
@RestController
@RequestMapping("/api/nl2sql")
public class NL2SQLController {
private final NL2SQLService nl2sqlService;
public NL2SQLController(NL2SQLService nl2sqlService) {
this.nl2sqlService = nl2sqlService;
}
@PostMapping("/query")
public ResponseEntity<NL2SQLResponse> processQuery(
@RequestBody NL2SQLRequest request) {
NL2SQLResponse response = nl2sqlService.processNaturalLanguageQuery(
request.getQuery(), request.isUseML());
return response.getError() == null ?
ResponseEntity.ok(response) :
ResponseEntity.badRequest().body(response);
}
@GetMapping("/schema")
public ResponseEntity<Map<String, Object>> getSchema() {
// Return available tables and columns for UI assistance
return ResponseEntity.ok(Map.of("schema", "database_schema_info"));
}
}
// DTO classes
public class NL2SQLRequest {
private String query;
private boolean useML = false;
// Getters and setters
}
public class NL2SQLResponse {
private final String naturalLanguageQuery;
private final String generatedSQL;
private final QueryResult result;
private final String error;
public NL2SQLResponse(String naturalLanguageQuery, String generatedSQL,
QueryResult result, String error) {
this.naturalLanguageQuery = naturalLanguageQuery;
this.generatedSQL = generatedSQL;
this.result = result;
this.error = error;
}
// Getters
}
Testing and Validation
Comprehensive Test Suite
public class NL2SQLTest {
private NL2SQLService nl2sqlService;
@BeforeEach
void setUp() {
// Setup with test database
DataSource dataSource = createTestDataSource();
nl2sqlService = new NL2SQLService(dataSource);
}
@Test
void testSimpleSelectQuery() {
NL2SQLResponse response = nl2sqlService.processNaturalLanguageQuery(
"show all customers", false);
assertNull(response.getError());
assertEquals("SELECT * FROM customers", response.getGeneratedSQL());
}
@Test
void testQueryWithConditions() {
NL2SQLResponse response = nl2sqlService.processNaturalLanguageQuery(
"find customers from New York", false);
assertNull(response.getError());
assertTrue(response.getGeneratedSQL().contains("WHERE"));
assertTrue(response.getGeneratedSQL().contains("city = 'new york'"));
}
@Test
void testAggregationQuery() {
NL2SQLResponse response = nl2sqlService.processNaturalLanguageQuery(
"count total orders", false);
assertNull(response.getError());
assertTrue(response.getGeneratedSQL().toUpperCase().contains("COUNT"));
assertTrue(response.getGeneratedSQL().toUpperCase().contains("ORDERS"));
}
@Test
void testComplexQuery() {
NL2SQLResponse response = nl2sqlService.processNaturalLanguageQuery(
"show customers who have more than 5 orders placed after 2023", false);
assertNull(response.getError());
// Verify complex condition generation
}
private DataSource createTestDataSource() {
// Create in-memory H2 database for testing
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1");
dataSource.setUsername("sa");
dataSource.setPassword("");
return dataSource;
}
}
Performance Optimization
Caching and Optimization
@Service
public class CachingNL2SQLService {
private final NL2SQLService delegate;
private final Cache<String, NL2SQLResponse> queryCache;
public CachingNL2SQLService(NL2SQLService delegate) {
this.delegate = delegate;
this.queryCache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(1, TimeUnit.HOURS)
.build();
}
public NL2SQLResponse processNaturalLanguageQuery(String query, boolean useML) {
String cacheKey = generateCacheKey(query, useML);
return queryCache.get(cacheKey, key ->
delegate.processNaturalLanguageQuery(query, useML));
}
private String generateCacheKey(String query, boolean useML) {
return query.toLowerCase() + "|" + useML;
}
}
@Component
public class QueryOptimizer {
public String optimizeSQL(String sql) {
// Remove unnecessary whitespace
sql = sql.replaceAll("\\s+", " ").trim();
// Convert to uppercase for consistency
sql = sql.toUpperCase();
// Remove redundant conditions
sql = removeRedundantConditions(sql);
return sql;
}
private String removeRedundantConditions(String sql) {
// Simple optimization: remove 1=1 conditions
return sql.replace("WHERE 1 = 1 AND", "WHERE")
.replace("WHERE 1 = 1", "");
}
}
Conclusion
This NL2SQL implementation provides:
- Rule-based approach for reliable SQL generation
- ML integration capability for complex queries
- Schema awareness for accurate entity extraction
- REST API for easy integration
- Caching and optimization for performance
- Comprehensive testing for reliability
The system can be extended with more sophisticated NLP techniques, better ML models, and additional SQL features to handle increasingly complex natural language queries.