-- T-SQL equivalent (no direct %TYPE/%ROWTYPE, use explicit declarations)
DECLARE @EmpName VARCHAR(50); -- Like %TYPE
DECLARE @EmpID INT, @EmpSalary DECIMAL(10,2); -- Like %ROWTYPE subset
SELECT @EmpName = Name, @EmpID = EmployeeID, @EmpSalary = Salary
FROM Employees WHERE EmployeeID = 1;
PRINT @EmpName;
-- MySQL (no direct equivalent, use stored procedure variables)
DELIMITER //
CREATE PROCEDURE UseTypeRowType()
BEGIN
DECLARE emp_name VARCHAR(50); -- Like %TYPE
DECLARE emp_id INT;
DECLARE emp_salary DECIMAL(10,2); -- Like %ROWTYPE subset
SELECT Name, EmployeeID, Salary INTO emp_name, emp_id, emp_salary
FROM Employees WHERE EmployeeID = 1;
SELECT emp_name;
END //
DELIMITER ;
-- T-SQL batch processing
CREATE PROCEDURE ProcessLargeData
AS
BEGIN
SET NOCOUNT ON;
DECLARE @BatchSize INT = 10000;
WHILE EXISTS (SELECT 1 FROM LargeTable WHERE Processed = 0)
BEGIN
UPDATE TOP (@BatchSize) LargeTable
SET Processed = 1
WHERE Processed = 0;
END;
END;
-- MySQL batch processing
DELIMITER //
CREATE PROCEDURE ProcessLargeData()
BEGIN
DECLARE batch_size INT DEFAULT 10000;
REPEAT
UPDATE LargeTable
SET Processed = 1
WHERE Processed = 0
LIMIT batch_size;
UNTIL ROW_COUNT() = 0 END REPEAT;
END //
DELIMITER ;
-- BEFORE INSERT equivalent (use INSTEAD OF)
CREATE TRIGGER trg_BeforeInsertEmployee
ON Employees
INSTEAD OF INSERT
AS
BEGIN
INSERT INTO Employees (EmployeeID, Name, Salary)
SELECT EmployeeID, Name, CASE WHEN Salary < 0 THEN 0 ELSE Salary END
FROM inserted;
END;
-- AFTER INSERT
CREATE TRIGGER trg_AfterInsertEmployee
ON Employees
AFTER INSERT
AS
BEGIN
INSERT INTO EmployeeAudit (EmployeeID, Operation, ChangeDate)
SELECT EmployeeID, 'INSERT', GETDATE()
FROM inserted;
END;
-- BEFORE INSERT
DELIMITER //
CREATE TRIGGER trg_BeforeInsertEmployee
BEFORE INSERT ON Employees
FOR EACH ROW
BEGIN
IF NEW.Salary < 0 THEN
SET NEW.Salary = 0;
END IF;
END //
-- AFTER INSERT
CREATE TRIGGER trg_AfterInsertEmployee
AFTER INSERT ON Employees
FOR EACH ROW
BEGIN
INSERT INTO EmployeeAudit (EmployeeID, Operation, ChangeDate)
VALUES (NEW.EmployeeID, 'INSERT', NOW());
END //
DELIMITER ;
-- T-SQL debugging
CREATE PROCEDURE ProcessData
AS
BEGIN
BEGIN TRY
DECLARE @DebugMsg VARCHAR(200);
SET @DebugMsg = 'Starting procedure';
PRINT @DebugMsg;
-- Logic here
SET @DebugMsg = 'Processing complete';
PRINT @DebugMsg;
END TRY
BEGIN CATCH
PRINT 'Error: ' + ERROR_MESSAGE();
END CATCH;
END;
-- MySQL debugging
DELIMITER //
CREATE PROCEDURE ProcessData()
BEGIN
DECLARE debug_msg VARCHAR(200);
SET debug_msg = 'Starting procedure';
INSERT INTO DebugLog (Message, LogTime) VALUES (debug_msg, NOW());
-- Logic here
SET debug_msg = 'Processing complete';
INSERT INTO DebugLog (Message, LogTime) VALUES (debug_msg, NOW());
END //
DELIMITER ;
-- T-SQL equivalent
BEGIN TRY
DECLARE @MinSalary DECIMAL(10,2) = 30000;
IF EXISTS (SELECT 1 FROM Employees WHERE Salary < @MinSalary)
THROW 50001, 'Salary below minimum threshold', 1;
UPDATE Employees SET Salary = Salary * 1.1;
END TRY
BEGIN CATCH
INSERT INTO ErrorLog (ErrorMessage, ErrorTime)
VALUES (ERROR_MESSAGE(), GETDATE());
PRINT ERROR_MESSAGE();
END CATCH;
-- MySQL equivalent
DELIMITER //
CREATE PROCEDURE UpdateSalary()
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
INSERT INTO ErrorLog (ErrorMessage, ErrorTime)
VALUES (@error_msg, NOW());
SELECT @error_msg;
END;
SET @error_msg = 'Salary below minimum threshold';
IF EXISTS (SELECT 1 FROM Employees WHERE Salary < 30000) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = @error_msg;
END IF;
UPDATE Employees SET Salary = Salary * 1.1;
END //
DELIMITER ;
-- SQL Server (T-SQL equivalent)
BEGIN TRY
BEGIN TRANSACTION;
UPDATE Employees
SET Salary = Salary * 1.10,
LastRaiseDate = GETDATE()
WHERE LastRaiseDate IS NULL OR LastRaiseDate < DATEADD(YEAR, -1, GETDATE());
COMMIT;
END TRY
BEGIN CATCH
ROLLBACK;
PRINT ERROR_MESSAGE();
END CATCH;
-- MySQL (Stored Procedure equivalent)
DELIMITER //
CREATE PROCEDURE UpdateSalaries()
BEGIN
START TRANSACTION;
UPDATE Employees
SET Salary = Salary * 1.10,
LastRaiseDate = CURDATE()
WHERE LastRaiseDate IS NULL OR LastRaiseDate < DATE_SUB(CURDATE(), INTERVAL 1 YEAR);
COMMIT;
END //
DELIMITER ;
CALL UpdateSalaries();
-- T-SQL function
CREATE FUNCTION IsPalindrome (@Number INT)
RETURNS BIT
AS
BEGIN
DECLARE @NumStr VARCHAR(20) = CAST(@Number AS VARCHAR);
DECLARE @Reversed VARCHAR(20) = REVERSE(@NumStr);
RETURN CASE WHEN @NumStr = @Reversed THEN 1 ELSE 0 END;
END;
-- Test
SELECT dbo.IsPalindrome(121); -- Returns 1
-- MySQL function
DELIMITER //
CREATE FUNCTION IsPalindrome(Number INT)
RETURNS BOOLEAN
DETERMINISTIC
BEGIN
DECLARE num_str VARCHAR(20);
DECLARE reversed VARCHAR(20);
SET num_str = CAST(Number AS CHAR);
SET reversed = REVERSE(num_str);
RETURN num_str = reversed;
END //
DELIMITER ;
-- Test
SELECT IsPalindrome(121); -- Returns 1
-- T-SQL explicit cursor equivalent
DECLARE @EmpID INT, @EmpName VARCHAR(50);
DECLARE emp_cursor CURSOR FOR
SELECT EmployeeID, Name FROM Employees;
OPEN emp_cursor;
FETCH NEXT FROM emp_cursor INTO @EmpID, @EmpName;
WHILE @@FETCH_STATUS = 0
BEGIN
PRINT @EmpName;
FETCH NEXT FROM emp_cursor INTO @EmpID, @EmpName;
END;
CLOSE emp_cursor;
DEALLOCATE emp_cursor;
-- MySQL cursor
DELIMITER //
CREATE PROCEDURE ProcessEmployees()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE emp_id INT;
DECLARE emp_name VARCHAR(50);
DECLARE emp_cursor CURSOR FOR
SELECT EmployeeID, Name FROM Employees;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN emp_cursor;
read_loop: LOOP
FETCH emp_cursor INTO emp_id, emp_name;
IF done THEN
LEAVE read_loop;
END IF;
SELECT emp_name;
END LOOP;
CLOSE emp_cursor;
END //
DELIMITER ;
-- T-SQL
WITH Duplicates AS (
SELECT EmployeeID,
ROW_NUMBER() OVER (PARTITION BY Name, DepartmentID ORDER BY EmployeeID) AS rn
FROM Employees
)
DELETE FROM Duplicates WHERE rn > 1;
-- MySQL
CREATE TEMPORARY TABLE Duplicates AS
SELECT EmployeeID
FROM (
SELECT EmployeeID,
ROW_NUMBER() OVER (PARTITION BY Name, DepartmentID ORDER BY EmployeeID) AS rn
FROM Employees
) t
WHERE rn > 1;
DELETE FROM Employees WHERE EmployeeID IN (SELECT EmployeeID FROM Duplicates);
DROP TEMPORARY TABLE Duplicates;
-- T-SQL bulk insert (using table-valued parameter)
CREATE TYPE EmployeeType AS TABLE (ID INT, Name VARCHAR(50));
GO
CREATE PROCEDURE BulkInsertEmployees @Employees EmployeeType READONLY
AS
BEGIN
INSERT INTO Employees (EmployeeID, Name)
SELECT ID, Name FROM @Employees;
END;
-- Test
DECLARE @EmpTable EmployeeType;
INSERT INTO @EmpTable VALUES (1, 'John'), (2, 'Jane');
EXEC BulkInsertEmployees @EmpTable;
-- MySQL (no FORALL, use temporary table)
DELIMITER //
CREATE PROCEDURE BulkInsertEmployees()
BEGIN
CREATE TEMPORARY TABLE TempEmployees (ID INT, Name VARCHAR(50));
INSERT INTO TempEmployees VALUES (1, 'John'), (2, 'Jane');
INSERT INTO Employees (EmployeeID, Name)
SELECT ID, Name FROM TempEmployees;
DROP TEMPORARY TABLE TempEmployees;
END //
DELIMITER ;
-- Error log table
CREATE TABLE ErrorLog (
LogID INT IDENTITY PRIMARY KEY,
ErrorMessage NVARCHAR(4000),
ErrorTime DATETIME,
ProcedureName NVARCHAR(128)
);
-- Procedure with error logging
CREATE PROCEDURE ProcessData
AS
BEGIN
BEGIN TRY
-- Simulate error
SELECT 1/0;
END TRY
BEGIN CATCH
INSERT INTO ErrorLog (ErrorMessage, ErrorTime, ProcedureName)
VALUES (ERROR_MESSAGE(), GETDATE(), OBJECT_NAME(@@PROCID));
THROW;
END CATCH;
END;
-- Error log table
CREATE TABLE ErrorLog (
LogID INT AUTO_INCREMENT PRIMARY KEY,
ErrorMessage VARCHAR(4000),
ErrorTime DATETIME,
ProcedureName VARCHAR(128)
);
-- Procedure with error logging
DELIMITER //
CREATE PROCEDURE ProcessData()
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
INSERT INTO ErrorLog (ErrorMessage, ErrorTime, ProcedureName)
VALUES (@error_msg, NOW(), 'ProcessData');
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = @error_msg;
END;
SET @error_msg = 'Division by zero';
SELECT 1/0;
END //
DELIMITER ;
-- Using DENSE_RANK
WITH RankedSalaries AS (
SELECT EmployeeID, Name, Salary,
DENSE_RANK() OVER (ORDER BY Salary DESC) AS SalaryRank
FROM Employees
)
SELECT EmployeeID, Name, Salary
FROM RankedSalaries
WHERE SalaryRank = 2;
-- Using DENSE_RANK
WITH RankedSalaries AS (
SELECT EmployeeID, Name, Salary,
DENSE_RANK() OVER (ORDER BY Salary DESC) AS SalaryRank
FROM Employees
)
SELECT EmployeeID, Name, Salary
FROM RankedSalaries
WHERE SalaryRank = 2;
-- Assume Holidays table exists
WITH WorkDays AS (
SELECT WorkDate,
ROW_NUMBER() OVER (ORDER BY WorkDate) -
ROW_NUMBER() OVER (PARTITION BY CASE WHEN DATEPART(WEEKDAY, WorkDate) IN (1, 7)
OR WorkDate IN (SELECT HolidayDate FROM Holidays)
THEN 1 ELSE 0 END ORDER BY WorkDate) AS Grp
FROM (
SELECT DATEADD(DAY, n-1, '2025-01-01') AS WorkDate
FROM (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS n
FROM sys.all_objects) nums
WHERE n <= 365
) Dates
WHERE DATEPART(WEEKDAY, WorkDate) NOT IN (1, 7)
AND WorkDate NOT IN (SELECT HolidayDate FROM Holidays)
)
SELECT MIN(WorkDate) AS StartDate, MAX(WorkDate) AS EndDate
FROM WorkDays
GROUP BY Grp
ORDER BY StartDate;
-- Assume Holidays table exists
WITH RECURSIVE Dates AS (
SELECT '2025-01-01' AS WorkDate
UNION ALL
SELECT DATE_ADD(WorkDate, INTERVAL 1 DAY)
FROM Dates
WHERE WorkDate < '2025-12-31'
), WorkDays AS (
SELECT WorkDate,
ROW_NUMBER() OVER (ORDER BY WorkDate) -
ROW_NUMBER() OVER (PARTITION BY CASE WHEN DAYOFWEEK(WorkDate) IN (1, 7)
OR WorkDate IN (SELECT HolidayDate FROM Holidays)
THEN 1 ELSE 0 END ORDER BY WorkDate) AS Grp
FROM Dates
WHERE DAYOFWEEK(WorkDate) NOT IN (1, 7)
AND WorkDate NOT IN (SELECT HolidayDate FROM Holidays)
)
SELECT MIN(WorkDate) AS StartDate, MAX(WorkDate) AS EndDate
FROM WorkDays
GROUP BY Grp
ORDER BY StartDate;
CREATE PROCEDURE GetRowCount
@TableName NVARCHAR(128)
AS
BEGIN
SET NOCOUNT ON;
IF EXISTS (SELECT 1 FROM sys.tables WHERE name = @TableName)
BEGIN
DECLARE @SQL NVARCHAR(200) = 'SELECT COUNT(*) AS RowCount FROM ' + QUOTENAME(@TableName);
EXEC sp_executesql @SQL;
END
ELSE
THROW 50001, 'Table does not exist', 1;
END;
-- Test
EXEC GetRowCount @TableName = 'Employees';
DELIMITER //
CREATE PROCEDURE GetRowCount(IN TableName VARCHAR(128))
BEGIN
SET @sql = CONCAT('SELECT COUNT(*) AS RowCount FROM ', QUOTE_IDENTIFIER(TableName));
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = TableName) THEN
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
ELSE
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Table does not exist';
END IF;
END //
DELIMITER ;
-- Test
CALL GetRowCount('Employees');
-- CASE in query
SELECT EmployeeID, Name, Salary,
CASE WHEN Salary > 50000 THEN 'High' ELSE 'Low' END AS SalaryCategory
FROM Employees;
-- IF/ELSE in procedure
CREATE PROCEDURE UpdateSalary
@EmployeeID INT,
@NewSalary DECIMAL(10,2)
AS
BEGIN
IF @NewSalary < 30000
THROW 50001, 'Salary too low', 1;
ELSE
UPDATE Employees SET Salary = @NewSalary WHERE EmployeeID = @EmployeeID;
END;
-- CASE in query
SELECT EmployeeID, Name, Salary,
CASE WHEN Salary > 50000 THEN 'High' ELSE 'Low' END AS SalaryCategory
FROM Employees;
-- IF/ELSE in procedure
DELIMITER //
CREATE PROCEDURE UpdateSalary(IN EmployeeID INT, IN NewSalary DECIMAL(10,2))
BEGIN
IF NewSalary < 30000 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Salary too low';
ELSE
UPDATE Employees SET Salary = NewSalary WHERE EmployeeID = EmployeeID;
END IF;
END //
DELIMITER ;
-- T-SQL (no packages, use schema for grouping)
CREATE PROCEDURE EmployeePkg_GetEmployee
@EmployeeID INT
AS
BEGIN
SELECT EmployeeID, Name, Salary FROM Employees WHERE EmployeeID = @EmployeeID;
END;
CREATE FUNCTION EmployeePkg_IsHighEarner (@Salary DECIMAL(10,2))
RETURNS BIT
AS
BEGIN
RETURN CASE WHEN @Salary > 50000 THEN 1 ELSE 0 END;
END;
-- Call
EXEC EmployeePkg_GetEmployee @EmployeeID = 1;
SELECT dbo.EmployeePkg_IsHighEarner(60000);
-- MySQL (no packages, use stored procedures)
DELIMITER //
CREATE PROCEDURE EmployeePkg_GetEmployee(IN EmployeeID INT)
BEGIN
SELECT EmployeeID, Name, Salary FROM Employees WHERE EmployeeID = EmployeeID;
END //
CREATE FUNCTION EmployeePkg_IsHighEarner(Salary DECIMAL(10,2))
RETURNS BOOLEAN
DETERMINISTIC
BEGIN
RETURN Salary > 50000;
END //
DELIMITER ;
-- Call
CALL EmployeePkg_GetEmployee(1);
SELECT EmployeePkg_IsHighEarner(60000);
-- PIVOT
SELECT * FROM (
SELECT EmployeeID, Month, Sales
FROM Sales
) AS Source
PIVOT (
SUM(Sales)
FOR Month IN (Jan, Feb, Mar)
) AS PivotTable;
-- CASE
SELECT EmployeeID,
SUM(CASE WHEN Month = 'Jan' THEN Sales END) AS Jan,
SUM(CASE WHEN Month = 'Feb' THEN Sales END) AS Feb,
SUM(CASE WHEN Month = 'Mar' THEN Sales END) AS Mar
FROM Sales
GROUP BY EmployeeID;
-- CASE (MySQL lacks PIVOT)
SELECT EmployeeID,
SUM(CASE WHEN Month = 'Jan' THEN Sales END) AS Jan,
SUM(CASE WHEN Month = 'Feb' THEN Sales END) AS Feb,
SUM(CASE WHEN Month = 'Mar' THEN Sales END) AS Mar
FROM Sales
GROUP BY EmployeeID;
-- View
CREATE VIEW EmployeeDepartment AS
SELECT e.EmployeeID, e.Name, e.Salary, d.DepartmentName
FROM Employees e
JOIN Departments d ON e.DepartmentID = d.DepartmentID;
-- INSTEAD OF INSERT trigger
CREATE TRIGGER trg_InsteadOfInsert
ON EmployeeDepartment
INSTEAD OF INSERT
AS
BEGIN
INSERT INTO Employees (EmployeeID, Name, Salary, DepartmentID)
SELECT i.EmployeeID, i.Name, i.Salary, d.DepartmentID
FROM inserted i
JOIN Departments d ON i.DepartmentName = d.DepartmentName;
END;
-- Test
INSERT INTO EmployeeDepartment (EmployeeID, Name, Salary, DepartmentName)
VALUES (1, 'John', 50000, 'HR');
-- MySQL (use stored procedure)
CREATE VIEW EmployeeDepartment AS
SELECT e.EmployeeID, e.Name, e.Salary, d.DepartmentName
FROM Employees e
JOIN Departments d ON e.DepartmentID = d.DepartmentID;
DELIMITER //
CREATE PROCEDURE InsertEmployeeDepartment(
IN p_EmployeeID INT, IN p_Name VARCHAR(50),
IN p_Salary DECIMAL(10,2), IN p_DepartmentName VARCHAR(50)
)
BEGIN
INSERT INTO Employees (EmployeeID, Name, Salary, DepartmentID)
SELECT p_EmployeeID, p_Name, p_Salary, DepartmentID
FROM Departments WHERE DepartmentName = p_DepartmentName;
END //
DELIMITER ;
-- Test
CALL InsertEmployeeDepartment(1, 'John', 50000, 'HR');
-- T-SQL batch processing
CREATE PROCEDURE OptimizeCursor
AS
BEGIN
DECLARE @BatchSize INT = 10000;
WHILE EXISTS (SELECT 1 FROM Employees WHERE Processed = 0)
BEGIN
UPDATE TOP (@BatchSize) Employees
SET Processed = 1
WHERE Processed = 0;
END;
END;
-- MySQL batch processing
DELIMITER //
CREATE PROCEDURE OptimizeCursor()
BEGIN
DECLARE batch_size INT DEFAULT 10000;
REPEAT
UPDATE Employees
SET Processed = 1
WHERE Processed = 0
LIMIT batch_size;
UNTIL ROW_COUNT() = 0 END REPEAT;
END //
DELIMITER ;
-- T-SQL dynamic SQL
CREATE PROCEDURE DynamicUpdate
@TableName NVARCHAR(128),
@ColumnName NVARCHAR(128),
@Value NVARCHAR(100)
AS
BEGIN
DECLARE @SQL NVARCHAR(500);
SET @SQL = N'UPDATE ' + QUOTENAME(@TableName) +
N' SET ' + QUOTENAME(@ColumnName) + N' = @Val';
EXEC sp_executesql @SQL, N'@Val NVARCHAR(100)', @Value;
END;
-- Test
EXEC DynamicUpdate @TableName = 'Employees', @ColumnName = 'Salary', @Value = '50000';
-- MySQL dynamic SQL
DELIMITER //
CREATE PROCEDURE DynamicUpdate(
IN TableName VARCHAR(128),
IN ColumnName VARCHAR(128),
IN Value VARCHAR(100)
)
BEGIN
SET @sql = CONCAT('UPDATE ', QUOTE_IDENTIFIER(TableName),
' SET ', QUOTE_IDENTIFIER(ColumnName), ' = ?');
PREPARE stmt FROM @sql;
SET @val = Value;
EXECUTE stmt USING @val;
DEALLOCATE PREPARE stmt;
END //
DELIMITER ;
-- Test
CALL DynamicUpdate('Employees', 'Salary', '50000');
-- T-SQL (no autonomous transactions, use table variables or separate connection)
CREATE PROCEDURE LogError
@ErrorMsg NVARCHAR(4000)
AS
BEGIN
INSERT INTO ErrorLog (ErrorMessage, ErrorTime)
VALUES (@ErrorMsg, GETDATE());
END;
CREATE PROCEDURE ProcessData
AS
BEGIN
BEGIN TRY
SELECT 1/0;
END TRY
BEGIN CATCH
EXEC LogError @ErrorMsg = ERROR_MESSAGE();
THROW;
END CATCH;
END;
-- MySQL (no autonomous transactions, use separate procedure)
DELIMITER //
CREATE PROCEDURE LogError(IN ErrorMsg VARCHAR(4000))
BEGIN
INSERT INTO ErrorLog (ErrorMessage, ErrorTime)
VALUES (ErrorMsg, NOW());
END //
CREATE PROCEDURE ProcessData()
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
CALL LogError(@error_msg);
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = @error_msg;
END;
SET @error_msg = 'Division by zero';
SELECT 1/0;
END //
DELIMITER ;
-- Using LEAD
WITH InvoiceGaps AS (
SELECT InvoiceNumber, LEAD(InvoiceNumber) OVER (ORDER BY InvoiceNumber) AS NextInvoice
FROM Invoices
)
SELECT InvoiceNumber, NextInvoice, (NextInvoice - InvoiceNumber - 1) AS GapSize
FROM InvoiceGaps
WHERE NextInvoice - InvoiceNumber > 1;
-- Using sequence
WITH Numbers AS (
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS n
FROM sys.all_objects
)
SELECT n AS MissingInvoice
FROM Numbers n
LEFT JOIN Invoices i ON i.InvoiceNumber = n
WHERE i.InvoiceNumber IS NULL AND n <= (SELECT MAX(InvoiceNumber) FROM Invoices);
-- Using LEAD
WITH InvoiceGaps AS (
SELECT InvoiceNumber, LEAD(InvoiceNumber) OVER (ORDER BY InvoiceNumber) AS NextInvoice
FROM Invoices
)
SELECT InvoiceNumber, NextInvoice, (NextInvoice - InvoiceNumber - 1) AS GapSize
FROM InvoiceGaps
WHERE NextInvoice - InvoiceNumber > 1;
-- Using sequence
WITH RECURSIVE Numbers AS (
SELECT 1 AS n
UNION ALL
SELECT n + 1 FROM Numbers WHERE n < (SELECT MAX(InvoiceNumber) FROM Invoices)
)
SELECT n AS MissingInvoice
FROM Numbers n
LEFT JOIN Invoices i ON i.InvoiceNumber = n
WHERE i.InvoiceNumber IS NULL;
-- Control table
CREATE TABLE ExecutionLog (
ParamID INT PRIMARY KEY,
ExecutionTime DATETIME
);
-- Idempotent procedure
CREATE PROCEDURE ProcessPayment
@PaymentID INT
AS
BEGIN
IF NOT EXISTS (SELECT 1 FROM ExecutionLog WHERE ParamID = @PaymentID)
BEGIN
BEGIN TRANSACTION;
INSERT INTO ExecutionLog (ParamID, ExecutionTime)
VALUES (@PaymentID, GETDATE());
-- Process payment
INSERT INTO Payments (PaymentID, Amount) VALUES (@PaymentID, 100);
COMMIT;
END;
END;
-- Control table
CREATE TABLE ExecutionLog (
ParamID INT PRIMARY KEY,
ExecutionTime DATETIME
);
-- Idempotent procedure
DELIMITER //
CREATE PROCEDURE ProcessPayment(IN PaymentID INT)
BEGIN
IF NOT EXISTS (SELECT 1 FROM ExecutionLog WHERE ParamID = PaymentID) THEN
START TRANSACTION;
INSERT INTO ExecutionLog (ParamID, ExecutionTime)
VALUES (PaymentID, NOW());
-- Process payment
INSERT INTO Payments (PaymentID, Amount) VALUES (PaymentID, 100);
COMMIT;
END IF;
END //
DELIMITER ;
-- T-SQL
BEGIN TRY
BEGIN TRANSACTION;
DECLARE @OrderID INT;
INSERT INTO Orders (CustomerID, OrderDate)
VALUES (101, GETDATE());
SET @OrderID = SCOPE_IDENTITY();
INSERT INTO OrderDetails (OrderID, ProductID, Quantity)
VALUES (@OrderID, 501, 10);
COMMIT;
END TRY
BEGIN CATCH
ROLLBACK;
PRINT ERROR_MESSAGE();
END CATCH;
-- MySQL
DELIMITER //
CREATE PROCEDURE InsertOrder()
BEGIN
DECLARE order_id INT;
START TRANSACTION;
INSERT INTO Orders (CustomerID, OrderDate)
VALUES (101, NOW());
SET order_id = LAST_INSERT_ID();
INSERT INTO OrderDetails (OrderID, ProductID, Quantity)
VALUES (order_id, 501, 10);
COMMIT;
END //
DELIMITER ;
CALL InsertOrder();
-- Using LEFT JOIN
SELECT e.EmployeeID, e.Name
FROM Employees e
LEFT JOIN TimeSheets t ON e.EmployeeID = t.EmployeeID
WHERE t.EmployeeID IS NULL;
-- Using NOT EXISTS
SELECT EmployeeID, Name
FROM Employees e
WHERE NOT EXISTS (
SELECT 1 FROM TimeSheets t WHERE t.EmployeeID = e.EmployeeID
);
-- Using LEFT JOIN
SELECT e.EmployeeID, e.Name
FROM Employees e
LEFT JOIN TimeSheets t ON e.EmployeeID = t.EmployeeID
WHERE t.EmployeeID IS NULL;
-- Using NOT EXISTS
SELECT EmployeeID, Name
FROM Employees e
WHERE NOT EXISTS (
SELECT 1 FROM TimeSheets t WHERE t.EmployeeID = e.EmployeeID
);