How to convert a Tsql scalar function into a table function? - function

I am using SSMS 2008 and I have the following scalar function to take a text string and remove all metatags from Microsoft Word. The tags are enclosed in "<...>" and there can be any number of tags / record in one column.
I created this scalar function to update each row in this column.
create function dbo.ufn_StripHTML
( #Input varchar(max),
#Delimiter char(1)
)
returns varchar(max)
as
begin
declare #Output varchar(max)
select #Input = replace(replace(#input, '<', #Delimiter), '>', #Delimiter)
select #Output = isnull(#Output, '') + s
from ( select row_number() over (order by n.id asc) [i],
substring(#Delimiter + #Input + #Delimiter, n.id + 1, charindex(#Delimiter, #Delimiter + #Input + #Delimiter, n.id + 1) - n.id - 1) [s]
from [evolv_cs].[dbo].[progress_note] n
where n.id = charindex(#Delimiter, #Delimiter + #Input + #Delimiter, n.id) and
n.id <= len(#Delimiter + #Input)
) d
where i % 2 = 1
return #Output
end
This scalar function would work if [progress_note] had an "id" int column. But it does not and I cannot modify this table either, by adding an int column. So the problem is that I am trying to use this function on a temp table.
So I tried creating a view based on this table and then adding a PK int column to it. Because when I tried to create the view with this additional PK int column ("id"), it gave me an error:
Msg 156, Level 15, State 1, Line 1
Incorrect syntax near the keyword 'identity'.
But ALTER VIEW does not support adding columns. Is there another way to do this? Here is my original temp table I am trying to modify:
select [progress_note].[note_text], [progress_note].[event_log_id]
INTO #TEMP_PN
from [evolv_cs].[dbo].[progress_note]
group by [progress_note].[event_log_id], [progress_note].[note_text]
[note_text] is varchar(max) and event_log_id is uniqueidentifier. So [note_text] contains a bunch of "<" and ">" chars. How can I modify this function to make it a table function?
Of course, if I try to replace [progress_note] table with #TEMP_PN in this function, it will error because it won't recognize it. So how can I modify this function for my case?
Meanwhile, I developed a table function which accepts and outputs a table parameter. It does not error, but it does not return the parsed data I was hoping for either. What is missing?
CREATE TYPE dbo.MyTableType AS TABLE
(
col1 int identity(1,1) NOT NULL,
col2 varchar(max) NULL
)
GO
CREATE TABLE [dbo].[MyTable] (
[col1] [int] identity(1,1) NOT NULL,
[col2] [varchar](max) NULL
)
GO
create PROC usp_AddRowsToMyTable #MyTableParam MyTableType READONLY, #Delimiter varchar(30)
as
INSERT INTO MyTable([col2])
SELECT [col2]
FROM #MyTableParam
--update MyTable
--set col2 = replace(replace(MyTable.col2, '<', #Delimiter), '>', #Delimiter)
select s, i, t
from(
select MyTableInput.col1 [i],
replace(replace(MyTable.col2, '<', #Delimiter), '>', #Delimiter) as t,
substring(#Delimiter + MyTableInput.col2 + #Delimiter, MyTable.col1 + 1,
charindex(#Delimiter, #Delimiter + MyTableInput.col2 + #Delimiter, MyTable.col1 + 1) - MyTable.col1 - 1) [s]
from MyTable
inner join MyTable as MyTableInput on MyTable.col1 = MyTableInput.col1
where MyTable.col1 = CHARINDEX(#Delimiter, #Delimiter + MyTableInput.col2 + #Delimiter, MyTable.col1)
and MyTable.col1 <= LEN(#Delimiter + MyTableInput.col2)
) d
DECLARE #MyTable MyTableType
INSERT INTO #MyTable(col2)
VALUES ('<h><dsf>2000<h><dsf>'),
('<sd><dsf>2001'),
('2002<vnv><dsf>'),
('<gsd><dsf>2003<h><dsf>'),
('<eefs><dsf><h><dsf>2004<dfgd><dsf>')
EXEC dbo.usp_AddRowsToMyTable #MyTableParam = #MyTable, #Delimiter = '|'

Not sure I'm understanding your question, but here is how you would modify your function so that it returns a table (it's called a Table-Valued Function):
create function dbo.ufn_StripHTML
( #Input varchar(max),
#Delimiter char(1)
)
returns #retYourNewTable table
(
id int primary key clustered not null,
yoursecond column varchar(100) null,
....
)
as
....
Is this what you're looking for?

Related

How to match any value of search string from a column containing multiple values separated by space in table in sql?

I have a column in table which has multiple values separated by space.
i want to return those rows which has any of the matching values from search string.
Eg:
search string= 'mumbai pune'
This need to return rows matching word 'mumbai' or 'pune' or matching both
Declare #str nvarchar(500)
SET #str='mumbai pune'
create table #tmp
(
ID int identity(1,1),
citycsv nvarchar(500)
)
insert into #tmp(citycsv)Values
('mumbai pune'),
('mumbai'),
('nagpur')
select *from #tmp t
select *from #tmp t
where t.citycsv like '%'+#str+'%'
drop table #tmp
Required Out put:
ID CityCSV
1 mumbai pune
2 mumbai
You can use a splitter function to split your search string out as a table contain the desired search keys. Then you can join your main table with the table containing the search key using the LIKE statement.
For completeness I have included an example of a string splitter function, however there are plenty of example here on SO.
Example string splitter function:
CREATE FUNCTION [dbo].[SplitString]
(
#string NVARCHAR(MAX),
#delimiter CHAR(1)
)
RETURNS #output TABLE(splitdata NVARCHAR(MAX)
)
BEGIN
DECLARE #start INT, #end INT
SELECT #start = 1, #end = CHARINDEX(#delimiter, #string)
WHILE #start < LEN(#string) + 1 BEGIN
IF #end = 0
SET #end = LEN(#string) + 1
INSERT INTO #output (splitdata)
VALUES(SUBSTRING(#string, #start, #end - #start))
SET #start = #end + 1
SET #end = CHARINDEX(#delimiter, #string, #start)
END
RETURN
END
The following query demonstrates how the string splitter function can be combined with regular expressions to get the desired result:
SELECT DISTINCT
C.ID
,C.citycsv
FROM #tmp C
INNER JOIN (
SELECT splitdata + '[ ]%' AS MatchFirstWord -- Search pattern to match the first word in the string with the target search word.
,'%[ ]' + splitdata AS MatchLastWord -- Search pattern to match the last word in the string with the target search word.
,'%[ ]' + splitdata + '[ ]%' AS MatchMiddle -- Search pattern to match any words in the middle of the string with the target search word.
,splitdata AS MatchExact -- Search pattern for exact match.
FROM dbo.SplitString(#str, ' ')
) M ON (
(C.citycsv LIKE M.MatchFirstWord) OR
(C.citycsv LIKE M.MatchLastWord) OR
(C.citycsv LIKE M.MatchMiddle) OR
(C.citycsv LIKE M.MatchExact)
)
ORDER BY C.ID
Another approach , by using ReplaceFunction
Its syntax as following:
REPLACE ( string_expression , string_pattern , string_replacement )
so we could reach the target via replacing the every space that separated the values with the next pattern
'%'' OR t.citycsv like ''%'
An example:
Declare #str nvarchar(500),
#Where nvarchar (1000),
#Query nvarchar (4000)
SET #str='mumbai pune'
create table #tmp
(
ID int identity(1,1),
citycsv nvarchar(500)
)
insert into #tmp(citycsv)Values
('mumbai pune'),
('mumbai'),
('nagpur')
select * from #tmp t
Set #Where = 'where t.citycsv like ' + '''%'+ replace (RTRIM(LTRIM(#str)), ' ', '%'' OR t.citycsv like ''%') +'%'''
Set #Query = 'select * from #tmp t ' + #Where
execute sp_executesql #Query
drop table #tmp
The Result:

Update col1 for capitalising the first letter of each word in a string in col1 with SQL Server

After searching a code for capitalise the first letter of each word in a string in SQL Server I found this :
CREATE FUNCTION [dbo].[InitCap]
(#InputString varchar(4000) )
RETURNS VARCHAR(4000)
AS
BEGIN
DECLARE #Index INT
DECLARE #Char CHAR(1)
DECLARE #PrevChar CHAR(1)
DECLARE #OutputString VARCHAR(255)
SET #OutputString = LOWER(#InputString)
SET #Index = 1
WHILE #Index <= LEN(#InputString)
BEGIN
SET #Char = SUBSTRING(#InputString, #Index, 1)
SET #PrevChar = CASE WHEN #Index = 1 THEN ' '
ELSE SUBSTRING(#InputString, #Index - 1, 1)
END
IF #PrevChar IN (' ', ';', ':', '!', '?', ',', '.', '_', '-', '/', '&', '''', '(')
BEGIN
IF #PrevChar != '''' OR UPPER(#Char) != 'S'
SET #OutputString = STUFF(#OutputString, #Index, 1, UPPER(#Char))
END
SET #Index = #Index + 1
END
RETURN #OutputString
END
GO
but I don't now how to associate with updating code in SSMS ... something like this
update tabel1
set #InputString = #OutputString
You Cross apply
Cross Apply executes for Each row of outer query,so Assuming tableyouwanttoupdate has the string you want to capitalize ,you can pass it function and use that in your update
Update t1
set t1.string=b.string
from
tableyouwanttoupdate t1
cross apply
[dbo].[InitCap] (t1.string) b(string)
What about this:
DECLARE #tbl TABLE (ID INT IDENTITY,s NVARCHAR(100));
INSERT INTO #tbl(s) VALUES
('this is all lower case!')
,('Here we have a sentence. And another one!')
,('This IS mIxEd!!! CoMMpletelY MixeD!');
WITH Splitted AS
(
SELECT ID
,s
,CAST(N'<x>' + REPLACE((SELECT s AS [*] FOR XML PATH('')),N' ',N'</x><x>') + N'</x>' AS XML) AS InParts
FROM #tbl
)
SELECT ID
,s
,(
STUFF(
(
SELECT ' ' + UPPER(LEFT(x.value('.','nvarchar(max)'),1)) + LOWER(SUBSTRING(x.value('.','nvarchar(max)'),2,1000))
FROM Splitted.InParts.nodes('/x') AS A(x)
FOR XML PATH('')
),1,1,'')
) AS NewString
FROM Splitted
The result
ID s NewString
1 this is all lower case! This Is All Lower Case!
2 Here we have a sentence. And another one! Here We Have A Sentence. And Another One!
3 This IS mIxEd!!! CoMMpletelY MixeD! This Is Mixed!!! Commpletely Mixed!
UPDATE
If you want to update your column this is very easy too:
DECLARE #tbl TABLE (ID INT IDENTITY,s NVARCHAR(100));
INSERT INTO #tbl(s) VALUES
('this is all lower case!')
,('Here we have a sentence. And another one!')
,('This IS mIxEd!!! CoMMpletelY MixeD!');
WITH Splitted AS
(
SELECT ID
,s
,CAST(N'<x>' + REPLACE((SELECT s AS [*] FOR XML PATH('')),N' ',N'</x><x>') + N'</x>' AS XML) AS InParts
FROM #tbl
)
UPDATE Splitted SET s=
(
STUFF(
(
SELECT ' ' + UPPER(LEFT(x.value('.','nvarchar(max)'),1)) + LOWER(SUBSTRING(x.value('.','nvarchar(max)'),2,1000))
FROM Splitted.InParts.nodes('/x') AS A(x)
FOR XML PATH('')
),1,1,'')
)
FROM Splitted;
SELECT * FROM #tbl;

Split address column into multiple on large table

I have a table with 500k rows where the address is in one field, delimited by Char(13)+Char(10). I have added 5 fields to the table in the hope of splitting this up.
Found online this split function that seems to perform well as I cannot use parsename due to there being 5 parts and also that the . may be in the field.
This is a table-valued function so I would have to loop the rows and update the record, previously I would have used a cursor or sql while or possibly even c# to do this but I feel their must be a cte or set based answer to do this.
So given some source data:
CREATE TABLE dbo.Addresses
(
AddressID INT IDENTITY(1,1),
[Address] VARCHAR(255),
Address1 VARCHAR(255),
Address2 VARCHAR(255),
Address3 VARCHAR(255),
Address4 VARCHAR(255),
Address5 VARCHAR(255)
);
INSERT dbo.Addresses([Address])
SELECT 'foo
bar'
UNION ALL SELECT 'add1
add2
add3
add4
add5';
Let's create a function that returns the address parts in a sequence:
CREATE FUNCTION dbo.SplitAddressOrdered
(
#AddressID INT,
#List VARCHAR(MAX),
#Delimiter VARCHAR(32)
)
RETURNS TABLE
AS
RETURN
(
SELECT
AddressID = #AddressID,
rn = ROW_NUMBER() OVER (ORDER BY Number),
AddressItem = Item
FROM (SELECT Number, Item = LTRIM(RTRIM(SUBSTRING(#List, Number,
CHARINDEX(#Delimiter, #List + #Delimiter, Number) - Number)))
FROM (SELECT ROW_NUMBER() OVER (ORDER BY [object_id])
FROM sys.all_objects) AS n(Number)
WHERE Number <= CONVERT(INT, LEN(#List))
AND SUBSTRING(#Delimiter + #List, Number, LEN(#Delimiter)) = #Delimiter
) AS y
);
GO
Now you can do this (you will have to run the query 5 times):
DECLARE
#i INT = 1,
#sql NVARCHAR(MAX),
#src NVARCHAR(MAX) = N';WITH x AS
(
SELECT a.*, Original = s.AddressID, s.rn, s.AddressItem
FROM dbo.Addresses AS a
CROSS APPLY dbo.SplitAddressOrdered(a.AddressID, a.Address,
CHAR(13) + CHAR(10)) AS s WHERE rn = #i
)';
WHILE #i <= 5
BEGIN
SET #sql = #src + N'UPDATE x SET Address' + RTRIM(#i)
+ ' = CASE WHEN AddressID = Original AND rn = '
+ RTRIM(#i) + ' THEN AddressItem END;';
EXEC sp_executesql #sql, N'#i INT', #i;
SET #i += 1;
END
Then you can drop the Address column:
ALTER TABLE dbo.Addresses DROP COLUMN [Address];
Then the table has:
AddressID Address1 Address2 Address3 Address4 Address5
--------- -------- -------- -------- -------- --------
1 foo bar NULL NULL NULL
2 add1 add2 add3 add4 add5
I'm sure someone more clever than I will show how to utilize that function without having to loop.
I could also envision a slight change to the function that would allow you to simply pull out a certain element... hold please...
EDIT
Here's a scalar function that is more expensive on its own but allows you to make one pass of the table instead of 5:
CREATE FUNCTION dbo.ElementFromOrderedList
(
#List VARCHAR(MAX),
#Delimiter VARCHAR(32),
#Index SMALLINT
)
RETURNS VARCHAR(255)
AS
BEGIN
RETURN
(
SELECT Item
FROM (SELECT rn = ROW_NUMBER() OVER (ORDER BY Number),
Item = LTRIM(RTRIM(SUBSTRING(#List, Number,
CHARINDEX(#Delimiter, #List + #Delimiter, Number) - Number)))
FROM (SELECT ROW_NUMBER() OVER (ORDER BY [object_id])
FROM sys.all_objects) AS n(Number)
WHERE Number <= CONVERT(INT, LEN(#List))
AND SUBSTRING(#Delimiter + #List, Number, LEN(#Delimiter)) = #Delimiter
) AS y WHERE rn = #Index
);
END
GO
Now the update, given the above table above (prior to the update and prior to the drop), is simply:
UPDATE dbo.Addresses
SET Address1 = dbo.ElementFromOrderedList([Address], CHAR(13) + CHAR(10), 1),
Address2 = dbo.ElementFromOrderedList([Address], CHAR(13) + CHAR(10), 2),
Address3 = dbo.ElementFromOrderedList([Address], CHAR(13) + CHAR(10), 3),
Address4 = dbo.ElementFromOrderedList([Address], CHAR(13) + CHAR(10), 4),
Address5 = dbo.ElementFromOrderedList([Address], CHAR(13) + CHAR(10), 5);
You have couple of options:
You can create a temp table and then parse the address into the temp table and then update the original table by joining it to the temp table.
or
You can write your own T-SQL functions and use those functions in your update statement function like follows:
UPDATE myTable
SET address1 = myGetAddress1Function(address),
address2 = myGetAddress2Function(address)....

stored procedure for inserting comma seperated values in table using ms sql?

I have a table named assignRole.
I am passing string of userid (int) csv ,and passing roleid(int).
I want a stored procedure which split userid from string and take roleid and insert these values in table.this thing is to happen for all values in userid string.
First, create a function:
CREATE FUNCTION [dbo].[SplitInts]
(
#List VARCHAR(MAX),
#Delimiter CHAR(1)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN ( SELECT Item = CONVERT(INT, Item) FROM (
SELECT Item = x.i.value('(./text())[1]', 'int') FROM (
SELECT [XML] = CONVERT(XML, '<i>' + REPLACE(#List, #Delimiter, '</i><i>')
+ '</i>').query('.') ) AS a CROSS APPLY [XML].nodes('i') AS x(i)) AS y
WHERE Item IS NOT NULL
);
Now you can say:
INSERT dbo.assignRole(RoleID, UserID)
SELECT #RoleID, Item
FROM dbo.SplitInts(#UserIDList, ',');
I like to use a table-valued function to do the split.
IF OBJECT_ID (N'dbo.StrSplit') IS NOT NULL DROP FUNCTION dbo.[StrSplit]
GO
CREATE FUNCTION [dbo].[StrSplit]
(
#String VARCHAR(MAX), #Delimiter char(1)
)
RETURNS
#Results TABLE (
Items NVARCHAR(MAX)
)
AS
BEGIN
DECLARE #INDEX INT
DECLARE #SLICE nvarchar(MAX)
-- HAVE TO SET TO 1 SO IT DOESNT EQUAL ZERO FIRST TIME IN LOOP
SELECT #INDEX = 1
-- Early exit if passed string is null
IF #String IS NULL RETURN
WHILE #INDEX !=0
BEGIN
-- GET THE INDEX OF THE FIRST OCCURENCE OF THE SPLIT CHARACTER
SELECT #INDEX = CHARINDEX(#Delimiter,#STRING)
-- NOW PUSH EVERYTHING TO THE LEFT OF IT INTO THE SLICE VARIABLE
IF #INDEX !=0
SELECT #SLICE = LEFT(#STRING,#INDEX - 1)
ELSE
SELECT #SLICE = #STRING
-- PUT THE ITEM INTO THE RESULTS SET
INSERT INTO #Results(Items) VALUES(#SLICE)
-- CHOP THE ITEM REMOVED OFF THE MAIN STRING
SELECT #STRING = RIGHT(#STRING,LEN(#STRING) - #INDEX)
-- BREAK OUT IF WE ARE DONE
IF LEN(#STRING) = 0 BREAK
END
RETURN
END
GO
GRANT SELECT ON [dbo].[StrSplit] TO [public]

SQL server 2008 - Insert into table from SP, using two input parameter list

I've to modify a Stored Procedure which inserts the data passed as input parameter in CSV varchar(MAX) format,
but now i've to pass two lists to SP and have to insert it into the table
data which I pass is as following
lstid = 1,2,3,4,5 etc.
lstvalue = 10,20,22,35,60 etc.
here lstid maps to lstvalue, means lstid = 1's value will be 10, lstid = 2's value will be 20 and so on
what should I do to insert the record based on mapping
I am using a function to seprate the CSV value and than stores it in temptable, but it work for obly one column
function is same as here
http://www.sqlteam.com/Forums/topic.asp?TOPIC_ID=14185
If you are forced to do this in a stored procedure and your arrays are equal size you can join the two lists, split them, and then join on position (the number of elements in each array) to get the linked set you need.
The below example uses a number table, but you can replace that split operation with any.
-- if you dont have a number table:
/*
create table [dbo].[Number](n int not null primary key clustered);
insert into dbo.Number
values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),
(11),(12),(13),(14),(15),(16),(17),(18),(19),(20)
*/
declare #lstid varchar(100) = '1,2,3,4,51',
#lstvalue varchar(100) = '10,20,22,35,60'
declare #Length tinyint,
#Input varchar(8000),
#Delimiter char(1)
-- sanity check
if len(#lstid)-len(replace(#lstid, ',', '')) <> len(#lstvalue)-len(replace(#lstvalue, ',', ''))
begin
raiserror('lists are not equal', 16, 1);
return;
end
--count the numbers of elements in one of the arrays
select #Length = len(#lstid)-len(replace(#lstid, ',', ''))+1;
--join the two arrays into one
select #Input = #lstid + ',' + #lstvalue;
set #Delimiter = ',';
;with cte (i,s)
as (
select row_number() over (order by n.n asc) [i],
substring(#Delimiter + #Input + #Delimiter, n.n + 1, charindex(#Delimiter, #Delimiter + #Input + #Delimiter, n.n + 1) - n.n - 1) [s]
from dbo.Number n
where n.n = charindex(#Delimiter, #Delimiter + #Input + #Delimiter, n.n) and
n.n <= len(#Delimiter + #Input)
)
select a.s, b.s
from cte a
join cte b on
a.i+#Length = b.i
order
by a.i;
return
Create a data table with the data in .net code and pass that to SP.
How to pass data table to Sp from .net
You can pass your parameter lists as XML.
The parameter:
<lst><id>1</id><value>10</value></lst>
<lst><id>2</id><value>20</value></lst>
<lst><id>3</id><value>22</value></lst>
<lst><id>4</id><value>35</value></lst>
<lst><id>5</id><value>60</value></lst>
and the procedure
create procedure AddXML
#XML xml
as
insert into YourTable(id, value)
select N.value('id[1]', 'int'),
N.value('value[1]', 'int')
from #XML.nodes('/lst') as T(N)