SQL Between Begins With - sql

I'm attempting to use an alpha between in SQL; however, I want it to be based on the beginnings of the words only. I am using T-SQL on Microsoft SQL Server 2005.
My current SQL is:
SELECT * FROM table WHERE LEFT(word,1) BETWEEN 'a' AND 't
However, this only works for first letter. I'd like to expand this to work for any beginnings of words. For instance, between 'a' and 'ter'.
Now, I am building this dynamically, so I could do:
SELECT * FROM table WHERE LEFT(word,1) >= 'a' AND LEFT(word,3) <= 'ter'
However, I'd like to know if there is a simpler way in SQL to make a dynamic beginning-of-word between.
EDIT:::
Follow up question, words less than the length of the checked value should be considered less than in the between. For instance, me is less than mem so word < 'mem' should include me.
EDIT:::
Attempting using padding, as suggested. The below does work; however, the added 'a's can cause issue. For instance, if we want words between 'a' and 'mera' and the word being checked is 'mer', this will be included because the left trim of 'mer' becomes 'mera' with added characters. I would like a solution that does not include this issue.
DECLARE #lb varchar(50)
DECLARE #ub varchar(50)
SET #lb='ly'
SET #ub='z'
SELECT name
FROM table
WHERE
LEFT(
CASE
WHEN LEN(name) < LEN(#lb) THEN name+REPLICATE('a',LEN(#lb)-LEN(name))
ELSE name
END,
LEN(#lb)
) >= #lb
AND
LEFT(CASE
WHEN LEN(name) < LEN(#ub) THEN name+REPLICATE('a',LEN(#ub)-LEN(name))
ELSE name
END,
LEN(#ub)
) <= #ub
EDIT:::
Attempted solution, although CASE heavy. Mack's solution is better, though this works as well. LEFT('andy', 200000) will return 'andy', not an error as an OO language would, behavior I did not expect.
DECLARE #lb varchar(50)
DECLARE #ub varchar(50)
SET #lb='a'
SET #ub='lyar'
SELECT *
FROM testtable
WHERE
CASE
WHEN LEN(word) < LEN(#lb) THEN 0
WHEN LEFT(word, LEN(#lb)) >= #lb THEN 1
ELSE 0
END = 1
AND
CASE
WHEN LEN(word) < LEN(#ub) THEN
CASE
WHEN LEFT(#ub,LEN(word)) = word THEN 1
ELSE 0
END
WHEN LEFT(word, LEN(#ub)) <= #ub THEN 1
ELSE 0
END = 1
Thanks in advance!

This should work:
SELECT * FROM table WHERE LEFT(word,3) BETWEEN 'a' AND 'ter'
There's no reason why BETWEEN shouldn't be able to compare your three-letter data string to the one-letter 'a'. Any 'axx' will be "greater than" just 'a' by itself, and so will be included.

You need to use the STUFF function to achieve what you are looking for explicitly.
If you follow the link says it deletes a specified number of characters at the end of the string and replaces them with another string. Combine the with the LEN function and we can get you on the road.
--Test Data
DECLARE #table AS TABLE (word char(10))
INSERT INTO #table VALUES ('me')
INSERT INTO #table VALUES ('mem')
INSERT INTO #table VALUES ('tap')
INSERT INTO #table VALUES ('t')
DECLARE #minword char(5)
DECLARE #maxword char(5)
SET #minword='ai'
SET #maxword='t'
--SET #maxword='tb'--(remove the rem at the start of this line to unlock an extra test for comparison...)
--Query
SELECT word
FROM #table
WHERE STUFF(word, LEN(word)+1, 5, 'aaaaa') BETWEEN STUFF(#minword, LEN(#minword)+1, 5, 'aaaaa')
AND STUFF(#maxword, LEN(#maxword)+1, 5, 'aaaaa')
Alternative solution based on your revised requirements:
DECLARE #testtable AS TABLE (word varchar(20))
INSERT INTO #testtable VALUES ('ly')
INSERT INTO #testtable VALUES ('Ly')
INSERT INTO #testtable VALUES ('Zoo')
INSERT INTO #testtable VALUES ('r')
INSERT INTO #testtable VALUES ('traci')
DECLARE #minword varchar(20)
DECLARE #maxword varchar(20)
SET #minword='ly'
SET #maxword='zol'
SELECT word, LEFT(word,LEN(#minword)), LEFT(word,LEN(#maxword)), #minword, #maxword
FROM #testtable
WHERE LEFT(word,LEN(#minword))>=#minword
AND LEFT(word,LEN(#maxword))<=#maxword

If I understand you right you are trying to make this into a proc. If so, what you have will work in a proc with very little change. Something like the following (untested)...
CREATE PROC myProc(#low varchar(30), #high varchar(30)) AS
SELECT * FROM table WHERE
(LEN(word) >= LEN(#low)
AND
(LEN(word) >= LEN(#high)
AND
(LEFT(word, LEN(#low)) >= #low)
AND
LEFT(word, LEN(#high)) <= #high
There are additional conditions to exclude records when 'word' is shorter than either of your parameters. Otherwise, you will get errors on the LEFT function. This may not be 100% but it should get you close.

I believe I've found a working solution. I'm not sure of the speed sacrifices here, but the DB will remain small, so it's a non-issue in my specific case.
I am using C# to build my SQL string with parameters. #lb is the lower bound word-part. #rb is the upper bound word-part. The where clause is inclusive, but could easily be change to exclusive as needed.
SELECT * FROM table
WHERE
CASE
WHEN LEN(word) < LEN(#lb) THEN 0
WHEN LEFT(word, LEN(#lb)) >= #lb THEN 1
ELSE 0
END = 1
AND
CASE
WHEN LEN(word) < LEN(#rb) THEN 1
WHEN LEFT(word, LEN(#rb)) <= #rb THEN 1
ELSE 0
END = 1

Related

SQL Server 2012 string functions

I have a field that can vary in length of the format CxxRyyy where x and y are numeric. I want to choose xx and yyy. For instance, if the field value is C1R12, then I want to get 1 and 12. if I use substring and charindex then I have to use a length, but I would like to use a position like
SUBSTRING(WPLocationNew, CHARINDEX('C',WPLocationNew,1)+1, CHARINDEX('R',WPLocationNew,1)-1)
or
SUBSTRING(WPLocationNew, CHARINDEX('C',WPLocationNew,1)+1, LEN(WPLocationNew) - CHARINDEX('R',WPLocationNew,1))
to get x, but I know that doesn't work. I feel like there is a fairly simple solution, but I am not coming up with it yet. Any suggestions
If these are cell references and will always be in the form C{1-5 digits}R{1-5 digits} you can do this:
DECLARE #t TABLE(Original varchar(32));
INSERT #t(Original) VALUES ('C14R4535'),('C1R12'),('C57R123');
;WITH src AS
(
SELECT Original, c = REPLACE(REPLACE(Original,'C',''),'R','.')
FROM #t
)
SELECT Original, C = PARSENAME(c,2), R = PARSENAME(c,1)
FROM src;
Output
Original
C
R
C14R4535
14
4535
C1R12
1
12
C57R123
57
123
Example db<>fiddle
If you need to protect against other formats, you can add
FROM #t WHERE Original LIKE 'C%[0-9]%R%[0-9]%'
AND PATINDEX('%[^C^R^0-9]%', Original) = 0
Updated db<>fiddle
It appears that you are attempting to parse an Excel cell reference. Those are predictably structured or I wouldn't suggest such an embarrassing hack as this.
Basically, take advantage of the fact that a try_cast in SQL ignores spaces when converting strings to numbers.
declare #val as varchar(20) = 'C1R12'
declare #newval as varchar(20)
declare #c as smallint
declare #r as smallint
--replace the C with 5 spaces
set #newval = replace(#val,'C',' ')
--replace the R with 5 spaces
set #newval = replace(#newval,'R',' ')
--take a look at the intermediate result, which is ' 1 14'
select #newval
set #c = try_cast(left(#newval,11) as smallint)
set #r = try_cast(right(#newval,6) as smallint)
--take a look at the results... two smallint, 1 and 14
select #c, #r
That can all be accomplished in one line for each element (a line for column and a line for row) but I wanted you to be able to understand what was happening so this example goes through the steps individually.
Here's yet another way:
declare #val as varchar(20) = 'C12R345'
declare #c as varchar(5)
declare #r as varchar(5)
set #c = SUBSTRING(#val, patindex('C%', #val)+1,(patindex('%R%', #val)-1)-patindex('C%', #val) )
set #r = SUBSTRING(#val, patindex('%R%', #val)+1, LEN(#val) -patindex('%R%', #val))
select cast(#c as int) as 'C', cast(#r as int) as 'R'
dbfiddle
There are lots of different ways to approach string parsing. Here's just one possible idea:
declare #s varchar(10) = 'C01R002';
select
rtrim( left(replace(stuff(#s, 1, 1, ''), 'R', ' '), 10)) as c,
ltrim(right(replace(substring(#s, 2, 10), 'R', ' '), 10)) as r
Strip out the 'C' and then replace the 'R' with enough spaces so that the left and right sides can be extracted using a fixed length and then easily trimmed back.
stuff() and substring() as used above are just different ways accomplish exactly the same thing. One advantage here is that it does use fairly portable string functions and it's conceivable that this is somewhat faster. This is also done inline and without multiple steps.

How can we read a varchar column, take the integer part out and add new column incrementing that integer part using script

I need to write a SCRIPT for below scenario:
We have a column X with rows value for this column X as X01,X02,X03,X04........
The problem I am stuck with is that I needed to add another row to this table based on the value of the last row that is X04, Well I am able to identify the logic that I need to work which is given below:
I need to read value X04
Take the integer part 04
Increment by 1 => 05
Save column value as X05
I am able to pass with the 1st step which is not very hard. The problem that I am facing is the next steps. I have researched and tried quite a lot commands but none worked.
Any help is highly appreciated. Thanks.
You seem to be describing:
select concat(left(max(x), 1),
right(concat('00', try_convert(int, right(max(x), 2)) + 1), 2)
from t;
This is doing the following:
Taking the left most character.
Converting the two right characters to a number and adding one.
Converting that back to a zero-padded string.
Here is a db<>fiddle.
Now: That you want to increment a string value seems broken. You should just use an identity column or sequence to assign a number. You can format the value as a string when you query the table -- or use a computed column to store that.
Try below Script
CREATE TABLE #table (x varchar(20))
INSERT INTO #table VALUES('X01'),('X02'),('X03'),('X04')
DECLARE #maxno NVARCHAR(20)
DECLARE #maxstring NVARCHAR(20)
DECLARE #finalno NVARCHAR(20)
DECLARE #loopminno INT =1 -- you can change based on the requirement
DECLARE #loopmaxno INT =10 -- how many number we want to increment
WHILE #loopminno < #loopmaxno
BEGIN
select #maxno = MAX(CAST(SUBSTRING(x, PATINDEX('%[0-9]%', x), 100) as INT))
, #maxstring = MAX(SUBSTRING(x, 1, PATINDEX('%[0-9]%',x)-1))
from #table
where PATINDEX('%[1-9]%',x)>0
SELECT #finalno = #maxstring + CASE WHEN CAST(#maxno AS INT)<9 THEN '0' ELSE '' END + CAST(#maxno+1 AS VARCHAR(20))
INSERT INTO #table
SELECT #finalno
SET #loopminno = #loopminno+1
END

Validating string format using sql server

Check if the string is following the correct format or not. The correct format is as follows:
2 upper case letters; 2 digits; 1 to 30 characters alpha-numerical (case insensitive)
e.g. GB29RBOS60161331926819,
GB29RBOS60161331926819A,
GB29RBOS60161331926819B1
So far this is what i have got...
declare #accountNumber varchar(1000) = 'GB99AERF12FDG8AERF12FDG8AERF12FDG8'
select
case when #accountNumber not like '[A-Z][A-Z][0-9][0-9][0-9a-zA-Z]{30}$'
then 'ERROR' else null end
First, your structure assumes a case sensitive collation. Second, SQL Server doesn't recognize {} or $, so you have to repeat the pattern. However, you want up to 30 characters, so splitting the pieces apart is probably the best solution:
select (case when len(#accountNumber) not between 5 and 34 or
#accountNumber not like '[A-Z][A-Z][0-9][0-9]%' or
right(#accountNumber, 34) like '%[^A-Za-z0-9]%'
then 'ERROR'
end)
I think this should work... taking some tips from John.
declare #table table (i varchar(36))
insert into #table
values
('GR09xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'), --30 x's
('GR09xxxxxxxxxxxxxxxxxxxxxxxxxxxx'), --28 x's
('GR09xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'), --31 x's
('Gx09xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'), --lower case 2'd letter
('G509xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'), --digit second letter
('GRg9xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx') --charcater first number (3rd index)
select
case
when i + replicate('a',case when 34-len(i) < 0 then 0 else 34-len(i) end) not like '[A-Z][A-Z][0-9][0-9]' + replicate('[a-zA-Z0-9]',30)
then 'ERROR' else null end
from #table

Modify int result of count in sql server 2005

I am working on sql server 2005 and I am taking count from a specific table
SELECT count(StudentIdReference) as studentCount FROM StudentTable
Right now this select statement is returning me result like 2 or 78 or 790. But in future it will grow rapidly and on UI I don't have sufficient space to show the digit like 1000000.
What I want that after 3 digit, I will get the number like 1K or 1.6K, just as we see on stackoverflow.
This would be simpler to be done in the Presentation Layer of your application.
You coud write a user function and do something like this....
CREATE FUNCTION prettyPrint
(#number int)
RETURNS varchar(30)
AS
BEGIN
declare #return varchar(30)
set #return = cast(#number as varchar(3))
if #number > 1000
set #return = ''+ cast((#number/1000) as varchar(3)) + '.' + cast((#number % 1000)/100 as varchar(3)) +'K'
-- here must be more 'exceptions' or change all this about the magic number 1000
return #return
end
select dbo.prettyPrint(1500)
SELECT prettyPrint(count(StudentIdReference)) as studentCount FROM StudentTable
As others have stated you should really be doing this in your Presentation Layer not at the DB, however, this will do it for you:
Declare #StudentCount int,
#StudentCountFormatted varchar(10)
Select #StudentCount = Count(StudentIdReference) as studentCount FROM StudentTable
If #StudentCount > 999
Begin
Select #StudentCountFormatted = Convert(Varchar(10), Convert(numeric(19,1), (#StudentCount/ 1000.00))) + 'K'
End
Else
Begin
Select #StudentCountFormatted = #StudentCount
End
Select #StudentCountFormatted
You need to write your own logic to show such text. There is no built-in method.
I would return the COUNT as-is from SQL Server and leave the formatting up to the UI. This is because:
1) usually easier/performant to do formatting/string manipulation outside of SQL
2) different places in your code using the same query may want to use the data in different ways (maybe not now, but could do in future) so returning the count as-is gives you that flexibility - i.e. won't need 1 version to return the count as an INT and another to return the same as a formatted VARCHAR
You could do it in SQL, but in general I believe in pushing this in to the UI as it's a display/formatting behaviour.
You can always try something like this
SELECT
CASE
WHEN len(cast(count(*) as varchar(10)))< 4 then cast(count(*) as varchar(10))
WHEN len(cast(count(*) as varchar(10)))> 4 and len(cast(count(*)as varchar(10)))< 7
THEN cast(cast(count(*) / 1000.0 as decimal(10,1)) as varchar(10)) + 'k'
ELSE cast(cast(count(*) / 1000000.0 as decimal(10,1)) as varchar(10)) + 'm'
END StudentCount
FROM StudentTable

A small Help needed in a _Sql Converstion of Decimal_ using **CASE**

I am need to convert a value in to decimal.Before that I am checking a condition.I want to eliminate the decimal values if #tbt=1.
Eg if #tbt=1 then 15
if #tbt=0 then 15.233
declare #tbt int =1
1) select
CASE WHEN #tbt=1 THEN CONVERT(DECIMAL(24,0),15.23335)
ELSE CONVERT(DECIMAL(24,3),15.23335) END
2) select
CASE WHEN #tbt=1 THEN '1'
ELSE '2' END
The first Query will returns 15.000.
1. Is it possible to get 15?
2. If CONVERT(DECIMAL(24,0),15.23335) returns 15.then why it is coming 15.000 in the query.
For checking I used another query and it will prints 2.
Thanks
you could use your current solution and add additional cast to Varchar(30) on both.
You can't force it to return 2 separate datatypes like this depending on the CASE.
If you insert that result into a table using SELECT INTO syntax, you'll actually see the datatype is not DECIMAL(24,0) but DECIMAL(27,3)
i.e.
declare #tbt int =1
select
CASE WHEN #tbt=1 THEN CONVERT(DECIMAL(24,0),15.23335)
ELSE CONVERT(DECIMAL(24,3),15.23335) END AS Col
INTO SomeTestTable
--Now check the SomeTestTable schema
So what SQL Server has done, is rationalised it down to a single datatype definition that can fulfil BOTH cases.
WITH T(tbt, val) AS
(
select 1,15.23335 UNION ALL
select 0,15.23335
)
Select
CASE WHEN tbt=1 THEN cast( CONVERT(DECIMAL(24,0),val) as sql_variant)
ELSE CONVERT(DECIMAL(24,3),val) END
FROM T
returns
15
15.233
Thanks to bw_üezi
I got the answer after considering his advice. thanks for all others .
Here my answer..
declare #tbt int =0
select
CASE WHEN #tbt=1 THEN CAST(CONVERT(DECIMAL(24,0),15.23335)AS NVARCHAR)
ELSE CAST(CONVERT(DECIMAL(24,3),15.23335)AS NVARCHAR) END