Using Basic macro functions in SQL statements?

(I guess it can’t be done in LO, at least not yet. … As @Ratslinger has hinted, you can mess with the queries under a report or the like, but this is not really using functions in queries, and also as @peterwt has reminded us you can use SQL functions, but these are not basic functions, and so you can’t edit and debug them like you can with basic.)


Say you have a BASIC macro function called sLastFirst(first,last) that you want to use to format names as “Last, First”.

Function sLastFirst (sFirst As String, sLast As String) As String
      sLastFirst = sLast & ", " & sFirst
End Function

Is there any way you can use it in SQL like this:

SELECT sLastFirst(`First`,`Last) As Name From Names;

Is there any way to make this work, or do I instead have to rewrite it into a SQL function?

MORE EXAMPLES

To help others further understand the need for what I am asking for, here are a bunch of functions that I used in Access to help w/ my SQL. This is very hard I think to do with stored procedures, although it might be faster if you can ever get it to work.

    '=== NAMES ===============================================================
' --- Just first part of name
Function FirstNameString(First As Variant, NickName As Variant, Middle As Variant)
FirstNameString = Trim(Nz([First])) & _
IIf(Nz([Middle]) <> "", " " & Trim(Nz([Middle])), "") & _
IIf(Nz([NickName]) <> "", " " & Chr(34) & Trim(Nz([NickName])) & Chr(34), "")
End Function


' --- Just first part of name; Return First or Nick; Give priority to First
Function CommonFirstNameString(First As Variant, NickName As Variant, Middle As Variant) As String
If Nz(First) = "" Then
	If Nz(NickName) = "" Then
	CommonFirstNameString = Trim(Nz(Middle))
	Else
	CommonFirstNameString = Trim(Nz([NickName]))
	End If
Else
	CommonFirstNameString = Trim(Nz([First]))
End If
' Broken - returns nick when both exist
'  If Nz(NickName) = "" Then
'    CommonFirstNameString = Trim(Nz([First]))
'  Else
'    CommonFirstNameString = Trim(Nz([NickName]))
'  End If
End Function


' --- Full name
'     Pack together name components with appropriate spaces and projects for nickname
'     note: the call for this must make sure that the parameters aren't null.
'           the easiest way to do this is to add '& ""' to each parameter to
'           force it to be a non-null string.   - hwj
Function FormattedName(First As Variant, NickName As Variant, Middle As Variant, Last As Variant)
FormattedName = FirstNameString(Nz([First]), Nz([NickName]), Nz([Middle])) & " " & Trim(Nz([Last]))
End Function


' --- Full name, but with only middle initial
Function ShortFormattedName(First As Variant, NickName As Variant, Middle As Variant, Last As Variant)
ShortFormattedName = FirstNameString(Nz([First]), Nz([NickName]), Left$(Nz([Middle]), 1)) & " " & Trim(Nz([Last]))
End Function


' --- "Last, First" style name
Function LastFirstName(First As Variant, NickName As Variant, Middle As Variant, Last As Variant)
LastFirstName = Trim(Nz([Last])) & ", " & FirstNameString(First, NickName, Middle)
End Function



    '=== PHONES ===============================================================
' --- Print phone if not private
Function PublicPhone(Pub As Variant, Phone As Variant)
If (Nz([Pub]) = "") Then
	PublicPhone = IIf(Nz([Phone]) = "", "___-____", "(Permission?)")
Else
	If ([Pub] = "none") Then
	PublicPhone = "(none)"
	Else
	PublicPhone = IIf([Pub] = "Public" Or [Pub] = "Book", Nz([Phone]), "(Private#)")
	End If
End If
End Function


' --- Print phone if not private + extension
Function PublicPhoneX(Pub As Variant, Phone As Variant, Ext As Variant)
If (Nz([Pub]) = "") Then
	PublicPhoneX = IIf(Nz([Phone]) = "", "___-____", "(Permission?)")
Else
	If ([Pub] = "none") Then
	PublicPhoneX = "(none)"
	Else
	PublicPhoneX = IIf(Nz([Pub]) = "Public" Or Nz([Pub]) = "Book", Nz([Phone]) & IIf(Nz([Ext]) = "", "", " x " & Nz([Ext])), "(Private#)")
	End If
End If
End Function


' --- Print phone if not private + extension
Function PPublicPhoneX(Pub As Variant, aCode As Variant, Phone As Variant, Ext As Variant)
If (Nz([Pub]) = "") Then
	PPublicPhoneX = IIf(Nz([Phone]) = "", "___-____", "(Permission?)")
Else
	If ([Pub] = "none") Then
	PPublicPhoneX = "(none)"
	Else
	PPublicPhoneX = IIf(Nz([Pub]) = "Public" Or Nz([Pub]) = "Book", IIf(Nz([aCode]) = "", "   ", Nz([aCode]) & "-") & Nz([Phone]) & IIf(Nz([Ext]) = "", "", " x " & Nz([Ext])), "(Private#)")
	End If
End If
End Function


' --- Print phone if not private + extension
Function PPublicBlockedPhoneX(Pub As Variant, Blocked As Variant, aCode As Variant, Phone As Variant, Ext As Variant)
Dim Pfx1 As String
Dim Blkd As String
Dim Pfx2 As String
Dim Extn As String

If (Nz([Pub]) = "") Then
	PPublicBlockedPhoneX = IIf(Nz([Phone]) = "", "___-___-____ x ___", "(Permission?)")
Else
	If ([Pub] = "none") Then
	PPublicBlockedPhoneX = "(none)"
	Else
	If Nz([Pub]) = "Public" Or Nz([Pub]) = "Book" Then
		Pfx1 = IIf(Nz([aCode]) = "", "   ", "")
		Blkd = IIf(Nz([Blocked]) = "", "   ", Nz([Blocked]) & "-")
		Pfx2 = IIf(Nz([aCode]) = "", "", Nz([aCode]) & "-")
		PPublicBlockedPhoneX = Pfx1 & Blkd & Pfx2 & PhoneExt(Nz([Phone]), Nz([Ext]))
	Else
		PPublicBlockedPhoneX = "(Private#)"
	End If
	End If
End If
End Function


' --- Format either "(ACode)" or "", i.e. nothing.
Function OptionalACode(aCode As Variant) As String
If Nz(aCode) = "" Then
	OptionalACode = ""
Else
	OptionalACode = "(" & [aCode] & ") "
End If
End Function


' --- Build phone# and add "___" if missing
Function Phone___(aCode As Variant, Phone As Variant, Ext As Variant) As String
Phone___ = DefaultaCodePhone("___", aCode, Phone, Ext)
End Function


' --- Build phone# and add "650" if missing
Function Phone650(aCode As Variant, Phone As Variant, Ext As Variant) As String
Phone650 = DefaultaCodePhone("650", aCode, Phone, Ext)
End Function


' --- Build phone# and add "760" if missing
Function Phone760(aCode As Variant, Phone As Variant, Ext As Variant) As String
Phone760 = DefaultaCodePhone("760", aCode, Phone, Ext)
End Function


' --- Build phone# and add default area code if missing (allow for just "911" type areaCode only numbers)
Function DefaultaCodePhone(defaultACode As Variant, aCode As Variant, Phone As Variant, Ext As Variant) As String
DefaultaCodePhone = aCodePhoneExt(IIf(Nz([aCode]) = "", defaultACode, aCode), Phone, Ext)
End Function


' --- Build phone# and add default area code if missing (allow for just "911" type areaCode only numbers)
Function aCodePhoneExt(aCode As Variant, Phone As Variant, Ext As Variant) As String
If Nz(aCode) & Nz([Phone]) = "" Then
	aCodePhoneExt = ""
Else
	aCodePhoneExt = Nz([aCode]) & IIf(Nz(Phone) = "", "", "-" & PhoneExt(Phone, Ext))
End If
End Function


' === Private sub to build extension
Private Function PhoneExt(Phone As Variant, Ext As Variant) As String
PhoneExt = Nz([Phone]) & IIf(Nz([Ext]) = "", "", " x " & Nz([Ext]))
End Function



    '=== ADDRESSES =============================================================
' --- Add Street# to Street name (DEPRECIATED function)
Function StreetAdr(No As Variant, Street As Variant) As String
StreetAdr = Trim([No]) & IIf(Nz([No]) = "", "", " ") & Nz([Street])
End Function


' --- Format street# & Apt#
'Function AdrApt(No As Variant, Apt As Variant) (<-DEPRECIATED name)
Function NoApt(No As Variant, Apt As Variant) As String
NoApt = Trim(Nz([No])) & IIf(Trim(Nz([Apt])) = "", "", " " & IIf(IsNumeric([Apt]), "#", "") & Trim([Apt]))
End Function


' --- Add Street & Apt to Street name
'Function AdrAptStreet(No As Variant, Apt As Variant, Street As Variant) (<-DEPRECIATED name)
Function NoAptStreet(No As Variant, Apt As Variant, Street As Variant) As String
NoAptStreet = Trim(NoApt(No, Apt) & " " & Trim(Nz([Street])))
End Function


' === #/Apt/Street/City  (without POB.  See also one below)
Function NoAptStreetCity(No As Variant, Apt As Variant, Street As Variant, City As Variant) As String
NoAptStreetCity = NoAptStreet(No, Apt, Street) & ", " & Nz([City])
End Function


' === #/Apt/Street/City  (with    POB.  See also one above)
Function NoAptStreetPOBCity(No As Variant, Apt As Variant, Street As Variant, POB As Variant, City As Variant) As String
NoAptStreetPOBCity = NoAptStreet(No, Apt, Street) & IIf(IsNull(No) And IsNull(Apt) And IsNull(Street), "", ", ") & IIf(IsNull(POB), "", "POB " & POB & ", ") & Nz([City])
End Function


' === City/St
Function CitySt(City As Variant, ST As Variant) As String
CitySt = Nz(City) & ", " & Nz([ST])
End Function

Function CityCountySt(City As Variant, County As Variant, ST As Variant) As Variant
CityCountySt = Nz(City) & IIf(IsNull([City]) Or IsNull([County]), "", ", ") & IIf(IsNull([County]), "", [County]) & IIf(IsNull([ST]), "", ", " & [ST])
End Function


' === #/Apt/Street/City/St      (without POB.  See also one below)
Function NoAptStreetCitySt(No As Variant, Apt As Variant, Street As Variant, City As Variant, ST As Variant) As String
NoAptStreetCitySt = NoAptStreetCity(No, Apt, Street, City) & ", " & Nz([ST])
End Function

' === #/Apt/Street/POB/City/St  (with    POB.  See also one above)
Function NoAptStreetPOBCitySt(No As Variant, Apt As Variant, Street As Variant, POB As Variant, City As Variant, ST As Variant) As String
NoAptStreetPOBCitySt = NoAptStreetPOBCity(No, Apt, Street, POB, City) & ", " & Nz([ST])
End Function


' === #/Apt/Street/City/St/Zip      (without POB.  See also one below)
Function NoAptStreetCityStZip(No As Variant, Apt As Variant, Street As Variant, City As Variant, ST As Variant, Zip As Variant) As String
NoAptStreetCityStZip = NoAptStreetCitySt(No, Apt, Street, City, ST) & "  " & Nz([Zip])
End Function


' === #/Apt/Street/POB/City/St/Zip  (with    POB.  See also one above)
Function NoAptStreetPOBCityStZip(No As Variant, Apt As Variant, Street As Variant, POB As Variant, City As Variant, ST As Variant, Zip As Variant) As String
NoAptStreetPOBCityStZip = NoAptStreetPOBCitySt(No, Apt, Street, POB, City, ST) & "  " & Nz([Zip])
End Function


' === Zip/Plus4
Function ZipPlus4(Zip As Variant, Plus4 As Variant) As String
ZipPlus4 = Zip & IIf(Trim(Nz([Plus4])) = "", "", "-" & Nz([Plus4]))
End Function


' === No/Apt/Street/City/St/Zip/Plus4      (without POB.  See also one below)
Function NoAptStreetCityStZipPlus4(No As Variant, Apt As Variant, Street As Variant, City As Variant, ST As Variant, Zip As Variant, Plus4 As Variant) As String
NoAptStreetCityStZipPlus4 = NoAptStreetCityStZip(No, Apt, Street, City, ST, ZipPlus4(Zip, Plus4))
' NoAptStreetCityStZipPlus4 = NoAptStreetCityStZip(No, Apt, Street, City, ST, Zip) & IIf(Trim(Nz([Plus4])) = "", "", "-" & Nz([Plus4]))
End Function


' === No/Apt/Street/POB/City/St/Zip/Plus4  (with    POB.  See also one above)
Function NoAptStreetPOBCityStZipPlus4(No As Variant, Apt As Variant, Street As Variant, POB As Variant, City As Variant, ST As Variant, Zip As Variant, Plus4 As Variant) As String
NoAptStreetPOBCityStZipPlus4 = NoAptStreetPOBCityStZip(No, Apt, Street, POB, City, ST, ZipPlus4(Zip, Plus4))
End Function


' === City/State/Zip/Plus4 (use Null if Plus4 doesn't exist)
Function CityStateZip(City As Variant, ST As Variant, Zip As Variant, Plus4 As Variant) As String
CityStateZip = Nz([City]) & ", " & Nz([ST]) & "  " & Nz([Zip]) & IIf(Nz([Plus4]) = "", "", "-" & Nz([Plus4]))
End Function


' === City/CA/Zip
Function CityCaZip(City As Variant, Zip As Variant, Plus4 As Variant) As String
CityCaZip = Trim(CityStateZip([City], "CA", [Zip], [Plus4]))
End Function


' === StCity (for sorting by location)
Function StCity(ST As Variant, City As Variant) As String
StCity = ST & ", " & City
End Function


' === StCityStreetAdrApt (for sorting by location) As String
Function StCityStreetAdrApt(ST As Variant, City As Variant, Street As Variant, Number As Variant, Apt As Variant)
StCityStreetAdrApt = StCity(ST, City) & ", " & Street & ", " & Number & " " & Apt
End Function

Even if you could (which you can’t), why would you do something in two steps when it can be done in one?

To reuse code. This is a simple example, but I have a bunch of much more extensive functions to deal with names and address and other data structures, and format them correctly in a variety of ways and dealing with any nulls.

…And to make it easy to prompt for user input and then use it for record selection, not just as simple text, but via things like pull down lists of dates based on the available dates, to select from. And this by setting SQL criteria via fields in a dialog form.

Now I know I can still do this with a macro which builds the sql and executes it, but that is much harder than allowing it inside a query in the GUI.

@Ratslinger, “… If you want to set up SQL in GUI for user prompts, do it with parameters. Then just call it in a macro, fill in the parameters from whatever prompts you have and execute it however you want.”
But I want to select record IDs (integers) to filter the query, and those are not so easily typed into a parameter box, as for example 4672 is sort of obscure when I really mean ‘xyz’ company?

You can use stored procedures/functions for the database engine that you are using. For HSQLDB you could use:-

SELECT IFNULL("First",'')||','||IFNULL("Last",'') AS "Name" FROM "Names"

which will cater for nulls.

You may be able to create your own stored procedures/functions depending on the database engine.

Thanks, but I was already aware of stored procedures, "or do I instead have to rewrite it into a SQL function? " Sorry, I probably should have used the words, “stored procedures”. FWIW, and the reason I asked this Q is because I find stored procedures hard to debug, hard to use, and Basic is much easier. Have been spoiled on Access where I can use my VBA as SQL functions. Hope LO can do this someday.

@EasyTrieve This answer is only in response to the comments regarding the use of Queries with parameters.

You mention it is easier to set up in GUI. OK, set this up with parameters then call from basic and fill in the parameters. This doesn’t mean the user is prompted for the input. It allows you to create & test the statement in the GUI and then (sometimes you have to alter your thinking) you fill in the parameters through code.

Given this SQL statement saved as “MyQuery”:

SELECT *  FROM MYTABLE WHERE ID = :my_id

This code:

SUB QueryContent
	DIM oDatabaseFile AS OBJECT
	DIM oQuery AS OBJECT
	DIM stQuery AS STRING
	oDatabaseFile = ThisComponent.Parent.CurrentController.DataSource
	oQuery = oDatabaseFile.getQueryDefinitions()
	stQuery = oQuery.getByName("MyQuery").Command
	stQuery = Replace(stQuery, ":my_id","?")
    if IsNull(Thisdatabasedocument.CurrentController.ActiveConnection) then
       Thisdatabasedocument.CurrentController.connect
    endif
    oConnection = Thisdatabasedocument.CurrentController.ActiveConnection
    oStmt = oConnection.prepareCall(stQuery)
	oStmt.setInt(1, "125")
    oResult = oStmt.executeQuery()
    oResult.next()
    MsgBox oResult.getInt(1)
    oResult.close()
    oStmt.close()
END SUB

Will return the record with ID = 125. With this you can use the GUI and not have to use the “harder” macro method.

Note: In the future (and you have done this multiple times) you should not quote something from a deleted comment or answer from anyone. It usually was deleted for a purpose. In this case I wanted time to verify my comment and have something to present.

Interesting. Thanks. I also found that I could write to .Command.