Agile Data

Catalog of Database Refactorings - Method Refactorings

Follow @scottwambler on Twitter!

A method database refactoring is a change which improves the quality of a stored procedure, stored function, or trigger so as to improve your database design without changing its semantics.
Refactoring Example
Add Parameter.  An existing method needs information that was not passed in before.
Consolidate Conditional Expression.  Combine sequence of conditional tests into a single conditional expression and extract it.
Before After
CREATE OR REPLACE FUNCTION GetAccountAverageBalance
 ( inAccountID IN NUMBER)
 RETURN NUMBER;
AS
 averageBalance := 0;
BEGIN
 IF inAccountID > 10000 THEN
   RETURN 0;
 END IF;

 IF inAccountID = 123456 THEN
   RETURN 0;
 END IF;

  IF inAcountID = 987654 THEN
   RETURN 0;
 END IF;

 -- Code to calculate the average balance
 RETURN averageBalance;
END;

CREATE OR REPLACE FUNCTION GetAccountAverageBalance
 ( inAccountID IN NUMBER)
 RETURN NUMBER;
AS
 averageBalance := 0;
BEGIN
 IF inAccountID < 10000 || inAccountID = 123456 || inAcountID = 987654 THEN
   RETURN 0;
 END IF;

 -- Code to calculate the average balance
   RETURN averageBalance;
END;

Decompose Conditional.  Extract methods from the condition.
Before After
CREATE OR REPLACE FUNCTION CalculateInterest
 ( inBalance IN NUMBER )
 RETURN NUMBER;
AS
 lowBalance NUMBER;
 highBalance NUMBER;
 lowInterestRate NUMBER;
 highInterestRate NUMBER;

BEGIN 
 lowBalance := GetLowBalance();
 highBalance := GetHighBalance();
 lowInterestRate := GetLowInterestRate();
 highInterestRate := GetHighInterestRate(); 

 IF inBalance < lowBalance THEN
   RETURN 0;
 END IF

  IF inBalance >= lowBalance && inBalance <= highBalance THEN
   RETURN inBalance * lowInterestRate;
 ELSE
   RETURN inBalance * highInterestRate;
 END IF;
END;

CREATE OR REPLACE FUNCTION CalculateInterest
 ( inBalance IN NUMBER )
 RETURN NUMBER;
AS
BEGIN

 IF BalanceIsInsufficient( inBalance ) THEN
   RETURN 0;
 END IF 

 IF IsLowInterestBalance( inBalance ) THEN
   RETURN CalculateLowInterest( inBalance );
 ELSE
   RETURN CalculateHighInterest( inBalance );
 END IF;
END;

Extract Method.  Turn the code fragment into a method whose name explains the purpose of the method.
Before After
CREATE OR REPLACE FUNCTION CalculateAccountInterest
 ( inAccountID IN NUMBER,
 inStart IN DATE,
 inEnd IN DATE )
 RETURN NUMBER;
AS
 medianBalance NUMBER;
 startBalance NUMBER;
 endBalance NUMBER;
 interest := 0;
BEGIN
 BEGIN
   -- Determine the starting balance
   SELECT Balance INTO startBalance
     FROM DailyEndBalance
     WHERE AccountID = inAccountID && PostingDate = inStart;
   EXCEPTION WHEN NO_DATA_FOUND THEN
     startBalance := 0;

   -- Determine the ending balance
    SELECT Balance INTO endBalance
    FROM DailyEndBalance
     WHERE AccountID = inAccountID && PostingDate = inEnd;
    EXCEPTION WHEN NO_DATA_FOUND THEN
    endBalance := 0;
  END;

  medianBalance := ( startBalance + endBalance ) / 2;
   IF medianBalance < 0 THEN
     medianBalance := 0;
   END IF;
 IF medianBalance >= 500 THEN
     interest := medianBalance * 0.01;
   END IF;

 RETURN interest;
END;

CREATE OR REPLACE FUNCTION CalculateAccountInterest
( inAccountID IN NUMBER,
 inStart IN DATE,
 inEnd IN DATE )
 RETURN NUMBER;
AS
 medianBalance NUMBER;
 startBalance NUMBER;
 endBalance NUMBER;
BEGIN
 startBalance := GetDailyEndBalance ( inAccountID, inStart );
 endBalance:= GetDailyEndBalance ( inAccountID, inEnd );
 medianBalance := CalculateMedianBalance ( startBalance, endBalance );
 RETURN CalculateInterest ( medianBalance );
END;
Introduce Variable.  Put the result of the expression, or parts of the expression, in a temporary variable with a name that explains the purpose.
Before After
CREATE OR REPLACE FUNCTION DetermineAccountStatus
 ( inAccountID IN NUMBER,
   inStart IN DATE,
   inEnd IN DATE )
 RETURN VARCHAR;
AS
 lastAccessedDate DATE;
BEGIN

 -- Some code to calculate lastAccessDate

  IF ( inDate < lastAccessDate && outdate > lastAccessDate )
   && ( inAccountID > 10000 )
   && ( inAccountID != 123456 && inAcountID != 987654) THEN
  -- do something

 END IF;
 -- do another thing
