SQL Trigger to split string during insert without a common delimiter and store it into another table - sql

Currently I have a system that is dumping data into a table with the format:
Table1
Id#, row#, row_dump
222, 1, “set1 = aaaa set2 =aaaaaa aaaa dd set4=1111”
I want to take the row dump and transpose it into rows and insert it into another table of the format:
Table2
Id#, setting, value
222, ‘set1’,’aaa’
222, ‘set2’,’aaaaaa aaaa dd’
222, ‘set4’,’1111’
Is there a way to make a trigger in MSSQL that will parse this string on insert in Table1 and insert it into Table2 properly?
All of the examples I’ve found required a common delimiter. ‘=’ separates the setting from the value, space(s) separate a value from a setting but a value could have spaces in it (settings do not have spaces in them so the last word before the equal sign is the setting name but there could be spaces between the setting name and equal sign).
There could be 1-5 settings and values in any given row. The values can have spaces. There may or may not be space between the setting name and the ‘=’ sign.
I have no control over the original insert process or format as it is used for other purposes.

You could use 'set' as a delimiter. This is a simple sample. It obviously may have to be molded to your environment.
use tempdb
GO
IF OBJECT_ID('dbo.fn_TVF_Split') IS NOT NULL
DROP FUNCTION dbo.fn_TVF_Split;
GO
CREATE FUNCTION dbo.fn_TVF_Split(#arr AS NVARCHAR(2000), #sep AS NCHAR(3))
RETURNS TABLE
AS
RETURN
WITH
L0 AS (SELECT 1 AS C UNION ALL SELECT 1) --2 rows
,L1 AS (SELECT 1 AS C FROM L0 AS A, L0 AS B) --4 rows (2x2)
,L2 AS (SELECT 1 AS C FROM L1 AS A, L1 AS B) --16 rows (4x4)
,L3 AS (SELECT 1 AS C FROM L2 AS A, L2 AS B) --256 rows (16x16)
,L4 AS (SELECT 1 AS C FROM L3 AS A, L3 AS B) --65536 rows (256x256)
,L5 AS (SELECT 1 AS C FROM L4 AS A, L4 AS B) --4,294,967,296 rows (65536x65536)
,Nums AS (SELECT row_number() OVER (ORDER BY (SELECT 0)) AS N FROM L5)
SELECT
(n - 1) - LEN(REPLACE(LEFT(#arr, n-1), #sep, N'')) + 1 AS pos,
SUBSTRING(#arr, n, CHARINDEX(#sep, #arr + #sep, n) - n) AS element
FROM Nums
WHERE
n <= LEN(#arr) + 3
AND SUBSTRING(#sep + #arr, n, 3) = #sep
AND N<=100000
GO
declare #t table(
Id int,
row int,
row_dump varchar(Max)
);
insert into #t values(222, 1, 'set1 = aaaa set2 =aaaaaa aaaa dd set4=1111')
insert into #t values(111, 2, ' set1 =cx set2 =4444set4=124')
DECLARE #t2 TABLE(
Id int,
Setting VARCHAR(6),
[Value] VARCHAR(50)
)
insert into #t2 (Id,Setting,Value)
select
Id,
[Setting]='set' + left(LTRIM(element),1),
[Value]=RIGHT(element,charindex('=',reverse(element))-1)
from #t t
cross apply dbo.fn_TVF_Split(row_dump,'set')
where pos > 1
order by
id asc,
'set' + left(LTRIM(element),1) asc
select *
from #t2
Update
You could do something like this. It is not optimal and could probably be better handled in the transformation tool or application. Anyway here we go.
Note: You will need the split function I posted before.
declare #t table(
Id int,
row int,
row_dump varchar(Max)
);
insert into #t values(222, 1, 'set1 = aaaa set2 =aaaaaa aaaa dd set3=abc set4=1111 set5=7373')
insert into #t values(111, 2, 'set1 =cx set2 = 4444 set4=124')
DECLARE #t2 TABLE(
Id int,
Setting VARCHAR(6),
[Value] VARCHAR(50)
)
if OBJECT_ID('tempdb.dbo.#Vals') IS NOT NULL
BEGIN
DROP TABLE #Vals;
END
CREATE TABLE #Vals(
Id INT,
Row INT,
Element VARCHAR(MAX),
pos int,
value VARCHAR(MAX)
);
insert into #Vals
select
Id,
row,
element,
pos,
Value=STUFF(LEFT(element,len(element) - CHARINDEX(' ',reverse(element))),1,1,'')
from(
select
Id,
row,
row_dump = REPLACE(REPLACE(REPLACE(row_dump,'= ','='),' =','='),'=','=|')
from #t
) AS t
cross apply dbo.fn_TVF_Split(row_dump,'=')
where pos >=1 and pos < 10
insert into #t2 (Id,Setting,Value)
select
t1.Id,
Setting =
(
SELECT TOP 1
CASE WHEN t2.pos = 1
THEN LTRIM(RTRIM(t2.element))
ELSE LTRIM(RTRIM(RIGHT(t2.element,CHARINDEX(' ',REVERSE(t2.element)))))
END
FROM #Vals t2
where
t2.Id = t1.id
and t2.row = t1.row
and t2.pos < t1.pos
ORDER BY t2.pos DESC
),
t1.Value
from #Vals t1
where t1.pos > 1 and t1.pos < 10
order by t1.id,t1.row,t1.pos
select * from #t2

Related

SQL Server stored procedure looping through a comma delimited cell

I am trying to figure out how to go about getting the values of a comma separated string that's present in one of my cells.
This is the query I current am trying to figure out in my stored procedure:
SELECT
uT.id,
uT.permissions
FROM
usersTbl AS uT
INNER JOIN
usersPermissions AS uP
/*Need to loop here I think?*/
WHERE
uT.active = 'true'
AND
uT.email = 'bbarker#thepriceisright.com'
The usersPermissions table looks like this:
And so a row in the usersTbl table looks like this for permissions:
1,3
I need to find a way to loop through that cell and get each number and place the name ****, in my returned results for the usersTbl.permissions.
So instead of returning this:
Name | id | permissions | age |
------------------------------------
Bbarker | 5987 | 1,3 | 87 |
It needs to returns this:
Name | id | permissions | age |
------------------------------------
Bbarker | 5987 | Read,Upload | 87 |
Really just replacing 1,3 with Read,Upload.
Any help would be great from a SQL GURU!
Reworked query
SELECT
*
FROM
usersTbl AS uT
INNER JOIN
usersPermissionsTbl AS uPT
ON
uPT.userId = uT.id
INNER JOIN
usersPermissions AS uP
ON
uPT.permissionId = uP.id
WHERE
uT.active='true'
AND
uT.email='bBarker#thepriceisright.com'
I agree with all of the comments... but strictly trying to do what you want, here's a way with a splitter function
declare #usersTbl table ([Name] varchar(64), id int, [permissions] varchar(64), age int)
insert into #usersTbl
values
('Bbarker',5987,'1,3',87)
declare #usersTblpermissions table (id int, [type] varchar(64))
insert into #usersTblpermissions
values
(1,'Read'),
(2,'Write'),
(3,'Upload'),
(4,'Admin')
;with cte as(
select
u.[Name]
,u.id as UID
,p.id
,p.type
,u.age
from #usersTbl u
cross apply dbo.DelimitedSplit8K([permissions],',') x
inner join #usersTblpermissions p on p.id = x.Item)
select distinct
[Name]
,UID
,age
,STUFF((
SELECT ',' + t2.type
FROM cte t2
WHERE t.UID = t2.UID
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
from cte t
Jeff Moden Splitter
CREATE FUNCTION [dbo].[DelimitedSplit8K] (#pString VARCHAR(8000), #pDelimiter CHAR(1))
--WARNING!!! DO NOT USE MAX DATA-TYPES HERE! IT WILL KILL PERFORMANCE!
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
/* "Inline" CTE Driven "Tally Table" produces values from 1 up to 10,000...
enough to cover VARCHAR(8000)*/
WITH E1(N) AS (
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
), --10E+1 or 10 rows
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS (--==== This provides the "base" CTE and limits the number of rows right up front
-- for both a performance gain and prevention of accidental "overruns"
SELECT TOP (ISNULL(DATALENGTH(#pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
SELECT 1 UNION ALL
SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(#pString,t.N,1) = #pDelimiter
),
cteLen(N1,L1) AS(--==== Return start and length (for use in substring)
SELECT s.N1,
ISNULL(NULLIF(CHARINDEX(#pDelimiter,#pString,s.N1),0)-s.N1,8000)
FROM cteStart s
)
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
Item = SUBSTRING(#pString, l.N1, l.L1)
FROM cteLen l
;
GO
First, you should read Is storing a delimited list in a database column really that bad?, where you will see a lot of reasons why the answer to this question is Absolutely yes!
Second, you should add a table for user permissions since this is clearly a many to many relationship.
Your tables might look something like this (pseudo code):
usersTbl
(
Id int primary key
-- other user related columns
)
usersPermissionsTbl
(
UserId int, -- Foreign key to usersTbl
PermissionId int, -- Foreign key to permissionsTbl
Primary key (UserId, PermissionId)
)
permissionsTbl
(
Id int primary key,
Name varchar(20)
)
Once you have your tables correct, it's quite easy to get a list of comma separated values from the permissions table.
Adapting scsimon's sample data script to a correct many to many relationship:
declare #users table ([Name] varchar(64), id int, age int)
insert into #users values
('Bbarker',5987,87)
declare #permissions table (id int, [type] varchar(64))
insert into #permissions values
(1,'Read'),
(2,'Write'),
(3,'Upload'),
(4,'Admin')
declare #usersPermissions as table (userId int, permissionId int)
insert into #usersPermissions values (5987, 1), (5987, 3)
Now the query looks like this:
SELECT u.Name,
u.Id,
STUFF(
(
SELECT ','+ [type]
FROM #permissions p
INNER JOIN #usersPermissions up ON p.id = up.permissionId
WHERE up.userId = u.Id
FOR XML PATH('')
)
, 1, 1, '') As Permissions,
u.Age
FROM #Users As u
And the results:
Name Id Permissions Age
Bbarker 5987 Read,Upload 87
You can see a live demo on rextester.
I concur with much of the advice being presented to you in the other responses. The structure you're starting with is not going to be fun to maintain and work with. However, your situation may mean you are stuck with it so maybe some of the tools below will help you.
You can parse the delimiter with charindex() as others demonstrated here- MSSQL - How to split a string using a comma as a separator
... and even better here (several functions are provided) - Split function equivalent in T-SQL?
If you still want to do it with raw inline SQL and are committed to a loop, then pair the string manipulation with a CURSOR. Cursors have their own controversies BTW. The code below will work if your permission syntax remains consistent, which it probably doesn't.
They used charindex(',',columnName) and fed the location into the left() and right() functions along with some additional string evaluation to pull values out. You should be able to piece those together with a cursor
Your query might look like this...
--creating my temp structure
declare #userPermissions table (id int, [type] varchar(16))
insert into #userPermissions (id, [type]) values (1, 'Read')
insert into #userPermissions (id, [type]) values (2, 'Write')
insert into #userPermissions (id, [type]) values (3, 'Upload')
insert into #userPermissions (id, [type]) values (4, 'Admin')
declare #usersTbl table ([Name] varchar(16), id int, [permissions] varchar(8), age int)
insert into #usersTbl ([Name], id, [permissions], age) values ('Bbarker', 5987, '1,3', 87)
insert into #usersTbl ([Name], id, [permissions], age) values ('Mmouse', 5988, '2,4', 88)
--example query
select
ut.[Name]
, (select [type] from #userPermissions where [id] = left(ut.[permissions], charindex(',', ut.[permissions])-1) )
+ ','
+ (select [type] from #userPermissions where [id] = right(ut.[permissions], len(ut.[permissions])-charindex(',', ut.[permissions])) )
from #usersTbl ut

How do I replace strings of a table from another table column

How do I update/replace the value of the first table from the list of my second table in SQL. Sorry im not so good in using replace() of SQL especially replacing from values base from different table
First table.
ID | Value
======================
1 | Fruits[Apple]
2 | Fruits[Apple,Mango]
3 | Apple[Red,Green]
Second table
Search | Replace
=========================
Apple | Orange
Green | Yellow
You will need some kind of recursive replace.
something like a loop
declare #t1 table (ID int, Value varchar(max))
declare #t2 table (Search varchar(max), ReplaceWith varchar(max))
insert #t1 values (1, 'Fruits[Apple]'),(2, 'Fruits[Apple,Mango]'), (3, 'Apple[Red,Green]')
insert #t2 values ('Apple', 'Orange'),('Green', 'Yellow')
--loop nth times for rows that have more than one match
while exists(select top 1 * from #t1 inner join #t2 on charindex(Search, Value ) > 0)
begin
update #t1
set Value = replace(Value, Search, ReplaceWith)
from #t2
inner join #t1 on charindex(Search, Value ) > 0
end
select * from #t1
results
ID Value
----- -----------------------
1 Fruits[Orange]
2 Fruits[Orange,Mango]
3 Orange[Red,Yellow]
Alternatively, you could use recursive CTE
;with CTE(ID, Value, rec_count)
as (
select distinct ID, Value, 1 as rec_count from #t1 inner join #t2 on charindex(Search, Value ) > 0
union all
select ID, Value = replace(Value, Search, ReplaceWith), rec_count +1
from CTE
inner join #t2 on charindex(Search, Value ) > 0
)
update #t1
set Value= replaced.Value
from #t1 t
inner join
( select distinct ID, Value
from CTE c
where rec_count > 1
and rec_count = (select max(rec_count) from CTE where ID = c.ID) ) replaced on replaced.ID = t.ID
Simply use following UPDATE by cross-joined select statement and enjoy it! ;)
UPDATE tFirst
SET Value = REPLACE(tFirst.Value, tSecond.Search, tSecond.Replace)
FROM
[First] tFirst
CROSS JOIN [Second] tSecond

Break row out into multiple rows then collapse values back

OK - I have a table - where I can have a row with multiple quanties - what I need to be able to do is to take all rows where there is a qty > 1 - create multiple rows - one for each qty - perform a simple calculation against each row - say multiply the val field by 2 - and then roll the rows back up into another temp table or something...?
DECLARE #table TABLE (id int IDENTITY(1,1),
code varchar(10),
codeStatus varchar,
qty int,
val money)
INSERT INTO #table
SELECT
'12345',
'T',
2,
1
A numbers table is your friend here.
The join against the numbers table effectively performs your expansion. The code below is essentially just the 'expansion' part of your question. After this its trivial to apply whatever transformations you need, push into temp table and so on.
/*
--create numbers table if don't already have one...
select top 1000000 row_number() over(order by t1.number) as N
into dbo.Numbers
from master..spt_values t1
cross join master..spt_values t2
*/
DECLARE #table TABLE (id int IDENTITY(1,1),code varchar(10), codeStatus varchar, qty int, val money)
INSERT INTO #table SELECT '12345', 'T', 2, 1
select t.id, t.code, t.codeStatus, t.qty, t.val
from #table t
inner join dbo.Numbers n on n.N <= t.qty

First unused number in a varchar(5) field padded with zeros

I have a varchar(5) field. I need find the first unused number across all records such that they begin with leading zeros. like if there is a 00001 and a 00003 I'd like the query to return 00002. Also, many of the records contain letters and look like 'G0542'. These can be ignored.
I know I'm close. This seems to work in SQL Server 2005 but not in 2008 or 2012
http://sqlfiddle.com/#!3/4016a0/1
create table b_addr ( inst_no varchar(5) Unique );
insert into b_addr (inst_no) values ('00001');
insert into b_addr (inst_no) values ('00002');
insert into b_addr (inst_no) values ('00004');
--this is the problem line
insert into b_addr (inst_no) values ('A0045');
With usedNos as(
select CAST(b_addr.inst_no AS INT) as inst
from b_addr
where b_addr.inst_no LIKE '[0-9][0-9][0-9][0-9][0-9]')
SELECT
RIGHT('00000' + CONVERT(VARCHAR(5), COALESCE(min(inst)+1, 0)),5) AS next_inst_no
from usedNos where not exists (select null from usedNos usn where usn.inst = usedNos.inst +1)
How can I structure this so it will work in sql server 2008+ as well?
Here is a version of your query that works:
With usedNos as (
select CAST(case when isnumeric(b_addr.inst_no) = 1
then b_addr.inst_no
end AS INT) as inst
from b_addr
)
SELECT RIGHT('00000' + CONVERT(VARCHAR(5), COALESCE(min(inst)+1, 0)),5) AS next_inst_no
from usedNos
where inst is not null and
not exists (select 1
from usedNos usn
where usn.inst = usedNos.inst +1
);
The key is the use of isnumeric() inside of case. This guarantees that the cast() is not attempted unless the value looks like a number. If it doesn't look like a number, then the result is NULL, which is filtered out in the outer where clause.
Your where clause:
where b_addr.inst_no LIKE '[0-9][0-9][0-9][0-9][0-9]'
attempts to do the same thing. However, SQL Server doesn't guarantee that the where clause is processed before the select -- which is why you are getting an unexpected error.
You can check whether the number you're trying to cast is a real number:
Instead of
CAST(b_addr.inst_no AS INT) as inst
Do something like
CAST(CASE WHEN ISNUMERIC(b_addr.inst_no) = 1 THEN b_addr.inst_no ELSE 0 END AS INT) as inst
It's a bit convoluted, but it works.
Demo: http://sqlfiddle.com/#!3/1aee5/6
here's another one using patindex instead of like.
http://sqlfiddle.com/#!6/1aee5/12
With usedNos as(
select b_addr.inst_no as inst
from b_addr
where patindex('[0-9][0-9][0-9][0-9][0-9]',b_addr.inst_no) > 0
)
SELECT
RIGHT('00000' + COALESCE(cast((min(cast(inst as int)) + 1) as varchar), '00000'),5) AS next_inst_no
from usedNos
where not exists (select null from usedNos usn where cast(usn.inst as int) = cast(usedNos.inst as int) + 1 )
--should return 00003, trying to find the first unused inst_no and padd it with zeros to make it 5 chars.
create table b_addr ( inst_no varchar(5) Unique );
insert into b_addr (inst_no) values ('00001');
insert into b_addr (inst_no) values ('00002');
insert into b_addr (inst_no) values ('00004');
--this is the problem line
insert into b_addr (inst_no) values ('A0045');
--Specify how many of the "next" unused numbers you want.
DECLARE #HowMany INT = 10
--Common Table Expression construct to generate sequential numbers.
;WITH
L0 AS(SELECT 1 AS c UNION ALL SELECT 1),
L1 AS(SELECT 1 AS c FROM L0 AS A, L0 AS B),
L2 AS(SELECT 1 AS c FROM L1 AS A, L1 AS B),
L3 AS(SELECT 1 AS c FROM L2 AS A, L2 AS B),
L4 AS(SELECT 1 AS c FROM L3 AS A, L3 AS B),
L5 AS(SELECT 1 AS c FROM L4 AS A, L4 AS B),
L6 AS(SELECT 1 AS c FROM L5 AS A, L5 AS B),
SequentialNumbers AS(SELECT ROW_NUMBER() OVER(ORDER BY c) AS Num FROM L6)
SELECT TOP(#HowMany) RIGHT('00000' + CAST(sn.Num AS VARCHAR), 5) AS NextNumbers
FROM SequentialNumbers sn
WHERE sn.Num NOT IN (
SELECT CAST(b_addr.inst_no AS INT) AS inst
FROM b_addr
WHERE b_addr.inst_no LIKE '[0-9][0-9][0-9][0-9][0-9]'
)

How to generate a new code

Using VB.Net and SQL Server
Table1
Id Value .....
1001P0010001 100
1001P0010002 200
1001P0010003 300
1001P0010004 400
...
I have n columns and rows in table1, from the table1, I want to copy all the column details with new id no...
id no is like this 1001P0020001, 1001P0020002, .......
P002 is next id, P003 is the next Id.....
The first 4 digits and last 4 digits will remain as read from table1, middle 4 digits should change to next series
Expected output
Id Value .....
1001P0010001 100
1001P0010002 200
1001P0010003 300
1001P0010004 400
1001P0020001 100
1001P0020002 200
1001P0020003 300
1001P0020004 400
...
Which the best way to do this?
I can do it in VB.Net or a SQL query...? Please suggest ways of doing this.
CREATE TABLE CocoJambo (
Id CHAR(12) NOT NULL,
Value INT NULL,
CHECK( Id LIKE '[0-9][0-9][0-9][0-9][A-Z][0-9][0-9][0-9][0-9][0-9][0-9][0-9]' )
);
GO
CREATE UNIQUE INDEX IUN_CocoJambo_Id
ON CocoJambo (Id);
GO
INSERT CocoJambo (Id, Value)
SELECT '1001P0010001', 100
UNION ALL SELECT '1001P0010002', 200
UNION ALL SELECT '1001P0010003', 300
UNION ALL SELECT '1001P0010004', 400
UNION ALL SELECT '1001P0020001', 100
UNION ALL SELECT '1001P0020002', 200
UNION ALL SELECT '1001P0020003', 300
UNION ALL SELECT '1001P0020004', 400;
GO
-- Test 1: generating a single Id
DECLARE #Prefix CHAR(5),
#Sufix CHAR(4);
SELECT #Prefix = '1001P',
#Sufix = '0001';
BEGIN TRAN
DECLARE #LastGeneratedMiddleValue INT,
#LastValue INT;
SELECT #LastGeneratedMiddleValue = y.MiddleValue,
#LastValue = y.Value
FROM
(
SELECT x.MiddleValue, x.Value,
ROW_NUMBER() OVER(ORDER BY x.MiddleValue DESC) AS RowNum
FROM
(
SELECT CONVERT(INT,SUBSTRING(a.Id,6,3)) AS MiddleValue, a.Value
FROM CocoJambo a WITH(UPDLOCK) -- It will lock the rows (U lock) during transaction
WHERE a.Id LIKE #Prefix+'%'+#Sufix
) x
) y
WHERE y.RowNum=1;
SELECT #LastGeneratedMiddleValue = ISNULL(#LastGeneratedMiddleValue ,0)
SELECT #Prefix
+RIGHT('00'+CONVERT(VARCHAR(3),#LastGeneratedMiddleValue +1),3)
+#Sufix AS MyNewId,
#LastValue AS Value
COMMIT TRAN;
GO
-- Test 2: generating many Id's
BEGIN TRAN
DECLARE #Results TABLE (
Prefix CHAR(5) NOT NULL,
Sufix CHAR(4) NOT NULL,
LastGeneratedMiddleValue INT NOT NULL,
LastValue INT NULL
);
INSERT #Results (Prefix, Sufix, LastGeneratedMiddleValue, LastValue)
SELECT y.Prefix, y.Sufix, y.MiddleValue, y.Value
FROM
(
SELECT x.Prefix, x.MiddleValue, x.Sufix, x.Value,
ROW_NUMBER() OVER(PARTITION BY x.Prefix, x.Sufix ORDER BY x.MiddleValue DESC) AS RowNum
FROM
(
SELECT SUBSTRING(a.Id,1,5) AS Prefix,
CONVERT(INT,SUBSTRING(a.Id,6,3)) AS MiddleValue,
SUBSTRING(a.Id,9,4) AS Sufix,
a.Value
FROM CocoJambo a WITH(UPDLOCK) -- It will lock the rows (U lock) during transaction
) x
) y
WHERE y.RowNum=1;
SELECT r.*,
r.Prefix
+RIGHT('00'+CONVERT(VARCHAR(3),r.LastGeneratedMiddleValue +1),3)
+r.Sufix AS MyNewId,
r.LastValue AS Value
FROM #Results r;
COMMIT TRAN;
GO
insert into table1 (id, value)
select
l +
replicate('0', 3 - lenght(m)) + m +
r,
value
from (
select
left(id, 5) l,
cast(cast(substring(id, 6, 3) as integer) + 1 as varchar(3)) m,
right(id, 4),
value
from table1
) s