I have created small table value function using T-SQL which takes one input parameter phone number as and returns area code from it. Function compiles successfully but when I run it I am getting error:
Msg 4121, Level 16, State 1, Line 1776
Cannot find either column "dbo" or the user-defined function or aggregate "dbo.getareacode1", or the name is ambiguous
Refer to my code:
create or alter function dbo.getareacode1 (#phoneno as nvarchar(20))
returns table
with schemabinding
as
return
(select top 1
#phoneno as PhoneNumber, value as AreaCode
from
string_split(#phoneno,'-')
);
select dbo.getareacode1( N'323-234-2111');
First off, the order of the rows returned from STRING_SPLIT is not guaranteed, so this function broken to begin with.
But the error is caused by trying to invoke a Table-Valued Function where a scalar is expected.
A TVF cannot be treated like a scalar function. So
create or alter function dbo.getareacode1 (#phoneno as nvarchar(20))
returns table
with schemabinding
as
return
(
select top 1 #phoneno as PhoneNumber, value as AreaCode
from string_split(#phoneno,'-')
);
go
select * from dbo.getareacode1( N'323-234-2111');
And if you want to call it from another query use CROSS APPLY, eg
with phoneNumbers as
(
select N'323-234-2111' phoneno
from sys.objects
)
select ac.*
from phoneNumbers p
cross apply dbo.getareacode1(p.phoneno) ac;
On the flip-side of what David said: for your function to work like you were expecting you will need a scalar user-defined function (udf). This function:
-- Create function
create or alter function dbo.getareacode1 (#phoneno as nvarchar(20))
returns nvarchar(20) with schemabinding as
begin
return
(
select top (1) AreaCode = ss.[value]
from string_split(#phoneno,'-') ss
);
end
GO
... will allow this query to work:
select dbo.getareacode1( N'323-234-2111');
All that said, I strongly advise against using scalar udfs. I'll include a a couple good links that explain why at the bottom of this post. Leaving your function as-is and using APPLY, as David demonstrated, is the way to go. Also, string_split is not required here. If phone numbers are always coming in this format: NNN-NNN-NNNN, you could just use SUBSTRING:
SUBSTRING(N'323-234-2111', 1, 3).
For countries with varible-length area codes in the format (area code(2 or more digits))-NNN-NNNN you could do this:
declare #phoneno nvarchar(30) = N'39-234-2111'; -- Using a variable for brevity
select substring(#phoneno,1, charindex('-',#phoneno)-1);
If you, for whatever reason, really need a function, then this is how I'd write it:
-- using the variable-length requirement as an example
create or alter function dbo.getareacode1_itvf(#phoneno as nvarchar(20))
returns table with schemabinding as
return (select areacode = substring(#phoneno,1, charindex('-',#phoneno)-1));
go
Great articles about Scalar UDFs and why iTVFs are better:
How to Make Scalar UDFs Run Faster -- Jeff Moden
Scalar functions, inlining, and performance -- Adam Machanic
TSQL Scalar functions are evil – Andy Irving -- Stackoerflow post
Related
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
In a SQL Server database, one can use table variables like this:
declare #table as table (a int)
In an Azure Data Warehouse, that throws an error.
Parse error at line: 1, column: 19: Incorrect syntax near 'table'
In an Azure Data Warehouse, you can use temporary tables:
create table #table (a int)
but not inside functions.
Msg 2772, Level 16, State 1, Line 6 Cannot access temporary tables
from within a function.
This document from Microsoft says,
◦Must be declared in two steps (rather than inline): ◾CREATE TYPE
my_type AS TABLE ...; , then ◾DECLARE #mytablevariable my_type;.
But when I try this:
create type t as table (a int);
drop type t;
I get this :
Msg 103010, Level 16, State 1, Line 1 Parse error at line: 1, column:
8: Incorrect syntax near 'type'.
My objective is to have a function in an Azure Data Warehouse which uses a temporary table. Is it achievable?
Edit Start Here
Note that I am not looking for other ways to create one specific function. I have actually done that and moved on. I'm a veteran programmer but an Azure Data Warehouse rookie. I want to know if it's possible to incorporate some concept of temporary tables in an Azure Data Warehouse function.
Ok, I believe this is what you are after.
Firstly, this uses a Table Value Function, which are significantly faster than Scalar or Multi-statement Table value Functions.
Secondly, there was no use for a Table Variable, or Temporary Table, just some good odd string manipulation, a bit of maths, and a CTE. Definitely no expensive WHILE loop.
I've tested this against the examples in the link, and they all return the expected values.
USE Sandbox;
GO
CREATE FUNCTION ValidateHealthNumber (#HealthNumber varchar(10))
RETURNS TABLE
AS
RETURN
WITH Doubles AS(
SELECT CONVERT(tinyint,SUBSTRING(V.HN,O.P,1)) AS HNDigit,
CONVERT(tinyint,SUBSTRING(V.HN,O.P,1)) * CASE WHEN O.P % 2 = 0 THEN 1 ELSE 2 END ToAdd
FROM (VALUES(#HealthNumber)) V(HN)
CROSS APPLY (VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9)) O(P)),
Parts AS (
SELECT CONVERT(tinyint,SUBSTRING(CONVERT(varchar(2),ToAdd),1,1)) AS FirstDigit, --We know that the highest value can be 18 (2*9)
CONVERT(tinyint,SUBSTRING(CONVERT(varchar(2),ToAdd),2,1)) AS SecondDigit --so no need for more than 2 digits.
FROM Doubles)
SELECT CASE RIGHT(#HealthNumber, 1) WHEN 10 - RIGHT(SUM(FirstDigit + SecondDigit),1) THEN 1 ELSE 0 END AS IsValid
FROM Parts;
GO
CREATE TABLE #Sample(HealthNumber varchar(10));
INSERT INTO #Sample
VALUES ('9876543217'), --Sample
('5322369835'), --Valid
('7089771195'), --Valid
('8108876957'), --Valid
('4395667779'), --Valid
('6983806917'), --Valid
('2790412845'), --not Valid
('5762696912'); --not Valid
SELECT *
FROM #Sample S
CROSS APPLY ValidateHealthNumber(HealthNumber) VHN;
GO
DROP TABLE #Sample
DROP FUNCTION ValidateHealthNumber;
If you don't understand any of this, please do ask.
No you can't. Object can't be created inside User Defined Functions (UDF). Use table variables instead.
If you want yo use user defined type, first create it outside the UDF and use it as a variable type within the UDF.
-- Create the data type
CREATE TYPE TestType AS TABLE
(
Id INT NOT NULL,
Col1 VARCHAR(20) NOT NULL)
GO
-- Create the tabled valued function
CREATE FUNCTION TestFunction
()
RETURNS
#Results TABLE
(Result1 INT, Result2 INT)
AS
BEGIN
-- Fill the table variable with the rows for your result set
DECLARE #Var1 TestType;
RETURN
END
GO
Question: Create a UDF that accepts the State Province and returns the associated Sales Tax Rate, StateProvinceCode and CountryRegionCode (Database is AdventureWorks2012)
Name took the place of State Province because there is no column
called 'State Province', and 'Name' contains the needed information.
This is what I did in the code below.
Is there another way to run this?
In this form Ambiguous column name keeps showing up.
CREATE FUNCTION fx_TxSpCr (#Name Nvarchar(50),
#TaxRate Smallmoney,
#StateProvinceCode Nchar(3),
#CountryRegionCode Nvarchar(3)
)
RETURNS TABLE
AS
RETURN (
SELECT Name,TaxRate,StateProvinceCode,CountryRegionCode
FROM Sales.SalesTaxRate s
JOIN Person.StateProvince t
ON s.StateProvinceID=t.StateProvinceID
JOIN Sales.SalesTerritory u
ON t.TerritoryID=u.TerritoryID
)
SELECT s.TaxRate,t.StateProvinceCode,u.CountryRegionCode
FROM fx_TxSpCr
GROUP BY Name
Thank you both for the contribution. This worked:
CREATE FUNCTION fx_TaxStCtry(#StateProvince nvarchar(50))
RETURNS #TaxStCtry TABLE
(TaxRate SmallMoney not null,
StateProvinceCode Nchar(3) not null ,
CountryRegionCode Nvarchar(3) not null)
AS
BEGIN INSERT #TaxStCtry
SELECT tr.TaxRat,sp.StateProvinceCode,sp.CountryRegionCode
FROM Person.StateProvince sp
JOIN Sales.SalesTaxRate tr
ON tr.stateprovinceid = sp.stateprovinceid
WHERE sp.Name=#StateProvince
RETURN
END
The ambiguity is coming from the lack of aliases in the select statement from your function. It seems like you are trying to use the aliases in the execution of the function, but they actually need to be contained within the function itself. (For example, all three tables have a "name" column.)
After correcting that issue you will find that you actually have not used any of the parameters you have set up for your function. Which does not have to be the full return set of the select statement contained within.
The correct path here is to start with a select statement that returns what you're needing and then turn that into a function. If the data is not being returned at a base level, then you're not ready to start creating a function.
I have migrated a database from oracle, and now have a few Scalar-valued Functions.
However, when I call them, I get an error saying:
Cannot find either column "dbo" or the user-defined function or aggregate "dbo.chk_mgr", or the name is ambiguous.
I'm calling it like this:
SELECT dbo.chk_mgr('asdf')
What am I doing wrong?
Are you sure it's not a Table-Valued Function?
The reason I ask:
CREATE FUNCTION dbo.chk_mgr(#mgr VARCHAR(50))
RETURNS #mgr_table TABLE (mgr_name VARCHAR(50))
AS
BEGIN
INSERT #mgr_table (mgr_name) VALUES ('pointy haired boss')
RETURN
END
GO
SELECT dbo.chk_mgr('asdf')
GO
Result:
Msg 4121, Level 16, State 1, Line 1
Cannot find either column "dbo" or the user-defined function
or aggregate "dbo.chk_mgr", or the name is ambiguous.
However...
SELECT * FROM dbo.chk_mgr('asdf')
mgr_name
------------------
pointy haired boss
Can do the following
PRINT dbo.[FunctionName] ( [Parameter/Argument] )
E.g.:
PRINT dbo.StringSplit('77,54')
That syntax works fine for me:
CREATE FUNCTION dbo.test_func
(#in varchar(20))
RETURNS INT
AS
BEGIN
RETURN 1
END
GO
SELECT dbo.test_func('blah')
Are you sure that the function exists as a function and under the dbo schema?
You are using an inline table value function. Therefore you must use Select * From function.
If you want to use select function() you must use a scalar function.
https://msdn.microsoft.com/fr-fr/library/ms186755%28v=sql.120%29.aspx
Make sure you have the correct database selected. You may have the master database selected if you are trying to run it in a new query window.
Scenario
I have a stored procedure written in T-Sql using SQL Server 2005.
"SEL_ValuesByAssetName"
It accepts a unique string "AssetName".
It returns a table of values.
Question
Instead of calling the stored procedure multiple times and having to make a database call everytime I do this, I want to create another stored procedure that accepts a list of all the "AssetNames", and calls the stored procedure "SEL_ValueByAssetName" for each assetname in the list, and then returns the ENTIRE TABLE OF VALUES.
Pseudo Code
foreach(value in #AllAssetsList)
{
#AssetName = value
SEL_ValueByAssetName(#AssetName)
UPDATE #TempTable
}
How would I go about doing this?
It will look quite crippled with using Stored Procedures. But can you use Table-Valued Functions instead?
In case of Table-Valued functions it would look something like:
SELECT al.Value AS AssetName, av.* FROM #AllAssetsList AS al
CROSS APPLY SEL_ValuesByAssetName(al.Value) AS av
Sample implementation:
First of all, we need to create a Table-Valued Parameter type:
CREATE TYPE [dbo].[tvpStringTable] AS TABLE(Value varchar(max) NOT NULL)
Then, we need a function to get a value of a specific asset:
CREATE FUNCTION [dbo].[tvfGetAssetValue]
(
#assetName varchar(max)
)
RETURNS TABLE
AS
RETURN
(
-- Add the SELECT statement with parameter references here
SELECT 0 AS AssetValue
UNION
SELECT 5 AS AssetValue
UNION
SELECT 7 AS AssetValue
)
Next, a function to return a list AssetName, AssetValue for assets list:
CREATE FUNCTION [dbo].[tvfGetAllAssets]
(
#assetsList tvpStringTable READONLY
)
RETURNS TABLE
AS
RETURN
(
-- Add the SELECT statement with parameter references here
SELECT al.Value AS AssetName, av.AssetValue FROM #assetsList al
CROSS APPLY tvfGetAssetValue(al.Value) AS av
)
Finally, we can test it:
DECLARE #names tvpStringTable
INSERT INTO #names VALUES ('name1'), ('name2'), ('name3')
SELECT * FROM [Test].[dbo].[tvfGetAllAssets] (#names)
In MSSQL 2000 I would make #allAssetsList a Varchar comma separated values list. (and keep in mind that maximum length is 8000)
I would create a temporary table in the memory, parse this string and insert into that table, then do a simple query with the condition where assetName in (select assetName from #tempTable)
I wrote about MSSQL 2000 because I am not sure whether MSSQL 2005 has some new data type like an array that can be passed as a literal to the SP.