I have two tables, in one I have data like this:
id description
2 12.07.13y 1000eur to bank account KZ21321o0002134
4 To bank account KZasd9093636 12 of May 2016y 200dusd
And I have a second table where I need to put filtered information from table first like:
id
data
bank_account
tranfered_money
First i need to split description,then i need to recognize ban_account which always started with "KZ",data and transfered_money
This is just awful but seems to be able to extract the ban_acount:
CREATE TABLE exp
(
column1 varchar(400)
);
Insert into exp (column1) values ('12.07.13y 1000eur to bank account KZ21321o0002134');
Insert into exp (column1) values ('To bank account KZasd9093636 12 of May 2016y 200dusd');
Select
CASE
WHEN CHARINDEX ( SPACE(1), SUBSTRING ( column1, CHARINDEX('KZ' , column1),LEN(column1))) = 0
THEN SUBSTRING ( column1, CHARINDEX('KZ' , column1),LEN(column1))
ELSE SUBSTRING ( SUBSTRING (column1, CHARINDEX('KZ' , column1),LEN(column1)), 0, CHARINDEX (SPACE(1), SUBSTRING(column1, CHARINDEX('KZ' , column1),LEN(column1))))
END result
From exp
At first convert your table to XML.
Then create table with month/weekdays names and digits from 1 to 3000 (or you can take 2016 as current year)
You will need a table with currency. I made one based on data from here.
DECLARE #x xml
;WITH YourTable AS ( --I use this CTE, you should use your table in scripts below
SELECT *
FROM (VALUES
(2, '12.07.13y 1000eur to bank account KZ21321o0002134'),
(4, 'To bank account KZasd9093636 12 of May 2016y 200dusd')
) as t(id, [description])
)
SELECT #x = ( --XML sample that we get you can see below after output
SELECT CAST(N'<row id="'+CAST(id as nvarchar(max))+'"><b>'+REPLACE([description],' ','</b><b>')+'</b></row>' as xml)
FROM YourTable
FOR XML PATH('')
)
;WITH CurrencyList AS ( --Currency table
SELECT *
FROM (VALUES
('AED', 'United Arab Emirates Dirham'),
('AFN', 'Afghanistan Afghani'),
('ALL', 'Albania Lek'),
('AMD', 'Armenia Dram'),
...
('ZAR', 'South Africa Rand'),
('ZMW', 'Zambia Kwacha'),
('ZWD', 'Zimbabwe Dollar')
) as t(code, countryname)
),cte AS ( --generate numbers 1 to 3000
SELECT 0 as d
UNION ALL
SELECT d+1
FROM cte
WHERE d < 3000
), datenames AS ( --generate datenames
SELECT d,
CASE WHEN d < 7 THEN DATENAME(weekday,DATEADD(day,d,'1970-01-01 00:00:00.000')) ELSE NULL END as weekday_name,
CASE WHEN d < 12 THEN DATENAME(month,DATEADD(month,d,'1970-01-01 00:00:00.000')) ELSE NULL END as mon_name
FROM cte
)
--Final query
SELECT t.c.value('../#id','int') as id,
t.c.value('.','nvarchar(max)') as str_part,
CASE WHEN t.c.value('.','nvarchar(max)') LIKE 'KZ%' THEN 'bank_account'
WHEN countryname IS NOT NULL THEN 'tranfered_money'
WHEN dn.d IS NOT NULL OR RIGHT(t.c.value('.','nvarchar(max)'),1) ='y' THEN 'datepart'
ELSE NULL END as what_is
FROM #x.nodes('/row/b') as t(c)
LEFT JOIN CurrencyList cl
ON RIGHT(t.c.value('.','nvarchar(max)'),3) = cl.code --check 3 last symbols of string with currency codes
LEFT JOIN datenames dn
ON dn.d = t.c.value('. cast as xs:int?','int') -- if it is a day/month/year number
OR t.c.value('.','nvarchar(max)') = dn.weekday_name -- or it is a week day name
OR t.c.value('.','nvarchar(max)') = dn.mon_name --or month name
OPTION (MAXRECURSION 0)
Will bring you:
id str_part what_is
2 12.07.13y datepart
2 1000eur tranfered_money
2 to NULL
2 bank NULL
2 account NULL
2 KZ21321o0002134 bank_account
4 To NULL
4 bank NULL
4 account NULL
4 KZasd9093636 bank_account
4 12 datepart
4 of NULL
4 May datepart
4 2016y datepart
4 200dusd tranfered_money
After that you need to bring dates in normal date form and that is all.
XML Sample:
<row id="2">
<b>12.07.13y</b>
<b>1000eur</b>
<b>to</b>
<b>bank</b>
<b>account</b>
<b>KZ21321o0002134</b>
</row>
<row id="4">
<b>To</b>
<b>bank</b>
<b>account</b>
<b>KZasd9093636</b>
<b>12</b>
<b>of</b>
<b>May</b>
<b>2016y</b>
<b>200dusd</b>
</row>
Related
I have a table in the following format
ProjectID LocationID
1 [1,2,3,4]
2 [2,3]
Can I split the data in the column LocationID into multiple rows like below?
ProjectID LocationID
1 1
1 2
1 3
1 4
2 2
2 3
I need to get the data loaded to Power-Bi using the SQL only.
is it possible?
If data type of locationID is varchar then:
create table projects (ProjectID int, LocationID varchar(50));
insert into projects values(1, '[1,2,3,4]');
insert into projects values(2, '[2,3]');
Query:
select projectid, value
from projects
CROSS APPLY STRING_SPLIT(replace(replace(locationid,'[',''),']',''),',')
Output:
projectid
value
1
1
1
2
1
3
1
4
2
2
2
3
db<fiddle here
Solution for SQL Server 2014
create table projects (ProjectID int, LocationID nvarchar(max));
insert into projects values(1, '[1,2,3,4]');
insert into projects values(2, '[2,3]');
Query:
WITH tmp AS
(
SELECT
ProjectID,
LEFT(replace(replace(locationid,'[',''),']',''), CHARINDEX(',', replace(replace(locationid,'[',''),']','') + ',') - 1) LocationID,
STUFF(replace(replace(locationid,'[',''),']',''), 1, CHARINDEX(',', replace(replace(locationid,'[',''),']','') + ','), '') b
FROM projects
UNION all
SELECT
ProjectID,
LEFT(b, CHARINDEX(',', b + ',') - 1),
STUFF(b, 1, CHARINDEX(',', b + ','), '')
FROM tmp
WHERE
b > ''
)
SELECT
ProjectID, LocationID
FROM tmp
ORDER BY projectid
Output:
ProjectID
LocationID
1
1
1
2
1
3
1
4
2
2
2
3
db<fiddle here
In SQL Server 2014, you can use a recursive CTE -- which Kazi also proposes. I think this is a slightly simpler version:
with cte as (
select projectId, convert(varchar(max), null) as locationid,
convert(varchar(max), substring(LocationId, 2, len(locationId) - 2)) + ',' as rest
from t
union all
select projectId,
left(rest, charindex(',', rest) - 1),
stuff(rest, 1, charindex(',', rest), '')
from cte
where rest <> ''
)
select projectid, locationid
from cte
where locationid is not null;
Here is a db<>fiddle.
In particular, the anchor part just sets up the data -- it does not extract any elements from the string. So, all the logic is in the recursive part, which I find easier to maintain.
I used STRING_SPLIT() which is a table valued function supports SQL server 2016 and higher versions. You need to provide the formatted string into this function and use cross apply to join and generate the desired output.
SELECT
projectID
, REPLACE(REPLACE(locationId,'[',''),']','') as [locationid]
INTO #temp_table
FROM split_string -- Add your table name here
SELECT
projectID
,VALUE [locationid]
FROM #temp_table
CROSS APPLY STRING_SPLIT( [locationid] , ',');
Thanks everyone for helping me out. This solution works for my scenario.
Select projectID,locationid_list from project
CROSS APPLY OPENJSON(locationid, '$') WITH (locationid_list int '$')
I have a table with a column which represent hierarchy path, so when i execute the SQL query
select hierachypath from mytable where id=10
for a particular row i will get the result like this
hieracheypath
--------------
1,2,3,4,5,6,7,8,9,10
select hierachypath from mytable where id=10
I want to get a result like
1,2,3,4,5,6,7,8,9,10
1,1,2,3,4,5,6,7,8,9
1,2,3,4,5,6,7,8
1,2,3,4,5,6,7
1,2,3,4,5,6
1,2,3,4,5
1,2,3,4
1,2,3
1,2
1
OR
1
1,2
1,2,3
1,2,3,4
1,2,3,4,5
1,2,3,4,5,6
1,2,3,4,5,6,7
1,2,3,4,5,6,7,8
1,2,3,4,5,6,7,8,9
1,2,3,4,5,6,7,8,9,10
I had try this way
Declare #heiracheypath nvarchar(4000) ='1,2,3,4,5,6,7,8,9,10'
declare #Result TABLE (Column1 VARCHAR(100))
Declare #tcount int
SELECT #tcount=(len(#heiracheypath) - LEN(REPLACE(#heiracheypath,',','')) + 1)
DECLARE #IntLocation INT
WHILE (CHARINDEX(',', #heiracheypath, 0) > 0)
BEGIN
SET #IntLocation = CHARINDEX(',', #heiracheypath, 0)
INSERT INTO #Result (Column1)
--LTRIM and RTRIM to ensure blank spaces are removed
SELECT RTRIM(LTRIM(SUBSTRING(#heiracheypath, 0, #IntLocation)))
SET #heiracheypath = STUFF(#heiracheypath, 1, #IntLocation, '')
END
INSERT INTO #Result (Column1)
SELECT RTRIM(LTRIM(#heiracheypath))--LTRIM and RTRIM to ensure blank spaces are removed
select * from #Result
but the result was
Column1
-------
1
2
3
4
5
6
7
8
9
10
The code in the question Looks like T-SQL - so here's a simple solution without common table expressions:
DECLARE #heiracheypath nvarchar(4000) ='1,2,3,4,5,6,7,8,9,10';
SELECT SUBSTRING(#heiracheypath, 1, ci-1) As Paths
FROM
(
SELECT CHARINDEX(',',#heiracheypath, N) As ci
FROM
(
SELECT TOP(LEN(#heiracheypath)) ROW_NUMBER() OVER(ORDER BY ##SPID) As N
FROM sys.objects A
) AS Tally
UNION
SELECT LEN(#heiracheypath) + 1
) As CommaIndexes
WHERE ci > 0
ORDER BY ci
The Tally derived table contains numbers from 1 to the length of the value,
the CommaIndexes table contains the distinct indexes of each comma in the value,
the union part is to also return the full string,
and the outer most select statement simply use substring to return the relevant parts of the string.
This could be simplified further by combining the tally derived table with the commaIndexs derived table:
SELECT SUBSTRING(#heiracheypath, 1, ci-1) As Paths
FROM
(
SELECT TOP(LEN(#heiracheypath)) CHARINDEX(',',#heiracheypath, ROW_NUMBER() OVER(ORDER BY ##SPID)) As ci
FROM sys.objects A
UNION SELECT LEN(#heiracheypath) + 1
) As CommaIndexes
WHERE ci > 0
ORDER BY ci
Result:
Paths
1
1,2
1,2,3
1,2,3,4
1,2,3,4,5
1,2,3,4,5,6
1,2,3,4,5,6,7
1,2,3,4,5,6,7,8
1,2,3,4,5,6,7,8,9
1,2,3,4,5,6,7,8,9,10
Say I have the following data set
Column1 (VarChar(50 or something))
Elias
Sails
Pails
Plane
Games
What I'd like to produce from this column is the following set:
LETTER COUNT
E 3
L 4
I 3
A 5
S 5
And So On...
One solution I thought of was combining all strings into a single string, and then count each instance of the letter in that string, but that feels sloppy.
This is more an exercise of curiosity than anything else, but, is there a way to get a count of all distinct letters in a dataset with SQL?
I would do this by creating a table of your letters similar to:
CREATE TABLE tblLetter
(
letter varchar(1)
);
INSERT INTO tblLetter ([letter])
VALUES
('a'),
('b'),
('c'),
('d'); -- etc
Then you could join the letters to your table where your data is like the letter:
select l.letter, count(n.col) Total
from tblLetter l
inner join names n
on n.col like '%'+l.letter+'%'
group by l.letter;
See SQL Fiddle with Demo. This would give a result:
| LETTER | TOTAL |
|--------|-------|
| a | 5 |
| e | 3 |
| g | 1 |
| i | 3 |
| l | 4 |
| m | 1 |
| p | 2 |
| s | 4 |
If you create a table of letters, like this:
create table letter (ch char(1));
insert into letter(ch) values ('A'),('B'),('C'),('D'),('E'),('F'),('G'),('H')
,('I'),('J'),('K'),('L'),('M'),('N'),('O'),('P')
,('Q'),('R'),('S'),('T'),('U'),('V'),('W'),('X'),('Y'),('Z');
you could do it with a cross join, like this:
select ch, SUM(len(str) - len(replace(str,ch,'')))
from letter
cross join test -- <<== test is the name of the table with the string
group by ch
having SUM(len(str) - len(replace(str,ch,''))) <> 0
Here is a running demo on sqlfiddle.
You can do it without defining a table by embedding a list of letters into a query itself, but the idea of cross-joining and grouping by the letter would remain the same.
Note: see this answer for the explanation of the expression inside the SUM.
To me, this is a problem almost tailored for a CTE (Thanks, Nicholas Carey, for the original, my fiddle here: http://sqlfiddle.com/#!3/44f77/8):
WITH cteLetters
AS
(
SELECT
1 AS CharPos,
str,
MAX(LEN(str)) AS MaxLen,
SUBSTRING(str, 1, 1) AS Letter
FROM
test
GROUP BY
str,
SUBSTRING(str, 1, 1)
UNION ALL
SELECT
CharPos + 1,
str,
MaxLen,
SUBSTRING(str, CharPos + 1, 1) AS Letter
FROM
cteLetters
WHERE
CharPos + 1 <= MaxLen
)
SELECT
UPPER(Letter) AS Letter,
COUNT(*) CountOfLetters
FROM
cteLetters
GROUP BY
Letter
ORDER BY
Letter;
Use the CTE to calculate character positions and deconstruct each string. Then you can just aggregate from the CTE itself. No need for additional tables or anything.
This should work even if you have case sensitivity turned on.
The setup:
CREATE TABLE _test ( Column1 VARCHAR (50) )
INSERT _test (Column1) VALUES ('Elias'),('Sails'),('Pails'),('Plane'),('Games')
The work:
DECLARE #counter AS INT
DECLARE #results TABLE (LETTER VARCHAR(1),[COUNT] INT)
SET #counter=65 --ascii value for 'A'
WHILE ( #counter <=90 ) -- ascii value for 'Z'
BEGIN
INSERT #results (LETTER,[COUNT])
SELECT CHAR(#counter),SUM(LEN(UPPER(Column1)) - LEN(REPLACE(UPPER(Column1), CHAR(#counter),''))) FROM _test
SET #counter=#counter+1
END
SELECT * FROM #results WHERE [Count]>0
It's often useful to have a range or sequence table that gives you a source of large runs of contiguous sequential numbers, like this one covering the range -100,000–+100,000.
drop table dbo.range
go
create table dbo.range
(
id int not null primary key clustered ,
)
go
set nocount on
go
declare #i int = -100000
while ( #i <= +100000 )
begin
if ( #i > 0 and #i % 1000 = 0 ) print convert(varchar,#i) + ' rows'
insert dbo.range values ( #i )
set #i = #i + 1
end
go
set nocount off
go
Once you have such a table, you can do something like this:
select character = substring( t.some_column , r.id , 1 ) ,
frequency = count(*)
from dbo.some_table t
join dbo.range r on r.id between 1 and len( t.some_column )
group by substring( t.some_column , r.id , 1 )
order by 1
If you want to ensure case-insensitivity, just mix in the desired upper() or lower():
select character = upper( substring( t.some_column , r.id , 1 ) ) ,
frequency = count(*)
from dbo.some_table t
join dbo.range r on r.id between 1 and len( t.some_column )
group by upper( substring( t.some_column , r.id , 1 ) )
order by 1
Given your sample data:
create table dbo.some_table
(
some_column varchar(50) not null
)
go
insert dbo.some_table values ( 'Elias' )
insert dbo.some_table values ( 'Sails' )
insert dbo.some_table values ( 'Pails' )
insert dbo.some_table values ( 'Plane' )
insert dbo.some_table values ( 'Games' )
go
The latter query above produces the following results:
character frequency
A 5
E 3
G 1
I 3
L 4
M 1
N 1
P 2
S 5
I have a varchar column in one of my tables with data like:
1234abc
1234abcde456757
1234abc Supervisor
1234abc456 Administrator
I want to "clean it" by removing any letters and numbers immediately following them so for the above examples I want to have:
1234
1234
1234 Supervisor
1234 Administrator
In another word, I want to keep the initial number and the last word. I'm using the SUBSTRING and CHARINDEX but those functions remove everything till the end of the string and I don't know the length of the part I need to remove.
Any suggestions?
Thanks
You could search for the first non-digit and the first space in a subquery. That also works if the number of digits isn't exactly four:
declare #t table (col1 varchar(50))
insert into #t select '12abc'
union all select '1234abcde456757'
union all select '1234abc Supervisor'
union all select '1234abc456 Administrator'
union all select '123456abc456 Administrator'
select case when FirstNonDigit = 0 then col1
when FirstSpace = 0 then substring(col1, 1, FirstNonDigit-1)
else substring(col1, 1, FirstNonDigit-1) +
substring(col1, FirstSpace, len(col1) - FirstSpace + 1)
end
from (
select patindex('%[^0-9]%', col1) FirstNonDigit
, patindex('% %', col1) FirstSpace
, col1
from #t
) subqueryalias
-->
12
1234
1234 Supervisor
1234 Administrator
123456 Administrator
try this:
DECLARE #YourTable table (RowValue varchar(50))
INSERT #YourTable VALUES ('1234abc')
INSERT #YourTable VALUES ('1234abcde456757')
INSERT #YourTable VALUES ('1234abc Supervisor')
INSERT #YourTable VALUES ('1234abc456 Administrator')
UPDATE #YourTable
SET RowValue=LEFT(RowValue,4)+RIGHT(RowValue,CHARINDEX(' ',REVERSE(RowValue)))
FROM #YourTable
SELECT * FROM #YourTable
OUTPUT:
RowValue
--------------------------------------------------
1234
1234
1234 Supervisor
1234 Administrator
(4 row(s) affected)
EDIT: set based any number of digits and handles no digits or no words
DECLARE #YourTable table (RowValue varchar(50))
set nocount on
INSERT #YourTable VALUES ('13')
INSERT #YourTable VALUES ('1234abc')
INSERT #YourTable VALUES ('1234abc')
INSERT #YourTable VALUES ('1234abcde456757')
INSERT #YourTable VALUES ('1234abc Supervisor')
INSERT #YourTable VALUES ('1234abc456 Administrator')
INSERT #YourTable VALUES ('1234567abc456 Administrator')
INSERT #YourTable VALUES ('Administrator')
INSERT #YourTable VALUES ('abcde Administrator')
set nocount off
;WITH Digits AS
(SELECT 0 AS Digit UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9
)
,Numbers AS
(SELECT 1 AS Number
UNION ALL
SELECT Number+1 FROM Numbers where Number<1000
)
,FindDigits AS
(
SELECT
y.RowValue,n.Number,SUBSTRING(y.RowValue,n.Number,1) AS CharOf,CASE WHEN SUBSTRING(y.RowValue,n.Number,1) LIKE '[0-9]' THEN 'N' ELSE 'A' END AS TypeOf
FROM #YourTable y
INNER JOIN Numbers n ON 1=1
WHERE n.Number<=LEN(y.RowValue)
)
,LenOf AS
(
SELECT
RowValue,MIN(Number)-1 AS Digits
FROM FindDigits
WHERE TypeOf='A'
GROUP BY RowValue
HAVING MIN(Number)-1>0
UNION
SELECT
f.RowValue,LEN(f.RowValue)
FROM FindDigits f
WHERE NOT EXISTS (SELECT 1 FROM FindDigits f2 WHERE f.RowValue=f2.RowValue AND TypeOf='A')
)
UPDATE y
SET RowValue=CASE WHEN l.Digits IS NOT NULL THEN LEFT(y.RowValue,l.Digits)+RIGHT(y.RowValue,CHARINDEX(' ',REVERSE(y.RowValue)))
WHEN CHARINDEX(' ',REVERSE(y.RowValue))=0 THEN y.RowValue
ELSE RIGHT(y.RowValue,CHARINDEX(' ',REVERSE(y.RowValue))-1) END
FROM #YourTable y
LEFT JOIN LenOf l ON y.RowValue=l.RowValue
OPTION (MAXRECURSION 1000)
SELECT * FROM #YourTable
OUTPUT:
RowValue
--------------------------------------------------
13
1234
1234
1234
1234 Supervisor
1234 Administrator
1234567 Administrator
Administrator
Administrator
(9 row(s) affected)
You actually want two strings, the characters at indices 0-3 and those from the position after the space till the end of the string. I (think) this will work (have not tried it):
UPDATE TableName SET ColumnName = SUBSTRING(ColumnName,1,4) +
SUBSTRING(ColumnName,CHARINDEX(' ',ColumnName)+1,LEN(ColumnName))
The code below uses a "tally table" of values to find the first non-numeric character and the last space. KM's solution using PATINDEX is probably more elegant!
DECLARE #t TABLE
(
c VARCHAR(MAX)
);
INSERT INTO #t VALUES('1234abc');
INSERT INTO #t VALUES('1234abcde456757');
INSERT INTO #t VALUES('1234abc Supervisor');
INSERT INTO #t VALUES('1234abc456 Administrator');
WITH Tally AS
(
SELECT ROW_NUMBER() OVER (ORDER BY s1.[id]) AS i
FROM sys.sysobjects s1 CROSS JOIN sys.sysobjects s2 CROSS JOIN sys.sysobjects s3
),
NumPart AS
(
SELECT c, MIN(i) AS firstNonNumber
FROM #t CROSS JOIN Tally
WHERE i <= LEN(c)
AND SUBSTRING(c, i, 1) < '0' OR SUBSTRING(c, i, 1) > '9'
GROUP BY c
),
SpacePart AS
(
SELECT c, MAX(i) AS spacePos
FROM #t t CROSS JOIN Tally
WHERE i <= LEN(c)
AND SUBSTRING(c, i, 1) = ' '
GROUP BY c
)
UPDATE t
SET t.c = LEFT(n.c, n.firstNonNumber - 1) +
CASE WHEN ISNULL(s.SpacePos, 0) > 0 THEN
RIGHT(n.c, LEN(n.c) - s.SpacePos + 1)
ELSE
''
END
FROM #t t
INNER JOIN NumPart n ON t.c = n.c
LEFT JOIN SpacePart s ON n.c = s.c;
SELECT * FROM #t;
I have the following SQL query:
SELECT DISTINCT ProductNumber, PageNumber FROM table
I am trying to modify the query so that PageNumber will be formatted. You see, PageNumber is in any of the following formats, where 'x' is a digit:
xxx, xxx
xxx
xxx-xxx
xx, xxx-xxx
xx-xx, xxx
xx-xx, xxx-xxx
I want to format PageNumber so that it is only in the format: xxx. To do so, I have parse out the following bolded numbers from the above formats:
xxx, xxx
xxx
xxx-xxx
xx, xxx-xxx
xx-xx, xxx
xx-xx, xxx-xxx
I want to do this all without writing any functions, but I don't know if that is possible. I am having trouble "detecting" all of the different formats, though:
Here is what I have so far:
SELECT ProductNumber,
CASE WHEN CHARINDEX(',', PageNumber) > 0
THEN SUBSTRING(PageNumber, 0, CHARINDEX('-', PageNumber))
WHEN CHARINDEX('-', PageNumber) > 0
THEN SUBSTRING(PageNumber, 0, CHARINDEX('-', PageNumber))
ELSE PageNumber
END AS PageNumber
FROM table
WHERE PageNumber IS NOT NULL
AND PageNumber <> ''
Can anyone offer me some help? Thanks!
Use pattern matching rather than CHARINDEX
CASE also forces ordering of evaluation which helps here for the 3rd case which overlaps with the first 2 cases.
Not tested, something like
CASE
WHEN PageNumber LIKE '[0-9][0-9][0-9]%' THEN LEFT(PageNumber, 3)
WHEN PageNumber LIKE '[0-9][0-9]-[0-9][0-9], [0-9][0-9][0-9]') THEN RIGHT(PageNumber , 3)
WHEN PageNumber LIKE '[0-9][0-9]%') THEN LEFT(PageNumber, 2)
END
try this:
DECLARE #YourTable table (ProductNumber int, PageNumber varchar(20))
INSERT #YourTable VALUES (1,'123, 456')
INSERT #YourTable VALUES (2,'123')
INSERT #YourTable VALUES (3,'123-456')
INSERT #YourTable VALUES (4,'12, 345-678')
INSERT #YourTable VALUES (5,'12-34, 567')
INSERT #YourTable VALUES (6,'12-34, 567-789')
;WITH AllNumbers AS ---builds a Numbers table 1-100
( SELECT 1 AS Number
UNION ALL
SELECT Number+1
FROM AllNumbers
WHERE Number<101
)
, RowChars AS --one row for each non-numeric single character value per #YourTable row
( SELECT DISTINCT
ProductNumber,Number, SUBSTRING(PageNumber,Number,1) AS CharacterOF
FROM #YourTable
INNER JOIN AllNumbers ON 1=1
WHERE SUBSTRING(PageNumber,Number,1) IS NOT NULL AND SUBSTRING(PageNumber,Number,1) NOT LIKE '[0-9]' AND SUBSTRING(PageNumber,Number,1)!=''
)
,FirstSplit AS --get first non-numeric single character value per #YourTable row
( SELECT
ProductNumber,MIN(Number) AS SplitOf
FROM RowChars
GROUP BY ProductNumber
)
SELECT
t.ProductNumber, LEFT(t.PageNumber,COALESCE(s.SplitOf-1,LEN(t.PageNumber))) AS NewPage,t.PageNumber AS OldPage
FROM #YourTable t
LEFT OUTER JOIN FirstSplit s ON t.ProductNumber=s.ProductNumber
OUTPUT:
ProductNumber NewPage OldPage
------------- -------------------- --------------------
1 123 123, 456
2 123 123
3 123 123-456
4 12 12, 345-678
5 12 12-34, 567
6 12 12-34, 567-789
(6 row(s) affected)