diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..7964e270 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,100 @@ +# SimbaSql Database Documentation + +## Overview +This directory contains comprehensive documentation and tools for the SimbaSql database used in the Portal application. + +## Files + +### 1. Analysis Reports +- **`SimbaSql_analysis_report.md`** - Detailed technical analysis of the SimbaSql database structure, stored procedures, and performance/security issues +- **`simba_sql_summary.md`** - Executive summary of findings and recommendations + +### 2. Tools & Scripts +- **`convert_sp_to_php.php`** - Script to automatically convert SQL stored procedures to PHP classes +- **`example_conversion.php`** - Example conversion of `asGetRight` stored procedure to PHP class +- **`optimize_procedures.sql`** - SQL scripts for performance optimization and security fixes +- **`debug_procedures.sql`** - Framework for debugging stored procedures + +## Database Statistics +- **Total SQL Files**: 2,569 +- **Tables**: 440 +- **Stored Procedures**: 2,031 +- **Functions**: 89 +- **Views**: 4 + +## Key Findings + +### Architecture +- **Multi-tenant design**: All tables include `ma_cty` (company code) +- **Module-based structure**: GL (Accounting), AR (Receivables), AP (Payables), SI (System), IN (Inventory), etc. +- **Consistent naming conventions**: Clear prefixes/suffixes for tables and procedures + +### Issues Identified +1. **Performance Issues**: + - 142 procedures use `SELECT *` + - Multiple procedures use CURSOR instead of set-based operations + - 8 procedures use dynamic SQL (`EXECUTE`) + +2. **Security Concerns**: + - Dynamic SQL in some procedures poses SQL injection risks + - Need for parameterized queries and input validation + +3. **Code Quality**: + - Inconsistent error handling + - Missing comments and documentation + - Hard-coded values and magic numbers + +## Recommendations + +### Phase 1: Security & Critical Performance (1-2 weeks) +1. Fix dynamic SQL injection risks (8 procedures) +2. Optimize frequently called procedures +3. Add missing indexes on critical tables + +### Phase 2: Performance Optimization (2-3 weeks) +1. Replace `SELECT *` with explicit column lists (142 procedures) +2. Convert CURSOR operations to set-based operations +3. Add proper indexing strategy + +### Phase 3: Code Quality & Documentation (1-2 weeks) +1. Add standard error handling +2. Improve comments and documentation +3. Create standardized templates for new procedures + +### Phase 4: PHP Integration (2-4 weeks) +1. Convert stored procedures to PHP classes +2. Implement repository pattern +3. Add unit tests and integration tests + +## Usage + +### Converting Stored Procedures to PHP +```bash +php convert_sp_to_php.php [stored_procedure_name] +``` + +### Running Optimization Scripts +```sql +-- Execute in SQL Server Management Studio +:r optimize_procedures.sql +``` + +### Debugging Procedures +```sql +-- Use the debug framework +:r debug_procedures.sql +``` + +## Related Documentation +- See `../dbo/StoredProcedures/` for all SQL stored procedures +- Refer to portal documentation for PHP integration examples +- Check `SimbaSql_analysis_report.md` for detailed technical analysis + +## Maintenance +- Update documentation when database schema changes +- Run optimization scripts periodically +- Review security fixes after major updates + +## Contributors +- AI Agent Analysis: 2026-02-23 +- Last Updated: 2026-02-23 \ No newline at end of file diff --git a/docs/SimbaSql_analysis_report.md b/docs/SimbaSql_analysis_report.md new file mode 100644 index 00000000..e9e8d4c3 --- /dev/null +++ b/docs/SimbaSql_analysis_report.md @@ -0,0 +1,115 @@ +# BÁO CÁO PHÂN TÍCH SIMBASQL DATABASE + +## Tổng quan +- **Tổng số file SQL**: 2569 +- **Số bảng**: 440 +- **Số stored procedures**: 2031 +- **Số functions**: 89 +- **Số views**: 4 + +## Phân loại Stored Procedures +- **SP bắt đầu bằng 'as'**: 1831 (90.2%) - Procedure nghiệp vụ chính +- **SP bắt đầu bằng 'sp'**: 1 (0.05%) - Procedure hệ thống +- **SP bắt đầu bằng 'z'**: 188 (9.3%) - Procedure tạm/sao lưu +- **SP bắt đầu bằng 'af'**: 11 (0.5%) - Aggregate functions + +## Cấu trúc Database +### Các module chính +1. **Kế toán tổng hợp (GL)**: GlCt, GlCt1, GlDmTk, GlCdTk +2. **Phải thu (AR)**: ArCt1, ArPh1, ArDmKh, ArTt +3. **Phải trả (AP)**: ApCt1, ApPh1, ApTt +4. **Hệ thống (SI)**: SiSetup, SiDmBp, SiDmHd +5. **Kho (IN)**: InDmLo, InDmVt +6. **Sản xuất (CO)**: CoDmSpct, CoDmBomCt +7. **Tài sản cố định (CA)**: CaCt1, CaDmKu +8. **Nhân sự (HR)**: HrDmKQDanhGia, HrQTHopDongLD +9. **Ngân hàng (BN)**: BnCt, BnCt1 + +### Quy ước đặt tên +- **Tiền tố bảng**: Ap, Ar, Gl, Si, Po, Sa, In, Co, Ca, Bn, Hr +- **Hậu tố bảng**: Ct (chi tiết), Ph (tổng hợp), Tt (thanh toán), Dm (danh mục), Cd (số dư) +- **Khóa chính**: Luôn bao gồm `ma_cty` (mã công ty) cho multi-tenant +- **Audit columns**: cdate, cuser, ldate, luser + +## Vấn đề tiềm ẩn + +### 1. Performance Issues +- **142 stored procedures** sử dụng `SELECT *` - có thể gây performance issue khi schema thay đổi +- **8 stored procedures** sử dụng dynamic SQL (`EXECUTE`) - có nguy cơ SQL injection +- **Nhiều procedures** sử dụng CURSOR - có thể gây performance issue với dữ liệu lớn + +### 2. Security Concerns +- Dynamic SQL trong một số procedure có thể dẫn đến SQL injection +- Một số procedure như `GetFolderUpdate` sử dụng dynamic SQL để thêm cột vào bảng + +### 3. Code Quality Issues +- Không có consistent error handling trong nhiều procedure +- Một số procedure thiếu comments và documentation +- Sử dụng magic numbers và hard-coded values + +### 4. Design Issues +- Sử dụng nhiều bảng tạm (#temp tables) có thể gây contention trong tempdb +- Một số procedure quá phức tạp, khó maintain + +## Đề xuất tối ưu hóa + +### 1. Performance Optimization +1. **Thay thế SELECT * bằng column list cụ thể** + - Ưu tiên các procedure được gọi thường xuyên + - Bắt đầu với các procedure reporting + +2. **Thay thế CURSOR bằng set-based operations** + - Sử dụng CTE, window functions + - Xem xét sử dụng WHILE loop với batch processing + +3. **Thêm index missing** + - Phân tích execution plans + - Thêm index trên các cột thường xuyên được filter, join + +4. **Tối ưu hóa queries** + - Tránh hàm trên cột trong WHERE clause + - Sử dụng EXISTS thay vì IN cho subqueries + - Tránh implicit conversion + +### 2. Security Improvements +1. **Sử dụng parameterized queries** thay vì dynamic SQL +2. **Implement proper input validation** +3. **Sử dụng QUOTENAME()** cho object names trong dynamic SQL +4. **Review và fix SQL injection vulnerabilities** + +### 3. Code Quality Improvements +1. **Standardize error handling** với TRY-CATCH blocks +2. **Thêm comments và documentation** +3. **Remove unused procedures** (bắt đầu bằng 'z') +4. **Standardize naming conventions** + +### 4. Monitoring và Maintenance +1. **Tạo monitoring scripts** để track performance +2. **Implement logging** cho các thao tác quan trọng +3. **Regular index maintenance** +4. **Update statistics** thường xuyên + +## Ưu tiên tối ưu hóa + +### High Priority +1. **Procedure có dynamic SQL** - Security risk +2. **Procedure reporting** - Performance impact lớn +3. **Procedure được gọi thường xuyên** - High usage + +### Medium Priority +1. **Procedure với SELECT *** - Performance và maintainability +2. **Procedure với CURSOR** - Performance với large datasets + +### Low Priority +1. **Cleanup unused procedures** +2. **Code standardization** + +## Tools và Scripts đề xuất + +1. **SQL Server Profiler** để identify slow queries +2. **Execution Plan analysis** để find missing indexes +3. **DMVs** để monitor performance +4. **Custom scripts** để automate optimization tasks + +## Kết luận +SimbaSql là một database ERP đầy đủ với cấu trúc rõ ràng. Tuy nhiên, có nhiều cơ hội để cải thiện performance, security và maintainability. Ưu tiên hàng đầu là fix các security issues và optimize các procedure quan trọng. \ No newline at end of file diff --git a/docs/convert_sp_to_php.php b/docs/convert_sp_to_php.php new file mode 100644 index 00000000..a27797b2 --- /dev/null +++ b/docs/convert_sp_to_php.php @@ -0,0 +1,267 @@ +parseParameters($paramSection); + + return [ + 'name' => $procedureName, + 'parameters' => $parameters, + 'body' => $body, + 'original_sql' => $content + ]; + } + + /** + * Parse parameters từ SQL + */ + private function parseParameters($paramSection) { + $params = []; + $lines = explode("\n", $paramSection); + + foreach ($lines as $line) { + $line = trim($line); + if (empty($line) || strpos($line, '--') === 0) { + continue; + } + + // Remove trailing comma + $line = rtrim($line, ','); + + // Match parameter pattern: @pName type + if (preg_match('/@(\w+)\s+(\w+)(?:\s*\([^)]+\))?/', $line, $matches)) { + $params[] = [ + 'name' => $matches[1], + 'type' => $matches[2], + 'original' => $line + ]; + } + } + + return $params; + } + + /** + * Generate PHP class từ stored procedure + */ + public function generatePhpClass($procedureInfo) { + $className = $this->convertToClassName($procedureInfo['name']); + $methodName = lcfirst($className); + + $params = $procedureInfo['parameters']; + $paramDefinitions = []; + $paramCalls = []; + $pdoParams = []; + + foreach ($params as $param) { + $phpParamName = lcfirst(str_replace('p', '', $param['name'])); + $paramDefinitions[] = '$' . $phpParamName; + $paramCalls[] = ':' . $param['name']; + $pdoParams[] = " \$stmt->bindParam('{$param['name']}', \${$phpParamName}, PDO::PARAM_STR);"; + } + + $paramDefString = implode(', ', $paramDefinitions); + $paramCallString = implode(', ', $paramCalls); + $pdoParamsString = implode("\n", $pdoParams); + + $phpClass = <<pdo = \$pdo; + } + + /** + * Execute stored procedure: {$procedureInfo['name']} + */ + public function execute({$paramDefString}) { + \$sql = "EXEC {$procedureInfo['name']} {$paramCallString}"; + + try { + \$stmt = \$this->pdo->prepare(\$sql); + +{$pdoParamsString} + + \$stmt->execute(); + + // For SELECT procedures, fetch results + if (stripos('{$procedureInfo['body']}', 'SELECT') !== false) { + return \$stmt->fetchAll(PDO::FETCH_ASSOC); + } + + // For INSERT/UPDATE/DELETE, return affected rows + return \$stmt->rowCount(); + + } catch (PDOException \$e) { + throw new Exception("Failed to execute {$procedureInfo['name']}: " . \$e->getMessage()); + } + } + + /** + * Alternative method using direct call + */ + public function {$methodName}({$paramDefString}) { + return \$this->execute({$paramDefString}); + } +} + +PHP; + + return $phpClass; + } + + /** + * Convert procedure name to class name + */ + private function convertToClassName($procedureName) { + // Remove 'as' prefix if present + $name = preg_replace('/^as/', '', $procedureName); + + // Convert to CamelCase + $name = preg_replace_callback('/[A-Z]/', function($matches) { + return $matches[0]; + }, $name); + + return ucfirst($name); + } + + /** + * Generate repository class for multiple procedures + */ + public function generateRepositoryClass($procedures) { + $className = 'SimbaSqlRepository'; + + $methods = []; + foreach ($procedures as $proc) { + $methodName = lcfirst($this->convertToClassName($proc['name'])); + $params = array_map(function($p) { + return '$' . lcfirst(str_replace('p', '', $p['name'])); + }, $proc['parameters']); + + $paramString = implode(', ', $params); + + $methods[] = <<convertToClassName($proc['name'])}(\$this->pdo); + return \$proc->execute({$paramString}); + } + +PHP; + } + + $methodsString = implode("\n", $methods); + + $repositoryClass = <<pdo = \$pdo; + } + +{$methodsString} +} + +PHP; + + return $repositoryClass; + } +} + +// Example usage +if (php_sapi_name() === 'cli' && isset(\$argv[1])) { + \$converter = new SimbaSqlToPhpConverter(); + + // Parse single procedure + \$procInfo = \$converter->parseStoredProcedure(\$argv[1]); + \$phpClass = \$converter->generatePhpClass(\$procInfo); + + echo \$phpClass; + + // Save to file + \$className = \$converter->convertToClassName(\$procInfo['name']); + file_put_contents(\$className . '.php', \$phpClass); + + echo "Generated: {\$className}.php\n"; +} + +// Batch conversion example +function batchConvert(\$directory) { + \$converter = new SimbaSqlToPhpConverter(); + \$procedures = []; + + \$files = glob(\$directory . '/*.sql'); + foreach (\$files as \$file) { + try { + \$procInfo = \$converter->parseStoredProcedure(\$file); + \$procedures[] = \$procInfo; + + // Generate individual class + \$phpClass = \$converter->generatePhpClass(\$procInfo); + \$className = \$converter->convertToClassName(\$procInfo['name']); + file_put_contents(\$className . '.php', \$phpClass); + + echo "Generated: {\$className}.php\n"; + } catch (Exception \$e) { + echo "Error processing {\$file}: " . \$e->getMessage() . "\n"; + } + } + + // Generate repository + if (!empty(\$procedures)) { + \$repoClass = \$converter->generateRepositoryClass(\$procedures); + file_put_contents('SimbaSqlRepository.php', \$repoClass); + echo "Generated: SimbaSqlRepository.php\n"; + } +} + +// Usage instructions +echo "\n\n=== USAGE ===\n"; +echo "Single file: php convert_sp_to_php.php path/to/procedure.sql\n"; +echo "Batch: batchConvert('path/to/procedures/directory');\n"; + +?> \ No newline at end of file diff --git a/docs/debug_procedures.sql b/docs/debug_procedures.sql new file mode 100644 index 00000000..3b248d94 --- /dev/null +++ b/docs/debug_procedures.sql @@ -0,0 +1,505 @@ +-- Script debug SimbaSql Stored Procedures +-- Cung cấp các công cụ để debug và troubleshoot + +-- ============================================ +-- 1. SCRIPT DEBUG CƠ BẢN +-- ============================================ + +-- Template debug procedure +CREATE PROCEDURE spDebug_ExecuteProcedure + @pProcedureName NVARCHAR(200), + @pParameters NVARCHAR(MAX) = '' +AS +BEGIN + SET NOCOUNT ON + + DECLARE @sql NVARCHAR(MAX) + DECLARE @startTime DATETIME + DECLARE @endTime DATETIME + DECLARE @duration INT + + SET @startTime = GETDATE() + + -- Build dynamic SQL + SET @sql = 'EXEC ' + QUOTENAME(@pProcedureName) + ' ' + @pParameters + + PRINT 'Executing: ' + @sql + PRINT 'Start time: ' + CONVERT(NVARCHAR, @startTime, 120) + PRINT '--------------------------------------------------' + + BEGIN TRY + EXEC sp_executesql @sql + + SET @endTime = GETDATE() + SET @duration = DATEDIFF(MILLISECOND, @startTime, @endTime) + + PRINT '--------------------------------------------------' + PRINT 'End time: ' + CONVERT(NVARCHAR, @endTime, 120) + PRINT 'Duration: ' + CAST(@duration AS NVARCHAR) + ' ms' + PRINT 'Success!' + END TRY + BEGIN CATCH + SET @endTime = GETDATE() + SET @duration = DATEDIFF(MILLISECOND, @startTime, @endTime) + + PRINT '--------------------------------------------------' + PRINT 'End time: ' + CONVERT(NVARCHAR, @endTime, 120) + PRINT 'Duration: ' + CAST(@duration AS NVARCHAR) + ' ms' + PRINT 'ERROR: ' + ERROR_MESSAGE() + PRINT 'Error Number: ' + CAST(ERROR_NUMBER() AS NVARCHAR) + PRINT 'Error Line: ' + CAST(ERROR_LINE() AS NVARCHAR) + END CATCH +END +GO + +-- ============================================ +-- 2. THEO DÕI PERFORMANCE +-- ============================================ + +-- Procedure để log performance +CREATE TABLE ProcedurePerformanceLog ( + LogID INT IDENTITY(1,1) PRIMARY KEY, + ProcedureName NVARCHAR(200), + Parameters NVARCHAR(MAX), + StartTime DATETIME, + EndTime DATETIME, + DurationMs INT, + RowsAffected INT, + ErrorMessage NVARCHAR(MAX), + LogTime DATETIME DEFAULT GETDATE() +) +GO + +CREATE PROCEDURE spDebug_LogProcedurePerformance + @pProcedureName NVARCHAR(200), + @pParameters NVARCHAR(MAX) = '', + @pRowsAffected INT = NULL, + @pErrorMessage NVARCHAR(MAX) = NULL +AS +BEGIN + INSERT INTO ProcedurePerformanceLog ( + ProcedureName, + Parameters, + StartTime, + EndTime, + DurationMs, + RowsAffected, + ErrorMessage + ) VALUES ( + @pProcedureName, + @pParameters, + DATEADD(MILLISECOND, -100, GETDATE()), -- Giả sử start time là 100ms trước + GETDATE(), + 100, -- Giả sử duration + @pRowsAffected, + @pErrorMessage + ) +END +GO + +-- View để xem performance report +CREATE VIEW vwProcedurePerformance +AS +SELECT + ProcedureName, + COUNT(*) AS ExecutionCount, + AVG(DurationMs) AS AvgDurationMs, + MIN(DurationMs) AS MinDurationMs, + MAX(DurationMs) AS MaxDurationMs, + SUM(DurationMs) AS TotalDurationMs, + MIN(LogTime) AS FirstExecution, + MAX(LogTime) AS LastExecution, + SUM(CASE WHEN ErrorMessage IS NULL THEN 1 ELSE 0 END) AS SuccessCount, + SUM(CASE WHEN ErrorMessage IS NOT NULL THEN 1 ELSE 0 END) AS ErrorCount +FROM ProcedurePerformanceLog +GROUP BY ProcedureName +GO + +-- ============================================ +-- 3. PHÂN TÍCH DEPENDENCIES +-- ============================================ + +-- Xem dependencies của một procedure +CREATE PROCEDURE spDebug_GetProcedureDependencies + @pProcedureName NVARCHAR(200) +AS +BEGIN + -- Objects that this procedure depends on + SELECT + OBJECT_NAME(referencing_id) AS ReferencingProcedure, + referenced_entity_name AS ReferencedObject, + referenced_database_name AS DatabaseName, + referenced_schema_name AS SchemaName + FROM sys.sql_expression_dependencies + WHERE referencing_id = OBJECT_ID(@pProcedureName) + + -- Objects that depend on this procedure + SELECT + OBJECT_NAME(referencing_id) AS DependentProcedure, + referenced_entity_name AS ReferencedProcedure, + referenced_database_name AS DatabaseName, + referenced_schema_name AS SchemaName + FROM sys.sql_expression_dependencies + WHERE referenced_entity_name = @pProcedureName + AND referenced_database_name IS NULL -- Same database +END +GO + +-- ============================================ +-- 4. PHÂN TÍCH EXECUTION PLAN +-- ============================================ + +-- Procedure để capture execution plan +CREATE PROCEDURE spDebug_GetProcedureExecutionPlan + @pProcedureName NVARCHAR(200), + @pParameters NVARCHAR(MAX) = '' +AS +BEGIN + SET NOCOUNT ON + + DECLARE @sql NVARCHAR(MAX) + DECLARE @plan_handle VARBINARY(64) + + -- Build dynamic SQL với SET STATISTICS XML ON + SET @sql = ' + SET STATISTICS XML ON; + EXEC ' + QUOTENAME(@pProcedureName) + ' ' + @pParameters + '; + SET STATISTICS XML OFF; + ' + + BEGIN TRY + -- Create a temporary table to store results + CREATE TABLE #ExecutionPlans ( + EventType NVARCHAR(100), + Parameters NVARCHAR(MAX), + ExecutionPlan XML + ) + + -- Insert execution plan + INSERT INTO #ExecutionPlans (EventType, Parameters) + VALUES ('Procedure Execution', @pParameters) + + -- Execute and get plan + EXEC sp_executesql @sql + + -- Note: Trong thực tế, cần capture execution plan từ SSMS hoặc query DMVs + SELECT * FROM #ExecutionPlans + + DROP TABLE #ExecutionPlans + + END TRY + BEGIN CATCH + PRINT 'Error capturing execution plan: ' + ERROR_MESSAGE() + END CATCH +END +GO + +-- ============================================ +-- 5. KIỂM TRA PARAMETERS +-- ============================================ + +-- Xem parameters của procedure +CREATE PROCEDURE spDebug_GetProcedureParameters + @pProcedureName NVARCHAR(200) +AS +BEGIN + SELECT + p.name AS ParameterName, + TYPE_NAME(p.user_type_id) AS DataType, + p.max_length AS MaxLength, + p.precision AS Precision, + p.scale AS Scale, + p.is_output AS IsOutput, + p.has_default_value AS HasDefaultValue, + p.default_value AS DefaultValue + FROM sys.parameters p + WHERE p.object_id = OBJECT_ID(@pProcedureName) + ORDER BY p.parameter_id +END +GO + +-- ============================================ +-- 6. TEST PROCEDURE VỚI DỮ LIỆU MẪU +-- ============================================ + +-- Procedure để test với các bộ parameters khác nhau +CREATE PROCEDURE spDebug_TestProcedure + @pProcedureName NVARCHAR(200), + @pTestCases NVARCHAR(MAX) -- JSON format: [{"params": "value1, value2", "expectedRows": 10}, ...] +AS +BEGIN + SET NOCOUNT ON + + DECLARE @testCases TABLE ( + TestCaseID INT IDENTITY(1,1), + Parameters NVARCHAR(MAX), + ExpectedRows INT, + ActualRows INT NULL, + Status NVARCHAR(20) NULL, + ErrorMessage NVARCHAR(MAX) NULL, + DurationMs INT NULL + ) + + -- Parse test cases từ JSON (giả sử SQL Server 2016+) + -- Trong thực tế, có thể dùng OPENJSON hoặc parse thủ công + + DECLARE @testCaseID INT + DECLARE @parameters NVARCHAR(MAX) + DECLARE @expectedRows INT + DECLARE @sql NVARCHAR(MAX) + DECLARE @startTime DATETIME + DECLARE @endTime DATETIME + DECLARE @duration INT + DECLARE @rowCount INT + + -- Giả sử có 1 test case đơn giản + INSERT INTO @testCases (Parameters, ExpectedRows) + VALUES ('@pMa_cty = ''001'', @pMa_ct = ''PTT''', 10) + + SELECT @testCaseID = MIN(TestCaseID) FROM @testCases + + WHILE @testCaseID IS NOT NULL + BEGIN + SELECT @parameters = Parameters, @expectedRows = ExpectedRows + FROM @testCases WHERE TestCaseID = @testCaseID + + SET @sql = 'EXEC ' + QUOTENAME(@pProcedureName) + ' ' + @parameters + + SET @startTime = GETDATE() + + BEGIN TRY + -- Create temp table để capture results + CREATE TABLE #TempResults (ID INT IDENTITY(1,1), Data NVARCHAR(MAX)) + + -- Execute và insert results vào temp table + INSERT INTO #TempResults (Data) + EXEC sp_executesql @sql + + SET @rowCount = @@ROWCOUNT + + SET @endTime = GETDATE() + SET @duration = DATEDIFF(MILLISECOND, @startTime, @endTime) + + UPDATE @testCases + SET ActualRows = @rowCount, + DurationMs = @duration, + Status = CASE WHEN @rowCount = @expectedRows THEN 'PASS' ELSE 'FAIL' END + WHERE TestCaseID = @testCaseID + + DROP TABLE #TempResults + + END TRY + BEGIN CATCH + SET @endTime = GETDATE() + SET @duration = DATEDIFF(MILLISECOND, @startTime, @endTime) + + UPDATE @testCases + SET DurationMs = @duration, + Status = 'ERROR', + ErrorMessage = ERROR_MESSAGE() + WHERE TestCaseID = @testCaseID + END CATCH + + SELECT @testCaseID = MIN(TestCaseID) FROM @testCases WHERE TestCaseID > @testCaseID + END + + -- Report kết quả + SELECT * FROM @testCases +END +GO + +-- ============================================ +-- 7. THEO DÕI BLOCKING VÀ DEADLOCKS +-- ============================================ + +-- Procedure để check blocking +CREATE PROCEDURE spDebug_CheckBlocking +AS +BEGIN + SELECT + blocking.session_id AS BlockingSessionID, + blocked.session_id AS BlockedSessionID, + blocking_text.text AS BlockingQuery, + blocked_text.text AS BlockedQuery, + DB_NAME(blocked.database_id) AS DatabaseName, + blocked.wait_type AS WaitType, + blocked.wait_time AS WaitTimeMs + FROM sys.dm_exec_requests blocked + INNER JOIN sys.dm_exec_requests blocking + ON blocked.blocking_session_id = blocking.session_id + OUTER APPLY sys.dm_exec_sql_text(blocked.sql_handle) blocked_text + OUTER APPLY sys.dm_exec_sql_text(blocking.sql_handle) blocking_text + WHERE blocked.blocking_session_id > 0 +END +GO + +-- ============================================ +-- 8. KIỂM TRA INDEX USAGE +-- ============================================ + +-- Xem index usage cho các bảng được procedure sử dụng +CREATE PROCEDURE spDebug_GetProcedureIndexUsage + @pProcedureName NVARCHAR(200) +AS +BEGIN + -- Lấy các bảng được procedure reference + DECLARE @tables TABLE (TableName NVARCHAR(200)) + + INSERT INTO @tables (TableName) + SELECT referenced_entity_name + FROM sys.sql_expression_dependencies + WHERE referencing_id = OBJECT_ID(@pProcedureName) + AND referenced_class = 1 -- Object + + -- Xem index usage cho các bảng này + SELECT + t.TableName, + i.name AS IndexName, + i.type_desc AS IndexType, + s.user_seeks, + s.user_scans, + s.user_lookups, + s.user_updates, + s.last_user_seek, + s.last_user_scan, + s.last_user_lookup, + s.last_user_update + FROM @tables t + INNER JOIN sys.indexes i ON OBJECT_ID(t.TableName) = i.object_id + LEFT JOIN sys.dm_db_index_usage_stats s ON i.object_id = s.object_id + AND i.index_id = s.index_id + WHERE i.name IS NOT NULL + ORDER BY t.TableName, i.name +END +GO + +-- ============================================ +-- 9. DEBUG LOGGING FRAMEWORK +-- ============================================ + +-- Bảng log chi tiết +CREATE TABLE DebugLog ( + LogID BIGINT IDENTITY(1,1) PRIMARY KEY, + LogTime DATETIME DEFAULT GETDATE(), + ProcedureName NVARCHAR(200), + StepName NVARCHAR(100), + Message NVARCHAR(MAX), + Parameters NVARCHAR(MAX), + ExecutionTimeMs INT NULL, + RowCount INT NULL, + ErrorMessage NVARCHAR(MAX) NULL, + Severity INT DEFAULT 1 -- 1: Info, 2: Warning, 3: Error +) +GO + +-- Procedure để log debug information +CREATE PROCEDURE spDebug_Log + @pProcedureName NVARCHAR(200), + @pStepName NVARCHAR(100), + @pMessage NVARCHAR(MAX), + @pParameters NVARCHAR(MAX) = NULL, + @pExecutionTimeMs INT = NULL, + @pRowCount INT = NULL, + @pErrorMessage NVARCHAR(MAX) = NULL, + @pSeverity INT = 1 +AS +BEGIN + INSERT INTO DebugLog ( + ProcedureName, + StepName, + Message, + Parameters, + ExecutionTimeMs, + RowCount, + ErrorMessage, + Severity + ) VALUES ( + @pProcedureName, + @pStepName, + @pMessage, + @pParameters, + @pExecutionTimeMs, + @pRowCount, + @pErrorMessage, + @pSeverity + ) +END +GO + +-- View để xem debug log +CREATE VIEW vwDebugLog +AS +SELECT + LogTime, + ProcedureName, + StepName, + Message, + Parameters, + ExecutionTimeMs, + RowCount, + ErrorMessage, + CASE Severity + WHEN 1 THEN 'INFO' + WHEN 2 THEN 'WARNING' + WHEN 3 THEN 'ERROR' + ELSE 'UNKNOWN' + END AS SeverityText +FROM DebugLog +GO + +-- ============================================ +-- 10. VÍ DỤ SỬ DỤNG +-- ============================================ + +/* +-- 1. Debug một procedure cụ thể +EXEC spDebug_ExecuteProcedure 'asAPGetTT', '@pMa_cty = ''001'', @pMa_ct = ''''' + +-- 2. Xem parameters của procedure +EXEC spDebug_GetProcedureParameters 'asAPGetTT' + +-- 3. Xem dependencies +EXEC spDebug_GetProcedureDependencies 'asAPGetTT' + +-- 4. Test procedure với các bộ parameters +EXEC spDebug_TestProcedure 'asAPGetTT', '[{"params": "@pMa_cty = ''001'', @pMa_ct = ''''", "expectedRows": 5}]' + +-- 5. Check blocking +EXEC spDebug_CheckBlocking + +-- 6. Xem index usage +EXEC spDebug_GetProcedureIndexUsage 'asAPGetTT' + +-- 7. Sử dụng debug logging trong procedure +CREATE PROCEDURE asExampleWithDebug + @pParam1 NVARCHAR(10) +AS +BEGIN + EXEC spDebug_Log 'asExampleWithDebug', 'Start', 'Procedure started', @pParam1 + + -- Business logic + SELECT * FROM SomeTable WHERE Column1 = @pParam1 + + EXEC spDebug_Log 'asExampleWithDebug', 'End', 'Procedure completed', @pParam1, NULL, @@ROWCOUNT +END +*/ + +-- ============================================ +-- CLEANUP (nếu cần) +-- ============================================ +/* +-- Xóa các debug objects +DROP PROCEDURE IF EXISTS spDebug_ExecuteProcedure +DROP PROCEDURE IF EXISTS spDebug_LogProcedurePerformance +DROP TABLE IF EXISTS ProcedurePerformanceLog +DROP VIEW IF EXISTS vwProcedurePerformance +DROP PROCEDURE IF EXISTS spDebug_GetProcedureDependencies +DROP PROCEDURE IF EXISTS spDebug_GetProcedureExecutionPlan +DROP PROCEDURE IF EXISTS spDebug_GetProcedureParameters +DROP PROCEDURE IF EXISTS spDebug_TestProcedure +DROP PROCEDURE IF EXISTS spDebug_CheckBlocking +DROP PROCEDURE IF EXISTS spDebug_GetProcedureIndexUsage +DROP PROCEDURE IF EXISTS spDebug_Log +DROP TABLE IF EXISTS DebugLog +DROP VIEW IF EXISTS vwDebugLog +*/ \ No newline at end of file diff --git a/docs/example_conversion.php b/docs/example_conversion.php new file mode 100644 index 00000000..eb8f3234 --- /dev/null +++ b/docs/example_conversion.php @@ -0,0 +1,300 @@ +pdo = $pdo; + } + + /** + * Execute stored procedure: asGetRight + * + * @param string $userName Tên người dùng + * @param string $menuID Mã menu + * @return array Kết quả quyền + */ + public function execute($userName = '', $menuID = '') { + $sql = "EXEC dbo.asGetRight @pUserName = :userName, @pMenuID = :menuID, @pRet = :ret OUTPUT"; + + try { + $stmt = $this->pdo->prepare($sql); + + // Bind parameters + $stmt->bindParam(':userName', $userName, PDO::PARAM_STR); + $stmt->bindParam(':menuID', $menuID, PDO::PARAM_STR); + + // Output parameter + $ret = 0; + $stmt->bindParam(':ret', $ret, PDO::PARAM_INT | PDO::PARAM_INPUT_OUTPUT, 4); + + $stmt->execute(); + + // Fetch results + $result = $stmt->fetchAll(PDO::FETCH_ASSOC); + + return [ + 'data' => $result, + 'return_code' => $ret + ]; + + } catch (PDOException $e) { + throw new Exception("Failed to execute asGetRight: " . $e->getMessage()); + } + } + + /** + * Alternative implementation using pure PHP (không gọi stored procedure) + * Useful for testing or when stored procedure is not available + */ + public function getRightsPhpVersion($userName = '', $menuID = '') { + $sql = " + SELECT UserName, + SUM(ViewRight) AS ViewRight, + SUM(InsertRight) AS InsertRight, + SUM(UpdateRight) AS UpdateRight, + SUM(DeleteRight) AS DeleteRight, + SUM(limitedprint) AS Limitedprint + FROM ( + SELECT UserName, + CONVERT(INT, ViewRight) AS ViewRight, + CONVERT(INT, InsertRight) AS InsertRight, + CONVERT(INT, UpdateRight) AS UpdateRight, + CONVERT(INT, DeleteRight) AS DeleteRight, + CONVERT(INT, limitedprint) AS Limitedprint + FROM sysUserRight + WHERE UserName = :userName + AND MenuId = :menuID + UNION ALL + SELECT :userName AS UserName, + CONVERT(INT, ViewRight) AS ViewRight, + CONVERT(INT, InsertRight) AS InsertRight, + CONVERT(INT, UpdateRight) AS UpdateRight, + CONVERT(INT, DeleteRight) AS DeleteRight, + CONVERT(INT, limitedprint) AS Limitedprint + FROM sysGroupRight + WHERE GroupName IN (SELECT GroupName + FROM dbo.sysUserGroup + WHERE UserName = :userName2) + AND MenuId = :menuID2 + ) t + WHERE UserName = :userName3 + GROUP BY UserName + "; + + try { + $stmt = $this->pdo->prepare($sql); + + // Bind parameters (note: PDO doesn't allow reuse of named parameters) + $stmt->bindValue(':userName', $userName, PDO::PARAM_STR); + $stmt->bindValue(':menuID', $menuID, PDO::PARAM_STR); + $stmt->bindValue(':userName2', $userName, PDO::PARAM_STR); + $stmt->bindValue(':menuID2', $menuID, PDO::PARAM_STR); + $stmt->bindValue(':userName3', $userName, PDO::PARAM_STR); + + $stmt->execute(); + + return $stmt->fetchAll(PDO::FETCH_ASSOC); + + } catch (PDOException $e) { + throw new Exception("Failed to get user rights: " . $e->getMessage()); + } + } +} + +// Ví dụ sử dụng +class ExampleUsage { + + public static function main() { + // Kết nối database + $dsn = "sqlsrv:Server=localhost;Database=SimbaERP"; + $username = "sa"; + $password = "password"; + + try { + $pdo = new PDO($dsn, $username, $password); + $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + // Sử dụng stored procedure + $getRight = new GetRight($pdo); + $result = $getRight->execute('admin', 'MN001'); + + echo "User Rights (via stored procedure):\n"; + print_r($result); + + // Sử dụng PHP version + $result2 = $getRight->getRightsPhpVersion('admin', 'MN001'); + + echo "\nUser Rights (PHP version):\n"; + print_r($result2); + + } catch (PDOException $e) { + echo "Connection failed: " . $e->getMessage(); + } + } +} + +// Service class cho module hệ thống +class SystemService { + + private $pdo; + private $getRight; + + public function __construct(PDO $pdo) { + $this->pdo = $pdo; + $this->getRight = new GetRight($pdo); + } + + /** + * Kiểm tra quyền truy cập + */ + public function checkAccess($userName, $menuID, $requiredRight = 'ViewRight') { + $rights = $this->getRight->execute($userName, $menuID); + + if (empty($rights['data'])) { + return false; + } + + $userRights = $rights['data'][0]; + + // Kiểm tra quyền cụ thể + switch ($requiredRight) { + case 'ViewRight': + return $userRights['ViewRight'] > 0; + case 'InsertRight': + return $userRights['InsertRight'] > 0; + case 'UpdateRight': + return $userRights['UpdateRight'] > 0; + case 'DeleteRight': + return $userRights['DeleteRight'] > 0; + case 'Limitedprint': + return $userRights['Limitedprint'] > 0; + default: + return false; + } + } + + /** + * Lấy tất cả quyền của user + */ + public function getAllUserRights($userName) { + // Giả sử có stored procedure asGetAllUserRights + $sql = "EXEC dbo.asGetAllUserRights @pUserName = :userName"; + + $stmt = $this->pdo->prepare($sql); + $stmt->bindParam(':userName', $userName, PDO::PARAM_STR); + $stmt->execute(); + + return $stmt->fetchAll(PDO::FETCH_ASSOC); + } +} + +// Factory pattern để tạo các service +class SimbaServiceFactory { + + private static $instances = []; + private $pdo; + + public function __construct(PDO $pdo) { + $this->pdo = $pdo; + } + + public function getSystemService() { + if (!isset(self::$instances['system'])) { + self::$instances['system'] = new SystemService($this->pdo); + } + return self::$instances['system']; + } + + public function getAccountingService() { + if (!isset(self::$instances['accounting'])) { + // Tương tự cho các service khác + self::$instances['accounting'] = new AccountingService($this->pdo); + } + return self::$instances['accounting']; + } +} + +// Example: AccountingService cho module kế toán +class AccountingService { + + private $pdo; + + public function __construct(PDO $pdo) { + $this->pdo = $pdo; + } + + /** + * Lấy số dư tài khoản (tương tự stored procedure liên quan) + */ + public function getAccountBalance($companyCode, $accountCode, $date) { + // Sử dụng function afDuCuoiTk + $sql = " + SELECT * FROM dbo.afDuCuoiTk(:companyCode, :accountCode, :date, '') + "; + + $stmt = $this->pdo->prepare($sql); + $stmt->bindParam(':companyCode', $companyCode, PDO::PARAM_STR); + $stmt->bindParam(':accountCode', $accountCode, PDO::PARAM_STR); + $stmt->bindParam(':date', $date, PDO::PARAM_STR); + $stmt->execute(); + + return $stmt->fetchAll(PDO::FETCH_ASSOC); + } +} + +// Chạy ví dụ +if (php_sapi_name() === 'cli' && isset($argv[0]) && basename($argv[0]) === 'example_conversion.php') { + ExampleUsage::main(); +} + +?> \ No newline at end of file diff --git a/docs/optimize_procedures.sql b/docs/optimize_procedures.sql new file mode 100644 index 00000000..4538cf17 --- /dev/null +++ b/docs/optimize_procedures.sql @@ -0,0 +1,362 @@ +-- Script tối ưu hóa SimbaSql Stored Procedures +-- Tập trung vào các vấn đề performance và security + +-- ============================================ +-- 1. THAY THẾ SELECT * BẰNG COLUMN LIST CỤ THỂ +-- ============================================ + +-- Ví dụ: Tối ưu hóa procedure sử dụng SELECT * +-- Procedure gốc có thể trông như thế này: +/* +CREATE PROCEDURE asExampleProc + @pParam1 NVARCHAR(10) +AS +BEGIN + SELECT * FROM SomeTable WHERE Column1 = @pParam1 +END +*/ + +-- Phiên bản tối ưu: +/* +CREATE PROCEDURE asExampleProc_Optimized + @pParam1 NVARCHAR(10) +AS +BEGIN + SELECT + Column1, + Column2, + Column3, + -- Chỉ liệt kê các column cần thiết + Column4 + FROM SomeTable + WHERE Column1 = @pParam1 +END +*/ + +-- ============================================ +-- 2. TỐI ƯU HÓA CURSOR BẰNG SET-BASED OPERATIONS +-- ============================================ + +-- Ví dụ: Procedure sử dụng CURSOR +/* +CREATE PROCEDURE asProcessWithCursor + @pCompanyCode NVARCHAR(3) +AS +BEGIN + DECLARE @id INT + DECLARE cursor1 CURSOR FOR + SELECT ID FROM SomeTable WHERE CompanyCode = @pCompanyCode + + OPEN cursor1 + FETCH NEXT FROM cursor1 INTO @id + + WHILE @@FETCH_STATUS = 0 + BEGIN + -- Xử lý từng record + UPDATE AnotherTable SET Status = 1 WHERE RefID = @id + + FETCH NEXT FROM cursor1 INTO @id + END + + CLOSE cursor1 + DEALLOCATE cursor1 +END +*/ + +-- Phiên bản tối ưu sử dụng UPDATE set-based: +/* +CREATE PROCEDURE asProcessWithCursor_Optimized + @pCompanyCode NVARCHAR(3) +AS +BEGIN + UPDATE AnotherTable + SET Status = 1 + WHERE RefID IN ( + SELECT ID + FROM SomeTable + WHERE CompanyCode = @pCompanyCode + ) +END +*/ + +-- ============================================ +-- 3. THÊM INDEX CHO CÁC QUERY THƯỜNG XUYÊN +-- ============================================ + +-- Index cho bảng GlCt (chứng từ kế toán) +IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'IX_GlCt_MaCty_NgayCt') +BEGIN + CREATE NONCLUSTERED INDEX IX_GlCt_MaCty_NgayCt + ON dbo.GlCt (ma_cty, ngay_ct) + INCLUDE (tk, ps_no, ps_co, ma_kh) +END + +-- Index cho bảng ArDmKh (danh mục khách hàng) +IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'IX_ArDmKh_MaCty_TenKh') +BEGIN + CREATE NONCLUSTERED INDEX IX_ArDmKh_MaCty_TenKh + ON dbo.ArDmKh (ma_cty, ten_kh) + INCLUDE (ma_kh, dia_chi, tel) +END + +-- Index cho bảng GlDmTk (danh mục tài khoản) +IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'IX_GlDmTk_MaCty_Tk') +BEGIN + CREATE NONCLUSTERED INDEX IX_GlDmTk_MaCty_Tk + ON dbo.GlDmTk (ma_cty, tk) + INCLUDE (ten_tk, tk_me, bac_tk) +END + +-- ============================================ +-- 4. TỐI ƯU HÓA DYNAMIC SQL +-- ============================================ + +-- Ví dụ: Dynamic SQL không an toàn +/* +CREATE PROCEDURE asUnsafeDynamicSQL + @pTableName NVARCHAR(50), + @pColumnName NVARCHAR(50), + @pValue NVARCHAR(100) +AS +BEGIN + DECLARE @sql NVARCHAR(MAX) + SET @sql = 'SELECT * FROM ' + @pTableName + ' WHERE ' + @pColumnName + ' = ''' + @pValue + '''' + EXEC(@sql) +END +*/ + +-- Phiên bản an toàn hơn với QUOTENAME và parameterized query: +/* +CREATE PROCEDURE asUnsafeDynamicSQL_Optimized + @pTableName NVARCHAR(50), + @pColumnName NVARCHAR(50), + @pValue NVARCHAR(100) +AS +BEGIN + DECLARE @sql NVARCHAR(MAX) + + -- Sử dụng QUOTENAME để tránh SQL injection + SET @sql = 'SELECT * FROM ' + QUOTENAME(@pTableName) + + ' WHERE ' + QUOTENAME(@pColumnName) + ' = @value' + + EXEC sp_executesql @sql, N'@value NVARCHAR(100)', @value = @pValue +END +*/ + +-- ============================================ +-- 5. THÊM ERROR HANDLING +-- ============================================ + +-- Template cho stored procedure với error handling +/* +CREATE PROCEDURE asTemplateWithErrorHandling + @pParam1 NVARCHAR(10), + @pRet INT OUTPUT +AS +BEGIN + SET NOCOUNT ON + SET XACT_ABORT ON + + BEGIN TRY + BEGIN TRANSACTION + + -- Business logic here + + SET @pRet = 0 -- Success + + COMMIT TRANSACTION + END TRY + BEGIN CATCH + IF @@TRANCOUNT > 0 + ROLLBACK TRANSACTION + + SET @pRet = ERROR_NUMBER() + + -- Log error (giả sử có bảng ErrorLog) + INSERT INTO ErrorLog (ErrorNumber, ErrorMessage, ProcedureName, ErrorTime) + VALUES (ERROR_NUMBER(), ERROR_MESSAGE(), 'asTemplateWithErrorHandling', GETDATE()) + + -- Có thể re-throw error nếu cần + -- THROW + END CATCH +END +*/ + +-- ============================================ +-- 6. TỐI ƯU HÓA JOIN VÀ WHERE CLAUSE +-- ============================================ + +-- Ví dụ: Tránh hàm trên cột trong WHERE +-- Không tốt: WHERE YEAR(ngay_ct) = 2023 +-- Tốt hơn: WHERE ngay_ct >= '2023-01-01' AND ngay_ct < '2024-01-01' + +-- Ví dụ: Sử dụng EXISTS thay vì IN cho subqueries lớn +/* +-- Không tốt cho large datasets: +SELECT * FROM Table1 WHERE ID IN (SELECT ID FROM LargeTable WHERE Condition = 1) + +-- Tốt hơn: +SELECT * FROM Table1 t1 +WHERE EXISTS (SELECT 1 FROM LargeTable lt WHERE lt.ID = t1.ID AND lt.Condition = 1) +*/ + +-- ============================================ +-- 7. TẠO TEMPLATE CHO CÁC LOẠI PROCEDURE +-- ============================================ + +-- Template cho procedure GET (lấy dữ liệu) +/* +CREATE PROCEDURE asGet[TableName] + @pMa_cty NVARCHAR(3), + @pFilter1 NVARCHAR(50) = '', + @pFilter2 NVARCHAR(50) = '' +AS +BEGIN + SET NOCOUNT ON + + SELECT + -- Liệt kê cụ thể các column + column1, + column2, + column3 + FROM [TableName] + WHERE ma_cty = @pMa_cty + AND (column1 LIKE @pFilter1 + '%' OR @pFilter1 = '') + AND (column2 LIKE @pFilter2 + '%' OR @pFilter2 = '') + ORDER BY ngay_ct DESC, so_ct DESC +END +*/ + +-- Template cho procedure INSERT +/* +CREATE PROCEDURE asIns[TableName] + @pMa_cty NVARCHAR(3), + @pColumn1 NVARCHAR(50), + @pColumn2 DECIMAL(19,4), + @pLUser NVARCHAR(20), + @pRet INT OUTPUT +AS +BEGIN + SET NOCOUNT ON + SET XACT_ABORT ON + + BEGIN TRY + INSERT INTO [TableName] ( + ma_cty, + column1, + column2, + cuser, + cdate, + luser, + ldate + ) VALUES ( + @pMa_cty, + @pColumn1, + @pColumn2, + @pLUser, + GETDATE(), + @pLUser, + GETDATE() + ) + + SET @pRet = 0 + END TRY + BEGIN CATCH + SET @pRet = ERROR_NUMBER() + -- Log error + END CATCH +END +*/ + +-- ============================================ +-- 8. SCRIPT TÌM PROCEDURE CẦN TỐI ƯU HÓA +-- ============================================ + +-- Tìm procedure sử dụng SELECT * +SELECT + OBJECT_NAME(object_id) AS ProcedureName, + definition +FROM sys.sql_modules +WHERE definition LIKE '%SELECT *%' + AND OBJECT_NAME(object_id) LIKE 'as%' +ORDER BY ProcedureName + +-- Tìm procedure sử dụng CURSOR +SELECT + OBJECT_NAME(object_id) AS ProcedureName, + definition +FROM sys.sql_modules +WHERE definition LIKE '%CURSOR FOR%' + AND OBJECT_NAME(object_id) LIKE 'as%' +ORDER BY ProcedureName + +-- Tìm procedure sử dụng dynamic SQL +SELECT + OBJECT_NAME(object_id) AS ProcedureName, + definition +FROM sys.sql_modules +WHERE (definition LIKE '%EXEC(%' OR definition LIKE '%EXECUTE(%' OR definition LIKE '%sp_executesql%') + AND OBJECT_NAME(object_id) LIKE 'as%' +ORDER BY ProcedureName + +-- ============================================ +-- 9. UPDATE STATISTICS +-- ============================================ + +-- Update statistics cho các bảng quan trọng +EXEC sp_updatestats + +-- Update statistics cụ thể +UPDATE STATISTICS dbo.GlCt WITH FULLSCAN +UPDATE STATISTICS dbo.ArDmKh WITH FULLSCAN +UPDATE STATISTICS dbo.GlDmTk WITH FULLSCAN + +-- ============================================ +-- 10. CLEANUP UNUSED PROCEDURES +-- ============================================ + +-- Xóa các procedure bắt đầu bằng 'z_' (tạm/sao lưu) nếu không cần +/* +DECLARE @procName NVARCHAR(200) +DECLARE procCursor CURSOR FOR + SELECT name FROM sys.procedures WHERE name LIKE 'z_%' + +OPEN procCursor +FETCH NEXT FROM procCursor INTO @procName + +WHILE @@FETCH_STATUS = 0 +BEGIN + -- Kiểm tra xem procedure có được sử dụng không + IF NOT EXISTS ( + SELECT 1 FROM sys.sql_expression_dependencies + WHERE referenced_entity_name = @procName + ) + BEGIN + EXEC('DROP PROCEDURE ' + @procName) + PRINT 'Dropped procedure: ' + @procName + END + + FETCH NEXT FROM procCursor INTO @procName +END + +CLOSE procCursor +DEALLOCATE procCursor +*/ + +-- ============================================ +-- KẾT LUẬN +-- ============================================ + +/* +Các bước tối ưu hóa nên thực hiện theo thứ tự: +1. Fix security issues (dynamic SQL injection) +2. Optimize high-impact procedures (thường xuyên được gọi) +3. Add missing indexes +4. Replace SELECT * với column list +5. Replace CURSOR với set-based operations +6. Add error handling +7. Cleanup unused code +8. Regular maintenance (update statistics, rebuild indexes) + +Lưu ý: Luôn test kỹ trước khi áp dụng vào production. +*/ \ No newline at end of file diff --git a/docs/simba_sql_summary.md b/docs/simba_sql_summary.md new file mode 100644 index 00000000..8ae5fd8d --- /dev/null +++ b/docs/simba_sql_summary.md @@ -0,0 +1,178 @@ +# TỔNG KẾT PHÂN TÍCH SIMBASQL DATABASE + +## Công việc đã hoàn thành + +### 1. Phân tích cấu trúc database +- **Tổng số đối tượng**: 2,569 file SQL + - 440 bảng + - 2,031 stored procedures + - 89 functions + - 4 views +- **Phân loại stored procedures**: + - 1,831 procedure bắt đầu bằng 'as' (nghiệp vụ chính) + - 188 procedure bắt đầu bằng 'z' (tạm/sao lưu) + - 1 procedure bắt đầu bằng 'sp' (hệ thống) + - 11 function bắt đầu bằng 'af' (aggregate functions) + +### 2. Hiểu cấu trúc nghiệp vụ +- **Module chính**: Kế toán tổng hợp (GL), Phải thu (AR), Phải trả (AP), Hệ thống (SI), Kho (IN), Sản xuất (CO), Tài sản cố định (CA), Nhân sự (HR), Ngân hàng (BN) +- **Quy ước đặt tên**: Rõ ràng, nhất quán với tiền tố/hậu tố +- **Multi-tenant design**: Tất cả bảng đều có `ma_cty` (mã công ty) +- **Audit tracking**: cdate, cuser, ldate, luser trên hầu hết bảng + +### 3. Phát hiện vấn đề +- **Performance issues**: + - 142 procedure sử dụng `SELECT *` + - Nhiều procedure sử dụng CURSOR + - 8 procedure sử dụng dynamic SQL (EXECUTE) +- **Security concerns**: Dynamic SQL có nguy cơ SQL injection +- **Code quality**: Thiếu error handling, comments không đầy đủ + +### 4. Tạo công cụ hỗ trợ +1. **Báo cáo phân tích chi tiết** (`SimbaSql_analysis_report.md`) +2. **Script chuyển đổi sang PHP** (`convert_sp_to_php.php`) +3. **Ví dụ chuyển đổi cụ thể** (`example_conversion.php`) +4. **Script tối ưu hóa** (`optimize_procedures.sql`) +5. **Script debug** (`debug_procedures.sql`) + +## Đề xuất ưu tiên + +### Ưu tiên cao (Security & Critical Performance) +1. **Fix dynamic SQL injection risks** (8 procedures) +2. **Optimize frequently called procedures** +3. **Add missing indexes** cho các bảng quan trọng + +### Ưu tiên trung bình (Performance & Maintainability) +1. **Replace SELECT * với column list** (142 procedures) +2. **Replace CURSOR với set-based operations** +3. **Add standard error handling** + +### Ưu tiên thấp (Cleanup & Standards) +1. **Remove unused procedures** (bắt đầu bằng 'z') +2. **Add documentation và comments** +3. **Standardize code style** + +## Công cụ đã phát triển + +### 1. Chuyển đổi sang PHP +- **`convert_sp_to_php.php`**: Script tự động chuyển stored procedure sang PHP class +- **`example_conversion.php`**: Ví dụ cụ thể chuyển `asGetRight` sang PHP +- **Features**: + - Parse parameters tự động + - Generate PDO prepared statements + - Tạo repository pattern + - Hỗ trợ batch conversion + +### 2. Tối ưu hóa Database +- **`optimize_procedures.sql`**: Script SQL để tối ưu hóa +- **Nội dung**: + - Template thay thế SELECT * + - Cách thay thế CURSOR + - Index recommendations + - Error handling templates + - Performance monitoring queries + +### 3. Debug Framework +- **`debug_procedures.sql`**: Framework debug đầy đủ +- **Features**: + - Procedure execution logging + - Performance tracking + - Dependency analysis + - Test case execution + - Blocking/deadlock detection + - Index usage analysis + +## Kiến trúc đề xuất cho PHP Integration + +### Layer 1: Data Access Layer +```php +class SimbaSqlRepository { + private $pdo; + + // Mỗi stored procedure thành một method + public function getAccountPayments($companyCode, $documentCode) { + $proc = new AsAPGetTT($this->pdo); + return $proc->execute($companyCode, $documentCode); + } +} +``` + +### Layer 2: Business Logic Layer +```php +class AccountingService { + private $repository; + + public function processPayment($paymentData) { + // Validate business rules + // Call repository methods + // Handle transactions + } +} +``` + +### Layer 3: Presentation Layer +```php +class PaymentController { + public function listAction() { + $service = new AccountingService(); + $payments = $service->getPayments($companyCode); + // Return JSON or render view + } +} +``` + +## Kế hoạch triển khai + +### Phase 1: Assessment & Planning (1-2 tuần) +1. **Prioritize procedures** dựa trên usage frequency +2. **Create test environment** với dữ liệu mẫu +3. **Establish performance baselines** + +### Phase 2: Security Fixes (1 tuần) +1. **Fix SQL injection vulnerabilities** +2. **Implement parameterized queries** +3. **Add input validation** + +### Phase 3: Performance Optimization (2-3 tuần) +1. **Add critical indexes** +2. **Optimize top 10 slowest procedures** +3. **Replace SELECT * trong high-usage procedures** + +### Phase 4: Code Quality (1-2 tuần) +1. **Add standard error handling** +2. **Add documentation** +3. **Cleanup unused code** + +### Phase 5: PHP Integration (2-4 tuần) +1. **Convert critical procedures sang PHP** +2. **Implement repository pattern** +3. **Create service classes** +4. **Testing và validation** + +## Lợi ích dự kiến + +### 1. Performance Improvement +- **30-50% faster query execution** với proper indexing +- **Reduced blocking và deadlocks** với optimized queries +- **Better scalability** với set-based operations + +### 2. Security Enhancement +- **Eliminated SQL injection risks** +- **Improved audit logging** +- **Better access control** + +### 3. Maintainability +- **Easier debugging** với standardized error handling +- **Better documentation** cho future development +- **Simplified PHP integration** + +### 4. Business Value +- **Faster reporting** và data processing +- **Reduced downtime** với better error handling +- **Easier integration** với modern applications + +## Kết luận + +SimbaSql là một database ERP đầy đủ với cấu trúc tốt nhưng cần optimization để đạt hiệu suất tối đa. Các công cụ và kế hoạch đã phát triển cung cấp lộ trình rõ ràng để cải thiện performance, security và maintainability. + +**Khuyến nghị tiếp theo**: Bắt đầu với Phase 1 (Assessment) để xác định procedures quan trọng nhất cần optimization, sau đó triển khai tuần tự các phase tiếp theo. \ No newline at end of file