Advertisement
Ben_S

SQL Replace IIF with CASE

May 28th, 2019
299
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
VBScript 10.40 KB | None | 0 0
  1.  
  2. Public Function Replace_IIF_with_CASE(ByVal strInput As String) As String
  3. ' Parse the passed string and find the first "IIF(" and parse it into
  4. ' a standard T-SQL CASE WHEN statement.  If a nested IIF is found,
  5. ' recurse and call this function again.
  6. '
  7. ' Ben Sacherich - May 2016 - Feb 2017
  8. '
  9. ' This supports:
  10. '   IIF() embedded anywhere in the input string.
  11. '   Nested IIF() statements.
  12. '   The input string containing multiple IIF() statements on the same level.
  13. '   Strings between open and close IIF parenthesis that contains literal commas or commas for other functions.
  14. '       Example:  IIF(a=1, nz(b,0) , Left('xy,z',2))
  15. '
  16. ' Be aware:
  17. '   This does not optimize the CASE statement in places where a nested IIF could
  18. '     be written as a single CASE statement.
  19. '   It will fail if text inside IIF() contains the pipe character |. If you
  20. '     need to process statements with this character, modify this routine
  21. '     to use another temporary character for |.
  22. '
  23. ' Try these in the Immediate window:
  24. '   ? Replace_IIF_with_CASE("IIF(a=1, nz(b,0) , Left('xy,z',2))")
  25. '   ? Replace_IIF_with_CASE("IIf(x='one',IIf(Abs(Z)=1,2,3),'three')")
  26. '   ? Replace_IIF_with_CASE("IIF(a=1,'1st)', '2nd)')")
  27. '   ? Replace_IIF_with_CASE("SELECT Name, IIF(Gender='M', 'Boy', 'Girl') FROM Students")
  28. '
  29. ' BS 2/24/2017: After never getting the RegEx to work and getting advice that it
  30. '   was too complex, I developed a new method that does not use RegEx.
  31. '
  32. ' How this works:
  33. '   Find "IIF(" in the passed string.  Return original string if not found.
  34. '   Search for the matching closing parenthesis.
  35. '   When the match is found, recurse and make sure an "IIF(" is not nested.
  36. '   After recursing, replace the IIF with a CASE statement.
  37. '       xxxxx Use a regular expression to find the commas inside the IIF that
  38. '       xxxxx are not contained within single quotes or parenthesis.
  39. '       - Once I find the inner part of an IIF this will use the Split function
  40. '         to delimit by commas "," into an array.
  41. '       - Then it looks at each array element. If it contains an odd number of
  42. '         single or double quote characters or different number of opening and
  43. '         closing parenthesis, it combines the array element part with the next
  44. '         part and tests again.
  45. '       - When there are matched single/double quotes and equivalent number of
  46. '         parenthesis it holds that part and appends the "|" character.  This
  47. '         means that it has identified one of the 3 parameters that is passed
  48. '         to the IIF function.
  49. '       - Then it splits the string by the "|" character into three pieces
  50. '         and builds the CASE statement.
  51. '   Continue searching the passed string for another occurrence of "IIF(" (not nested).
  52. '
  53. ' Old issues that should now be resolved:
  54. '   String containing open or close parenthesis inside of quotes (as a literal).
  55. '       This is a problem if they are unpaired inside the IIF function.
  56. '       Example:  IIF(a=1,'1st)', '2nd)')
  57. '       I had this working until I started using RegEx to exclude commas
  58. '       within function calls like "Left('abc',2)".
  59. '       An error will be returned in the string if this condition is found.
  60. '     http://regexr.com/3dc2u
  61. '     https://regex101.com/r/qH0wD8/2
  62. '
  63.  
  64.     Dim lngFuncStart As Long
  65.     Dim lngPosition As Long
  66.     Dim intStack As Integer
  67.     Dim strFunction As String
  68.     Dim strChar As String
  69.     Dim strQuoteChar As String
  70.     Dim bolInQuotes As Boolean
  71.     Dim strSplit() As String
  72.     Dim ReturnValue As String
  73.     Dim bolHint As Boolean
  74.  
  75.     On Error GoTo ErrorHandler
  76.  
  77.     strFunction = "IIF("
  78.     strQuoteChar = "'"      ' Define the style of quotes to look for and exclude.
  79.    bolInQuotes = False     ' We are currently not inside quotes.
  80.  
  81.     lngFuncStart = InStr(1, strInput, strFunction, vbTextCompare)
  82.  
  83.     If lngFuncStart > 0 Then
  84.         lngFuncStart = lngFuncStart + Len(strFunction)
  85.         intStack = 1
  86.         lngPosition = lngFuncStart
  87.  
  88.         Do While lngPosition <= Len(strInput)
  89.             ' Use a WHILE loop instead of a FOR loop because the current and end positions will change inside the loop.
  90.  
  91.             strChar = Mid(strInput, lngPosition, 1)
  92.  
  93.             If strChar = strQuoteChar Then
  94.                 bolInQuotes = Not bolInQuotes
  95.                 ' Move on to the next character
  96.  
  97.             ElseIf bolInQuotes = False Then
  98.                 ' We are currently not inside quotes.
  99.  
  100.                 Select Case strChar
  101.                     Case ")"
  102.                         ' Closing a group
  103.                        intStack = intStack - 1
  104.                     Case "("
  105.                         ' Starting new group
  106.                        intStack = intStack + 1
  107.                 End Select
  108.  
  109.                 If intStack = 0 Then    ' Found closing parenthesis.
  110.  
  111.                     ' See if there is a nested match.  ### Recursive ###
  112.                    ReturnValue = Replace_IIF_with_CASE(Mid(strInput, lngFuncStart, lngPosition - lngFuncStart))
  113.  
  114.                     ' Begin parsing commas.
  115.                    strSplit() = Split(ReturnValue, ",")
  116.  
  117.                     Dim strPart As String
  118.                     Dim strRebuilt As String
  119.                     Dim i As Integer
  120.  
  121.                     strRebuilt = ""
  122.                     If UBound(strSplit()) > 2 Then    ' There are more than 2 commas.  Piece together the parts.
  123.  
  124.                         strPart = strSplit(0)
  125.                         For i = 1 To UBound(strSplit)
  126.                             ' Debug.Print i, strPart, strRebuilt
  127.  
  128.                             If UBound(Split(strPart, "'")) Mod 2 = 0 _
  129.                                And UBound(Split(strPart, """")) Mod 2 = 0 _
  130.                                And UBound(Split(strPart, "(")) = UBound(Split(strPart, ")")) Then
  131.                                 ' Number of single quotes is Even or Zero (matched)
  132.                                ' Number of double quotes is Even or Zero (matched)
  133.                                ' Number of parenthesis is matched
  134.  
  135.                                 ' Add the "|" symbol where the IIF should have commas.
  136.                                strRebuilt = strRebuilt & "|" & strPart
  137.                                 strPart = strSplit(i)
  138.                             Else
  139.                                 strPart = strPart & "," & strSplit(i)
  140.                             End If
  141.                         Next
  142.                         ReturnValue = Mid(strRebuilt & "|" & strPart, 2)
  143.  
  144.                         strSplit() = Split(ReturnValue, "|")
  145.                     End If
  146.  
  147.                     If UBound(strSplit) = 2 Then
  148.                         ' IIF has 3 parameters and is the normal case.
  149.  
  150.                         ' Check to see if this could be a good candiadate for the COALESCE() or ISNULL()
  151.                        ' functions instead of CASE WHEN.
  152.                        ' https://docs.microsoft.com/en-us/sql/t-sql/language-elements/coalesce-transact-sql?view=sql-server-2017
  153.                        If InStr(1, Trim(strSplit(0)), " IS NOT NULL", vbTextCompare) > 1 _
  154.                           Or InStr(1, Trim(strSplit(0)), " IS NULL", vbTextCompare) > 1 Then
  155.                             bolHint = True
  156.                         End If
  157.  
  158.                         '--- Replace the IIF statement with CASE WHEN ---
  159.                        ' CASE statement help:  https://msdn.microsoft.com/en-us/library/ms181765.aspx
  160.                        ReturnValue = "(CASE WHEN " & Trim(strSplit(0)) & " THEN " & Trim(strSplit(1)) & " ELSE " & Trim(strSplit(2)) & " END)"
  161.                         If Right(Mid(strInput, 1, lngFuncStart - Len(strFunction) - 1), 2) = vbCrLf Then
  162.                             ' Don't bother to add a CrLf
  163.                            ' Stop
  164.                        Else
  165.                             ' Add a CrLf before the CASE statement to make identification easier.
  166.                            ' Comment this out if you don't want it added.
  167.                            ReturnValue = vbCrLf & ReturnValue
  168.                         End If
  169.                        
  170.                         If bolHint Then
  171.                             ' Add the hint to the SQL.  You may want to disable this in your implementation.
  172.                            ReturnValue = ReturnValue & vbCrLf & "/* This CASE may be better using ISNULL() or COALESCE() */ "
  173.                         End If
  174.                        
  175.                         strInput = Mid(strInput, 1, lngFuncStart - Len(strFunction) - 1) & ReturnValue & Mid(strInput, lngPosition + 1)
  176.  
  177.                         'Debug.Print ReturnValue
  178.                    Else
  179.                         ' Something is wrong.  Return the original IIF statement.
  180.                        ' Known issues:
  181.                        '       Text inside IIF() contained pipe character |
  182.                        '       Text contained unmatched parenthesis, maybe inside of a literal string like '1st)'
  183.                        '       Three parameters were not passed to the IIF()
  184.                        ReturnValue = "IIF(" & ReturnValue & ") /*### Unable to parse IIF() ###*/ "
  185.                         strInput = Mid(strInput, 1, lngFuncStart - Len(strFunction) - 1) & ReturnValue & Mid(strInput, lngPosition + 1)
  186.  
  187.                     End If
  188.  
  189.                     '--- Check to see if there is another function call following the one just addressed. ---
  190.                    lngFuncStart = InStr(lngFuncStart + Len(ReturnValue) - Len(strFunction), strInput, strFunction, vbTextCompare)
  191.                     If lngFuncStart > 0 Then
  192.                         ' Another IIF function call is at the same level as the one just processed.
  193.                        lngFuncStart = lngFuncStart + Len(strFunction)
  194.                         intStack = 1
  195.                         lngPosition = lngFuncStart
  196.                     Else
  197.                         ReturnValue = strInput
  198.                         Exit Do
  199.                     End If
  200.  
  201.                 End If
  202.             End If
  203.             lngPosition = lngPosition + 1
  204.         Loop
  205.  
  206.     Else
  207.         ' Function not found in passed string.
  208.        ReturnValue = strInput
  209.     End If
  210.  
  211.     Replace_IIF_with_CASE = ReturnValue
  212.  
  213. ExitHere:
  214.     Exit Function
  215.  
  216. ErrorHandler:
  217.  
  218.     MsgBox "Error #" & Err.Number & " - " & Err.Description & vbCrLf & "in procedure Replace_IIF_with_CASE()" _
  219.          & vbCrLf & "Input:  " & strInput, vbExclamation, "Error"
  220.  
  221.     Resume ExitHere
  222.  
  223. End Function
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement