Sql function for finding a guid - sql

What I'm trying to do is search through a specific column in my database and pull out any rows that match a pattern (i.e. any rows where the nvarchar contains a guid).
I've got this function.
Create FUNCTION [dbo].[HasGuid] (
#Data varchar(50)
) RETURNS bit WITH SCHEMABINDING AS
BEGIN
return case when #Data like REPLACE('%00000000-0000-0000-0000-000000000000%', '0', '[0-9a-fA-F]') then
1
else
0
end
END
It works on most of my data
values it works on:
12341234-1234-1234-1234-123412341234.ext
sometext:12341234-1234-1234-1234-123412341234:c:\path\12341234-1234-1234-1234-123412341234\12341234-1234-1234-1234-123412341234.ext
sometext:12341234-1234-1234-1234-123412341234.ext
values it doesn't work on:
c:\path\12341234-1234-1234-1234-123412341234\12341234-1234-1234-1234-123412341234.ext
Where .ext is one of any number of different extensions and the 1234 guid is just representative and not actually the same guid multiple times in any one string.
And I'm using the function like so (should be obvious, but just to be complete)
SELECT * from SomeTable s
WHERE dbo.HasGuid(s.SomeColumn) = 0
At this point I would expect to see only the rows where that column is not a guid but I'm getting the discrepancy above. I'm not seeing what's wrong in my function, but if someone could point out what I'm missing it'd be super helpful.

It looks like it's just a shortcoming of azure sql. It works as expected outside of azure.
I figured out the actual issue. The #Data declaration was too limiting. The following is the updated function.
Create FUNCTION [dbo].[HasGuid] (#Data nvarchar(400)) RETURNS bit WITH SCHEMABINDING AS BEGIN return case when #Data like REPLACE('%00000000-0000-0000-0000-000000000000%', '0', '[0-9a-fA-F]') then 1 else 0 end END
It's still a difference between azure sql and mssql, but at least it's figured out now.

Related

how to convert persian number to int

There is a nvarchar(100) column named value, when users insert into this column I need to check this code below in a trigger:
if exists
(
select *
from inserted i
where isnumeric(value)=0
)
begin
rollback transaction
raiserror('when productType is numeric, You have to insert numeric character',18,1)
return
end
but in application interface numbers inserted in persian, so always isnumeric(value)=0.
For example I need to if user insert ۴۵ in interface in my trigger value shown as 45.
So far I use CAST,CONVERT and collate Persian_100_CI_AI but I couldn't get any result.
Thanks.
Which version of SQL Server? v2017+ offers a new function TRANSLATE.
Might be, there is a more elegant way, but a pragmatic one is this:
DECLARE #PersianNumber NVARCHAR(100)=N'۴۵';
SELECT CAST(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE
(#PersianNumber,N'۰',N'0'),N'۱',N'1'),N'۲',N'2'),N'۳',N'3'),N'۴',N'4')
,N'۵',N'5'),N'۶',N'6'),N'۷',N'7'),N'۸',N'8'),N'۹',N'9') AS INT);
Take a look at this topic, it's the opposite of what you asked but it might help you if you could reverse it :
https://social.msdn.microsoft.com/Forums/sqlserver/en-US/a44ce5c1-d487-4043-be73-b64fa98ed7a5/converting-english-numbers-to-arabic-numbers-and-vice-versa
If you are using the latest version of sql server, try this link :
https://learn.microsoft.com/en-us/sql/t-sql/functions/translate-transact-sql
the obvious thing is that SQL does not have a solution out-of-the-box and you have to implement some kind of function yourself and use the returned value in the WHERE statement.
I have used Shungo's answer to implement the function you need (also works for English numbers or a mix of both):
CREATE FUNCTION IS_NORMALIZED_NUMBER (#PersianNumber NVARCHAR(MAX))
RETURNS NVARCHAR(MAX)
BEGIN
SET #PersianNumber = CAST(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE
(#PersianNumber,N'۰',N'0'),N'۱',N'1'),N'۲',N'2'),N'۳',N'3'),N'۴',N'4')
,N'۵',N'5'),N'۶',N'6'),N'۷',N'7'),N'۸',N'8'),N'۹',N'9') AS NVARCHAR(MAX));
RETURN ISNUMERIC(#PersianNumber)
END
Here is a more optimized version (which will only work for Persian numbers) :
CREATE FUNCTION IS_NUMBER (#PersianNumber NVARCHAR(MAX))
RETURNS NVARCHAR(MAX)
BEGIN
RETURN IIF(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE
(#PersianNumber,N'۰',N''),N'۱',N''),N'۲',N''),N'۳',N''),N'۴',N'')
,N'۵',N''),N'۶',N''),N'۷',N''),N'۸',N''),N'۹',N'') = N'',1 ,0 );
END
You can use TRANSLATE (Transact-SQL) function
SELECT TRANSLATE('1234', '0123456789', N'٠١٢٣٤٥٦٧٨٩') AS KurdishNumber

