Incorrect Syntax; Access VBA function to SQL Server - sql

I am converting a large MS Access application to run almost entirely on SQL Server to increase performance as the Access DB is ancient and extremely slow. As such I am working on recreating the VBA functions into SQL functions. SQL Server is giving me syntax errors all over the place and I am trying to understand why, here is the VBA:
Function AlphaNum(prodno As String)
Dim i As Long
If Len(prodno) = 0 Then Exit Function
For i = 1 To Len(prodno)
If Mid(prodno, i, 1) Like "[0-9a-z]" Then AlphaNum = AlphaNum & Mid(prodno, i, 1)
Next i
If IsNumeric(AlphaNum) Then
While Left(AlphaNum, 1) = "0"
AlphaNum = Mid(AlphaNum, 2)
Wend
End If
End Function
I've gotten this far with SQL.. I am certainly not an expert at this either, any ideas?
CREATE FUNCTION dbo.[NAL_AlphaNum]
(
#prodno varchar(10)
)
RETURNS VarChar(10)
BEGIN
DECLARE #counter INT,
#AlphaNum varchar(10)
SET #counter='1'
CASE WHEN Len(#prodno) = 0 THEN EXIT
WHILE #counter < Len(#prodno)
BEGIN
CASE WHEN Mid(#prodno, i, 1) LIKE '[0-9a-z]'
THEN #AlphaNum = #AlphaNum + Mid(#prodno, i, 1)
SET #counter = #counter + 1
END
CASE WHEN IsNumeric(#AlphaNum) THEN
WHILE Left(#AlphaNum, 1) = '0'
#AlphaNum = Mid(#AlphaNum, 2)
END
RETURN #AlphaNum
END;
Thank you in advance.

Related

Using levenshtein on parts of string in SQL

I am trying to figure out a way to work some fuzzy searching methods into our store front search field using the Levenshtein method, but I'm running into a problem with how to search for only part of product names.
For example, a customer searches for scisors, but we have a product called electric scissor. Using the Levenshtein method levenshtein("scisors","electric scissor") we will get a result of 11, because the electric part will be counted as a difference.
What I am looking for is a way for it to look at substrings of the product name, so it would compare it to levenshtein("scisors","electric") and then also levenshtein("scisors","scissor") to see that we can get a result of only 2 in that second substring, and thus show that product as part of their search result.
Non-working example to give you an idea of what I'm after:
SELECT * FROM products p WHERE levenshtein("scisors", p.name) < 5
Question: Is there a way to write an SQL statement that handles checking for parts of the string? Would I need to create more functions in my database to be able to handle it perhaps or modify my existing function, and if so, what would it look like?
I am currently using this implementation of the levenshtein method:
//levenshtein(s1 as VARCHAR(255), s2 as VARCHAR(255))
//returns int
BEGIN
DECLARE s1_len, s2_len, i, j, c, c_temp, cost INT;
DECLARE s1_char CHAR;
-- max strlen=255
DECLARE cv0, cv1 VARBINARY(256);
SET s1_len = CHAR_LENGTH(s1), s2_len = CHAR_LENGTH(s2), cv1 = 0x00, j = 1, i = 1, c = 0;
IF s1 = s2 THEN
RETURN 0;
ELSEIF s1_len = 0 THEN
RETURN s2_len;
ELSEIF s2_len = 0 THEN
RETURN s1_len;
ELSE
WHILE j <= s2_len DO
SET cv1 = CONCAT(cv1, UNHEX(HEX(j))), j = j + 1;
END WHILE;
WHILE i <= s1_len DO
SET s1_char = SUBSTRING(s1, i, 1), c = i, cv0 = UNHEX(HEX(i)), j = 1;
WHILE j <= s2_len DO
SET c = c + 1;
IF s1_char = SUBSTRING(s2, j, 1) THEN
SET cost = 0; ELSE SET cost = 1;
END IF;
SET c_temp = CONV(HEX(SUBSTRING(cv1, j, 1)), 16, 10) + cost;
IF c > c_temp THEN SET c = c_temp; END IF;
SET c_temp = CONV(HEX(SUBSTRING(cv1, j+1, 1)), 16, 10) + 1;
IF c > c_temp THEN
SET c = c_temp;
END IF;
SET cv0 = CONCAT(cv0, UNHEX(HEX(c))), j = j + 1;
END WHILE;
SET cv1 = cv0, i = i + 1;
END WHILE;
END IF;
RETURN c;
END
This is a bit long for a comment.
First, I would suggest using a full-text search with a synonyms list. That said, you might have users with really bad spelling abilities, so the synonyms list might be difficult to maintain.
If you use Levenshtein distance, then I suggest doing it on a per word basis. For each word in the user's input, calculate the closest word in the name field. Then add these together to get the best match.
In your example, you would have these comparisons:
levenshtein('scisors', 'electric')
levenshtein('scisors', 'scissor')
The minimum would be the second. If the user types multiple words, such as 'electrk scisors', then you would be doing
levenshtein('electrk', 'electric') <-- minimum
levenshtein('electrk', 'scissor')
levenshtein('scisors', 'electric')
levenshtein('scisors', 'scissor') <-- minimum
This is likely to be an intuitive way to approach the search.

SQL Binomial Distribution/Arithmetical Overflow

I created a function for a cumulative binomial distribution. It works well for extremely modest sample sizes, but I get a arithmetical overflow on larger samples.
The largest culprit is the n!. In excel 170! = 7.3E+306. 171! = #NUM!
Excel has an internal function that calculates binomial distribution, and it works with ns much, much larger than 170.
Is there something I can do to limit the magnitude of the #s generated?
EDIT: I played with this
SET #probout = 2*3*4*5*6*7*8*9*10*11*12
Worked fine
SET #probout = 2*3*4*5*6*7*8*9*10*11*12*13/10000
Resulted in overflow
Function below.
ALTER FUNCTION [dbo].[binomdist_cumulative]
(
#n int
,#k int
,#p float
)
RETURNS float
AS
BEGIN
-- Local Variable Declarations
-- ---------------------------
DECLARE #kfac float
,#nfac float
,#nkfac float
,#i float
,#f int
,#probout float
SET #i = 0
SET #f = 0
SET #nfac = 0
SET #kfac = 0
SET #nkfac = 0
SET #probout = 0
WHILE #i <= #k
BEGIN
--k!
SET #f = #i-1
SET #kfac = #i
IF #kfac > 0
BEGIN
WHILE #f > 0
BEGIN
SET #kfac = #kfac*#f
SET #f = #f -1
END
END
ELSE
BEGIN
SET #kfac = 1
END
--n!
SET #f = #n-1
SET #nfac = #n
IF #nfac > 0
BEGIN
WHILE #f > 0
BEGIN
SET #nfac = #nfac * #f
SET #f = #f -1
END
END
ELSE
BEGIN
SET #nfac = 1
END
--(n-k)!
SET #f = #n-#i-1
SET #nkfac = #n-#i
IF #nkfac > 0
BEGIN
WHILE #f > 0
BEGIN
SET #nkfac = #nkfac * #f
SET #f = #f -1
END
END
ELSE
BEGIN
SET #nkfac = 1
END
--Accumulate distribution
SET #probout = #probout + #nfac/(#kfac*#nkfac)*POWER(#p,#i)*POWER(1-#p,#n-#i)
SET #i = #i+1
END
RETURN #probout
END
Let me give you a hint.
If you calculate the full factorials, you are quickly going to get overflows. If you do an incremental calculation, the you won't.
For instance, instead of calculating (5 // 3) as (5*4*3*2*1) / ((3*2) * (3*2*1)), calculate it as: (5 / 3) * (4 / 2) * (3 / 3) * (2 / 2) * (1 / 1). . . oh, wait, you can see that the last three terms are all "1".
To be clear, you want to calculate the product of:
((n - i) / (n - k - i)
For i between 0 and k - 1. That is, you are dividing the product of k consecutive numbers ending in n with k consecutive numbers starting with 1.
You'll see that this incremental approach will forestall the issues with overflow.

Netcool/OMNIbus SQL procedure not running\doing nothing

So i have a tool that call procedure.
Tool looks like that:
call Attach_test('select TTID from alerts.status where Class in (73000,8891) and to_int(TTID) > 0 and ServerSerial in ($selected_rows.Serial)',[ $selected_rows.Serial ]); flush iduc;
it should get TTID (that field have only one of many selected alarms ) and array of server serials of selected alarms.
Then all this data is transfered to SQL procedure that looks like:
declare
tempservser integer; k integer;
begin
for k = 1 to array_len(serserial) do
begin
set tempservser = serserial[k];
update alerts.status set ParentTT = parentttid, TTFlag = 2 where ServerSerial = tempservser and TTID = '' ;
end;
end
Parameters:
in parentttid Char(11)
in serserial array of Integer
And here comes the trouble - procedure do nothing. There is no errors or something but there is no update on selected alarms.
I want it to work like this - you select many alarms with only one that have TTID, run this tool that set ParentTT = TTID on every other of selected alarms.
OS ver. 8.1
Sorry for my english
I figured out how to do it:
Tool
call AttachSelectedToTTID([ $selected_rows.Serial ],[ $selected_rows.ParentTT ]);
flush iduc;
Procedure
declare
tempservser integer; k integer;n integer;partt char(15);
begin
for n = 1 to array_len(ttid) do
begin
if (ttid[n] != '' ) then
set partt = ttid[n];
end if;
end;
for k = 1 to array_len(serserial) do
begin
set tempservser = serserial[k];
update alerts.status set ParentTT = partt,TTFlag = 2 where Serial = tempservser and TTID = '';
end;
end
Parameters:
in ttid array of Char(15)
in serserial array of Integer

Iterate through each string and its every character and make string using SQL?

I am making pattern for my strings in database here is my needs:
I have to iterate through each record and then I have to check its every character of string using SQL
After that I have to concatenate the string and show like that:
//DECLARING STRINGS IN VARIABLES
SET #str = '4444--6699p'
SET #CHS = '%[a-z]'
SET #CHE = '[a-z]'
SET #CHE2 = '[a-z]%'
SET #NUMS = '%[0-9]'
SET #NUME = '[0-9]'
SET #NUME2 = '[0-9]%'
SET #CHR = '-'
//GET THE IDENT OF SLASH
SET #INDENT = (SELECT PATINDEX('%-%', #str));
// CHECH CONTAIN CHARECTER OR NOT
IF PATINDEX('%[a-z]%' , #str) > 0
//BUILT STRING TILL INDENT
SET #CHA = #CHS
while #id <= #INDENT
begin
set #id = #id + 1
SET #CHA = #CHA + #CHE
end
SET #CHA = #CHA + #CHE2
print #CHA
end
//IF NO CHARECTER BUILT HERE
ELSE
print #NUMS + #NUME + #NUME2
but no want to concate string in that this is a pattern but here is explaination
have a look at database strings now
// DATABASE EXAMPLES OF STRING
(512) 482-2392
(518) 457-5181
http://www.menupages.com/restaurants/cafe-shane/
https://www.google.com/#q=auto
025-121-3453
429–432–2145
there expression returned as for 1 if record exist 3 times or more other wise null
(%[0-9][0-9][0-9]%) [0-9][0-9][0-9] - [0-9][0-9][0-9][0-9]
expressions can be like that abcz-aaassss-ccv so [a-z]{0,3}-[a-z]{0,10}-[a-z]{0,3}
HINT:
As in case of c sharp we do
string builtme;
builtme = builtme + builtme;
ACHEIVEMENT
HERE IS OUT PUT EXAMPLE
%[a-z][a-z][a-z]% - %[a-z][a-z][a-z][a-z]%-%[a-z][a-z][a-z]%

Which function is faster Abs or IIf?

I ran the following test to determine the difference in efficiency between IIf and Abs:
Public Sub TestSpeed()
Dim i As Long
Dim res As Integer
Debug.Print "*** IIF ***"
Debug.Print Format(Now, "HH:mm:ss")
For i = 1 To 999999999
res = IIf(-1 = True, 1, 0)
Next
Debug.Print Format(Now, "HH:mm:ss")
Debug.Print "*** ABS **"
Debug.Print Format(Now, "HH:mm:ss")
For i = 1 To 999999999
res = Abs(-1)
Next
Debug.Print Format(Now, "HH:mm:ss")
End Sub
The results show that Abs is about 12 times faster:
TestSpeed
*** IIF ***
15:59:08
16:01:26
*** ABS **
16:01:26
16:01:37
Can anyone support this or prove that the contrary is true?
EDIT:
One situation in which one may need to decide between the two functions is for doing multiple counts based on criteria in an SQL query such as:
SELECT Sum(Abs(Colour = 'Yellow')) AS CountOfYellowItems, Sum(Abs(Votes>3) AS CountOfMoreThanThreeVotes FROM tblItems
versus
SELECT Sum(IIf(Colour = 'Yellow' ,1 ,0)) AS CountOfYellowItems, Sum(IIf(Votes > 3 ,1 ,0) AS CountOfMoreThanThreeVotes FROM tblItems
I ran a similar test that supports your findings:
Option Compare Database
Option Explicit
Public Sub SpeedTest()
Const LoopLimit = 99999999
Dim Tasked(LoopLimit) As Boolean, i As Long
Dim Total As Long, t0 As Single, Elapsed As Single
For i = 0 To LoopLimit
Tasked(i) = False
Next
Debug.Print "*** IIF ***"
Total = 0
t0 = Timer
For i = 0 To LoopLimit
Total = Total + IIf(Tasked(i) = True, 1, 0)
Next
Elapsed = Timer - t0
Debug.Print "Elapsed time: " & Format(Elapsed, "0.0") & " seconds."
Debug.Print "Average time: " & Format(Elapsed / (LoopLimit + 1) * 1000000000, "0") & " nanoseconds."
Debug.Print "*** ABS ***"
Total = 0
t0 = Timer
For i = 0 To LoopLimit
Total = Total + Abs(Tasked(i))
Next
Elapsed = Timer - t0
Debug.Print "Elapsed time: " & Format(Elapsed, "0.0") & " seconds."
Debug.Print "Average time: " & Format(Elapsed / (LoopLimit + 1) * 1000000000, "0") & " nanoseconds."
End Sub
resulting in
*** IIF ***
Elapsed time: 19.0 seconds.
Average time: 190 nanoseconds.
*** ABS ***
Elapsed time: 2.4 seconds.
Average time: 24 nanoseconds.
In terms of raw execution speed, Abs(BooleanValue) appears to be an order of magnitude faster than IIf(BooleanValue = True, 1, 0).
Whether or not that difference has a significant impact on the overall performance of the code in which the functions may be used depends very much on the context, as illustrated here.
I have had a look at this in SQL for comparison.
DECLARE #i As BIGINT = 1
DECLARE #res As INT
declare #v BIGINT = -1
Print '*** ABS **'
DECLARE #s2 datetime = GETDATE()
Print Format(#s2, 'HH:mm:ss')
SET #i = 1
WHILE #i < 9999999
begin
SET #res = Abs(#v)
SET #i = #i + 1
end
DECLARE #e2 datetime = GETDATE()
Print Format(#e2, 'HH:mm:ss')
Print DATEDIFF(MILLISECOND, #s2,#e2)
DECLARE #i As BIGINT = 1
DECLARE #res As INT
declare #v INT = -1
Print '*** IIF **'
DECLARE #s1 datetime = GETDATE()
Print Format(#s1, 'HH:mm:ss')
SET #i = 1
WHILE #i < 9999999
begin
SET #res = IIf(#v < 0 , #v*-1, #v)
SET #i = #i + 1
END
DECLARE #e1 datetime = GETDATE()
Print Format(#e1, 'HH:mm:ss')
Print DATEDIFF(MILLISECOND, #s1,#e1)
You can't run both statements together to get a fair comparison. I think there is almost no difference. One case where IIF is better is where
#v BIGINT = -2147483648 (or greater), as ABS would just fail with an overflow.