END;

CREATE OR REPLACE FUNCTION DetermineAccountStatus
 ( inAccountID IN NUMBER,
   inStart IN DATE,
   inEnd IN DATE )
  RETURN VARCHAR;
AS
 lastAccessedDate DATE;
 isBetweenDates BOOLEAN;
 isValidAccountID BOOLEAN;
 isNotTestAccount BOOLEAN
BEGIN
 -- Some code to calculate lastAccessDate
 isBetweenDates := inDate < lastAccessDate && outdate > lastAccessDate;
 isValidAccountID := inAccountID > 100000;
 isNotTestAccount := inAccountID != 123456 && inAcountID != 987654;
 IF isBetweenDates && isValidAccountID && isNotTestAccount THEN
   -- do something
 END IF;
 -- do another thing
END;
Parameterize Methods.  Create one method that uses a parameter for the different values.
Remove Control Flag.  Use remove or break instead of a variable acting as a control flag.
Before After
DECLARE
 controlFlag := 0;
 anotherVariable := 0;
BEGIN
 WHILE controlFlag = 0 LOOP
   -- Do something
   IF anotherVariable > 20 THEN
      controlFlag = 1;
   ELSE
     -- Do something else
   END IF;
 END LOOP;
END;
DECLARE
  anotherVariable := 0;
BEGIN
 WHILE anotherVariable <= 20 LOOP
   -- Do something
   -- Do something else
 END LOOP;
END;
Remove Middleman.  Get the caller to call the method directly.
Before After
CREATE OR REPLACE PROCEDURE AProcedure
 parameter1 IN NUMBER;
 …
 parameterN IN VARCHAR;
AS
BEGIN
 EXECUTE AnotherProcedure ( parameter1, …, parameterN );
END;
 
Remove Parameter.  Remove a parameter no longer used by the method body.
Rename Method.  Rename an existing method with a name that explains the purpose.
Reorder Parameters.  Change the order of the parameters of a method.
Replace Literal With Table Lookup.  Replace Code constants with values from database tables.
Before After
CREATE OR REPLACE FUNCTION CalculateInterest
 ( inBalance IN NUMBER )
 RETURN NUMBER;
AS
 interest := 0;
BEGIN
 IF inBalance >= 500 THEN
   interest := medianBalance * 0.01;
 END IF;
 RETURN interest;
END;
CREATE OR REPLACE FUNCTION CalculateInterest
 ( inBalance IN NUMBER )
 RETURN NUMBER;
AS
 interest := 0;
 minimumBalance NUMBER;
 interestRate NUMBER;
BEGIN 
 minimumBalance := GetMinimumBalance();
 interestRate := GetInterestRate(); 
 IF inBalance >= minimumBalance THEN
   interest := medianBalance * interestRate;
 END IF;
 RETURN interest;
END;
Replace Nested Expression With Guard Clauses.  Remove nested if conditions with a series of separate IF statements.
Before After
BEGIN
 IF condition1 THEN
   -- do something 1
 ELSE
   IF condition2 THEN
     -- do something 2
   ELSE
     IF condition3 THEN
       -- do something 3
     END IF;
   END IF;
 END IF;
END;
BEGIN
 IF condition1 THEN
   -- do something 1
   RETURN;
 END IF;

 IF condition2 THEN
   -- do something 2
   RETURN;
 END IF;

 IF condition3 THEN
   -- do something 3
   RETURN;
 END IF;
END;

Replace Parameter With Specific Methods.  Create a separate method for each value of the parameter.
Split Temporary Variable.  Make a separate temporary variable for each assignment.
Before After
DECLARE
  aTemporaryVariable := 0;
 farenheitTemperature := 0;
 lengthInInches := 0;
BEGIN
 -- retrieve farenheitTemperature
 aTemporaryVariable := (farenheitTemperature - 32 ) * 5 / 9;
 -- do something
 -- retrieve lengthInInches
 aTemporaryVariable := lengthInInches * 2.54;
 -- do something
END;
DECLARE
  celciusTemperature := 0;
 farenheitTemperature := 0;
 lengthInCentimeters := 0;
 lengthInInches := 0;
BEGIN
 -- retrieve farenheitTemperature
 celciusTemperature := (farenheitTemperature - 32 ) * 5 / 9;
 -- do something
 -- retrieve lengthInInches
 lengthInCentimeters := lengthInInches * 2.54;
 -- do something
END;
Substitute Algorithm.  Replace the body of the method with the new algorithm.  

Source

Refactoring Databases This book describes, in detail, how to refactor a database schema to improve its design. The first section of the book overviews the fundamentals evolutionary database techniques in general and of database refactoring in detail. More importantly it presents strategies for implementing and deploying database refactorings, in the context of both "simple" single application databases and in "complex" multi-application databases. The second section, the majority of the book, is a database refactoring reference catalog. It describes over 60 database refactorings, presenting data models overviewing each refactoring and the code to implement it.