Create temp table with start and end index of a special char - sql-server-2014

I have string with special char as '|'
DECLARE #string NVARCHAR(MAX) = Column1,True,3|Column2,True,2|Column3,True,6;
DECLARE #columns AS TABLE(Clm VARCHAR(50));
Requirement: create a temp table as below from the above string
Column1
Column2
Column3
My approach:
WITH columns AS
(
SELECT
CAST(0 AS BIGINT) AS idx1,
CHARINDEX('|', #string) idx2
UNION ALL
SELECT
idx2 + 1, CHARINDEX('|', #string, idx2 + 1)
FROM
columns
WHERE
idx2 > 0
)
INSERT INTO #column
SELECT
LEFT(SUBSTRING(#string, idx1, COALESCE(NULLIF(idx2, 0), LEN(#string) + 1) - idx1,
CHARINDEX(',', SUBSTRING(#string, idx1, COALESCE(NULLIF(idx2,0), LEN(#string) + 1) - idx1)) - 1) AS value
FROM
columns;
I am able to achieve what is needed but seeing performance issue. Taking 30 sec in prod.
Please help me with better ways.

One option would be to do two splits, but only one is actually necessary, as the second split only needs the first value.
So we can split on |, then search for the first ,
DECLARE #string NVARCHAR(MAX) = N'Column1,True,3|Column2,True,2|Column3,True,6';
DECLARE #columns AS TABLE(Clm VARCHAR(50));
INSERT INTO #column (Clm)
SELECT
CASE WHEN v1.comma > 0
THEN LEFT(split.[value], v1.comma - 1)
ELSE split.[value]
END
FROM STRING_SPLIT(#string, '|') split
CROSS APPLY (VALUES (CHARINDEX(',', split.[value]) ) ) v1(comma);
You need at least SQL Server 2017 to be able to use STRING_SPLIT

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;

Extract numerics from text field

Got a table that contains a field similar to the following
856655.460000000000000+0.000000000000000+2200121.020000000000000
164171.720000000000000+0.000000000000000+421637.020000000000000
0.000000000000000+0.000000000000000+0.000000000000000+0.000000000000000
103176.220000000000000+0.000000000000000+264984.210000000000000
What I need to do is extract the numeric fields and total them. There may be a different number of numeric fields within the column, but they'll all be separated by the '+' symbol
Any help would be appreciated
Try this. to get a better answer you should add more detail about how the table is structured.
DECLARE #val VARCHAR(MAX) = '856655.460000000000000+0.000000000000000+2200121.020000000000000+164171.720000000000000+0.000000000000000+421637.020000000000000+0.000000000000000+0.000000000000000+0.000000000000000+0.000000000000000+103176.220000000000000+0.000000000000000+264984.210000000000000';
DECLARE #newVal VARCHAR(MAX);
CREATE TABLE #Table
(
value DECIMAL(30, 15)
);
WHILE LEN(#val) > 0
BEGIN
IF(#val LIKE '%+%')
BEGIN
SET #newVal = LEFT(#val, CHARINDEX('+', #val));
INSERT INTO #table
VALUES
(
CONVERT( DECIMAL(30, 15), LEFT(#newVal, LEN(#newVal) - 1))
);
SET #val = SUBSTRING(#val, LEN(#newVal)+1, LEN(#Val)-LEN(#newVal));
END;
ELSE
BEGIN
INSERT INTO #Table
VALUES
(
CONVERT( DECIMAL(30, 15), REPLACE(#val, '+', ''))
);
SET #val = '';
END;
END;
SELECT * FROM #Table
SELECT sum(value) FROM #Table;
DROP TABLE #Table
Edit:
To retrofit this to work on a table you could add a cursor which loops through each row in your table, runs the above query and updates the table with the results from the sum. I'm sure there are better ways but if this is a one time cleanup, it should work. Cheers
Clearly you will have to modify this to fit your situation, but the basic concept is to transform your plus sign separated string into xml and then use the nodes method to break it apart.
IF OBJECT_ID('tempdb..#temp', 'U') IS NOT NULL DROP TABLE #temp;
declare #string varchar(250)
declare #xml xml
set #string = '856655.460000000000000+0.000000000000000+2200121.020000000000000'
set #xml = ('<r>' + REPLACE(#string,'+','</r><r>') + '</r>')
select t.v.value('r[1]', 'decimal(25,15)') as Value1,
t.v.value('r[2]', 'decimal(25,15)') as Value2,
t.v.value('r[3]', 'decimal(25,15)') as Value3,
t.v.value('r[4]', 'decimal(25,15)') as Value4
into #temp
from #xml.nodes('/') AS t(v)
select *
from #temp
select coalesce(Value1, 0) + coalesce(Value2, 0) +
coalesce(Value3, 0) + coalesce(Value4, 0) as 'Total'
from #temp
You will have to add more code to the query that selects into #temp for each potential value in your string. If you have a massive amount of possible numbers this may not scale.
Hope this helps you get to what you need.

Loop through a split string variable to insert rows in a stored procedure in SQL Server 2008

I am working on SQL Server 2008 to create a stored procedure that:
takes a string variable like this: '1,2,3'
splits the string using a table-valued function to get each value separately
and then inserts each value into a new row in a table
What I am trying to do is something like this:
WHILE (select vlaue FROM dbo.SplitString('1,2,3',',')) has rows
insert into TableName (col1,col2) values (col1Data, value)
I am having a hard time trying to find the right syntax for this.
I use this Table-valued function:
CREATE FUNCTION [dbo].[Split] (#sep char(1), #s varchar(512))
RETURNS table
AS
RETURN (
WITH Pieces(pn, start, stop) AS (
SELECT 1, 1, CHARINDEX(#sep, #s)
UNION ALL
SELECT pn + 1, stop + 1, CHARINDEX(#sep, #s, stop + 1)
FROM Pieces
WHERE stop > 0
)
SELECT pn,
SUBSTRING(#s, start, CASE WHEN stop > 0 THEN stop-start ELSE 512 END) AS s
FROM Pieces
)
GO
Which takes a string with a separator and returns a table with two columns the first returns a 1-based position and the second the element at that position in the string:
Usage:
SELECT * FROM dbo.Split(',', '1,2,3')
Returns:
pn s
1 1
2 2
3 3
To Insert results into a table:
INSERT INTO TableName (Col1)
SELECT S FROM dbo.Split(',', '1,2,3)
For your specific example change your syntax to be:
insert into TableName (col1,col2)
select col1Data, value FROM dbo.SplitString('1,2,3',',')
The typical INSERT INTO ... SELECT ... should do:
INSERT INTO TableName (col1,col2)
SELECT #col1Data,value FROM dbo.SplitString('1,2,3',','))
If someone else is looking for this, I was about to make a split function as several answers mentioned but noticed there's a built-in function that does this already.
string_split was added in MSSQL 2016.
INSERT INTO Project.FormDropdownAnswers (FkTableId, CreatedBy, CreatedDate)
SELECT 123, TRY_CAST(value AS INT), #username, getdate()
FROM string_split('44,45,46,47,55',',')
https://learn.microsoft.com/en-us/sql/t-sql/functions/string-split-transact-sql
CREATE TABLE tablename
(
id SMALLINT ,
value INT
)
INSERT INTO tablename ( id, value )
SELECT * FROM dbo.Split('1,2,3',',')
try this....
If need to use as variables there is 2 nice options:
Procedure MF_SPLIT
CREATE PROC [MF_SPLIT] (#ELS NVARCHAR(MAX)=NULL OUTPUT, #RET NVARCHAR(MAX)=NULL OUTPUT, #PROC NVARCHAR(MAX)=NULL) AS BEGIN
IF #ELS IS NULL BEGIN
PRINT ' #ELS
List of elements in string (OUTPUT)
#RET
Next return (OUTPUT)
#PROC
NULL = '','', content to do split
Example:
DECLARE #NAMES VARCHAR(100) = ''ERICK,DE,VATHAIRE''
DECLARE #N VARCHAR(100)
WHILE #NAMES IS NOT NULL BEGIN
EXEC MF_SPLIT #NAMES OUTPUT, #N OUTPUT
SELECT List = #NAMES, ActiveWord = #N
END'
RETURN
END
SET #PROC = ISNULL(#PROC, ',')
IF CHARINDEX(#PROC, #ELS) = 0 BEGIN
SELECT #RET = #ELS, #ELS = NULL
RETURN
END
SELECT
#RET = LEFT(#ELS, CHARINDEX(#PROC, #ELS) - 1)
, #ELS = STUFF(#ELS, 1, LEN(#RET) + 1, '')
END
Usage:
DECLARE #NAMES VARCHAR(100) = '1,2,3'
DECLARE #N VARCHAR(100)
WHILE #NAMES IS NOT NULL BEGIN
EXEC MF_SPLIT #NAMES OUTPUT, #N OUTPUT
SELECT List = #NAMES, ActiveWord = #N
END
Procedure MF_SPLIT_DO (Depends of MF_SPLIT), less sintax to use BUT the code will be in a string and use default variable "#X"
CREATE PROC MF_SPLIT_DO (#ARR NVARCHAR(MAX), #DO NVARCHAR(MAX)) AS BEGIN
--Less sintax
DECLARE #X NVARCHAR(MAX)
WHILE #ARR IS NOT NULL BEGIN
EXEC MF_SPLIT #ARR OUT, #X OUT
EXEC SP_EXECUTESQL #DO, N'#X NVARCHAR(MAX)', #X
END
END
Usage:
EXEC MF_SPLIT_DO '1,2,3', 'SELECT #X'

How to convert a Tsql scalar function into a table 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?