TSQL statement being skipped over - tsql
I am having a hard time figuring out why only the beginning part of my code is executing. I have inserted the statement '--Nothing after this point is executing' where the problem lies. Any help would be greatly appreciated
IF #i_numrecs = 0
BEGIN
SET #nextid = dbo.Timesheetgetnextid(#EMP, #DATE)
IF #mode = #c_current
BEGIN
INSERT INTO timedetail (
ppenddate,tsdate,empnum,task1,
task2,task3,hours,minutes,
doecode,duration,SOURCE,enteredby,
enteredon,approved,completed,timein,
timeout,id)
VALUES(
#DATE,#DATE,#EMP,' ',
' ',' ',0,0,
' ',0,'BROWSER',#UID,
#actiondate,'N','Y',' ',
' ',#nextid)
END
ELSE
BEGIN
INSERT INTO adjst_timedetail (
ppenddate,tsdate,empnum,task1,
task2,task3,hours,minutes,
doecode,duration,SOURCE,enteredby,
enteredon,approved,completed,timein,
timeout,id)
VALUES (
#DATE,#DATE,#EMP,' ',
' ',' ',0,0,
' ',0,'BROWSER',#UID,
#actiondate,'N','Y',' ',
' ',#nextid)
END
--Nothing after this point is executing
INSERT INTO timesheetchangelog (
empnum,tsdate,tdid,changedate,
changeby,SOURCE,changetype)
VALUES(
#emp,#date,#nextid,#actiondate,
#UID,#TCPIP,'COMPLETION INSERT')
SELECT #changelogid = Scope_identity()
EXEC Auditrecordaction
#emp,
#date,
#payfrequency,
#c_insertaction,
#actiondate,
#UID,
#actionid OUT,
#doaudit OUT
SELECT #doaudit DoAudit, #actionid ActionID
IF Isnull(#doaudit, 0) > 0
BEGIN
IF #mode = #c_current
BEGIN
INSERT INTO timedetail_audit (
EMPNUM,PPENDDATE,TSDATE,TASK1,
TASK2,TASK3,HOURS,MINUTES,
DOECODE,DURATION,SOURCE,TSGROUP,
DEPTCHRG,PAYRATE,TIMEIN,TIMEOUT,
ERRORS,COMPLETED,APPROVED,VERIFIED,
DELETED,PROCESSEDBYPAYROLL,ID,ENTEREDBY,
ENTEREDON,APPROVEDBY,APPROVEDON,PROCESSEDON,
QUANTITY,SHIFT,processed_pd,RUNNUMBER,
overnight,actionid,logid)
SELECT
EMPNUM,PPENDDATE,TSDATE,TASK1,
TASK2,TASK3,HOURS,MINUTES,
DOECODE,DURATION,SOURCE,TSGROUP,
DEPTCHRG,PAYRATE,TIMEIN,TIMEOUT,
ERRORS,COMPLETED,APPROVED,VERIFIED,
DELETED,PROCESSEDBYPAYROLL,ID,ENTEREDBY,
ENTEREDON,APPROVEDBY,APPROVEDON,PROCESSEDON,
QUANTITY,SHIFT,processed_pd,RUNNUMBER,
overnight,#actionid,#changelogid
FROM timedetail
WHERE empnum = #EMP
AND tsdate = #DATE
AND id = #nextid
END
ELSE
BEGIN
INSERT INTO timedetail_audit (
EMPNUM,PPENDDATE,TSDATE,TASK1,
TASK2,TASK3,HOURS,MINUTES,
DOECODE,DURATION,SOURCE,TSGROUP,
DEPTCHRG,PAYRATE,TIMEIN,TIMEOUT,
ERRORS,COMPLETED,APPROVED,VERIFIED,
DELETED,PROCESSEDBYPAYROLL,ID,ENTEREDBY,
ENTEREDON,APPROVEDBY,APPROVEDON,PROCESSEDON,
QUANTITY,SHIFT,processed_pd,RUNNUMBER,
overnight,actionid,logid)
SELECT
EMPNUM,PPENDDATE,TSDATE,TASK1,
TASK2,TASK3,HOURS,MINUTES,
DOECODE,DURATION,SOURCE,TSGROUP,
DEPTCHRG,PAYRATE,TIMEIN,TIMEOUT,
ERRORS,COMPLETED,APPROVED,VERIFIED,
DELETED,PROCESSEDBYPAYROLL,ID,ENTEREDBY,
ENTEREDON,APPROVEDBY,APPROVEDON,PROCESSEDON,
QUANTITY,SHIFT,processed_pd,RUNNUMBER,
overnight,#actionid,#changelogid
FROM adjst_timedetail
WHERE empnum = #EMP
AND tsdate = #DATE
AND id = #nextid
END
END
END
If this piece executes without errors....
IF #mode = #c_current
BEGIN
INSERT INTO timedetail (
ppenddate,tsdate,empnum,task1,
task2,task3,hours,minutes,
doecode,duration,SOURCE,enteredby,
enteredon,approved,completed,timein,
timeout,id)
VALUES(
#DATE,#DATE,#EMP,' ',
' ',' ',0,0,
' ',0,'BROWSER',#UID,
#actiondate,'N','Y',' ',
' ',#nextid)
END
ELSE
BEGIN
INSERT INTO adjst_timedetail (
ppenddate,tsdate,empnum,task1,
task2,task3,hours,minutes,
doecode,duration,SOURCE,enteredby,
enteredon,approved,completed,timein,
timeout,id)
VALUES (
#DATE,#DATE,#EMP,' ',
' ',' ',0,0,
' ',0,'BROWSER',#UID,
#actiondate,'N','Y',' ',
' ',#nextid)
END
then the next statement should and will try to execute. My guess is you're missing some errors here. Try running it from management studio in chunk to make sure they are in fact executing successfully.
Related
Getting Error With GROUP BY when Converting Query to Dynamic SQL
I'm trying to convert the following query to dynamic SQL to allow for variations: UPDATE T SET SumCount = J.SUM FROM #temp T JOIN (SELECT Count_99221 + COUNT_99222 + Count_99223 [SUM], t2.userID FROM #temp t2 GROUP BY t2.userID, Count_99221 + COUNT_99222 + Count_99223 ) J ON T.userID = J.UserID This is what I have for the Dynamic SQL: DECLARE #sql3 nvarchar(2000) = 'UPDATE T ' + 'SET SumCount = J.SumOfColumns ' + 'FROM #temp T ' + 'JOIN (SELECT ' + #columnSumString + ' [SumOfColumns], t2.userID ' + 'FROM #temp t2 ' + 'GROUP BY t2.userID, ' + #columnSumString + ' ) J ON T.userID = J.UserID' EXEC sp_executesql #sql3 I am receiving the following error only when I run the query as Dynamic SQL: Each GROUP BY expression must contain at least one column that is not an outer reference. Can somebody help explain why this is happening? I am new to Dynamic SQL so I'm not privy to any limitations for running queries this way. Thank you in advance. EDIT: The variable #columnString is a string made by concatenating several other column names, created in the following way: DECLARE #Cursor Cursor DECLARE #code varchar(20) DECLARE #ID INT SET #cptCursor = CURSOR FOR SELECT * FROM dbo.Split(#UserInput,CHAR(44)) OPEN #cptCursor FETCH NEXT FROM #cptCursor INTO #ID, #code WHILE ##FETCH_STATUS = 0 BEGIN DECLARE #colName varchar(50) = 'Count_' + cast(#code as varchar(10)) DECLARE #sql nvarchar(50) = 'ALTER TABLE #temp ADD ' + #colName + ' int' EXEC sp_executesql #sql --Code that adds values to each column that is created..... SET #columnSumString = #colName + ' + ' + #columnSumString --SET #columnSumString = #code + ' + ' + #columnSumString FETCH NEXT FROM #cptCursor INTO #ID, #code END CLOSE #Cursor DEALLOCATE #Cursor SET #columnSumString = SUBSTRING(#columnSumString,1,LEN(#columnSumString)-2) SELECT #columnSumString The user input is a comma separated string. "Count_99221 + COUNT_99222 + Count_99223" is just one example of columns created from the user input "99221, 99222, 99223". I also realized I was concatenating the #code variable into #columnSumString instead of #colName. Now when I run the query I don't get the error (even though I don't understand how the above error message relates to that mistake) but every value of SumCount is NULL.
IMHO you must re-write your query as follow: UPDATE #temp SET SumCount = (SELECT Count_99221 + COUNT_99222 + Count_99223 FROM #temp t2 WHERE t2.userID = #temp.userID) So the dynamic SQL will become: DECLARE #columnString varchar(200) SET #columnString = Count_99221 + COUNT_99222 + Count_99223 DECLARE #sql3 nvarchar(2000) = N'UPDATE #temp ' + 'SET SumCount = (SELECT ' + #columnString + ' FROM #temp t2 WHERE t2.userID = #temp.userID)' EXEC sp_executesql #sql3
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?
how can I insert data by declaring all fields (not *)?
Here's my insert query: INSERT INTO listing_replica_child ( ( SELECT rtz_comma_list(column_name) FROM information_schema.columns WHERE table_name = 'listing' ) ) VALUES ( ( SELECT ( ( SELECT rtz_comma_list(column_name) FROM information_schema.columns WHERE table_name = 'listing' ) FROM listing WHERE listing_id = 9656 ) ) ); I'm using postgres.
do $$ DECLARE rec TEXT; BEGIN SELECT 'insert into listing_replica_child (' || t.col || ') select * from listing WHERE listing_id = 9656 ' INTO rec FROM ( SELECT string_agg(column_name, ',') col FROM information_schema.columns WHERE table_name = 'listing' ) t; EXECUTE rec; END;$$; You can wrap this dynamic query into a function like this CREATE OR replace FUNCTION insert_listing_replica_child (_listing_id INT) RETURNS void AS $$ DECLARE rec TEXT; BEGIN SELECT 'insert into listing_replica_child (' || t.col || ') select * from listing WHERE listing_id = ' || _listing_id || ' ' INTO rec FROM ( SELECT string_agg(column_name, ',') col FROM information_schema.columns WHERE table_name = 'listing' ) t; EXECUTE rec; END $$ LANGUAGE plpgsql So if want to insert values from listing table with lissting_id=9656 into table listing_replica_child just Call select insert_listing_replica_child (9656)
The way to specify all destination fields is to omit the column list entirely. Your attempt then becomes: INSERT INTO listing_replica_child SELECT * FROM listing WHERE listing_id = 9656 without loss of intention: If the number and type of the fields of the tables differs with your attempt, execution will explode (as will this query). Even though you have asked that * not be used, it is the simplest and best way to achieve the task.
Invalid length parameter passed to the SUBSTRING function
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
T-SQL Space Before Capital Letters
The question is self explanatory. Could you please point out a way to put spaces between each capital letter of a string. SELECT dbo.SpaceBeforeCap('ThisIsATestString') would result in This Is A Test String.
This will add spaces only if the previous and next character is lowercase. That way 'MyABCAnalysis' will be 'My ABC Analysis'. I added a check for a previous space too. Since some of our strings are prefixed with 'GR_' and some also contain underscores, we can use the replace function as follows: select dbo.GR_SpaceBeforeCap(replace('GR_ABCAnalysis_Test','_',' ')) Returns 'GR ABC Analysis Test' CREATE FUNCTION GR_SpaceBeforeCap ( #str nvarchar(max) ) returns nvarchar(max) as begin declare #i int, #j int , #cp nchar, #c0 nchar, #c1 nchar , #result nvarchar(max) select #i = 1 , #j = len(#str) , #result = '' while #i <= #j begin select #cp = substring(#str,#i-1,1) , #c0 = substring(#str,#i+0,1) , #c1 = substring(#str,#i+1,1) if #c0 = UPPER(#c0) collate Latin1_General_CS_AS begin -- Add space if Current is UPPER -- and either Previous or Next is lower -- and Previous or Current is not already a space if #c0 = UPPER(#c0) collate Latin1_General_CS_AS and ( #cp <> UPPER(#cp) collate Latin1_General_CS_AS or #c1 <> UPPER(#c1) collate Latin1_General_CS_AS ) and #cp <> ' ' and #c0 <> ' ' set #result = #result + ' ' end -- if #co set #result = #result + #c0 set #i = #i + 1 end -- while return #result end
Assuming SQL Server 2005 or later, this modified from code taken here: http://www.kodyaz.com/articles/case-sensitive-sql-split-function.aspx CREATE FUNCTION SpaceBeforeCap ( #str nvarchar(max) ) returns nvarchar(max) as begin declare #i int, #j int declare #returnval nvarchar(max) set #returnval = '' select #i = 1, #j = len(#str) declare #w nvarchar(max) while #i <= #j begin if substring(#str,#i,1) = UPPER(substring(#str,#i,1)) collate Latin1_General_CS_AS begin if #w is not null set #returnval = #returnval + ' ' + #w set #w = substring(#str,#i,1) end else set #w = #w + substring(#str,#i,1) set #i = #i + 1 end if #w is not null set #returnval = #returnval + ' ' + #w return ltrim(#returnval) end This can then be called just as you have suggested above.
This function combines previous answers. Selectively choose to preserve adjacent CAPS: CREATE FUNCTION SpaceBeforeCap ( #InputString NVARCHAR(MAX), #PreserveAdjacentCaps BIT ) RETURNS NVARCHAR(MAX) AS BEGIN DECLARE #i INT, #j INT, #previous NCHAR, #current NCHAR, #next NCHAR, #result NVARCHAR(MAX) SELECT #i = 1, #j = LEN(#InputString), #result = '' WHILE #i <= #j BEGIN SELECT #previous = SUBSTRING(#InputString,#i-1,1), #current = SUBSTRING(#InputString,#i+0,1), #next = SUBSTRING(#InputString,#i+1,1) IF #current = UPPER(#current) COLLATE Latin1_General_CS_AS BEGIN -- Add space if Current is UPPER -- and either Previous or Next is lower or user chose not to preserve adjacent caps -- and Previous or Current is not already a space IF #current = UPPER(#current) COLLATE Latin1_General_CS_AS AND ( #previous <> UPPER(#previous) COLLATE Latin1_General_CS_AS OR #next <> UPPER(#next) collate Latin1_General_CS_AS OR #PreserveAdjacentCaps = 0 ) AND #previous <> ' ' AND #current <> ' ' SET #result = #result + ' ' END SET #result = #result + #current SET #i = #i + 1 END RETURN #result END GO SELECT dbo.SpaceBeforeCap('ThisIsASampleDBString', 1) GO SELECT dbo.SpaceBeforeCap('ThisIsASampleDBString', 0)
CLR and regular expressions or 26 replace statements a case sensitive collate clause and a trim.
Another strategy would be to check the ascii value of each character: create function SpaceBeforeCap (#str nvarchar(max)) returns nvarchar(max) as begin declare #result nvarchar(max)= left(#str, 1), #i int = 2 while #i <= len(#str) begin if ascii(substring(#str, #i, 1)) between 65 and 90 select #result += ' ' select #result += substring(#str, #i, 1) select #i += 1 end return #result end /*** SELECT dbo.SpaceBeforeCap('ThisIsATestString') **/
To avoid loops altogether, use of a tally table can help here. If you are running on SQL 2022, then the generate_series function can remove even this dependency. This method will be significantly faster than iterating through a loop. create function core.ufnAddSpaceBeforeCapital ( #inputString nvarchar(max) ) returns nvarchar(max) as begin declare #outputString nvarchar(max) select #outputString = string_agg(iif(t.value = 1, upper(substring(#inputString,t.value,1)),iif(ascii(substring(#inputString,t.value,1)) between 65 and 90, ' ','') + substring(#inputString,t.value,1)),'') from generate_series(1,cast(len(#inputString) as int)) t return #outputString end The scalar function is not inlineable, so I've provided an alternative inline table-valued function if that's what you need. create function core.ufnAddSpaceBeforeCapitalITVF ( #inputString nvarchar(max) ) returns table as return ( select string_agg(iif(t.value = 1, upper(substring(#inputString,t.value,1)),iif(ascii(substring(#inputString,t.value,1)) between 65 and 90, ' ','') + substring(#inputString,t.value,1)),'') as outputString from generate_series(1,cast(len(#inputString) as int)) t ) end
While I really like the char looping answers I was not thrilled with the performance. I have found this performs in a fraction of the time for my use case. CREATE function SpaceBeforeCap (#examine nvarchar(max)) returns nvarchar(max) as begin DECLARE #index as INT SET #index = PatIndex( '%[^ ][A-Z]%', #examine COLLATE Latin1_General_BIN) WHILE #index > 0 BEGIN SET #examine = SUBSTRING(#examine, 1, #index) + ' ' + SUBSTRING(#examine, #index + 1, LEN(#examine)) SET #index = PatIndex( '%[^ ][A-Z]%', #examine COLLATE Latin1_General_BIN) END RETURN LTRIM(#examine) end This makes use of the fact that case sensitive pattern search only works in some collations. The character class [^ ] means anything except space, so as we add the missing spaces we match farther into the string until it is complete.