Invalid length parameter passed to the SUBSTRING function - tsql

I have written the query and it is working:
declare #word as nvarchar (20)
set #word = 'victOR aALEXander'
select upper(left(#word, 1)) + lower(SUBSTRING(#word,2,charindex(' ', #word)-2)) + ' ' +
upper(left(substring(#word,charindex(' ', #word)+1,len(#word)-1),1))
+ lower(SUBSTRING(#word,charindex(' ', #word)+2, len(#word)))
I have created the function:
alter function letters ( #word as nvarchar(20))
returns varchar(20) as begin
return upper(left(#word, 1)) + lower(SUBSTRING(#word,2,charindex(' ', #word)-2)) + ' ' +
upper(left(substring(#word,charindex(' ', #word)+1,len(#word)-1),1))
+ lower(SUBSTRING(#word,charindex(' ', #word)+2, len(#word))) end
Finally i have done:
select dbo.letters(users)
from dbo.tempdb
I have got:
Invalid length parameter passed to the SUBSTRING function
Why?

Try this function:
Create Function dbo.Proper(#Data VarChar(8000))
Returns VarChar(8000)
As
Begin
Declare #Position Int
Select #Data = Stuff(Lower(#Data), 1, 1, Upper(Left(#Data, 1))),
#Position = PatIndex('%[^a-zA-Z][a-z]%', #Data COLLATE Latin1_General_Bin)
While #Position > 0
Select #Data = Stuff(#Data, #Position, 2, Upper(SubString(#Data, #Position, 2))),
#Position = PatIndex('%[^a-zA-Z][a-z]%', #Data COLLATE Latin1_General_Bin)
Return #Data
End
This function works properly whether there are spaces, apostrophes, or anything else in your data. Unfortunately it won't convert macdonald to MacDonald or o'brien to O'Brien. However, it will work for any word(s) that only have 1 capital letter in it.

also try trimming in case you have spaces at the beginning and end:
alter function letters ( #word as nvarchar(20))
returns varchar(20) as
begin
set #word = ltrim(rtrim(#word))
return upper(left(#word, 1)) + lower(SUBSTRING(#word,2,charindex(' ', #word)-2)) + ' ' +
upper(left(substring(#word,charindex(' ', #word)+1,len(#word)-1),1))
+ lower(SUBSTRING(#word,charindex(' ', #word)+2, len(#word)))
end

Character count of #word parameter is less than substring count. Also, parameter could be null.

try if this query works. However if you have more than single ' ' then this query will fail.
declare #word as nvarchar (20)
set #word = 'alex' -- 'victOR aALEXander'
select
upper(left(#word, 1)) +
CASE WHEN charindex(' ', #word)>0 THEN lower(SUBSTRING(#word,2,charindex(' ', #word)-2)) + ' '
+ upper(left(substring(#word,charindex(' ', #word)+1,len(#word)-1),1))
+ lower(SUBSTRING(#word,charindex(' ', #word)+2, len(#word)))
ELSE lower(SUBSTRING(#word,2,len(#word)-1)) END

Related

Wrong value assigned to variable

I've started drafting a scalar function in SSMS 2012 to take a string and swap any occurrence of 3 hex URL characters (%2b, %2f and %3d) with their respective single character (-, / and =):
DECLARE #OutputText as VARCHAR(100)
DECLARE #c as Int
DECLARE #cIn as CHAR(3)
DECLARE #cOut as CHAR(1)
DECLARE #s as Int
SET #OutputText = '%2bBWCq6sE7OU%3d'
SET #c = 1
WHILE #c <= 3
BEGIN
-- Set the search/replace characters depending on iteration
IF #c = 1
SET #cIn = '%2b'
SET #cOut = '-';
IF #c = 2
SET #cIn = '%2f'
SET #cOut = '/';
IF #c = 3
SET #cIn = '%3d'
SET #cOut = '=';
SET #s = PATINDEX('%' + #cIn +'%', #OutputText)
WHILE #s > 0
BEGIN
PRINT 'Character Loop: ' + CAST(#c as VARCHAR(1)) + ' | looking for ' + #cIn + ' within ' + #OutputText
PRINT '(Replace ' + #cIn + ' with ' + #cOut + ')'
PRINT '-- ' + #cIn + ' found at position: ' + CAST(#s as VARCHAR(2))
SET #OutputText = STUFF(#OutputText, PATINDEX('%' + #cIn +'%', #OutputText) - 1, 3, #cOut)
PRINT '>> OutputText now: ' + #OutputText + CHAR(10)
SET #s = PATINDEX('%' + #cIn +'%', #OutputText)
END
SET #c = #c + 1
END
PRINT 'Final output: ' + #OutputText
The various PRINTs return this:
Notice the first character loop output says Replace %2b with = ... yet the if statement should be setting #cOut to - not = when #c = 1.
Another minor issue is that where the output says %2b found at position: the position number given seems 1 higher than it should be, like it's ignoring % of #cIn.
In your If statements, each setting of #cout will happen because they are not part of the IF. Only the next line after the IF executes. You need to wrap them in Begin End:
IF #c = 1
begin
SET #cIn = '%2b'
SET #cOut = '-'
end
else IF #c = 2
begin
SET #cIn = '%2f'
SET #cOut = '/'
end
else IF #c = 3
begin
SET #cIn = '%3d'
SET #cOut = '='
end
To prove this:
declare #thing int = 2
if #thing=1
select 'ralph'
select 'charles'
if #thing = 2
select 'ralph2'
select 'charles2'
This will produce charles, ralph2, charles2
Whereas this (with begin end):
declare #thing int = 2
if #thing=1
begin
select 'ralph'
select 'charles'
end
if #thing = 2
begin
select 'ralph2'
select 'charles2'
end
will correctly produce ralph2, charles2

Arithmetic overflow for smallint where no smallint is present

DECLARE #SchemaName VARCHAR(100)
DECLARE #TableName VARCHAR(256)
DECLARE #IndexName VARCHAR(256)
DECLARE #ColumnName VARCHAR(100)
DECLARE #is_unique VARCHAR(100)
DECLARE #IndexTypeDesc VARCHAR(100)
DECLARE #FileGroupName VARCHAR(100)
DECLARE #is_disabled VARCHAR(100)
DECLARE #IndexOptions VARCHAR(MAX)
DECLARE #IndexColumnId INT
DECLARE #IsDescendingKey INT
DECLARE #IsIncludedColumn INT
DECLARE #TSQLScripCreationIndex VARCHAR(MAX)
DECLARE #TSQLScripDisableIndex VARCHAR(MAX)
DECLARE CursorIndex CURSOR
FOR
SELECT SCHEMA_NAME(t.schema_id) [schema_name] ,
t.name ,
ix.name ,
CASE WHEN ix.is_unique = 1 THEN 'UNIQUE '
ELSE ''
END ,
ix.type_desc ,
CASE WHEN ix.is_padded = 1 THEN 'PAD_INDEX = ON, '
ELSE 'PAD_INDEX = OFF, '
END
+ CASE WHEN ix.allow_page_locks = 1 THEN 'ALLOW_PAGE_LOCKS = ON, '
ELSE 'ALLOW_PAGE_LOCKS = OFF, '
END
+ CASE WHEN ix.allow_row_locks = 1 THEN 'ALLOW_ROW_LOCKS = ON, '
ELSE 'ALLOW_ROW_LOCKS = OFF, '
END
+ CASE WHEN INDEXPROPERTY(t.object_id, ix.name, 'IsStatistics') = 1
THEN 'STATISTICS_NORECOMPUTE = ON, '
ELSE 'STATISTICS_NORECOMPUTE = OFF, '
END
+ CASE WHEN ix.ignore_dup_key = 1 THEN 'IGNORE_DUP_KEY = ON, '
ELSE 'IGNORE_DUP_KEY = OFF, '
END + 'SORT_IN_TEMPDB = OFF,DROP_EXISTING = ON' AS IndexOptions ,
ix.is_disabled ,
FILEGROUP_NAME(ix.data_space_id) FileGroupName
FROM sys.tables t
INNER JOIN sys.indexes ix ON t.object_id = ix.object_id
WHERE ix.type > 0
AND ix.is_primary_key = 0
AND ix.is_unique_constraint = 0 --and schema_name(tb.schema_id)= #SchemaName and tb.name=#TableName
AND t.is_ms_shipped = 0
AND t.name <> 'sysdiagrams'
AND t.name = 'LegalEntity'
ORDER BY SCHEMA_NAME(t.schema_id) ,
t.name ,
ix.name
OPEN CursorIndex
FETCH NEXT FROM CursorIndex INTO #SchemaName, #TableName, #IndexName,
#is_unique, #IndexTypeDesc, #IndexOptions, #is_disabled, #FileGroupName
WHILE ( ##fetch_status = 0 )
BEGIN
DECLARE #IndexColumns VARCHAR(MAX)
DECLARE #IncludedColumns VARCHAR(MAX)
SET #IndexColumns = ''
SET #IncludedColumns = ''
DECLARE CursorIndexColumn CURSOR
FOR
SELECT col.name ,
ixc.is_descending_key ,
ixc.is_included_column
FROM sys.tables tb
INNER JOIN sys.indexes ix ON tb.object_id = ix.object_id
INNER JOIN sys.index_columns ixc ON ix.object_id = ixc.object_id
AND ix.index_id = ixc.index_id
INNER JOIN sys.columns col ON ixc.object_id = col.object_id
AND ixc.column_id = col.column_id
WHERE ix.type > 0
AND ( ix.is_primary_key = 0
OR ix.is_unique_constraint = 0
)
AND SCHEMA_NAME(tb.schema_id) = #SchemaName
AND tb.name = #TableName
AND ix.name = #IndexName
ORDER BY ixc.index_column_id
OPEN CursorIndexColumn
FETCH NEXT FROM CursorIndexColumn INTO #ColumnName, #IsDescendingKey,
#IsIncludedColumn
WHILE ( ##fetch_status = 0 )
BEGIN
IF #IsIncludedColumn = 0
SET #IndexColumns = #IndexColumns + #ColumnName
+ CASE WHEN #IsDescendingKey = 1 THEN ' DESC, '
ELSE ' ASC, '
END
ELSE
SET #IncludedColumns = #IncludedColumns + #ColumnName
+ ', '
FETCH NEXT FROM CursorIndexColumn INTO #ColumnName,
#IsDescendingKey, #IsIncludedColumn
END
CLOSE CursorIndexColumn
DEALLOCATE CursorIndexColumn
SET #IndexColumns = SUBSTRING(#IndexColumns, 1, LEN(#IndexColumns) - 1)
SET #IncludedColumns = CASE WHEN LEN(#IncludedColumns) > 0
THEN SUBSTRING(#IncludedColumns, 1,
LEN(#IncludedColumns) - 1)
ELSE ''
END
-- print #IndexColumns
-- print #IncludedColumns
SET #TSQLScripCreationIndex = ''
SET #TSQLScripDisableIndex = ''
SET #TSQLScripCreationIndex = 'CREATE ' + #is_unique + #IndexTypeDesc
+ ' INDEX ' + QUOTENAME(#IndexName) + ' ON '
+ QUOTENAME(#SchemaName) + '.' + QUOTENAME(#TableName) + '('
+ #IndexColumns + ') '
+ CASE WHEN LEN(#IncludedColumns) > 0
THEN CHAR(13) + 'INCLUDE (' + #IncludedColumns + ')'
ELSE ''
END + CHAR(13) + 'WITH (' + #IndexOptions + ') ON '
+ '[SC_LE]([Id])' + ';'
IF #is_disabled = 1
SET #TSQLScripDisableIndex = CHAR(13) + 'ALTER INDEX '
+ QUOTENAME(#IndexName) + ' ON ' + QUOTENAME(#SchemaName)
+ '.' + QUOTENAME(#TableName) + ' DISABLE;' + CHAR(13)
--print #TSQLScripCreationIndex
--print #TSQLScripDisableIndex
FETCH NEXT FROM CursorIndex INTO #SchemaName, #TableName, #IndexName,
#is_unique, #IndexTypeDesc, #IndexOptions, #is_disabled,
#FileGroupName
END
CLOSE CursorIndex
DEALLOCATE CursorIndex
The issue here is probably where you call FILEGROUP_NAME() in the SELECT clause of the definition of CursorIndex. As per the MSDN definition, the filegroup_id parameter passed to FILEGROUP_NAME() is of type smallint. I expect you have one or more records in sys.indexes which have a data_space_id value greater than 32,767 (the maximum value which can be stored in a smallint variable).
You can check by running:
SELECT SCHEMA_NAME(t.schema_id) [schema_name] ,
t.name AS TableName,
ix.name AS IndexName,
ix.data_space_id
FROM sys.tables t
INNER JOIN sys.indexes ix ON t.object_id = ix.object_id
WHERE ix.type > 0
AND ix.is_primary_key = 0
AND ix.is_unique_constraint = 0
AND t.is_ms_shipped = 0
AND ix.data_space_id > 32767
ORDER BY SCHEMA_NAME(t.schema_id),
t.name,
ix.name
This will return any indexes with this problem. I can see two ways to fix your query:
Omit the FILEGROUP_NAME() call from the original cursor definition and subsequent FETCH calls - it doesn't look like you use it for anything later on, so there's no need for it to be included.
Amend the WHERE clause of the original cursor definition to exclude any indexes with data_space_id values greater than 32767. Note that this approach will mean some indexes are not included in your cursor, but these will only be the ones which have been causing it to fail anyway.
As for why SQL Server allows values greater than 32767 in sys.indexes.data_space_id while FILEGROUP_NAME() requires a smallint parameter, I have no idea - maybe someone else can enlighten us on this?

T-SQL Syntax - word parser

I have a word list in a variable. The words are comma delimited. I am trying to save them to individual record in a database. I found another question which does this and works, but it saves every word. I tried to modify it so that I only save unique words, and count duplicate as I go. I think the logic below is correct, but my syntax is not working.
if #FoundWord = 0 SET #WordsUsed = #WordsUsed + '' + #Word + ''
** is not concatenating the next word onto the end of the #WordsUsed variable
if #FoundWord = 0 INSERT INTO mydata.dbo.words (ProjectNumber, WordCount, Word) VALUES( '5', '1', #Word )
** doesn't seem to be doing anything at all ... I'm getting no records written to the words table
The entire code follows:
declare #SplitOn nvarchar(5) = ','
BEGIN
DECLARE #split_on_len INT = LEN(#SplitOn)
DECLARE #start_at INT = 1
DECLARE #end_at INT
DECLARE #data_len INT
DECLARE #WordsUsed varchar(max)
DECLARE #FoundWord int
DECLARE #Word varchar(100)
Set #WordsUsed = '**'
WHILE 1=1
BEGIN
SET #end_at = CHARINDEX(#SplitOn,#txt1,#start_at)
SET #data_len = CASE #end_at WHEN 0 THEN LEN(#txt1) ELSE #end_at-#start_at END
set #Word = SUBSTRING(#txt1,#start_at,#data_len)
SET #FoundWord = CHARINDEX('*' & #Word & '*', #WordsUsed)
if #FoundWord = 0 SET #WordsUsed = #WordsUsed & '*' & #Word & '*'
if #FoundWord = 0 INSERT INTO mydata.dbo.words (ProjectNumber, WordCount, Word) VALUES( '5', '1', #Word )
if #FoundWord > 0 Update mydata.dbo.words set WordCount = WordCount + 1 where projectnumber = 5 and word = #word
IF #end_at = 0 BREAK
SET #start_at = #end_at + #split_on_len
END
RETURN
END;
Here's a function you can try. It splits an unlimited-sized string based on the delimiter of your choice. The output of the function is a table - so you can then select distinct from that to store your word list. You'd call it something like:
insert YourWordlistTable ( Word ) --> just making up table/column names here
select distinct Data
from dbo.StringSplit( #yourVar, ',' )
Here's the definition of the function:
create function dbo.StringSplit
(
#string nvarchar( max ),
#delimiter nvarchar( 255 )
)
returns #t table ( Id int, Data nvarchar( 4000 ) )
as begin
with Split( startPosition, endPosition )
as
(
select
cast( 0 as bigint ) as startPosition,
charindex( #delimiter, #string ) as endPosition
union all
select
endPosition + 1, charindex( #delimiter, #string, endPosition + 1 )
from
Split
where
endPosition > 0
)
insert #t
select
row_number() over ( order by ( select 1 ) ) as Id,
substring( #string, startPosition, coalesce( nullif( endPosition, 0 ), len( #string ) + 1 ) - startPosition ) collate Latin1_General_CS_AS as Data
from
Split
option( maxrecursion 0 );
return;
end
I originally posted, then deleted, then reposted this when I realized I'd given an inline function I use that never got called on strings longer than 100 words. I've since modified it to support indefinite recursion - although it can't be an inline function this way.
Inline functions are generally faster because SQL can incorporate the inline function's statement into the query plan of the statements that call the function. However, that does not appear to be an option available in an inline function.
why can't you use
select count(distinct value) from dbo.split(',',#txt1) where #txt1 is he string will give you distinct count of words
If you are looking for word wise count
select value,count(1) from dbo.split(',',#txt1) group by value
shoud do this

Getting upper into lower case

In my SELECT statement, I have:
,UserName
When this comes through in the query, it appears as: JOHN.SMITH
Is it possible to use CAST or CONVERT to change this to John Smith?
Any advice gratefully appreciated.
Thanks.
First replace the period with a space:
SELECT REPLACE(SELECT UserName FROM YourTable, '.', ' ')
Save this in a variable, or put this select directly to the function below.
Unfortunately, I don't have t-sql at my disposal right now, so I can't check the syntax to be 100% correct.
Then to set only first chars to uppercase. If you were using oracle, I would tell you to use initcap, but this doesn't exist in t-sql.
Taken from link: http://www.devx.com/tips/Tip/17608
create function initcap (#text varchar(4000))
returns varchar(4000)
as
begin
declare #counter int,
#length int,
#char char(1),
#textnew varchar(4000)
set #text = rtrim(#text)
set #text = lower(#text)
set #length = len(#text)
set #counter = 1
set #text = upper(left(#text, 1) ) + right(#text, #length - 1)
while #counter <> #length --+ 1
begin
select #char = substring(#text, #counter, 1)
IF #char = space(1) or #char = '_' or #char = ',' or #char = '.' or #char = '\'
or #char = '/' or #char = '(' or #char = ')'
begin
set #textnew = left(#text, #counter) + upper(substring(#text,
#counter+1, 1)) + right(#text, (#length - #counter) - 1)
set #text = #textnew
end
set #counter = #counter + 1
end
return #text
end
So use this function to convert the uppercase string. Hope this helps.
You could go about it like so:
DECLARE #UserName AS varchar(50) = 'JOHN.SMITH'
SELECT LEFT(UPPER(LEFT(#UserName, CHARINDEX('.', #UserName)-1)),1) + SUBSTRING(LOWER(LEFT(#UserName, CHARINDEX('.', #UserName)-1)),2,LEN(LEFT(#UserName, CHARINDEX('.', #UserName)-1))-1) + ' ' + LEFT(UPPER(RIGHT(#UserName, LEN(#UserName) - CHARINDEX('.', #UserName))),1) + SUBSTRING(LOWER(RIGHT(#UserName, LEN(#UserName) - CHARINDEX('.', #UserName))),2,LEN(RIGHT(#UserName, LEN(#UserName) - CHARINDEX('.', #UserName)))-1)
It gets everything before the . and then uppers the first letter whilst lowering the rest and then does the same for everything after the ..
However, it would be much better if you handled this in your code as when you come back to reading this query you may not know what it's doing.

NULLIF check for empty string returns empty string with a column name, but NULL with the column value

I have a database column set to char(255) (yes, CHAR. Don't ask me why that's how the database was set up) that at present has an empty string with two spaces (i.e. " "). Using NULLIF(LTRIM(RTRIM(column_name)), '') does NOT work (the output is [two empty spaces]). However, using NULLIF(' ', '') works correctly and the output is NULL. In other words, the actual column value works correctly, while passing the name of the column returns an incorrect value.
Any ideas on this?
I believe the column must have more than just spaces. For example:
CREATE TABLE #x(id INT, y CHAR(255));
INSERT #X SELECT 1, ' '
UNION ALL SELECT 2, ' '
UNION ALL SELECT 3, ' ' + CHAR(9);
SELECT id, NULLIF(LTRIM(RTRIM(y)),'') FROM #x;
Results:
1 NULL
2 NULL
3
For a row where this fails, try this:
DECLARE #s CHAR(255);
SELECT #s = y FROM #x WHERE id = 3;
DECLARE #i INT;
SET #i = 1;
WHILE #i <= DATALENGTH(#s)
BEGIN
IF ASCII(SUBSTRING(#s, #i, 1)) <> 32
BEGIN
PRINT 'Position ' + RTRIM(#i) + ' = CHAR('
+ RTRIM(ASCII(SUBSTRING(#s, #i, 1))) + ')';
END
SET #i = #i + 1;
END
It should tell you what other characters are in there, and where.