Order string alpha numerically A1-1-1, A1-2-1, A1-10-1, A1-2-2, A1-2-3 etc - sql

I have a column with different length strings which has dashes (-) that separates alphanumeric strings.
The string could look like "A1-2-3".
I need to order by first "A1" then "2" then "3"
I want to achieve the following order for the column:
A1
A1-1-1
A1-1-2
A1-1-3
A1-2-1
A1-2-2
A1-2-3
A1-7
A2-1-1
A2-1-2
A2-1-3
A2-2-1
A2-2-2
A2-2-3
A2-10-1
A2-10-2
A2-10-3
A10-1-1
A10-1-2
A10-1-3
A10-2-1
A10-2-2
A10-2-3
I can separate the string with the following code:
declare #string varchar(max) = 'A1-2-3'
declare #first varchar(max) = SUBSTRING(#string,1,charindex('-',#string)-1)
declare #second varchar(max) = substring(#string, charindex('-',#string) + 1, charindex('-',reverse(#string))-1)
declare #third varchar(max) = right(#string,charindex('-',reverse(#string))-1)
select #first, #second, #third
With the above logic I thought that I could use the following:
Note this only regards strings with 2 dashes
select barcode from tabelWithBarcodes
order by
case when len(barcode) - len(replace(barcode,'-','')) = 2 then
len(SUBSTRING(barcode,1,charindex('-',barcode)-1))
end
, case when len(barcode) - len(replace(barcode,'-','')) = 2 then
SUBSTRING(barcode,1,(charindex('-',barcode)-1))
end
, case when len(barcode) - len(replace(barcode,'-','')) = 2 then
len(substring(barcode, charindex('-',barcode) + 1, charindex('-',reverse(barcode))-1))
end
, case when len(barcode) - len(replace(barcode,'-','')) = 2 then
substring(barcode, charindex('-',barcode) + 1, charindex('-',reverse(barcode))-1)
end
, case when len(barcode) - len(replace(barcode,'-','')) = 2 then
len(right(barcode,charindex('-',reverse(barcode))-1))
end
, case when len(barcode) - len(replace(barcode,'-','')) = 2 then
right(barcode,charindex('-',reverse(barcode))-1)
end
But the sorting is not working for the second and third section of the string.
(I haven't added the code for checking if the string has only 1 or no dash in it for simplicity)
Not sure if I'm on the right path here.
Is anybody able to solve this?

This is not pretty, however...
USE Sandbox;
GO
WITH VTE AS(
SELECT V.SomeString
--Randomised order
FROM (VALUES ('A1-1-1'),
('A10-1-3'),
('A10-2-2'),
('A1-1-3'),
('A10-2-1'),
('A2-2-2'),
('A1-2-1'),
('A1-2-2'),
('A2-1-1'),
('A10-1-2'),
('B2-1-2'),
('A1'),
('A2-2-1'),
('A2-10-3'),
('A10-2-3'),
('A2-1-2'),
('B1-4'),
('A2-10-2'),
('A2-2-3'),
('A10-1-1'),
('A1-A1-3'),
('A1-7'),
('A2-10-1'),
('A2-1-3'),
('A1-1-2'),
('A1-2-3')) V(SomeString)),
Splits AS(
SELECT V.SomeString,
DS.Item,
DS.ItemNumber,
CONVERT(int,STUFF((SELECT '' + NG.token
FROM dbo.NGrams8k(DS.item,1) NG
WHERE TRY_CONVERT(int, NG.Token) IS NOT NULL
ORDER BY NG.position
FOR XML PATH('')),1,0,'')) AS NumericPortion
FROM VTE V
CROSS APPLY dbo.DelimitedSplit8K(V.SomeString,'-') DS),
Pivoted AS(
SELECT S.SomeString,
MIN(CASE V.P1 WHEN S.Itemnumber THEN REPLACE(S.Item, S.NumericPortion,'') END) AS P1Alpha,
MIN(CASE V.P1 WHEN S.Itemnumber THEN S.NumericPortion END) AS P1Numeric,
MIN(CASE V.P2 WHEN S.Itemnumber THEN REPLACE(S.Item, S.NumericPortion,'') END) AS P2Alpha,
MIN(CASE V.P2 WHEN S.Itemnumber THEN S.NumericPortion END) AS P2Numeric,
MIN(CASE V.P3 WHEN S.Itemnumber THEN REPLACE(S.Item, S.NumericPortion,'') END) AS P3Alpha,
MIN(CASE V.P3 WHEN S.Itemnumber THEN S.NumericPortion END) AS P3Numeric
FROM Splits S
CROSS APPLY (VALUES(1,2,3)) AS V(P1,P2,P3)
GROUP BY S.SomeString)
SELECT P.SomeString
FROM Pivoted P
ORDER BY P.P1Alpha,
P.P1Numeric,
P.P2Alpha,
P.P2Numeric,
P.P3Alpha,
P.P3Numeric;
This outputs:
A1
A1-1-1
A1-1-2
A1-1-3
A1-2-1
A1-2-2
A1-2-3
A1-7
A1-A1-3
A2-1-1
A2-1-2
A2-1-3
A2-2-1
A2-2-2
A2-2-3
A2-10-1
A2-10-2
A2-10-3
A10-1-1
A10-1-2
A10-1-3
A10-2-1
A10-2-2
A10-2-3
B1-4
B2-1-2
This makes use of 2 user defined functions. Firstly or DelimitedSplit8k_Lead (I used DelimitedSplit8k as I don't have the other on my sandbox at the moment). Then you also have NGrams8k.
I really should explain how this works, but yuck... (edit coming).
OK... (/sigh) What it does. Firstly, we split the data into its relevant parts using delimitedsplit8k(_lead). Then, within the SELECT we use FOR XML PATH to get (only) the nuemrical part of that string (For example, for 'A10' we get '10') and we convert it to a numerical value (an int).
Then we pivot that data out into respective parts. The alphanumerical part, and the numerical part. So, for the value 'A10-A1-12' we end up with the row:
'A', 10, 'A', 1, 12
Then, now that we've pivoted the data, we sort it by each column individually. And voila.
This will fall over if you have a value like 'A1A' or '1B1', and honestly, I'm not changing it to catter for that. This was messy, and really isn't what the RDBMS should be doing.

Up to 3 dashes can be covered by fiddling with replace & parsename & patindex:
declare #TabelWithBarcodes table (id int primary key identity(1,1), barcode varchar(20) not null, unique (barcode));
insert into #TabelWithBarcodes (barcode) values
('2-2-3'),('A2-2-2'),('A2-2-1'),('A2-10-3'),('A2-10-2'),('A2-10-1'),('A2-1-3'),('A2-1-2'),('A2-1-1'),
('A10-2-3'),('A10-2-2'),('A10-2-10'),('A10-1-3'),('AA10-A111-2'),('A10-1-1'),
('A1-7'),('A1-2-3'),('A1-2-12'),('A1-2-1'),('A1-1-3'),('B1-1-2'),('A1-1-1'),('A1'),('A10-10-1'),('A12-10-1'), ('AB1-2-E1') ;
with cte as
(
select barcode,
replace(BarCode, '-', '.')
+ replicate('.0', 3 - (len(BarCode)-len(replace(BarCode, '-', '')))) as x
from #TabelWithBarcodes
)
select *
, substring(parsename(x,4), 1, patindex('%[0-9]%',parsename(x,4))-1)
,cast(substring(parsename(x,4), patindex('%[0-9]%',parsename(x,4)), 10) as int)
,substring(parsename(x,3), 1, patindex('%[0-9]%',parsename(x,3))-1)
,cast(substring(parsename(x,3), patindex('%[0-9]%',parsename(x,3)), 10) as int)
,substring(parsename(x,2), 1, patindex('%[0-9]%',parsename(x,2))-1)
,cast(substring(parsename(x,2), patindex('%[0-9]%',parsename(x,2)), 10) as int)
,substring(parsename(x,1), 1, patindex('%[0-9]%',parsename(x,1))-1)
,cast(substring(parsename(x,1), patindex('%[0-9]%',parsename(x,1)), 10) as int)
from cte
order by
substring(parsename(x,4), 1, patindex('%[0-9]%',parsename(x,4))-1)
,cast(substring(parsename(x,4), patindex('%[0-9]%',parsename(x,4)), 10) as int)
,substring(parsename(x,3), 1, patindex('%[0-9]%',parsename(x,3))-1)
,cast(substring(parsename(x,3), patindex('%[0-9]%',parsename(x,3)), 10) as int)
,substring(parsename(x,2), 1, patindex('%[0-9]%',parsename(x,2))-1)
,cast(substring(parsename(x,2), patindex('%[0-9]%',parsename(x,2)), 10) as int)
,substring(parsename(x,1), 1, patindex('%[0-9]%',parsename(x,1))-1)
,cast(substring(parsename(x,1), patindex('%[0-9]%',parsename(x,1)), 10) as int)
extend each barcode to 4 groups by adding trailing .0 if missing
split each barcode in 4 groups
split each group in leading characters and trailing digits
sort by the leading character first
then by casting the digits as numeric
See db<>fiddle

An alterative approach would be to use your technique to split the string into its 3 component parts, then left pad those strings with leading zeros (or characters of your choice). That avoids any issues where the string may contain alphanumerics rather than just numerics. However, it does mean that strings containing different length alphabetic characters may not be sorted as you may expect... Here's the code to play with (using the definitions from #dnoeth's excellent answer):
;with cte as
(
select barcode
, case
when barcode like '%-%' then
substring(barcode,1,charindex('-',barcode)-1)
else
barcode
end part1
, case
when barcode like '%-%' then
substring(barcode, charindex('-',barcode) + 1, case
when barcode like '%-%-%' then
(charindex('-',barcode,charindex('-',barcode) + 1)) - 1
else
len(barcode)
end
- charindex('-',barcode))
else
''
end part2
, case
when barcode like '%-%-%' then
right(barcode,charindex('-',reverse(barcode))-1) --note: assumes you don't have %-%-%-%
else
''
end part3
from #TabelWithBarcodes
)
select barcode
, part1, part2, part3
, right('0000000000' + coalesce(part1,''), 10) lpad1
, right('0000000000' + coalesce(part2,''), 10) lpad2
, right('0000000000' + coalesce(part3,''), 10) lpad3
from cte
order by lpad1, lpad2, lpad3
DBFiddle Example

Related

Is it possible to find the first occurrence of a string that's NOT within a set of delimiters in SQL Server 2016+?

I have a column in a SQL Server table that has strings of varying lengths. I need to find the position of the first occurrence of the string , -- that's not enclosed in single quotes or square brackets.
For example, in the following two strings, I've bolded the portion I would like to get the position of. Notice in the first string, the first time , -- appears on its own (without being between single quote or square bracket delimiters) is at position 13 and in the second string, it's at position 16.
'a, --'[, --]**, --**[, --]
[a, --b]aaaaaaa_ **, --**', --'
Also I should mention that , -- itself could appear multiple times in the string.
Here's a simple query that shows the strings and my desired output.
SELECT
t.string, t.desired_pos
FROM
(VALUES (N'''a, --''[, --], --[, --]', 14),
(N'[a, —-b]aaaaaaa_ , --'', --''', 18)) t(string, desired_pos)
Is there any way to accomplish this using a SELECT query (or multiple) without using a function?
Thank you in advance!
I've tried variations of SUBSTRING, CHARINDEX, and even some CROSS APPLYs but I can't seem to get the result I'm looking for.
Before i write down my solution, i must warn you: DON'T USE IT. Use a function, or do this in some other language. This code is probably buggy.
It doesn't handle stuff like escaped quotes etcetc.
The idea is to first remove the stuff inside brackets [] and quotes '' and then just do a "simple" charindex.
To remove the brackets, i'm using a recursive CTE that loops ever part of matching quotes and replaces their content with placeholder strings.
One important point is that quotes might be embedded in each other, so you have to try both variants and chose the one that is earliest.
WITH CTE AS (
SELECT *
FROM
(VALUES (N'''a, --''[, --], --[, --]', 14),
(N'[a, —-b]aaaaaaa_ , --'', --''', 18)) t(string, desired_pos)
)
, cte2 AS (
select x.start
, x.finish
, case when x.start > 0 THEN STUFF(string, x.start, x.finish - x.start + 1, REPLICATE('a', x.finish - x.start + 1)) ELSE string END AS newString
, 1 as level
, string as orig
, desired_pos
from cte
CROSS APPLY (
SELECT *
, ROW_NUMBER() OVER(ORDER BY case when start > 0 THEN 0 ELSE 1 END, start) AS sortorder
FROM (
SELECT charindex('[', string) AS start
, charindex(']', string) AS finish
UNION ALL
SELECT charindex('''', string) AS startQ
, charindex('''', string, charindex('''', string) + 1) AS finishQ
) x
) x
WHERE x.sortorder = 1
UNION ALL
select x.start
, x.finish
, STUFF(newString, x.start, x.finish - x.start + 1, REPLICATE('a', x.finish - x.start + 1))
, 1 as level
, orig
, desired_pos
from cte2
CROSS APPLY (
SELECT *
, ROW_NUMBER() OVER(ORDER BY case when start > 0 THEN 0 ELSE 1 END, start) AS sortorder
FROM (
SELECT charindex('[', newString) AS start
, charindex(']', newString) AS finish
UNION ALL
SELECT charindex('''', newString) AS startQ
, charindex('''', newString, charindex('''', newString) + 1) AS finishQ
) x
) x
WHERE x.sortorder = 1
AND x.start > 0
AND cte2.start > 0 -- Must have been a match
)
SELECT PATINDEX('%, --%', newString), *
from (
select *, row_number() over(partition by orig order by level desc) AS sort
from cte2
) x
where x.sort = 1
Try this approach. I'm replacing the strings you don't need for another string of the same length. Then look for the position of the interested string.
SELECT string, desired_pos,
CHARINDEX(', --', REPLACE(REPLACE(string, ''', --''', '******'), '[, --]', '******')
) start_index
FROM (VALUES (N''', --''[, --], --[, --]', 13),
(N'[, --]aaaaaaa_ , --'', --''', 16)) t(string, desired_pos)
I don't know if it makes sense with a C# solution, but this class for CVS is a nice little parcer: TextFieldParser
Then you just define Delimeters etc. and assuming the input is escaped consistently then all is good.
Im late the game here but This kind of thing is simple in SQL Server when leveraging NGrams8k. Not only do you not need REGEX, a CLR, C# required. Furthermore, NGrams8k will be the fastest by far. In 8 years nobody has produced anything remotely as fast. Furthermore, this code will be faster and far less complex than a recursive CTE solution (which are almost always slow in SQL Server)
;--==== Sample Data
DECLARE #T Table (String VARCHAR(100))
INSERT #T
VALUES (N'''a, --''[, --], --[, --]'),
(N'[a, —-b]aaaaaaa_ , --'', --''');
;--==== Solution
SELECT
t.String, ng.Position
FROM #t AS t
CROSS APPLY (VALUES(REPLACE(t.String,'[',CHAR(1)))) AS f(S)
CROSS APPLY samd.NGrams8k(f.S,4) AS ng
CROSS APPLY (VALUES(SUBSTRING(f.S,ng.Position-2,7))) AS g(String)
WHERE ng.Token = ', --'
AND g.String NOT LIKE '%''%''%'
AND g.String NOT LIKE '%'+CHAR(1)+'%]%';
Results:
String Position
----------------------------- --------------------
'a, --'[, --], --[, --] 14
[a, —-b]aaaaaaa_ , --', --' 18

SQL trying to replace middle characters with *

I am trying to replace SQL results with all the middle values with asterix, *. All results are words. I am using SSMS.
The words that are 4-5 letters, it should only show 1 letter in the beginning, one to the end.
6 letters and more, it it should only show 2 letter in the beginning, 2 letters in the end.
1-3 letters, no replacement.
For example:
(I am now using - instead of * so it does not make the text bold).
"Banana" 6 letters should become ba--na
"False" 5 letters should become F---e
"a" stays the same
"Selin is a vegetable and banana is a fruit" becomes "S---n is a ve-----le and ba--na is a f---t."
What I have done so far, is to make this for emails, after the #. But now I want it to happen with every word of the result.
What I've done:
DECLARE #String VARCHAR(100) = 'sample#gmail.com'
SELECT STUFF(STUFF(#STring,
CHARINDEX('#',#String)+2,
(CHARINDEX('.',#String, CHARINDEX('#',#String))-CHARINDEX('#',#String)-3),
REPLICATE('*',CHARINDEX('.',#String, CHARINDEX('#',#String))-CHARINDEX('#',#String)))
,2
,CHARINDEX('#',#String)-3
,REPLICATE('*',CHARINDEX('#',#String)-3))```
With result s----e#g------l.com
instead of -
And I tried the mask method
Select
--select first character from Email and use replicate
SUBSTRING(Sxolia,1,1) + REPLICATE('*',5)+
--function to put asterisks
SUBSTRING(Sxolia,CHARINDEX('#',Sxolia),len(Sxolia)-CHARINDEX('#',Sxolia)+1)
--at this statement i select this part #gmail,com and to first part to become like this A*****#gmail.com
as Emailmask
From [mytable]
With result
B***** Bana is a fruit
And
declare #str nvarchar(max)
select #str = '123456'
select '****' + substring(#str, 5, len(#str) - 3)
Result: ****56
Not what I am looking for.
How should I look into this?
If I had to deal with this in SQL Server I'd operate on each word as a row, however using string_split is not (currently) an option since it does not guarantee ordering.
The following uses json to split the string as an array and provides a key value for ordering, which allows the words to be aggregated in the correct order:
select t.Sentence,
String_Agg( masked, ' ') within group(order by seq) Masked
from t
cross apply (
select seq, [value] word,
case
when l<=3 then [value]
when l<=5 then Stuff([value],2,l-2,Replicate('*',l-2))
else
Stuff([value],3,l-4,Replicate('*',l-4))
end Masked
from (
select j.[value], 1 + Convert(tinyint,j.[key]) Seq
from OpenJson(Concat('["',replace(t.Sentence,' ', '","'),'"]')) j
)w
cross apply (values(Len([value])))x(l)
)w
group by t.Sentence;
See working demo
Result:
I'm not sure how e-mail fits into all this because you're asking for word masks, so I'm going to assume you actually want this. Use divide and conquer to implement this, so first implement an expression that would do this for simplest cases (e.g. single words). Then if you need it for e-mails, just split the e-mails however you see fit and then apply the same expression.
The expression itself is rather simple:
SELECT *
FROM (VALUES
('banana'),
('selin'),
('vegetable')
) words(word)
CROSS
APPLY (SELECT CASE
WHEN ln BETWEEN 4 AND 5
THEN LEFT(word, 1) + REPLICATE('*', ln-2) + RIGHT(word, 1)
WHEN ln >= 6
THEN LEFT(word, 2) + REPLICATE('*', ln-4) + RIGHT(word, 2)
ELSE word
END as result
FROM (VALUES (LEN(words.word))) x(ln)
) calc
This already provides the expected result. You could define a function out of this, if you have the permissions, and use it like so:
SELECT *
FROM (VALUES
('banana'),
('selin'),
('vegetable')
) words(word)
CROSS
APPLY fnMaskWord(word)
Here's a working demo on dbfiddle, it includes the statement to create the function.
Expanding on a few answers:
select case when len(#String) <= 3 then #String
when len(#String) > 3 AND len(#String) <= 5 then
substring(#String, 1, 2) +
REPLICATE('*', Len(#String) - 2) +
substring(#String, Len(#String) - 1, 2)
when len(#String) >= 6 then
substring(#String, 1, 2) +
REPLICATE('*', Len(#String) - 2) +
substring(#String, Len(#String) - 1, 2)
else 'unrecognized length!'
If the length of the string is less than or equal to 3, return the string.
If the length of the string is more than 3 and less than or equal to 5 then create a substring starting at position 1, then replicate * by the length of the string -2 and finally add another substring -1 from the end of the string.
Similar for if the result is over 6 characters.
Else unrecognized length!
Hope this helps understand what's going on!
Maybe this can help
declare #t table (word varchar(50))
insert into #t values ('banana'), ('selin'), ('vegetable')
select case when len(t.word) < 3 then t.word
else left(t.word, 1) + -- take first char from left
replicate('*', Len(t.word) - 2) + -- fill middle with *
right(t.word, 1) -- take last char from right
end
from #t t
this returns
COLUMN1
b****a
s***l
v*******e
If you want to keep 2 chars left and right when the len > 5 then maybe this
select case when len(t.word) < 3 then t.word
when len(t.word) < 6 then
left(t.word, 1) +
replicate('*', len(t.word) - 2) +
right(t.word, 1)
else left(t.word, 2) +
replicate('*', len(t.word) - 4) +
right(t.word, 2)
end
from #t t
The result
COLUMN1
ba**na
s***l
ve*****le
EDIT: What if there is a whole sentence ?
Well then we first split the sentence in words,
and then concat the individual words back together while putting the ** in them
declare #t table (word varchar(50))
insert into #t values ('banana'), ('selin'), ('vegetable'), ('Banana is a fruit')
select t.word,
-- put the words back togheter into the sentence, and ** them while we are at it
( select string_agg(case when len(value) < 3 then value
when len(value) < 6 then
left(value, 1) +
replicate('*', len(value) - 2) +
right(value, 1)
else left(value, 2) +
replicate('*', len(value) - 4) +
right(value, 2)
end,
' ')
)
from #t t
cross apply string_split(t.word, ' ') s -- split the sentence into words
group by t.word
the result is
word COLUMN1
---- -------
banana ba**na
Banana is a fruit Ba**na is a f***t
selin s***n
vegetable ve*****le

Cast substring to int only for numeric values in SQL

I have this query :
SUBSTRING (
dbo.Table.RNumber,
1,
CHARINDEX(
'+',
dbo.Table.RNumber
) - 1
) AS RoomNumber,
SUBSTRING (
dbo.Table.R.Number,
CHARINDEX(
'+',
dbo.Table.R.Number
) + 1,
LEN(
dbo.Table.R.Number
)
) AS HallNumber,
My Table RNumber is mostly like 2+3 or 3+5, but sometimes it is like x+5 or y+0. I want to convert fields to int, but I want to convert strings like "x" or "y" to 0. I googled it but I couldn't find a solution. How can I do that? Thanks.
You can use case statement try this
Edited to use isnumeric() method
CASE
WHEN isnumeric(SUBSTRING(dbo.Table.RNumber,1,CHARINDEX('+',dbo.Table.RNumber) - 1)) = 1
THEN SUBSTRING(dbo.Table.RNumber,1,CHARINDEX('+',dbo.Table.RNumber) - 1)
else 0
end AS RoomNumber,
CASE
WHEN isnumeric(SUBSTRING(dbo.Table.R.Number,CHARINDEX('+',dbo.Table.RNumber) + 1,LEN(dbo.Table.R.Number))) = 1
THEN SUBSTRING(dbo.Table.R.Number,CHARINDEX('+',dbo.Table.RNumber) + 1,LEN(dbo.Table.R.Number))
else 0
end AS HallNumber,
Hope this should solve your problem
Perhaps you can use ParseName() and Try_Convert()
Declare #YourTable table (SomeField varchar(50))
Insert Into #YourTable values
('2+3'),('3+5'),('x+5'),('y+0')
Select *
,RoomNumber = IsNull(Try_Convert(int,ParseName(Replace(SomeField,'+','.'),2)),0)
,HallNumber = IsNull(Try_Convert(int,ParseName(Replace(SomeField,'+','.'),1)),0)
From #YourTable
Returns
SomeField RoomNumber HallNumber
2+3 2 3
3+5 3 5
x+5 0 5
y+0 0 0
For versions prior to 2012, you can do it like this:
CASE
WHEN NOT columnName like '%[^0-9]%' -- Contains no non-digits
AND columnName like '%[0-9]%' -- contains at least one digit
THEN CAST(columnName as INT) ELSE NULL
END
(Note that this will reject negative numbers, but you can easily adapt it if you need to support them)
Alternatively using IsNumeric, you must first cast to float because Isnumeric accepts some strings that Cast(EXPRESSION as INT) does not accept:
CASE WHEN ISNUMERIC(columnName)=1
THEN CAST(CAST(columnName as float) as int) END

SQL Server REPLACE AND CHECK IF EXISTS

I have to check the string with the following scenarios in WHERE condition.
The data ProductId stored in the database can be like
7314-3337 sometimes with - symbol and not prefixed with 19
73143337 sometimes without symbol and not prefixed with 19
1973143337 correct format
197314-3337 sometimes with - symbol
I need to filter the record ProductId and the input is correct format , i.e 1973143337
WHERE P.ProductId=#ProductId
How can i filter it if the data stored in other 3 formats?
How to use the string replace(-) and prefix 19 if not exists in SQL server?
please check this 2 approach.
one is very simple and second is some trick. (I think you go with second option which cover everythings)
declare #t table (ProductId varchar(100))
insert into #t
values
('7314-3337')
,('73143337')
,('1973143337')
,('197314-3337')
,('73683337')
,('73143338')
declare #valuetosearch varchar(100) = '1973143337'
--this is very simple , but not work in each schenerio. the second approach is fine.
--select CHARINDEX ( '19','1973143337'), SUBSTRING('1973143337',3,len('1973143337'))
--select * from
--#t
--where
--replace(REPLACE(ProductId ,'-','') ,'19','') = replace(REPLACE(#valuetosearch ,'-','') ,'19','')
select * from
#t
where
REPLACE( case when CHARINDEX ( '19',ProductId) = 1
then SUBSTRING( ProductId ,3,LEN(ProductId))
else ProductId
end ,'-','')
=
REPLACE ( case when CHARINDEX ( '19',#valuetosearch) = 1
then SUBSTRING( #valuetosearch ,3,LEN(#valuetosearch))
else #valuetosearch
end ,'-','')
You should first sanitize your data, if it is not consistent then you won't be able to get the correct results.
For prefixing with 19:
UPDATE foo
SET ProductId = '19' + ProductId
WHERE Left(ProductID, 2) <> '19'
For removing the '-':
UPDATE foo
SET ProductId = REPLACE(ProductId, '-', '')
Then you should be able to get the results you want.
UPDATE:
You could construct a CTE with the results in a single format, and then, filter that CTE:
WITH cte (
FormattedPID
,ProductId
)
AS (
SELECT CASE
WHEN LEFT(ProductId, 2) = '19'
THEN REPLACE(ProductId, '-', '')
ELSE '19' + REPLACE(ProductId, '-', '')
END
,ProductId
FROM foo
)
SELECT FormattedPID
,ProductId
FROM cte
WHERE FormattedPID = #ProductID
You could make sure the column is in the correct format like this:
Remove the - by replacing it with an empty string (197314-3337 -> 1973143337, 7314-3337 -> 73143337).
Add 19 at the beginning (1973143337 -> 191973143337, 73143337 -> 1973143337).
Take 10 rightmost characters of the result and compare to the input (1973143337 -> 1973143337, 1973143337 -> 1973143337).
In Transact-SQL:
WHERE RIGHT('19' + REPLACE(P.ProductId, '-', ''), 10) = #ProductId
Of course, this means no index seek for you, because we are applying functions to the column.
An alternative to that would be to produce the three non-standard formats out of the input:
cut off the initial 19 (1973143337 -> 73143337);
insert the - (1973143337 -> 197314-3337);
insert the - and cut off the 19 (1973143337 -> 197314-3337 -> 7314-3337).
In Transact-SQL:
WHERE P.ProductId IN (
#ProductId,
SUBSTRING(#ProductId, 3, 999999999),
STUFF(#ProductId, 7, 0, '-'),
SUBSTRING(STUFF(#ProductId, 7, 0, '-'), 3, 999999999)
)
This way if there is an index on P.ProductId, it will be used efficiently.
Both approaches assume that the length of the correct format is fixed.

Sql concatenate problem?

In my table i have a column called Dep_user_code which is nothing but employeeid...Everytime I need to increment the employeeid when i insert a new value..but it has both alphabet and number..i need to increment the number alone.. for example if my employeeid is 'NECUSER0001' means next time when i insert a new employeeid it has to be 'NECUSER0002' dynamically i have to generate like this..everytime when i insert a value it has to increment..
I have tried like taking the string part and number part like this but dont know how to implement this...Any suggestion?
select SUBSTRING(Dep_user_code,1,7) from NEC_Customer_User_Map
select SUBSTRING(Dep_user_code,8,4) from NEC_Customer_User_Map
you should also keep an identity key. use SELECT IDENT_CURRENT('NEC_Customer_User_Map') to find out last inserted ID.
If the value is always text then numbers, you split apart the value using Patindex:
Select Substring( Dep_user_code, 1, PatIndex( '%[0-9]%', Dep_user_code) - 1 ) As TextPortion
, Substring( Dep_user_code, PatIndex( '%[0-9]%', Dep_user_code)
, Len(Dep_user_code) ) As NumberPortion
However, whether you can use an identity in combination with a prefix depends on whether you can allow gaps. If you cannot allow gaps, then you need to query for the next id value that you can use which can be done in a variety of ways depending on the needs.
I've had to support databases with setups like this before and while I'm generally not a fan of this style, I'm assuming you have some reason for not storing the NECUSER in one column and the incrementing identity integer in another column with the PK set to both. If not, I'd suggest going that route and letting SQL do the work for you.
Otherwise, using the result of the following query should yield the results you want. I've added comments to try and answer any questions the query might raise.
SELECT SUBSTRING(Dep_user_code, 1, 7) +
RIGHT(
REPLICATE('0', 3) + --Ensure we have padding 0s
IsNull(MAX(CAST(SUBSTRING(Dep_user_code, 8, 4) AS INT), -1) + 1 --Work with ints, find MAX or set NULL to -1 so +1 will = 0
, 4) --Only want 4 character total from RIGHT function
FROM NEC_Customer_User_Map
WITH last AS (
SELECT MAX(Dep_user_code) AS Code
FROM NEC_Customer_User_Map
WHERE LEFT(Dep_user_code, 7) = 'NECUSER'
)
SELECT LEFT(Dep_user_code, 7) + RIGHT(CAST(STUFF(Code, 1, 7, '1') AS int) + 1, 4)
FROM last
The RIGHT part does the following:
replaces 'NECUSER' with '1' thus getting something like '10002';
casts the result as int;
increments by 1;
(implicitly) casts the value to varchar and gets the last 4 chars.
Maybe STUFF(Code, 1, 7, '1') should better be replaced with '1' + RIGHT(Code, 4), not sure.
EDIT: As it happens, the implicit conversion could also be employed in case of converting the string to the integer too:
... + RIGHT(STUFF(Code, 1, 7, '1') + 1, 4) ...
or
... + RIGHT('1' + RIGHT(Code, 4) + 1, 4) ...
declare #max varchar(20)
declare #number varchar(20)
select #max = max(cast(substring(dep_user_name , 8, 4) as int)) from NEC_Customer_User_Map (nolock)
select #max = isnull(#max, 0) + 1
select #max = (case when len(#max) = 1 then '000' + #max
when len(#max) = 2 then '00' + #max
when len(#max) = 3 then '0' + #max
else #max
end)
Select #number = (Substring( dep_user_name, 1, PatIndex( '%[0-9]%', dep_user_name) - 1 ) + #max) from NEC_Customer_User_Map
insert into NEC_Customer_User_Map(Dep_User_Name) values (#number )
You can consider to have both parts of Dep_user_code as separate fileds in your db in order to take advantage of several tsql features like IDENTITY and IDENT_CURRENT()