How do you pass values for a parameter by position when you need to check multiple values?

I created a stored procedure (spBalanceRange) with 2 optional parameters. They've been set to a default value and the sp works fine when I pass only 1 value per parameter by position. However, I have a situation where I'm trying to pass, by position, two strings immediately followed by a wildcard. I want the user to be able to search for Vendor names that start with either 'C%' or 'F%'. Here's the gist of the CREATE PROC statement:
CREATE PROC spBalanceRange
#VendorVar varchar(40) = '%',
#BalanceMin money = 1.0
...
Here's what I've tried so far, but doesn't work:
EXEC spBalanceRange '(C%|F%)', 200.00;
EXEC spBalanceRange 'C%|F%', 200.00;
Is there a way to check for 2 or more string values with a wildcard when passed by position? Thanks.
EDIT: According to your comments you are looking for the first letter of a vendor's name only.
In this special case I could suggest an easy, not well performing but really simple approach. CHARINDEX returns a number greater than zero, if a character appears within a string. So you just have to pass in all your lookup-first-characters as a simple "chain":
DECLARE #DummyVendors TABLE(VendorName VARCHAR(100));
INSERT INTO #DummyVendors VALUES
('Camel Industries')
,('Fritz and Fox')
,('some other');
DECLARE #ListOfFirstLetters VARCHAR(100)='CF';
SELECT VendorName
FROM #DummyVendors AS dv
WHERE CHARINDEX(LEFT(dv.VendorName,1),#ListOfFirstLetters)>0
This was the former answer
Checking against more than one value needs either a dedicated list of compares
WHERE val=#prm1 OR val=#prm2 OR ... (you know the count before)
...or you use the IN-clause
WHERE LEFT(VenoderName,1) IN ('C','F', ...)
...but you cannot pass the IN-list with a parameter like ... IN(#allValues)
You might think about a created TYPE to pass in all your values like a table and use an INNER JOIN as filter: https://stackoverflow.com/a/337864/5089204 (and a lot of other examples there...)
Or you might think of dynamic SQL: https://stackoverflow.com/a/5192765/5089204
And last but not least you might think of one of the many split string approaches. This is one of my own answers, section "dynamic IN-statement": https://stackoverflow.com/a/33658220/5089204
I'm answering my own question, and maybe other solutions exist but here is what had to happen with my stored procedure in order to pass variables by position:
CREATE PROC spBalanceRange
#VendorVar varchar(40) = '%',
#BalanceMin money = 1.0
AS
IF (#VendorVar = '%' AND #BalanceMin IS NULL OR #BalanceMin = '')
BEGIN
PRINT 'BalanceMin cannot be null.';
END
IF (#VendorVar = % AND #BalanceMin IS NOT NULL)
BEGIN
(sql statement using parameters)
END
EXEC spBalanceRange '[C,F]%', 200.00;
That's what I know.

TSQL function which will raiserror if passed null?

I'm looking to add some code to my TSQL arsenal to defend against performing aggregations in SQL when the data in a column is null. Ideally there would be a SUM_NN (for sum no null), in sql server which would raiserror if any of the values were null.
Since you can't raiserror from a UDF, the only way I could think of doing it looked like this, though I don't like this solution:
CREATE FUNCTION dbo.NULL_TEST_F(#arg FLOAT)
RETURNS FLOAT
AS
BEGIN
IF(#arg IS NULL)
SELECT 1/0
RETURN #arg
END
Note: I think this is stupid solution, but I've gotten burned way too many times when I'm missing data. Also, we're using SQL Server 2005, but I'm open to 2008 and 2012 solutions. Additionally, I'd like to know how other databases deal with this type of issue.
This was my final solution:
CREATE FUNCTION [dbo].[NullTest_F]
(
#input FLOAT,
#message VARCHAR(100)
)
RETURNS FLOAT
AS
BEGIN
DECLARE #test INT
IF(#input IS NULL)
SELECT #test = CAST(#message AS INT)
RETURN #input
END
I can then embed this with a useful error message when running aggregate functions. Example:
CREATE TABLE Data (
Date DATETIME,
DataPoint FLOAT
)
INSERT INTO Data (Date, DataPoint) VALUES ('2012-03-01', 4)
INSERT INTO Data (Date, DataPoint) VALUES ('2012-03-02', 6)
SELECT SUM(NullTest_F(DataPoint, 'Missing data at' + CONVERT(VARCHAR(10), Data))
FROM Data
Maybe this one will help:
https://stackoverflow.com/a/4681815/1371070
You could create a function like suggested in the answer linked above and call it from your aggregate in case #arg is null.
It's still the same strategy overall but It's a better error to throw than divide-by-zero, I guess.

SQL: SELECT number text base on a number

Background: I have an SQL database that contain a column (foo) of a text type and not integer. In the column I store integer in a text form.
Question: Is it possible to SELECT the row that contains (in foo column) number greater/lesser than n?
PS: I have a very good reason to store them as text form. Please refrain from commenting on that.
Update: (Forgot to mention) I am storing it in SQLite3.
SELECT foo
FROM Table
WHERE CAST(foo as int)>#n
select *
from tableName
where cast(textColumn as int) > 5
A simple CAST in the WHERE clause will work as long as you are sure that the data in the foo column is going to properly convert to an integer. If not, your SELECT statement will throw an error. I would suggest you add an extra step here and take out the non-numeric characters before casting the field to an int. Here is a link on how to do something similar:
http://blog.sqlauthority.com/2007/05/13/sql-server-udf-function-to-parse-alphanumeric-characters-from-string/
The only real modification you would need to do on this function would be to change the following lines:
PATINDEX('%[^0-9A-Za-z]%', #string)
to
PATINDEX('%[^0-9]%', #string)
The results from that UDF should then be castable to an int without it throwing an error. It will further slow down your query, but it will be safer. You could even put your CAST inside the UDF and make it one call. The final UDF would look like this:
CREATE FUNCTION dbo.UDF_ParseAlphaChars
(
#string VARCHAR(8000)
)
RETURNS int
AS
BEGIN
DECLARE #IncorrectCharLoc SMALLINT
SET #IncorrectCharLoc = PATINDEX('%[^0-9]%', #string)
WHILE #IncorrectCharLoc > 0
BEGIN
SET #string = STUFF(#string, #IncorrectCharLoc, 1, '')
SET #IncorrectCharLoc = PATINDEX('%[^0-9]%', #string)
END
SET #string = #string
RETURN CAST(#string as int)
END
GO
Your final SELECT statement would look something like this:
SELECT *
FROM Table
WHERE UDF_ParseAlphaChars(Foo) > 5
EDIT
Based upon the new information that the database is SQLite, the above probably won't work directly. I don't believe SQLite has native support for UDFs. You might be able to create a type of UDF using your programming language of choice (like this: http://www.christian-etter.de/?p=439)
The other option I see to safely get all of your data (an IsNumeric would exclude certain rows from your results, which might not be what you want) would probably be to create an extra column that has the int representation of the string. It is a little more dangerous in that you need to keep two fields in sync, but it will allow you to quickly sort and filter the table data.
SELECT *
FROM Table
WHERE CAST(foo as int) > 2000

inserting a node into SQL 2008 xml datatype... checking if it exists first

I am reading through a plethora of articles at the moment to try to assist me.. just seems so many options and cannot seem to find a clean solution.. it probably is very basic so apologies in advance!
So I have an XML field in SQL 2008. It basically contains something like:
<root><id>1</id><id>4</id></root> and so on...
What I am hoping to do is pass a param in to a proc to insert an value IF it doesn't exist..
So rather than read the xml first and do this within say .NET code, is there a clean way of doing this within a stored proc/t-sql ???
Any help appreciated! I am sure this is a fairly common one!
An example using the value() method:
DECLARE
#x xml,
#param int
SET #x = '<root><id>1</id><id>2</id><id>3</id></root>'
SET #param = 1
IF NOT EXISTS (
SELECT * FROM #x.nodes('/root/id') n(x) WHERE x.value('.','int') = #param
)
PRINT 'Insert'
ELSE
PRINT 'Return'
You can use the .exist() XQuery function on your XML to find out if a given node exists or not.
Check out An Overview of XML Support in SQL Server 2005 - it's a great article on how to use the various XQuery functions available. Just after the middle of that page, you'll find this section:
Using the exist Method
The exist method takes an XPath
expression that selects a single node
within the XML document, and returns
either True (bit value 1) if the node
exists or False (bit value 0) if it
does not. If the source column is a
typed xml column (in which case you
must declare the namespace in your
query), and the element contains null,
the method returns NULL instead. So
the XQuery:
SELECT MyXml.exist('(/root/product[#id="304"])[1]' FROM MyTable
will return True if there is a product
with the id value "304" (a product
element with the attribute id="304"),
or False if not. You can also use the
exist method in the WHERE clause of a
SQL statement:
SELECT column1, column2, column3 FROM MyTable
WHERE MyXml.exist('(/root/product[#id="304"])[1]') = 1