My SQL Function is not returning the value I expected - sql

This was my first attempt at a SQL function. I wrote it in VB and it works like a charm. When I translated it to SQL Server, it returns not what I expect. What the function is intended to do is to return a percentage match of two strings.
How I expected it to function is this:
accept two strings, compare the two, and based on my rating values, return a percentage of the match value...matchscore/max possiblescore
The length of the larger string is multiplied by 3. This is the max possiblescore.
Go character by character of the first string and find that character in the second string.
If the character is found in the same position in the second string, add three to the matchscore and move on to the next letter.
If the character is found in the second word, but not in the same position, add one to match score and move on to the next character.
If the character is not found in the second string, add nothing and move on to the next character.
Divide the matchscore by the max possiblescore. This returns a decimal value. I read RETURN only returns an integer, so I multiplied the division result by 100.
An example of what I expected is I compare "CAT" to "CART". My expected return is 7/12...0.58. Instead, I get 0. If I compare "CAT" to "CAT", I expect 9/9...1.00. Instead, I get 2.
(Note from 9/17/2014: I appreciate your input. I used what you suggested, and did one more major change, that doesn't affect what I asked about, other than getting the correct final answer is that I got rid of the second While Loop. Instead, I search for #strLetter in #strWord2. If it is found, then, I look to see if it is in the same position in #strWord2 as #strWord1. If it is, then I add 3, if not, I add 1. This sped up the function and made the count accurate.
Here is the code:
CREATE FUNCTION [dbo].[CompareWords]
(#strWord1 VARCHAR(2000), #strWord2 VARCHAR(2000))
RETURNS DECIMAL
AS
BEGIN
SET #strWord1 = UPPER(#strWord1)
SET #strWord2 = UPPER(#strWord2)
DECLARE #intLength INT
IF LEN(#strWord1) >= LEN(#strWord2)
BEGIN
SET #intLength = LEN(#strWord1)
END
ELSE
BEGIN
SET #intLength = LEN(#strWord2)
END
DECLARE #iWordLoop1 INT
DECLARE #iWordLoop2 INT
DECLARE #intWordLoop2 INT
DECLARE #intWordScore INT
DECLARE #intLetterScore INT
SET #intWordScore = 0
SET #intWordLoop2 = Len(#strWord2)
DECLARE #strLetter VARCHAR(1000)
DECLARE #count1 INT
SET #count1 = 0
SET #iWordLoop1 = Len(#strWord1)
WHILE (#count1 < #iWordLoop1)
BEGIN
SET #strLetter = SUBSTRING(#strWord1, #count1+1, 1)
SET #intLetterScore = 0
DECLARE #count2 INT
SET #count2 = 0
SET #iWordLoop2 = Len(#strWord2)
WHILE (#count2 < #iWordLoop2)
BEGIN
If #strLetter = SUBSTRING(#strWord2, #count2+1, 1)
BEGIN
If #iWordLoop1 = #iWordLoop2
BEGIN
SET #intLetterScore = 3
SET #iWordLoop2 = Len(#strWord2)
END
ELSE
BEGIN
SET #intLetterScore = 1
END
END
SET #intWordScore = #intWordScore + #intLetterScore
SET #count2 = (#count2 + 1)
END
SET #count1 = (#count1 + 1)
END
DECLARE #sinScore DEC
SET #sinScore = (#intWordScore / (3 * #intLength)) * 100
RETURN #sinSCore
END;

The most significant changes I made were to
reset the intLetterScore to 0 after it's been used in the intWordScore calculation. Without it being reset, the same value was being used each time the inner loop and the character was not matched.
move the multiplication by 100 into the
brackets in the calculation of sinScore.
As referred to in a previous post, because you are doing integer multiplication, the decimal portion is truncated from the calculation. By growing the wordScore by a factor of 100, it is much more likely to be larger than the length and yield a result which is non-zero.
Multiplying outside the brackets has the multiplies the integer result of the division score by length. If this answer is already zero, then the multiplication result is also zero.
Other changes I made are commented in the code: the variable intWordLoop2 has no effect on the calculation and can be removed; strLetter can be declared as a Char(1) instead of VarChar(1000).
CREATE FUNCTION [dbo].[CompareWords]
(#strWord1 VARCHAR(2000), #strWord2 VARCHAR(2000))
RETURNS DECIMAL
AS
BEGIN
SET #strWord1 = UPPER(#strWord1)
SET #strWord2 = UPPER(#strWord2)
--Set #intLength (maxLength as len of word1 or word2)
DECLARE #intLength INT --maxLength
IF LEN(#strWord1) >= LEN(#strWord2)
BEGIN
SET #intLength = LEN(#strWord1)
END
ELSE
BEGIN
SET #intLength = LEN(#strWord2)
END
DECLARE #iWordLoop1 INT, #iWordLoop2 INT--, #intWordLoop2 INT --This variable doesn't impact the calculation
DECLARE #intWordScore INT
DECLARE #intLetterScore INT
SET #intWordScore = 0
--SET #intWordLoop2 = Len(#strWord2)--this value is not used anywhere else, so removing makes no difference.
--DECLARE #strLetter VARCHAR(1000)
DECLARE #strLetter CHAR(1)--there is no need for 1000 characters since we're only ever assigning a single character to this
DECLARE #count1 INT
SET #count1 = 0
SET #iWordLoop1 = Len(#strWord1)
WHILE (#count1 < #iWordLoop1)
BEGIN
SET #strLetter = SUBSTRING(#strWord1, #count1+1, 1)
SET #intLetterScore = 0
DECLARE #count2 INT
SET #count2 = 0
SET #iWordLoop2 = Len(#strWord2)
WHILE (#count2 < #iWordLoop2)
BEGIN
If #strLetter = SUBSTRING(#strWord2, #count2+1, 1)
BEGIN
If #iWordLoop1 = #iWordLoop2
BEGIN
SET #intLetterScore = 3
SET #iWordLoop2 = Len(#strWord2)
END
ELSE
BEGIN
SET #intLetterScore = 1
END
END
SET #intWordScore = #intWordScore + #intLetterScore
SET #intLetterScore = 0
SET #count2 = (#count2 + 1)
END
SET #count1 = (#count1 + 1)
END
DECLARE #sinScore DEC
SET #sinScore = (#intWordScore*100 / (3 * #intLength))
RETURN #sinSCore
END;
select dbo.comparewords ('Cat','cart')

Related

Validate Chilean RUT in a SQL database

I found this code at this website and is working well in SQL SERVER
CREATE FUNCTION [dbo].[CalculaDigitoRut]
(
#rut int
)
RETURNS char(1)
AS
BEGIN
DECLARE #digito int
DECLARE #contador int
DECLARE #multiplo int
DECLARE #acumulador int
DECLARE #ret char
set #contador = 2
set #acumulador = 0
while (#rut <> 0)
begin
set #multiplo = (#Rut % 10) * #contador
set #acumulador = #acumulador + #multiplo
set #rut = #rut / 10
set #contador = #contador + 1
if (#contador = 8)
set #contador = 2
end
set #digito = 11 - (#acumulador % 11)
if (#digito = 10)
return ('K')
if (#digito = 11)
return ('0')
return (#digito)
END
Expected result
RUT -- validation_digit
10214564 K
3781561 6
3433444 7
3066256 3
I tried to adapt it to the MariaDB (10.3.30-MariaDB) syntax and after a few tries there weren't syntax erros but the function isn't returning the expected result.
Heres the new code for MariaDB
CREATE FUNCTION Calculate_national_id_verifier (rut INT)
RETURNS CHAR(1)
BEGIN
DECLARE digito INT;
DECLARE contador INT;
DECLARE multiplo INT;
DECLARE acumulador INT;
DECLARE ret CHAR;
SET contador = 2;
SET acumulador = 0;
WHILE rut <> 0 DO
SET multiplo = (rut % 10) * contador;
SET acumulador = acumulador + multiplo;
SET rut = rut / 10;
SET contador = contador + 1; -- 3
IF (contador = 8) THEN
SET contador = 2;
END IF;
END WHILE;
SET digito = 11 - (acumulador % 11);
IF (digito = 10) THEN
RETURN ('K');
ELSEIF (digito = 11) THEN
RETURN ('0');
ELSE
RETURN (digito);
END IF;
END;
This problem seems very dumb, I've been comparing it for hours and I can't find where is the problem and why there is a difference between both functions.
I'm using DBeaver 21.3.5 as a client to run the queries.
Here are the actual results
RUT -- validation_digit
10214564 6
3781561 K
3433444 7
3066256 5
This is my firts time asking something so sorry if I made a mistake. I couldn't find the answer.

SQL Server stored procedure: finding the values of Odd and even

I'm using #subno as Input. And I had to find the odd and even numbers. 16 is not a fix and it can be any other number. My question is how to find the odd and even number of my input?
Lastly, subno includes ( . ) dot at any position. e.g 123456.789123 I need to find the odd and even number of "123456" and the odd and even number of "789123" the dot ( . ) is the separator.
Once you find the odd for the left side, sum them up together. Once you find the even number for left side sum it up as well and then add the total odd to the total even values. That goes the same for the right side eg "789123".
Please help me. this is my 2nd week of trying to find the solution. Once you find all the total values for each side, multiply them together. example "123456" - total value of odd and even * the "789123" total value of odd and even.
It is for the the check digit validation. Validating the subscriber number. after validating through the calculation it should match the calculated reference number to the valid check digit number. It's the business rule. Kind of algorithm
create procedure ProcedureName
(#subno VARCHAR(16), --Input the 16 subscriber number
#result INT OUT,
)
as
begin
IF(LEN(#subno) <> 16)
SET #result = 1 -- INVALID RESULT
ELSE
IF(#subno % 2 = 0)
SET #result = #subno - even numbers
ELSE
SET #result = #subno --odd numbers
end
Please see below my sample work
-- this is the sample
create procedure ProcedureName
(
#subno VARCHAR(20), --Subscriber no
#result INT OUT, --result is invalid for 1, valid for 0
#payamt int
)
as
DECLARE #WA VARCHAR(2)
DECLARE #Weights varchar(9)
DECLARE #I INT
DECLARE #WD INT
DECLARE #WP INT
DECLARE #A INT
DECLARE #B INT
DECLARE #R INT
DECLARE #WR INT
SET #WR = 0
SET #R = 0
SET #A = 0
SET #B = 0
SET #WP = 0
SET #I = 0
BEGIN
IF (LEN(#subNo) = 7) AND (SUBSTRING(#subno,1,1) = '2') OR (SUBSTRING(#subno,1,1) = '9')
BEGIN
SET #result = 0 --VALID
END
ELSE IF(LEN(#subno) = 8) AND (SUBSTRING(#subno,1,1) = '2') OR
(SUBSTRING(#subno,1,1) = '9')
BEGIN
SET #result = 0 --VALID
END
ELSE IF(LEN(#subno) = 9)
BEGIN
SET #WA = SUBSTRING(#subno,1,2)
IF(#WA = '65')
set #result = 1 -- INVALID
else
BEGIN
SET #Weights = '12121212'
SET #WA = SUBSTRING(#subno,9,1)
SET #WD = 0
SET #I = 1
WHILE #I<9
BEGIN
SET #WP = cast(SUBSTRING(#Weights, #I,1)as int) * cast(SUBSTRING(#subno, #I, 1) as int)
IF(#WP > 9)
BEGIN
SET #A = SUBSTRING(CAST(#WP AS VARCHAR),1,1)
SET #B = SUBSTRING(CAST(#WP AS VARCHAR),2,1)
SET #WP = CAST(#A AS INT) + CAST(#B AS INT)
END
SET #WD = #WP + #WD
SET #I = #I + 1
END
SET #R = #WD % 10
IF(#R <> 0)
SET #WR = 10 - #R
ELSE
SET #WR = #R
IF(#WR <> CAST(#WA AS INT))
BEGIN
SET #result = 1 -- INVALID
END
ELSE
BEGIN
SET #result = 0 -- VALID
END
END
END
ELSE IF (LEN(#subno) = 10)
BEGIN
SET #I =1
SET #WD = 0
SET #Weights = '121212121'
SET #WA = SUBSTRING(#subno,10,1)
WHILE(#I < 10)
BEGIN
SET #WP = CAST(SUBSTRING(#Weights, #I, 1)AS INT) * CAST(SUBSTRING(#subno, #I, 1) AS INT)
IF(#WP > 9)
BEGIN
SET #A = SUBSTRING(CAST(#WP AS VARCHAR),1,1)
SET #B = SUBSTRING(CAST(#WP AS VARCHAR),2,1)
SET #WP = CAST(#A AS INT) + CAST(#B AS INT)
END
SET #WD = #WP + #WD
SET #I = #I + 1
END
SET #R = #WD % 10
IF(#R <> 0)
SET #WR = 10 - #R
ELSE
SET #WR = #R
IF (#WR<> #WA)
BEGIN
SET #result = 1 -- INVALID
END
ELSE
BEGIN
SET #result = 0 -- VALID
END
END
ELSE
SET #result = 1 -- INVALID
END
Split the values which u get . Then iterate iver each side and add them. Please see the sample below.
declare #v varchar (16) , #num1 varchar(20) , #num2 varchar(20)
set #v = '1234567.78906656'
select #num1 = substring(#v,0,charindex('.',#v))
select #num2 = substring(#v,charindex('.',#v)+1,len(#v))
--select #num1 = convert(int, substring(#v,0,charindex('.',#v)))
--select #num2 = substring(#v,charindex('.',#v)+1,len(#v))
declare #index int = 1 ,#len INT , #char CHAR
declare #TotalOddL int = 0
declare #TotalEvenL int = 0
DECLARE #FullTotL INT = 0
declare #TotalOddR int = 0
declare #TotalEvenR int = 0
DECLARE #FullTotR INT = 0
DECLARE #TEMP INT
set #len= LEN(#num1)
WHILE #index <= #len
BEGIN
set #char = SUBSTRING(#num1, #index, 1)
SET #TEMP = cast(#char as int)
IF(#TEMP % 2 = 0)
SET #TotalEvenL = #TotalEvenL + #char
else
SET #TotalOddL = #TotalOddL + #char
SET #FullTotL = #TotalEvenL + #TotalOddL
SET #index= #index+ 1
END
Select 'LeftSide total' , #FullTotL
Select 'Left Side odd' , #TotalOddL
Select 'Left Side Even' , #TotalEvenL
SET #index = 1
set #len= LEN(#num2)
WHILE #index <= #len
BEGIN
set #char = SUBSTRING(#num2, #index, 1)
SET #TEMP = cast(#char as int)
IF(#TEMP % 2 = 0)
SET #TotalEvenR= #TotalEvenR + #char
else
SET #TotalOddR = #TotalOddR + #char
SET #FullTotR = #TotalEvenR + #TotalOddR
SET #index= #index+ 1
END
select 'TotalRSide' , #FullTotR
select 'RsideOdd' , #TotalOddR
select 'RSideEven' , #TotalEvenR
select 'Multiplied value' , #FullTotR * #FullTotL
create or replace procedure prc_even_odd(i_number in number, o_result out varchar2)
as
begin
if (mod(i_number,2) = 0) then
o_result := 'EVEN';
else
o_result := 'ODD';
end prc_even;

Function to check the input number of words

Need help to create a function that returns TRUE or FALSE. TRUE - if type 1 or 3 words (like '__hello_', '_hello', '_hello my frend' - spaces should be cut), if the condition is not fulfilled FALSE
CREATE FUNCTION dbo.nazvFac(#f nvarchar(30))
RETURNS bit
AS
BEGIN
DECLARE #l int = 1, #s nvarchar(30), #i int = 0, #b bit
WHILE LTRIM(RTRIM(LEN(#f))) >= #l --error here, but I do not know how to fix it
BEGIN
SET #s = SUBSTRING(#f, #l, 1)
IF #s BETWEEN 'А' AND 'я'
SET #l += 1
ELSE IF #s = ' '
BEGIN
SET #l -= 1
SET #s = SUBSTRING(#f, #l, 1)
SET #s = RTRIM(#s)
SET #l += 2
SET #i += 1
END
ELSE
BREAK
END
IF #i = 0 OR #i = 2
SET #b = 'TRUE'
ELSE
SET #b = 'FALSE'
RETURN #b
END
GO
WHILE LTRIM(RTRIM(LEN(#f))) >= #l --error here, but I do not know how to fix it
LEN() returns an int, which you are then passing to a string function: RTRIM().
You want to return TRUE only if there are one or three words? This should do it:
CREATE FUNCTION dbo.nazvFac(#f NVARCHAR(30))
RETURNS BIT
AS
BEGIN
DECLARE #l INT, #b BIT
SET #l = LEN(#f) - LEN(REPLACE(#f, ' ', '')) + 1
IF #l == 1 OR #l == 3
SET #b = 'TRUE'
ELSE
SET #b = 'FALSE'
RETURN #b
END
Also, JC. is right about the len() error.
You should trim the string and then check the length.
CREATE FUNCTION dbo.nazvFac(#f NVARCHAR(30))
RETURNS BIT
AS
BEGIN
DECLARE #l INT = 1, #s NVARCHAR(30), #i INT = 0, #b BIT
WHILE LEN(LTRIM(RTRIM(#f))) >= #l --I think youn need to trim the string and then check length
BEGIN
SET #s = SUBSTRING(#f, #l, 1)
IF #s BETWEEN 'А' AND 'я'
SET #l += 1
ELSE IF #s = ' '
BEGIN
SET #l -= 1
SET #s = SUBSTRING(#f, #l, 1)
SET #s = RTRIM(#s)
SET #l += 2
SET #i += 1
END
ELSE
BREAK
END
IF #i = 0 OR #i = 2
SET #b = 'TRUE'
ELSE
SET #b = 'FALSE'
RETURN #b
END
GO

Count Number of Links SQL

I have a table containing a column of raw email text including headers and message body. This may or may not include a url from my domain. If it does contain a url from my domain, I'd like to add a row to the result and increment the number of occurrences of that URL.
For example, the result should look like this:
Link Count
---- -----
a 19
b 5
c 1
What is a sensible approach to this?
I found a function on SQLMag (by Brian Moran) that may be of use:
Please note I haven't tested the function, so you might wanna do that yourself :)
CREATE function WordRepeatedNumTimes
(#SourceString varchar(8000),#TargetWord varchar(8000))
RETURNS int
AS
BEGIN
DECLARE #NumTimesRepeated int
,#CurrentStringPosition int
,#LengthOfString int
,#PatternStartsAtPosition int
,#LengthOfTargetWord int
,#NewSourceString varchar(8000)
SET #LengthOfTargetWord = len(#TargetWord)
SET #LengthOfString = len(#SourceString)
SET #NumTimesRepeated = 0
SET #CurrentStringPosition = 0
SET #PatternStartsAtPosition = 0
SET #NewSourceString = #SourceString
WHILE len(#NewSourceString) >= #LengthOfTargetWord
BEGIN
SET #PatternStartsAtPosition = CHARINDEX
(#TargetWord,#NewSourceString)
IF #PatternStartsAtPosition <> 0
BEGIN
SET #NumTimesRepeated = #NumTimesRepeated + 1
SET #CurrentStringPosition = #CurrentStringPosition + #PatternStartsAtPosition +
#LengthOfTargetWord
SET #NewSourceString = substring(#NewSourceString,
#PatternStartsAtPosition +
#LengthOfTargetWord, #LengthOfString)
END
ELSE
BEGIN
SET #NewSourceString = ''
END
END
RETURN #NumTimesRepeated
END
You can then use it in the following way:
DECLARE #link varchar(max)='http://YourLinkHere.com'
SELECT SUM(dbo.WordRepeatedNumTimes(field, #link))
FROM Table
The original article can be found here

SQL Server 2005:charindex starting from the end

I have a string 'some.file.name',I want to grab 'some.file'.
To do that,I need to find the last occurrence of '.' in a string.
My solution is :
declare #someStr varchar(20)
declare #reversedStr varchar(20)
declare #index int
set #someStr = '001.002.003'
set #reversedStr = reverse(#someStr)
set #index = len(#someStr) - charindex('.',#reversedStr)
select left(#someStr,#index)
Well,isn't it too complicated?I was just intented to using 'some.file' in a where-clause.
Anyone has a good idea?
What do you need to do with it?? Do you need to grab the characters after the last occurence of a given delimiter?
If so: reverse the string and search using the normal CHARINDEX:
declare #test varchar(100)
set #test = 'some.file.name'
declare #reversed varchar(100)
set #reversed = REVERSE(#test)
select
REVERSE(SUBSTRING(#reversed, CHARINDEX('.', #reversed)+1, 100))
You'll get back "some.file" - the characters up to the last "." in the original file name.
There's no "LASTCHARINDEX" or anything like that in SQL Server directly. What you might consider doing in SQL Server 2005 and up is great a .NET extension library and deploy it as an assembly into SQL Server - T-SQL is not very strong with string manipulation, whereas .NET really is.
A very simple way is:
SELECT
RIGHT(#str, CHARINDEX('.', REVERSE(#str)) - 1)
This will also work:
DECLARE
#test VARCHAR(100)
SET #test = 'some.file.name'
SELECT
LEFT(#test, LEN(#test) - CHARINDEX('.', REVERSE(#test)))
Take one ')'
declare #test varchar(100)
set #test = 'some.file.name'
select left(#test,charindex('.',#test)+charindex('.',#test)-1)
CREATE FUNCTION [dbo].[Instr] (
-------------------------------------------------------------------------------------------------
-- Name: [dbo].[Instr]
-- Purpose: Find The Nth Value Within A String
-------------------------------------------------------------------------------------------------
-- Revisions:
-- 25-FEB-2011 - HESSR - Initial Revision
-------------------------------------------------------------------------------------------------
-- Parameters:
-- 1) #in_FindString - NVARCHAR(MAX) - INPUT - Input Find String
-- 2) #in_String - NVARCHAR(MAX) - INPUT - Input String
-- 3) #in_StartPos - SMALLINT - INPUT - Position In The String To Start Looking From
-- (If Start Position Is Negative, Search Begins At The End Of The String)
-- (Negative 1 Starts At End Position 1, Negative 3 Starts At End Position Minus 2)
-- 4) #in_Nth - SMALLINT - INPUT - Nth Occurrence To Find The Location For
-------------------------------------------------------------------------------------------------
-- Returns: SMALLINT - Position Of String Segment (Not Found = 0)
-------------------------------------------------------------------------------------------------
#in_FindString NVARCHAR(MAX),
#in_String NVARCHAR(MAX),
#in_StartPos SMALLINT = NULL,
#in_Nth SMALLINT = NULL
)
RETURNS SMALLINT
AS
BEGIN
DECLARE #loc_FindString NVARCHAR(MAX);
DECLARE #loc_String NVARCHAR(MAX);
DECLARE #loc_Position SMALLINT;
DECLARE #loc_StartPos SMALLINT;
DECLARE #loc_Nth SMALLINT;
DECLARE #loc_Idx SMALLINT;
DECLARE #loc_FindLength SMALLINT;
DECLARE #loc_Length SMALLINT;
SET #loc_FindString = #in_FindString;
SET #loc_String = #in_String;
SET #loc_Nth = ISNULL(ABS(#in_Nth), 1);
SET #loc_FindLength = LEN(#loc_FindString+N'.') - 1;
SET #loc_Length = LEN(#loc_String+N'.') - 1;
SET #loc_StartPos = ISNULL(#in_StartPos, 1);
SET #loc_Idx = 0;
IF (#loc_StartPos = ABS(#loc_StartPos))
BEGIN
WHILE (#loc_Idx < #loc_Nth)
BEGIN
SET #loc_Position = CHARINDEX(#loc_FindString,#loc_String,#loc_StartPos);
IF (#loc_Position > 0)
SET #loc_StartPos = #loc_Position + #loc_FindLength
ELSE
SET #loc_Idx = #loc_Nth;
SET #loc_Idx = #loc_Idx + 1;
END;
END
ELSE
BEGIN
SET #loc_StartPos = ABS(#loc_StartPos);
SET #loc_FindString = REVERSE(#in_FindString);
SET #loc_String = REVERSE(#in_String);
WHILE (#loc_Idx < #loc_Nth)
BEGIN
SET #loc_Position = CHARINDEX(#loc_FindString,#loc_String,#loc_StartPos);
IF (#loc_Position > 0)
SET #loc_StartPos = #loc_Position + #loc_FindLength
ELSE
SET #loc_Idx = #loc_Nth;
SET #loc_Idx = #loc_Idx + 1;
END;
IF (#loc_Position > 0)
SET #loc_Position = #loc_Length - #loc_Position + (1 - #loc_FindLength) + 1;
END;
RETURN (#loc_Position);
END;
GO
Here is a shorter version
DECLARE #someStr varchar(20)
set #someStr = '001.002.003'
SELECT REVERSE(Substring(REVERSE(#someStr),CHARINDEX('.', REVERSE(#someStr))+1,20))