I've been looking in ithe internet but couldnt find a solution. So i decide to ask here:
I've a table contains specifications of my products. It has colum names as: productCode, specName, specStr, visibility
I need to put all specs in a single string. Something like (for productcode=54S4): "Category: Electronics, Weight: 50g, Brand: XYZ"
I've to do this using sql server functions. So i created a value-typed function. i got specs of a single product in format of a table. Here is the function:
CREATE FUNCTION getDescription1 (#code varchar(32))
RETURNS table
AS
RETURN (
SELECT (specName+': '+specStr+', ')as description
FROM productSpecs
where specStr<>'' and visibility=1 and productCode=#code
)
As a result, I have a table. But i couldnt read the data in it using while or something.
Is my route true? If yes what to do now? If no enlighten me please...
CREATE FUNCTION getDescription1 (#code varchar(32))
RETURNS nvarchar(1000)
AS
DECLARE #p_str NVARCHAR(1000)
SET #p_str = ''
SELECT #p_str = #p_str + (specName+': '+specStr+', ')
FROM productSpecs
where specStr<>'' and visibility=1 and productCode=#code
RETURN LEFT(#p_str, len(#p_str) -1)
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
For a project, we are using a table (named txtTable) that contains all the texts. And each column contains a different language (for example column L9 is English, column L7 is German, etc..).
TextID L9 L7 L16 L10 L12
------------------------------------------------------
26 Archiving Archivierung NULL NULL NULL
27 Logging Protokollierung NULL NULL NULL
28 Comments Kommentar NULL NULL NULL
This table is located in a database on a Microsoft SQL Server 2005. The big problem is that this database name changes each time the program is restarted. This is a behavior typically for this third-party program and cannot be changed.
Next to this database and on the same server is our own database. In this database are several tables that point to the textID for generating data for reporting (SQL Server Reporting Services) in the correct language. This database contains also a table "ProjectSettings" with some properties like the name of the texttable database, and the stored procedures to generate the reporting data.
The way we now are requesting the right texts of the right language from this table with the changing database name is by creating a dynamic SQL query and execute it in a stored procedure.
Now we were wondering if there is a cleaner way to get the texts in the right language. We were thinking about creating a function with the textID and the language as a parameter, but we cannot find a good way to do this. We thought about a function so we just can use it in the select statement, but this doesn’t work:
CREATE FUNCTION [dbo].[GetTextFromLib]
(
#TextID int,
#LanguageColumn Varchar(5)
)
RETURNS varchar(255)
AS
BEGIN
-- return variables
DECLARE #ResultVar varchar(255)
-- Local variables
DECLARE #TextLibraryDatabaseName varchar(1000)
DECLARE #nvcSqlQuery varchar(1000)
-- get the report language database name
SELECT #TextLibraryDatabaseName = TextLibraryDatabaseName FROM ProjectSettings
SET #nvcSqlQuery = 'SELECT #ResultVar =' + #LanguageColumn + ' FROM [' + #TextLibraryDatabaseName + '].dbo.TXTTable WHERE TEXTID = ' + cast(#TextID as varchar(30))
EXEC(#nvcSqlQuery)
-- Return the result of the function
RETURN #ResultVar
END
Is there any way to work around this so we don’t have to use the dynamic sql in our stored procedures so it is only ‘contained’ in 1 function?
Thanks in advance & kind regards,
Kurt
Yes, it is possible with the help of synonym mechanism introduced with SQL Server 2005. So, you can create synonym during your setting up procedure based on data from ProjectSettings table and you can use it in your function. Your code will look something like this:
UPDATE: The code of function is commented here because it still contains dynamic SQL which does not work in function as Kurt said in his comment. New version of function is below this code.
-- Creating synonym for TXTTable table
-- somewhere in code when processing current settings
-- Suppose your synonym name is 'TextLibrary'
--
-- Drop previously created synonym
IF EXISTS (SELECT * FROM sys.synonyms WHERE name = N'TextLibrary')
DROP SYNONYM TextLibrary
-- Creating synonym using dynamic SQL
-- Local variables
DECLARE #TextLibraryDatabaseName varchar(1000)
DECLARE #nvcSqlQuery varchar(1000)
-- get the report language database name
SELECT #TextLibraryDatabaseName = TextLibraryDatabaseName FROM ProjectSettings
SET #nvcSqlQuery = 'CREATE SYNONYM TextLibrary FOR [' + #TextLibraryDatabaseName + '].dbo.TXTTable'
EXEC(#nvcSqlQuery)
-- Synonym created
/* UPDATE: This code is commented but left for discussion consistency
-- Function code
CREATE FUNCTION [dbo].[GetTextFromLib]
(
#TextID int,
#LanguageColumn Varchar(5)
)
RETURNS varchar(255)
AS
BEGIN
-- return variables
DECLARE #ResultVar varchar(255)
-- Local variables
DECLARE #nvcSqlQuery varchar(1000)
SET #nvcSqlQuery = 'SELECT #ResultVar =' + #LanguageColumn + ' FROM TextLibrary WHERE TEXTID = ' + cast(#TextID as varchar(30))
EXEC(#nvcSqlQuery)
-- Return the result of the function
RETURN #ResultVar
END
*/
UPDATE This is one more attempt to solve the problem. Now it uses some XML trick:
-- Function code
CREATE FUNCTION [dbo].[GetTextFromLib]
(
#TextID int,
#LanguageColumn Varchar(5)
)
RETURNS varchar(255)
AS
BEGIN
-- return variables
DECLARE #ResultVar varchar(255)
-- Local variables
DECLARE #XmlVar XML
-- Select required record into XML variable
-- XML has each table column value in element with corresponding name
SELECT #XmlVar = ( SELECT * FROM TextLibrary
WHERE TEXTID = #TextID
FOR XML RAW, ELEMENTS )
-- Select value of required element from XML
SELECT #ResultVar = Element.value('(.)[1]', 'varchar(255)')
FROM #XmlVar.nodes('/row/*') AS T(Element)
WHERE Element.value('local-name(.)', 'varchar(50)') = #LanguageColumn
-- Return the result of the function
RETURN #ResultVar
END
Hope this helps.
Credits to answerer of this question at Stackoverflow - How to get node name and values from an xml variable in t-sql
To me, it sounds like a total PITA... However, how large is this database of "words" you are dealing with. Especially if it is not changing much and remains pretty constant. Why not have on some normal cycle (such as morning), just have one dynamic query generated that queries the one that changes and synchronize it to a "standard" table name in YOUR database that won't change. Then, all your queries run against YOUR version and completely remove the constant dynamic queries every time. Yes there would need to be this synchronizing stored procedure to run, but if it can be run on a schedule, you should be fine, and again, how large is the table of "words" for proper language context.
Note: This is in SQL Server 2008.
I'm trying to use the APPLY operator to allow me to call user-defined in-line table-valued functions on each other in a sensible manner, but it doesn't seem to faithfully do so. I'm using OUTER APPLY, but CROSS APPLY does the same thing. Here's the relevant chunk:
SELECT addresses.Book,addresses.Page,foo.BookInput,foo.PageInput
FROM [dbo].bookPageFromAddress(#address) addresses
outer apply [dbo].[imageFileFromBookPage](addresses.Book, addresses.Page) foo
Nothing is bizarre:
GO
CREATE FUNCTION [dbo].imageFileFromBookPage (#book nvarchar(max), #page nvarchar(max))
RETURNS TABLE AS RETURN(
WITH ids AS (
SELECT right('00000000'+ltrim(str(c.DocID)),8) as VarString,
right('0000000000'+ltrim(str(i.ImageID)),12) as PathVar6
FROM [Resolution].[dbo].[Constant] c, [Resolution].[dbo].[Images] i
WHERE ltrim(c.[Book]) like #book
AND ltrim(c.[Page]) like #page
AND i.DocID = c.DocID
)
SELECT '/Images/' +
substring(ids.VarString,1,2)+'/' +
substring(ids.VarString,3,2)+'/' +
substring(ids.VarString,5,2)+'/' +
right(ids.VarString,8)+'/' +
PathVar6 + '.tif' as ImageLocation
,#book as BookInput, #page as PageInput
FROM ids
);
So, in essence,imageFileFromBookPage outputs its input to BookInput and PageInput. Here's the result of that outer apply on sample input:
Book 4043
Page 125
BookInput NULL
PageInput NULL
Note that Book and Page are strings, not integers; they just happen to be holding numeric characters here, but BookInput and PageInput are really NULL, not strings. I thought at first it was a typing issue; both Book and Page are nchar(10) in their original table, and both of my functions expect nvarchar(max) inputs. I tried CASTing the arguments as nvarchar(max) and got identical results, however.
Am I barking up the wrong tree? I'm not familiar with APPLY, but it sure seems like what I want, here. How do I get APPLY or something like it to actually pass along values to the next function?
EDIT: Modified code above to include more information.
Looking at the APPLY docs, shouldn;t the function be declared as:
CREATE FUNCTION [dbo].imageFileFromBookPage (#book nvarchar(max), #page nvarchar(max))
returns #Data Table
(
book nvartchar(max),
page nvarchar(max)
)
as
begin
insert into #Data
select #book, #page;
end
In other words, it's not a problem with APPLY, it's a problem that your function needs to return a Table.
Cheers -
I made the following function in SQL Server 2008 earlier this week that takes two parameters and uses them to select a column of "detail" records and returns them as a single varchar list of comma separated values. Now that I get to thinking about it, I would like to take this table and application-specific function and make it more generic.
I am not well-versed in defining SQL functions, as this is my first. How can I change this function to accept a single "column" worth of data, so that I can use it in a more generic way?
Instead of calling:
SELECT ejc_concatFormDetails(formuid, categoryName)
I would like to make it work like:
SELECT concatColumnValues(SELECT someColumn FROM SomeTable)
Here is my function definition:
FUNCTION [DNet].[ejc_concatFormDetails](#formuid AS int, #category as VARCHAR(75))
RETURNS VARCHAR(1000) AS
BEGIN
DECLARE #returnData VARCHAR(1000)
DECLARE #currentData VARCHAR(75)
DECLARE dataCursor CURSOR FAST_FORWARD FOR
SELECT data FROM DNet.ejc_FormDetails WHERE formuid = #formuid AND category = #category
SET #returnData = ''
OPEN dataCursor
FETCH NEXT FROM dataCursor INTO #currentData
WHILE (##FETCH_STATUS = 0)
BEGIN
SET #returnData = #returnData + ', ' + #currentData
FETCH NEXT FROM dataCursor INTO #currentData
END
CLOSE dataCursor
DEALLOCATE dataCursor
RETURN SUBSTRING(#returnData,3,1000)
END
As you can see, I am selecting the column data within my function and then looping over the results with a cursor to build my comma separated varchar.
How can I alter this to accept a single parameter that is a result set and then access that result set with a cursor?
Others have answered your main question - but let me point out another problem with your function - the terrible use of a CURSOR!
You can easily rewrite this function to use no cursor, no WHILE loop - nothing like that. It'll be tons faster, and a lot easier, too - much less code:
FUNCTION DNet.ejc_concatFormDetails
(#formuid AS int, #category as VARCHAR(75))
RETURNS VARCHAR(1000)
AS
RETURN
SUBSTRING(
(SELECT ', ' + data
FROM DNet.ejc_FormDetails
WHERE formuid = #formuid AND category = #category
FOR XML PATH('')
), 3, 1000)
The trick is to use the FOR XML PATH('') - this returns a concatenated list of your data columns and your fixed ', ' delimiters. Add a SUBSTRING() on that and you're done! As easy as that..... no dogged-slow CURSOR, no messie concatenation and all that gooey code - just one statement and that's all there is.
You can use table-valued parameters:
CREATE FUNCTION MyFunction(
#Data AS TABLE (
Column1 int,
Column2 nvarchar(50),
Column3 datetime
)
)
RETURNS NVARCHAR(MAX)
AS BEGIN
/* here you can do what you want */
END
You can use Table Valued Parameters as of SQL Server 2008, which would allow you to pass a TABLE variable in as a parameter. The limitations and examples for this are all in that linked article.
However, I'd also point out that using a cursor could well be painful for performance.
You don't need to use a cursor, as you can do it all in 1 SELECT statement:
SELECT #MyCSVString = COALESCE(#MyCSVString + ', ', '') + data
FROM DNet.ejc_FormDetails
WHERE formuid = #formuid AND category = #category
No need for a cursor
Your question is a bit unclear. In your first SQL statement it looks like you're trying to pass columns to the function, but there is no WHERE clause. In the second SQL statement you're passing a collection of rows (results from a SELECT). Can you supply some sample data and expected outcome?
Without fully understanding your goal, you could look into changing the parameter to be a table variable. Fill a table variable local to the calling code and pass that into the function. You could do that as a stored procedure though and wouldn't need a function.
I understand that T-SQL is not object oriented. I need to write a set of functions that mimics method overloading in C#.
Is function overloading supported in T-SQL in any way? If there is a hack to do this, is it recommended?
No, there is no way to do this.
I recommend you revisit the requirement, as "make apples look like oranges" is often difficult to do, and of questionable value.
One thing I have done successfully is to write the function in such a way as to allow it to handle null values, and then call it with nulls in place of the parameters you would like to omit.
Example:
create function ActiveUsers
(
#departmentId int,
#programId int
)
returns int
as
begin
declare #count int
select #count = count(*)
from users
where
departmentId = isnull(#departmentId, departmentId)
and programId = isnull(#programId, programId)
return #count
end
go
Uses:
select ActiveUsers(1,3) -- users in department 1 and program 3
select ActiveUsers(null,3) -- all users in program 3, regardless of department
select ActiveUsers(null,null) -- all users
You could pass in a sql_variant, but it comes with all sorts of hazards around it; you can't really use strong typing like you can with OO languages and overloading.
If you need to find the base type within your function, you can use the SQL_VARIANT_PROPERTY function.
You can pass in an array of values within a single string and parse them out using this techique by Erland Sommarskog.
Create a function with a varchar(max) parameter or several if necessary, then have your parameter values in that string like:
param1;param2;parma3;param4
or
param1:type;param2:type;param3:type
or
calltype|param1;param2;param3
etc, you are only limited by your imagination...
Use the technique from the link to split apart this array and use program logic to use those values as you wish.
One solution would be to utilize the sql_variant data type. This example works as long as you use the same datatype for both values. Returns whatever datatype you send it.
create function dbo.Greater(
#val1 sql_variant
,#val2 sql_variant
) returns sql_variant
as
begin
declare #rV sql_variant
set #rV = case when #val1 >= #val2 then #val1
else #val2 end
return #rV
end
go
A solution I've had some luck with is either creating a number of functions that each takes a different data type - or casting all input to NVARCHAR(MAX).
1. creating a number of functions that each takes a different data type
CREATE FUNCTION [dbo].[FunctionNameDatetime2]
CREATE FUNCTION [dbo].[FunctionNameInt]
CREATE FUNCTION [dbo].[FunctionNameString] --(this is not a typo)
CREATE FUNCTION [dbo].[FunctionNameUniqueidentifier]
...
Problem: duplication of code, and a lot functions
2. Cast all input to NVARCHAR(MAX)
CREATE FUNCTION [dbo].[IntToNvarchar]
(
#Key INT
)
RETURNS NVARCHAR(MAX)
AS
BEGIN
RETURN ISNULL(CAST(#Key AS NVARCHAR), '');
END
CREATE FUNCTION [dbo].[FunctionName]
(
#Key NVARCHAR(MAX)
)
RETURNS CHAR(32)
AS
BEGIN
DECLARE #something CHAR(32)
do stuff ...
RETURN #something;
END
SELECT [dbo].[FunctionName]([dbo].[IntToNvarchar](25))
Problem: less elegant code than overloading
I overload Functions all the time, but I happen to know that these kind of issues are often highly dependent on platform.
On our DB2 system, I routinely overload like the following:
CREATE Function Schema1.F1 (parm date)
returns date
return date + 1month;
CREATE Function Schema1.F1 (parm timestamp)
returns date
return date(timestamp) + 1month;
This is actually quite useful when you have multiple queries which have similar formating requirements.
The only problem I have found about this so far, is you better be sure that you want the function because the standard drop function "schema"."name" fails because it cannot determine which function to drop. If anyone knows how to drop overloaded sql functions, let me know!