#!/usr/bin/env python3 """ SQL Dataset Validation Script This script validates the Text-to-SQL dataset by executing each SQL query against the database and capturing results for manual verification. Usage: python validate_sql_dataset.py Output: - validation_results.json: Detailed results for each query - validation_summary.json: Summary statistics """ import csv import json from datetime import datetime from pathlib import Path from typing import Any, Dict, List import pandas as pd # Import our database utilities from .db_utils import SQLiteDB, execute_sql def load_dataset(csv_path: str = "datasets/booksql_sample.csv") -> List[Dict[str, Any]]: """ Load the SQL dataset from CSV file. Args: csv_path: Path to the CSV file containing queries Returns: List of dictionaries containing query data """ dataset = [] csv_file = Path(csv_path) if not csv_file.exists(): raise FileNotFoundError(f"Dataset file not found: {csv_path}") with open(csv_file, 'r', encoding='utf-8') as f: reader = csv.DictReader(f) for i, row in enumerate(reader): dataset.append({ 'index': i, 'query': row['Query'].strip(), 'sql': row['SQL'].strip(), 'level': row['Levels'].strip(), 'split': row['split'].strip() }) return dataset def execute_and_validate_query(query_data: Dict[str, Any]) -> Dict[str, Any]: """ Execute a single SQL query and capture results. Args: query_data: Dictionary containing query information Returns: Dictionary with execution results """ result = { 'index': query_data['index'], 'natural_language_query': query_data['query'], 'sql_query': query_data['sql'], 'difficulty_level': query_data['level'], 'dataset_split': query_data['split'], 'execution_success': False, 'execution_time': None, 'error_message': None, 'result_data': None, 'result_shape': None, 'result_columns': None } # Record execution time start_time = datetime.now() try: # Execute the SQL query with case-insensitive string matching success, query_result = execute_sql(query_data['sql'], case_insensitive=True) end_time = datetime.now() result['execution_time'] = (end_time - start_time).total_seconds() if success and isinstance(query_result, pd.DataFrame): result['execution_success'] = True result['result_shape'] = list(query_result.shape) # [rows, columns] result['result_columns'] = list(query_result.columns) # Convert DataFrame to list of dictionaries for JSON serialization # Limit to first 10 rows to keep output manageable if len(query_result) > 10: sample_data = query_result.head(10) result['result_data'] = sample_data.to_dict('records') result['result_truncated'] = True result['total_rows'] = len(query_result) else: result['result_data'] = query_result.to_dict('records') result['result_truncated'] = False result['total_rows'] = len(query_result) # Classify result type for better reporting if len(query_result) == 0: result['result_type'] = 'empty' elif len(query_result) > 0: first_row = query_result.iloc[0] # Check if all values in the first row are null/None if all(pd.isna(value) or value is None for value in first_row): result['result_type'] = 'null_values' else: result['result_type'] = 'has_data' else: result['result_type'] = 'has_data' else: result['execution_success'] = False result['error_message'] = str(query_result) result['result_type'] = 'failed' except Exception as e: end_time = datetime.now() result['execution_time'] = (end_time - start_time).total_seconds() result['execution_success'] = False result['error_message'] = f"Unexpected error: {str(e)}" result['result_type'] = 'failed' return result def generate_summary_statistics(results: List[Dict[str, Any]]) -> Dict[str, Any]: """ Generate summary statistics from validation results. Args: results: List of validation results Returns: Dictionary containing summary statistics """ total_queries = len(results) successful_queries = sum(1 for r in results if r['execution_success']) failed_queries = total_queries - successful_queries # Count by result type result_type_counts = { 'has_data': sum(1 for r in results if r.get('result_type') == 'has_data'), 'null_values': sum(1 for r in results if r.get('result_type') == 'null_values'), 'empty': sum(1 for r in results if r.get('result_type') == 'empty'), 'failed': sum(1 for r in results if r.get('result_type') == 'failed') } # Group by difficulty level level_stats = {} for result in results: level = result['difficulty_level'] if level not in level_stats: level_stats[level] = { 'total': 0, 'successful': 0, 'failed': 0, 'has_data': 0, 'null_values': 0, 'empty': 0 } level_stats[level]['total'] += 1 if result['execution_success']: level_stats[level]['successful'] += 1 else: level_stats[level]['failed'] += 1 # Count by result type for this level result_type = result.get('result_type', 'unknown') if result_type in level_stats[level]: level_stats[level][result_type] += 1 # Calculate success rates for level in level_stats: total = level_stats[level]['total'] successful = level_stats[level]['successful'] level_stats[level]['success_rate'] = successful / total if total > 0 else 0 # Common error types error_types = {} for result in results: if not result['execution_success'] and result['error_message']: # Extract first part of error message as error type error_type = result['error_message'].split(':')[0] error_types[error_type] = error_types.get(error_type, 0) + 1 # Average execution time execution_times = [r['execution_time'] for r in results if r['execution_time'] is not None] avg_execution_time = sum(execution_times) / len(execution_times) if execution_times else 0 summary = { 'validation_timestamp': datetime.now().isoformat(), 'total_queries': total_queries, 'successful_queries': successful_queries, 'failed_queries': failed_queries, 'overall_success_rate': successful_queries / total_queries if total_queries > 0 else 0, 'average_execution_time_seconds': avg_execution_time, 'result_type_counts': result_type_counts, 'statistics_by_difficulty': level_stats, 'common_error_types': error_types, 'sample_successful_queries': [ r['index'] for r in results if r['execution_success'] ][:5], # First 5 successful queries 'sample_failed_queries': [ r['index'] for r in results if not r['execution_success'] ][:5] # First 5 failed queries } return summary def main(): """Main validation script.""" print("šŸ” Starting SQL Dataset Validation...") print("=" * 50) # Load dataset try: dataset = load_dataset("datasets/booksql_sample.csv") print(f"šŸ“Š Loaded {len(dataset)} queries from dataset") except FileNotFoundError as e: print(f"āŒ Error: {e}") return except Exception as e: print(f"āŒ Unexpected error loading dataset: {e}") return # Validate database connection print("šŸ”— Testing database connection...") db = SQLiteDB() success, message = db.connect() if not success: print(f"āŒ Database connection failed: {message}") print("šŸ’” Make sure the BookSQL database is available at: BookSQL-files/BookSQL/accounting.sqlite") return # Get database info success, tables = db.get_table_names() if success: print(f"āœ… Database connected. Found tables: {tables}") db.disconnect() # Execute all queries print(f"\nšŸš€ Executing {len(dataset)} SQL queries...") results = [] for i, query_data in enumerate(dataset): print(f"Processing query {i+1}/{len(dataset)}: {query_data['level']} level", end=" ... ") result = execute_and_validate_query(query_data) results.append(result) if result['execution_success']: print("āœ…") else: print("āŒ") # Generate summary print("\nšŸ“ˆ Generating summary statistics...") summary = generate_summary_statistics(results) # Save results print("šŸ’¾ Saving validation results...") # Save detailed results with open('validation_results.json', 'w', encoding='utf-8') as f: json.dump(results, f, indent=2, ensure_ascii=False) # Save summary with open('validation_summary.json', 'w', encoding='utf-8') as f: json.dump(summary, f, indent=2, ensure_ascii=False) # Print summary to console print("\n" + "=" * 50) print("šŸ“Š VALIDATION SUMMARY") print("=" * 50) print(f"Total Queries: {summary['total_queries']}") print(f"Successful: {summary['successful_queries']} ({summary['overall_success_rate']:.1%})") print(f"Failed: {summary['failed_queries']}") print(f"Average Execution Time: {summary['average_execution_time_seconds']:.3f}s") print("\nšŸ“ˆ Result Type Distribution:") result_counts = summary['result_type_counts'] total = summary['total_queries'] print(f" āœ… Has Data: {result_counts['has_data']}/{total} ({result_counts['has_data']/total:.1%})") print(f" šŸ” NULL Values: {result_counts['null_values']}/{total} ({result_counts['null_values']/total:.1%})") print(f" šŸ“­ Empty Results: {result_counts['empty']}/{total} ({result_counts['empty']/total:.1%})") print(f" āŒ Failed: {result_counts['failed']}/{total} ({result_counts['failed']/total:.1%})") print("\nšŸ“ˆ Success Rate by Difficulty:") for level, stats in summary['statistics_by_difficulty'].items(): print(f" {level.capitalize()}: {stats['successful']}/{stats['total']} ({stats['success_rate']:.1%})") print(f" āœ… Data: {stats['has_data']}, šŸ” NULL: {stats['null_values']}, šŸ“­ Empty: {stats['empty']}, āŒ Failed: {stats['failed']}") if summary['common_error_types']: print("\nāš ļø Common Error Types:") for error_type, count in sorted(summary['common_error_types'].items(), key=lambda x: x[1], reverse=True)[:5]: print(f" {error_type}: {count} occurrences") print("\nšŸ’¾ Detailed results saved to:") print(" - validation_results.json (detailed results)") print(" - validation_summary.json (summary statistics)") if summary['failed_queries'] > 0: print("\nšŸ” Review failed queries in validation_results.json") print("šŸ’” Check if database schema matches expected tables/columns") if __name__ == "__main__": main()