I am trying to save a data of string2 ='DOB;Mar 1199;passport;AW1234567' into multiple columns of the table but it only move it to 1st column. I am using string_split function to separate all 4 string parts separated by ";".
What should I do to move this data into a single row across 4 columns?
Please see the details and result image below:
use TEST
DECLARE #string2 varchar(max);
DECLARE #sep char(1);
set #string2 = 'DOB;Mar 1199;passport;AW1234567';
set #sep = ';'
DECLARE #myTableVariable TABLE (id INT IDENTITY(1,1) PRIMARY KEY, name varchar(max))
insert into #myTableVariable
(name)
SELECT value FROM STRING_SPLIT(#string2, #sep);
print #string2;
insert into EMPLOYEE (dob1)
select name from #myTableVariable
To guarantee column ordering you can't rely on string_split so need a different user defined function. This one returns the same value column and also a seq column for row ordering:
create function dbo.SplitString(#string varchar(max), #Delimiter varchar(1))
returns table
as
return(
select j.[value], 1 + Convert(tinyint,j.[key]) Seq
from OpenJson(Concat('["',replace(#string, #delimiter , '","'),'"]')) j
);
You can then make use of it as follows to create the columns from the sample string and insert into the target table:
declare #string2 varchar(max)='DOB;Mar 1199;passport;AW1234567', #sep char(1)=';'
insert into Employee(Dob1, DobNum, Pass1, PassNum)
select
Max(case when Seq=1 then Value end) Dob1,
Max(case when Seq=2 then Value end) DobNum,
Max(case when Seq=3 then Value end) Pass1,
Max(case when Seq=4 then Value end) PassNum
from dbo.SplitString(#string2, #sep);
Example Fiddle
SQL Server built-in PARSENAME() function could be handy for the task.
SQL
DECLARE #string2 varchar(max) = 'DOB;Mar 1199;passport;AW1234567';
DECLARE #sep char(1) = ';'
, #dot CHAR(1) = '.';
DECLARE #employee TABLE (
id INT IDENTITY(1,1) PRIMARY KEY,
Dob1 varchar(20),
DobNum VARCHAR(20),
Pass1 VARCHAR(20),
PassNum VARCHAR(20)
);
WITH rs AS
(
SELECT REPLACE(#string2, #sep, #dot) AS tokenList
)
INSERT INTO #employee (Dob1, DobNum, Pass1, PassNum)
SELECT PARSENAME(tokenList,4)
, PARSENAME(tokenList,3)
, PARSENAME(tokenList,2)
, PARSENAME(tokenList,1)
FROM rs;
-- test
SELECT * FROM #employee;
Output
+----+------+----------+----------+-----------+
| id | Dob1 | DobNum | Pass1 | PassNum |
+----+------+----------+----------+-----------+
| 1 | DOB | Mar 1199 | passport | AW1234567 |
+----+------+----------+----------+-----------+
It is possible to do this a number of ways, it is generally frowned upon, but because you have specifically requested STRING_SPLIT then I offer you this mechanism to parse the components of the string:
DECLARE #string2 varchar(max);
DECLARE #sep char(1);
set #string2 = 'DOB;Mar 1199;passport;AW1234567';
set #sep = ';'
SELECT ROW_NUMBER() OVER (order by (SELECT 1)) as RN, Value
FROM STRING_SPLIT(#string2, #sep, 1)
RN
Value
1
DOB
2
Mar 1199
3
passport
4
AW1234567
NOTE: This hack for the ORDER BY is generally advised against
From the docs: The output rows might be in any order. The order is not guaranteed to match the order of the substrings in the input string
However you could use this are your own risk if you really needed to use STRING_SPLIT
You could now access these tokens via their ordinal:
DECLARE #string2 varchar(max);
DECLARE #sep char(1);
set #string2 = 'DOB;Mar 1199;passport;AW1234567';
set #sep = ';'
;WITH Split as (
SELECT ROW_NUMBER() OVER (order by (select 1)) as RN, Value
FROM STRING_SPLIT(#string2, #sep)
)
SELECT s1.value as DOBKey, s2.value as DOBValue, s3.value as PassportKey, s4.value as PassportValue
FROM Split as s1, Split as s2, Split as s3, Split as s4
WHERE s1.RN = 1
AND s2.RN = 2
AND s3.RN = 3
AND s4.RN = 4
Results in this:
DOBKey
DOBValue
PassportKey
PassportValue
DOB
Mar 1199
passport
AW1234567
SQL Azure Supports an enable_ordinal flag
As an update to SQL 2019 and expected to be released soon, there is an enable_ordinal argument that can be used to ensure the sequence of the string tokens and it will include the ordinal value in an output column named ordinal.
HOWEVER as of 11/06/2021 enable_ordinal argument and ordinal output column are currently only supported in Azure SQL Database, Azure SQL Managed Instance, and Azure Synapse Analytics (serverless SQL pool only).
When available we can simplify the previous query:
DECLARE #string2 varchar(max);
DECLARE #sep char(1);
set #string2 = 'DOB;Mar 1199;passport;AW1234567';
set #sep = ';'
;WITH Split as (
SELECT Ordinal, Value
FROM STRING_SPLIT(#string2, #sep, 1)
)
SELECT s1.Value as DOBKey, s2.Value as DOBValue, s3.Value as PassportKey, s4.Value as PassportValue
FROM Split as s1, Split as s2, Split as s3, Split as s4
WHERE s1.Ordinal = 1
AND s2.Ordinal = 2
AND s3.Ordinal = 3
AND s4.Ordinal = 4
You can use the same CTE to extract the DOBValue and the PassportValue in the INSERT:
DECLARE #string2 varchar(max);
DECLARE #sep char(1);
set #string2 = 'DOB;Mar 1199;passport;AW1234567';
set #sep = ';'
;WITH Split as (
SELECT Ordinal, Value
FROM STRING_SPLIT(#string2, #sep, 1)
)
insert into EMPLOYEE (dob1, pass1)
select s2.Value, s4.Value
FROM Split as s1, Split as s2, Split as s3, Split as s4
WHERE s1.Ordinal = 1
AND s2.Ordinal = 2
AND s3.Ordinal = 3
AND s4.Ordinal = 4
Should result in this:
ID
dob1
pass1
1
Mar 1199
AW1234567
I have a string : Brand=b1-b2&Vendor=v1-v2-v3&CustomField=cf1-cf2-cf3
I want to make a table..
**Name** | **Value**
------------- | ------
Brand | b1
Brand | b2
Vendor | v1
Vendor | v2
Vendor | v3
CustomField | cf1
CustomField | cf2
CustomField | cf3
Thanks for your help
Edit Note : I tried, but I do not think it's performance
CREATE PROCEDURE [dbo].[SplitTableInStr]
(
#StrParameter NVARCHAR(MAX)
)
AS
BEGIN
DECLARE #Delimiter CHAR = '&',
#Index SMALLINT,
#Start SMALLINT,
#DelSize SMALLINT;
SET #DelSize = LEN(#Delimiter);
CREATE TABLE #FilteredItems
(
[Key] NVARCHAR(400),
[Values] NVARCHAR(MAX)
)
WHILE LEN(#StrParameter) > 0
BEGIN
SET #Index = CHARINDEX(#Delimiter, #StrParameter)
IF #Index = 0
BEGIN
DECLARE #Key NVARCHAR(400) = SUBSTRING(#StrParameter, 1, CHARINDEX('=', #StrParameter) - 1);
DECLARE #Values NVARCHAR(MAX) = SUBSTRING(#StrParameter, CHARINDEX('=', #StrParameter) + 1, LEN(#StrParameter));
INSERT INTO #FilteredItems ([Key],[Values])
VALUES (#Key,#Values)
BREAK
END
ELSE
BEGIN
DECLARE #IndexItem NVARCHAR(MAX) = SUBSTRING(#StrParameter, 1, #Index - 1);
DECLARE #IndexKey NVARCHAR(400) = SUBSTRING(#IndexItem, 1, CHARINDEX('=', #IndexItem) - 1);
DECLARE #IndexValues NVARCHAR(MAX) = SUBSTRING(#IndexItem, CHARINDEX('=', #IndexItem) + 1, LEN(#IndexItem));
INSERT INTO #FilteredItems ([Key],[Values])
VALUES (#IndexKey,#IndexValues)
SET #Start = #Index + #DelSize
SET #StrParameter = SUBSTRING(#StrParameter, #Start , LEN(#StrParameter) - #Start + 1)
END
END
DECLARE #KeyBase NVARCHAR(400),
#ValueBase NVARCHAR(MAX);
CREATE TABLE #DisplayOrderTmp
(
[EndName] NVARCHAR(400),
[EndValue] NVARCHAR(400)
)
WHILE EXISTS(SELECT * From #FilteredItems)
BEGIN
SELECT TOP 1 #KeyBase = [Key], #ValueBase = [Values] FROM #FilteredItems
DECLARE #OptionDelimiter CHAR = '-',
#OptionIndex SMALLINT,
#OptionStart SMALLINT,
#OptionDelSize SMALLINT;
SET #OptionDelSize = LEN(#OptionDelimiter)
WHILE LEN(#ValueBase) > 0
BEGIN
SET #OptionIndex = CHARINDEX(#OptionDelimiter, #ValueBase)
IF #OptionIndex = 0
BEGIN
INSERT INTO #DisplayOrderTmp VALUES (#KeyBase, #ValueBase)
BREAK;
END
ELSE
BEGIN
INSERT INTO #DisplayOrderTmp VALUES (#KeyBase, SUBSTRING(#ValueBase, 1, CHARINDEX(#OptionDelimiter, #ValueBase) - 1))
SET #OptionStart = #OptionIndex + #OptionDelSize
SET #ValueBase = SUBSTRING(#ValueBase, #OptionStart , LEN(#ValueBase) - #OptionStart + 1)
END
END
DELETE #FilteredItems WHERE [Key] = #KeyBase
END
SELECT * FROM #DisplayOrderTmp;
END
EXEC dbo.[SplitTableInStr] 'Brand=b1-b2&Vendor=v1-v2-v3&CustomField=cf1-cf2-cf3'
using a CSV Splitter table valued function by Jeff Moden with cross apply()
declare #s nvarchar(4000) = 'Brand=b1-b2&Vendor=v1-v2-v3&CustomField=cf1-cf2-cf3';
select
Name = left(s.Item,charindex('=',s.Item)-1)
, Value = i.Item
from dbo.DelimitedSplitN4k(#s,'&') s
cross apply dbo.DelimitedSplitN4k(replace(s.Item,'=','-'),'-') i
where i.ItemNumber > 1
rextester demo: http://rextester.com/RTAT28301
returns:
+-------------+------+
| Name | Item |
+-------------+------+
| Brand | b1 |
| Brand | b2 |
| Vendor | v1 |
| Vendor | v2 |
| Vendor | v3 |
| CustomField | cf1 |
| CustomField | cf2 |
| CustomField | cf3 |
+-------------+------+
splitting strings reference:
Tally OH! An Improved SQL 8K “CSV Splitter” Function - Jeff Moden
Splitting Strings : A Follow-Up - Aaron Bertrand
Split strings the right way – or the next best way - Aaron Bertrand
string_split() in SQL Server 2016 : Follow-Up #1 - Aaron Bertrand
I think you should get the best performance with CLR function (in C#) for this purpose:
https://msdn.microsoft.com/en-us/library/ms131103.aspx
You might manually split the input or create compiled Regex with named groups to parse input values.
I have data like this:
string 1: 003Preliminary Examination Plan
string 2: Coordination005
string 3: Balance1000sheet
The output I expect is
string 1: 003
string 2: 005
string 3: 1000
And I want to implement it in SQL.
First create this UDF
CREATE FUNCTION dbo.udf_GetNumeric
(
#strAlphaNumeric VARCHAR(256)
)
RETURNS VARCHAR(256)
AS
BEGIN
DECLARE #intAlpha INT
SET #intAlpha = PATINDEX('%[^0-9]%', #strAlphaNumeric)
BEGIN
WHILE #intAlpha > 0
BEGIN
SET #strAlphaNumeric = STUFF(#strAlphaNumeric, #intAlpha, 1, '' )
SET #intAlpha = PATINDEX('%[^0-9]%', #strAlphaNumeric )
END
END
RETURN ISNULL(#strAlphaNumeric,0)
END
GO
Now use the function as
SELECT dbo.udf_GetNumeric(column_name)
from table_name
SQL FIDDLE
I hope this solved your problem.
Reference
Try this one -
Query:
DECLARE #temp TABLE
(
string NVARCHAR(50)
)
INSERT INTO #temp (string)
VALUES
('003Preliminary Examination Plan'),
('Coordination005'),
('Balance1000sheet')
SELECT LEFT(subsrt, PATINDEX('%[^0-9]%', subsrt + 't') - 1)
FROM (
SELECT subsrt = SUBSTRING(string, pos, LEN(string))
FROM (
SELECT string, pos = PATINDEX('%[0-9]%', string)
FROM #temp
) d
) t
Output:
----------
003
005
1000
Query:
DECLARE #temp TABLE
(
string NVARCHAR(50)
)
INSERT INTO #temp (string)
VALUES
('003Preliminary Examination Plan'),
('Coordination005'),
('Balance1000sheet')
SELECT SUBSTRING(string, PATINDEX('%[0-9]%', string), PATINDEX('%[0-9][^0-9]%', string + 't') - PATINDEX('%[0-9]%',
string) + 1) AS Number
FROM #temp
Please try:
declare #var nvarchar(max)='Balance1000sheet'
SELECT LEFT(Val,PATINDEX('%[^0-9]%', Val+'a')-1) from(
SELECT SUBSTRING(#var, PATINDEX('%[0-9]%', #var), LEN(#var)) Val
)x
Getting only numbers from a string can be done in a one-liner.
Try this :
SUBSTRING('your-string-here', PATINDEX('%[0-9]%', 'your-string-here'), LEN('your-string-here'))
NB: Only works for the first int in the string, ex: abc123vfg34 returns 123.
I found this approach works about 3x faster than the top voted answer. Create the following function, dbo.GetNumbers:
CREATE FUNCTION dbo.GetNumbers(#String VARCHAR(8000))
RETURNS VARCHAR(8000)
AS
BEGIN;
WITH
Numbers
AS (
--Step 1.
--Get a column of numbers to represent
--every character position in the #String.
SELECT 1 AS Number
UNION ALL
SELECT Number + 1
FROM Numbers
WHERE Number < LEN(#String)
)
,Characters
AS (
SELECT Character
FROM Numbers
CROSS APPLY (
--Step 2.
--Use the column of numbers generated above
--to tell substring which character to extract.
SELECT SUBSTRING(#String, Number, 1) AS Character
) AS c
)
--Step 3.
--Pattern match to return only numbers from the CTE
--and use STRING_AGG to rebuild it into a single string.
SELECT #String = STRING_AGG(Character,'')
FROM Characters
WHERE Character LIKE '[0-9]'
--allows going past the default maximum of 100 loops in the CTE
OPTION (MAXRECURSION 8000)
RETURN #String
END
GO
Testing
Testing for purpose:
SELECT dbo.GetNumbers(InputString) AS Numbers
FROM ( VALUES
('003Preliminary Examination Plan') --output: 003
,('Coordination005') --output: 005
,('Balance1000sheet') --output: 1000
,('(111) 222-3333') --output: 1112223333
,('1.38hello#f00.b4r#\-6') --output: 1380046
) testData(InputString)
Testing for performance:
Start off setting up the test data...
--Add table to hold test data
CREATE TABLE dbo.NumTest (String VARCHAR(8000))
--Make an 8000 character string with mix of numbers and letters
DECLARE #Num VARCHAR(8000) = REPLICATE('12tf56se',800)
--Add this to the test table 500 times
DECLARE #n INT = 0
WHILE #n < 500
BEGIN
INSERT INTO dbo.NumTest VALUES (#Num)
SET #n = #n +1
END
Now testing the dbo.GetNumbers function:
SELECT dbo.GetNumbers(NumTest.String) AS Numbers
FROM dbo.NumTest -- Time to complete: 1 min 7s
Then testing the UDF from the top voted answer on the same data.
SELECT dbo.udf_GetNumeric(NumTest.String)
FROM dbo.NumTest -- Time to complete: 3 mins 12s
Inspiration for dbo.GetNumbers
Decimals
If you need it to handle decimals, you can use either of the following approaches, I found no noticeable performance differences between them.
change '[0-9]' to '[0-9.]'
change Character LIKE '[0-9]' to ISNUMERIC(Character) = 1 (SQL treats a single decimal point as "numeric")
Bonus
You can easily adapt this to differing requirements by swapping out WHERE Character LIKE '[0-9]' with the following options:
WHERE Letter LIKE '[a-zA-Z]' --Get only letters
WHERE Letter LIKE '[0-9a-zA-Z]' --Remove non-alphanumeric
WHERE Letter LIKE '[^0-9a-zA-Z]' --Get only non-alphanumeric
With the previous queries I get these results:
'AAAA1234BBBB3333' >>>> Output: 1234
'-çã+0!\aº1234' >>>> Output: 0
The code below returns All numeric chars:
1st output: 12343333
2nd output: 01234
declare #StringAlphaNum varchar(255)
declare #Character varchar
declare #SizeStringAlfaNumerica int
declare #CountCharacter int
set #StringAlphaNum = 'AAAA1234BBBB3333'
set #SizeStringAlfaNumerica = len(#StringAlphaNum)
set #CountCharacter = 1
while isnumeric(#StringAlphaNum) = 0
begin
while #CountCharacter < #SizeStringAlfaNumerica
begin
if substring(#StringAlphaNum,#CountCharacter,1) not like '[0-9]%'
begin
set #Character = substring(#StringAlphaNum,#CountCharacter,1)
set #StringAlphaNum = replace(#StringAlphaNum, #Character, '')
end
set #CountCharacter = #CountCharacter + 1
end
set #CountCharacter = 0
end
select #StringAlphaNum
declare #puvodni nvarchar(20)
set #puvodni = N'abc1d8e8ttr987avc'
WHILE PATINDEX('%[^0-9]%', #puvodni) > 0 SET #puvodni = REPLACE(#puvodni, SUBSTRING(#puvodni, PATINDEX('%[^0-9]%', #puvodni), 1), '' )
SELECT #puvodni
A solution for SQL Server 2017 and later, using TRANSLATE:
DECLARE #T table (string varchar(50) NOT NULL);
INSERT #T
(string)
VALUES
('003Preliminary Examination Plan'),
('Coordination005'),
('Balance1000sheet');
SELECT
result =
REPLACE(
TRANSLATE(
T.string COLLATE Latin1_General_CI_AI,
'abcdefghijklmnopqrstuvwxyz',
SPACE(26)),
SPACE(1),
SPACE(0))
FROM #T AS T;
Output:
result
003
005
1000
The code works by:
Replacing characters a-z (ignoring case & accents) with a space
Replacing spaces with an empty string.
The string supplied to TRANSLATE can be expanded to include additional characters.
I did not have rights to create functions but had text like
["blahblah012345679"]
And needed to extract the numbers out of the middle
Note this assumes the numbers are grouped together and not at the start and end of the string.
select substring(column_name,patindex('%[0-9]%', column_name),patindex('%[0-9][^0-9]%', column_name)-patindex('%[0-9]%', column_name)+1)
from table name
Although this is an old thread its the first in google search, I came up with a different answer than what came before. This will allow you to pass your criteria for what to keep within a string, whatever that criteria might be. You can put it in a function to call over and over again if you want.
declare #String VARCHAR(MAX) = '-123. a 456-78(90)'
declare #MatchExpression VARCHAR(255) = '%[0-9]%'
declare #return varchar(max)
WHILE PatIndex(#MatchExpression, #String) > 0
begin
set #return = CONCAT(#return, SUBSTRING(#string,patindex(#matchexpression, #string),1))
SET #String = Stuff(#String, PatIndex(#MatchExpression, #String), 1, '')
end
select (#return)
This UDF will work for all types of strings:
CREATE FUNCTION udf_getNumbersFromString (#string varchar(max))
RETURNS varchar(max)
AS
BEGIN
WHILE #String like '%[^0-9]%'
SET #String = REPLACE(#String, SUBSTRING(#String, PATINDEX('%[^0-9]%', #String), 1), '')
RETURN #String
END
Just a little modification to #Epsicron 's answer
SELECT SUBSTRING(string, PATINDEX('%[0-9]%', string), PATINDEX('%[0-9][^0-9]%', string + 't') - PATINDEX('%[0-9]%',
string) + 1) AS Number
FROM (values ('003Preliminary Examination Plan'),
('Coordination005'),
('Balance1000sheet')) as a(string)
no need for a temporary variable
Firstly find out the number's starting length then reverse the string to find out the first position again(which will give you end position of number from the end). Now if you deduct 1 from both number and deduct it from string whole length you'll get only number length. Now get the number using SUBSTRING
declare #fieldName nvarchar(100)='AAAA1221.121BBBB'
declare #lenSt int=(select PATINDEX('%[0-9]%', #fieldName)-1)
declare #lenEnd int=(select PATINDEX('%[0-9]%', REVERSE(#fieldName))-1)
select SUBSTRING(#fieldName, PATINDEX('%[0-9]%', #fieldName), (LEN(#fieldName) - #lenSt -#lenEnd))
T-SQL function to read all the integers from text and return the one at the indicated index, starting from left or right, also using a starting search term (optional):
create or alter function dbo.udf_number_from_text(
#text nvarchar(max),
#search_term nvarchar(1000) = N'',
#number_position tinyint = 1,
#rtl bit = 0
) returns int
as
begin
declare #result int = 0;
declare #search_term_index int = 0;
if #text is null or len(#text) = 0 goto exit_label;
set #text = trim(#text);
if len(#text) = len(#search_term) goto exit_label;
if len(#search_term) > 0
begin
set #search_term_index = charindex(#search_term, #text);
if #search_term_index = 0 goto exit_label;
end;
if #search_term_index > 0
if #rtl = 0
set #text = trim(right(#text, len(#text) - #search_term_index - len(#search_term) + 1));
else
set #text = trim(left(#text, #search_term_index - 1));
if len(#text) = 0 goto exit_label;
declare #patt_number nvarchar(10) = '%[0-9]%';
declare #patt_not_number nvarchar(10) = '%[^0-9]%';
declare #number_start int = 1;
declare #number_end int;
declare #found_numbers table (id int identity(1,1), val int);
while #number_start > 0
begin
set #number_start = patindex(#patt_number, #text);
if #number_start > 0
begin
if #number_start = len(#text)
begin
insert into #found_numbers(val)
select cast(substring(#text, #number_start, 1) as int);
break;
end;
else
begin
set #text = right(#text, len(#text) - #number_start + 1);
set #number_end = patindex(#patt_not_number, #text);
if #number_end = 0
begin
insert into #found_numbers(val)
select cast(#text as int);
break;
end;
else
begin
insert into #found_numbers(val)
select cast(left(#text, #number_end - 1) as int);
if #number_end = len(#text)
break;
else
begin
set #text = trim(right(#text, len(#text) - #number_end));
if len(#text) = 0 break;
end;
end;
end;
end;
end;
if #rtl = 0
select #result = coalesce(a.val, 0)
from (select row_number() over (order by m.id asc) as c_row, m.val
from #found_numbers as m) as a
where a.c_row = #number_position;
else
select #result = coalesce(a.val, 0)
from (select row_number() over (order by m.id desc) as c_row, m.val
from #found_numbers as m) as a
where a.c_row = #number_position;
exit_label:
return #result;
end;
Example:
select dbo.udf_number_from text(N'Text text 10 text, 25 term', N'term',2,1);
returns 10;
This is one of the simplest and easiest one. This will work on the entire String for multiple occurences as well.
CREATE FUNCTION dbo.fn_GetNumbers(#strInput NVARCHAR(500))
RETURNS NVARCHAR(500)
AS
BEGIN
DECLARE #strOut NVARCHAR(500) = '', #intCounter INT = 1
WHILE #intCounter <= LEN(#strInput)
BEGIN
SELECT #strOut = #strOut + CASE WHEN SUBSTRING(#strInput, #intCounter, 1) LIKE '[0-9]' THEN SUBSTRING(#strInput, #intCounter, 1) ELSE '' END
SET #intCounter = #intCounter + 1
END
RETURN #strOut
END
Following a solution using a single common table expression (CTE).
DECLARE #s AS TABLE (id int PRIMARY KEY, value nvarchar(max));
INSERT INTO #s
VALUES
(1, N'003Preliminary Examination Plan'),
(2, N'Coordination005'),
(3, N'Balance1000sheet');
SELECT * FROM #s ORDER BY id;
WITH t AS (
SELECT
id,
1 AS i,
SUBSTRING(value, 1, 1) AS c
FROM
#s
WHERE
LEN(value) > 0
UNION ALL
SELECT
t.id,
t.i + 1 AS i,
SUBSTRING(s.value, t.i + 1, 1) AS c
FROM
t
JOIN #s AS s ON t.id = s.id
WHERE
t.i < LEN(s.value)
)
SELECT
id,
STRING_AGG(c, N'') WITHIN GROUP (ORDER BY i ASC) AS value
FROM
t
WHERE
c LIKE '[0-9]'
GROUP BY
id
ORDER BY
id;
DECLARE #index NVARCHAR(20);
SET #index = 'abd565klaf12';
WHILE PATINDEX('%[0-9]%', #index) != 0
BEGIN
SET #index = REPLACE(#index, SUBSTRING(#index, PATINDEX('%[0-9]%', #index), 1), '');
END
SELECT #index;
One can replace [0-9] with [a-z] if numbers only are wanted with desired castings using the CAST function.
If we use the User Define Function, the query speed will be greatly reduced. This code extracts the number from the string....
SELECT
Reverse(substring(Reverse(rtrim(ltrim( substring([FieldName] , patindex('%[0-9]%', [FieldName] ) , len([FieldName]) )))) , patindex('%[0-9]%', Reverse(rtrim(ltrim( substring([FieldName] , patindex('%[0-9]%', [FieldName] ) , len([FieldName]) )))) ), len(Reverse(rtrim(ltrim( substring([FieldName] , patindex('%[0-9]%', [FieldName] ) , len([FieldName]) ))))) )) NumberValue
FROM dbo.TableName
CREATE OR REPLACE FUNCTION count_letters_and_numbers(input_string TEXT)
RETURNS TABLE (letters INT, numbers INT) AS $$
BEGIN
RETURN QUERY SELECT
sum(CASE WHEN input_string ~ '[A-Za-z]' THEN 1 ELSE 0 END) as letters,
sum(CASE WHEN input_string ~ '[0-9]' THEN 1 ELSE 0 END) as numbers
FROM unnest(string_to_array(input_string, '')) as input_string;
END;
$$ LANGUAGE plpgsql;
For the hell of it...
This solution is different to all earlier solutions, viz:
There is no need to create a function
There is no need to use pattern matching
There is no need for a temporary table
This solution uses a recursive common table expression (CTE)
But first - note the question does not specify where such strings are stored. In my solution below, I create a CTE as a quick and dirty way to put these strings into some kind of "source table".
Note also - this solution uses a recursive common table expression (CTE) - so don't get confused by the usage of two CTEs here. The first is simply to make the data avaliable to the solution - but it is only the second CTE that is required in order to solve this problem. You can adapt the code to make this second CTE query your existing table, view, etc.
Lastly - my coding is verbose, trying to use column and CTE names that explain what is going on and you might be able to simplify this solution a little. I've added in a few pseudo phone numbers with some (expected and atypical, as the case may be) formatting for the fun of it.
with SOURCE_TABLE as (
select '003Preliminary Examination Plan' as numberString
union all select 'Coordination005' as numberString
union all select 'Balance1000sheet' as numberString
union all select '1300 456 678' as numberString
union all select '(012) 995 8322 ' as numberString
union all select '073263 6122,' as numberString
),
FIRST_CHAR_PROCESSED as (
select
len(numberString) as currentStringLength,
isNull(cast(try_cast(replace(left(numberString, 1),' ','z') as tinyint) as nvarchar),'') as firstCharAsNumeric,
cast(isNull(cast(try_cast(nullIf(left(numberString, 1),'') as tinyint) as nvarchar),'') as nvarchar(4000)) as newString,
cast(substring(numberString,2,len(numberString)) as nvarchar) as remainingString
from SOURCE_TABLE
union all
select
len(remainingString) as currentStringLength,
cast(try_cast(replace(left(remainingString, 1),' ','z') as tinyint) as nvarchar) as firstCharAsNumeric,
cast(isNull(newString,'') as nvarchar(3999)) + isNull(cast(try_cast(nullIf(left(remainingString, 1),'') as tinyint) as nvarchar(1)),'') as newString,
substring(remainingString,2,len(remainingString)) as remainingString
from FIRST_CHAR_PROCESSED fcp2
where fcp2.currentStringLength > 1
)
select
newString
,* -- comment this out when required
from FIRST_CHAR_PROCESSED
where currentStringLength = 1
So what's going on here?
Basically in our CTE we are selecting the first character and using try_cast (see docs) to cast it to a tinyint (which is a large enough data type for a single-digit numeral). Note that the type-casting rules in SQL Server say that an empty string (or a space, for that matter) will resolve to zero, so the nullif is added to force spaces and empty strings to resolve to null (see discussion) (otherwise our result would include a zero character any time a space is encountered in the source data).
The CTE also returns everything after the first character - and that becomes the input to our recursive call on the CTE; in other words: now let's process the next character.
Lastly, the field newString in the CTE is generated (in the second SELECT) via concatenation. With recursive CTEs the data type must match between the two SELECT statements for any given column - including the column size. Because we know we are adding (at most) a single character, we are casting that character to nvarchar(1) and we are casting the newString (so far) as nvarchar(3999). Concatenated, the result will be nvarchar(4000) - which matches the type casting we carry out in the first SELECT.
If you run this query and exclude the WHERE clause, you'll get a sense of what's going on - but the rows may be in a strange order. (You won't necessarily see all rows relating to a single input value grouped together - but you should still be able to follow).
Hope it's an interesting option that may help a few people wanting a strictly expression-based solution.
In Oracle
You can get what you want using this:
SUBSTR('ABCD1234EFGH',REGEXP_INSTR ('ABCD1234EFGH', '[[:digit:]]'),REGEXP_COUNT ('ABCD1234EFGH', '[[:digit:]]'))
Sample Query:
SELECT SUBSTR('003Preliminary Examination Plan ',REGEXP_INSTR ('003Preliminary Examination Plan ', '[[:digit:]]'),REGEXP_COUNT ('003Preliminary Examination Plan ', '[[:digit:]]')) SAMPLE1,
SUBSTR('Coordination005',REGEXP_INSTR ('Coordination005', '[[:digit:]]'),REGEXP_COUNT ('Coordination005', '[[:digit:]]')) SAMPLE2,
SUBSTR('Balance1000sheet',REGEXP_INSTR ('Balance1000sheet', '[[:digit:]]'),REGEXP_COUNT ('Balance1000sheet', '[[:digit:]]')) SAMPLE3 FROM DUAL
If you are using Postgres and you have data like '2000 - some sample text' then try substring and position combination, otherwise if in your scenario there is no delimiter, you need to write regex:
SUBSTRING(Column_name from 0 for POSITION('-' in column_name) - 1) as
number_column_name
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
T-SQL: Opposite to string concatenation - how to split string into multiple records
Splitting variable length delimited string across multiple rows (SQL)
I have a database table that contains column data like this:
Data (field name)
1111,44,666,77
22,55,76,54
32,31,56
I realise this is a very poor design because it is not normalised (I didn't design it - I inherited it). Is there a query that will return the data like this:
1111
44
666
77
22
55
76
54
32
31
56
I am use to using CHARINDEX and SUBSTRING, but I cannot think of a way of doing this as the number of elements in each cell (delimited by a comma) is unknown.
You can use CTE to split the data:
;with cte (DataItem, Data) as
(
select cast(left(Data, charindex(',',Data+',')-1) as varchar(50)) DataItem,
stuff(Data, 1, charindex(',',Data+','), '') Data
from yourtable
union all
select cast(left(Data, charindex(',',Data+',')-1) as varchar(50)) DataItem,
stuff(Data, 1, charindex(',',Data+','), '') Data
from cte
where Data > ''
)
select DataItem
from cte
See SQL Fiddle with Demo
Result:
| DATAITEM |
------------
| 1111 |
| 22 |
| 32 |
| 31 |
| 56 |
| 55 |
| 76 |
| 54 |
| 44 |
| 666 |
| 77 |
Or you can create a split function:
create FUNCTION [dbo].[Split](#String varchar(MAX), #Delimiter char(1))
returns #temptable TABLE (items varchar(MAX))
as
begin
declare #idx int
declare #slice varchar(8000)
select #idx = 1
if len(#String)<1 or #String is null return
while #idx!= 0
begin
set #idx = charindex(#Delimiter,#String)
if #idx!=0
set #slice = left(#String,#idx - 1)
else
set #slice = #String
if(len(#slice)>0)
insert into #temptable(Items) values(#slice)
set #String = right(#String,len(#String) - #idx)
if len(#String) = 0 break
end
return
end;
Which you can use when you query and this will produce the same result:
select s.items declaration
from yourtable t1
outer apply dbo.split(t1.data, ',') s
I created a table called [dbo].[stack] and filled it with the data you provided and this script produced what you needed. There may be a more efficient way of doing this but this works exactly how you requested.
BEGIN
DECLARE #tmp TABLE (data VARCHAR(20))
DECLARE #tmp2 TABLE (data VARCHAR(20))
--Insert all fields from your table
INSERT INTO #tmp (data)
SELECT [data]
FROM [dbo].[stack] -- your table name here
--Loop through all the records in temp table
WHILE EXISTS (SELECT 1
FROM #tmp)
BEGIN
DECLARE #data VARCHAR(100) --Variable to chop up
DECLARE #data1 VARCHAR(100) -- Untouched variable to delete from tmp table
SET #data = (SELECT TOP 1 [data]
FROM #tmp)
SET #data1 = (SELECT TOP 1 [data]
FROM #tmp)
--Loop through variable to get individual value
WHILE PATINDEX('%,%',#data) > 0
BEGIN
INSERT INTO #tmp2
SELECT SUBSTRING(#data,1,PATINDEX('%,%',#data)-1);
SET #data = SUBSTRING(#data,PATINDEX('%,%',#data)+1,LEN(#data))
IF PATINDEX('%,%',#data) = 0
INSERT INTO #tmp2
SELECT #data
END
DELETE FROM #tmp
WHERE [data] = #data1
END
SELECT * FROM #tmp2
END
Not talking about performance, you can concatenate the data in a single column and then split it.
Concatenate data: http://sqlfiddle.com/#!6/487a4/3
Split it: T-SQL: Opposite to string concatenation - how to split string into multiple records
Take a look at this article referenced in a similar question:
http://www.codeproject.com/Articles/7938/SQL-User-Defined-Function-to-Parse-a-Delimited-Str
If you create the function that they have in that article, you can call it using:
select * from dbo.fn_ParseText2Table('100|120|130.56|Yes|Cobalt Blue','|')
SELECT REPLACE(field_name, ',', ' ') from table
EDIT: Never mind this answer as you changed your question.