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.
Related
I'd like to construct a 'union all' query with a while loop.
I've already tried += concatenation but doesn't work.
DECLARE #cnt1 int , #concat nvarchar(max), #qry nvarchar(500);
SET #cnt1 = 1;
WHILE #cnt1 < 99
BEGIN
SET #qry = 'select name_' + CAST(#cnt1 AS CHAR) + ' , name2_' + CAST(#cnt1 AS CHAR) + ', m.state1 FROM table1 P left join table2 M on M.name = P.name_' + CAST(#cnt1 AS CHAR) + ' where p.nb > 1';
SET #cnt1 = #cnt1 + 1;
SET #concat += ' UNION ALL ' + #qry
END
EXEC sp_executesql #concat
#concat is still empty at the end of the loop...
Thank you vm
Since #concat isn't initialized, the default value is null. Concatenating a value with null results in null, so no progress is made in your loop. Initializing it to an empty string:
declare #Concat as NVarChar(max) = N'';
will fix the problem.
Tip: The default length of a Char, VarChar, NChar or NVarChar is one character most of the time. When it is the target type of a Cast or Convert then it is 30 characters. Best practice: Always specify a length.
I want to rewrite a functioning, but slow stored procedure using CTE (common table Expression)
I have a big stored procedure where i build the nececary SQL dynamicaly based on parameter used. Currently there are 27 parameters, i compose the SQL string that i execute at the end it looks like:
DECLARE #SqlWhereClause NVARCHAR(MAX)
SET #SqlWhereClause = ' WHERE ([InTimeStamp] BETWEEN ''' + CONVERT(VARCHAR(19), #fromDate, 120) + ''' AND ''' + CONVERT(VARCHAR(19), #toDate, 120) + ''')'
IF #showOnlyErrors = '1'
BEGIN
SET #SqlWhereClause += ' AND Status = ''Error'''
END
IF LEN(LTRIM(RTRIM(#docNo))) > 0
BEGIN
IF #matchExact = '1'
BEGIN
SET #SqlWhereClause += ' AND DocumentNumber = ''' + #docNo + ''''
END
ELSE
BEGIN
SET #SqlWhereClause += ' AND (contains([DocumentNumber],'''+ #docNo +'''))'
END
END
At the end, i add the pagination and transform it to the final formSQL:
IF CONVERT(int, LTRIM(RTRIM(#takeRows))) > 0
BEGIN
SET #SqlOrderByClause += ' OFFSET ' + #rowNumberToSkip +' ROWS FETCH NEXT '+ #takeRows +' ROWS ONLY '
Set #RowCount = ' Select #totalRecords = count(1) from dbo.Messages WITH (NOLOCK) ' + #SqlWhereClause
END
SET #SQL = #SqlSelect + #SqlFrom + #SqlWhereClause + #SqlOrderByClause + ' ; ' + #RowCount
PRINT #SQL
EXECUTE sp_executesql #SQL, #params, #totalRecords OUTPUT
Everithing is working like a charm. No problems. Only performance problems. To solve one of it, i would try to use CTE (common table extpression)
But this is not working:
With DataSQL AS
(#SqlSelect + #SqlFrom + #SqlWhereClause + #SqlOrderByClause),
- incorrect syntax near #SqlSelect - Expecting '(' or Select.
I also tried this one:
WITH DataSQL AS
( Select #SqlSelect From #SqlFromFast
Where #SqlWhereClause Order By #SqlOrderByClause),
here i get:
An expression of non-boolean type specified in a context where a condition is expected, near 'Order'
Any idea? Or is it not possible to use CTE with multiple variables? All i found until now are simply queries with one, maybe two variables.
You could try:
WITH DataSQL AS
(
SELECT #SqlSelect SqlSelect
, #SqlFromFast SqlFromFast
, etcetera
)
Make sure you give aliases, or it won't work. I doubt it will help your performance issues though. You could try to use a temp table, that's usually quicker. you could also try to use inner variables:
DECLARE #Select VARCHAR(MAX) = #SQLSelect
And use those instead, that may help the optimizer, though that depends on your data.
my problem is pretty simple. I get a value from a sql select which looks like this:
ARAMAUBEBABRBGCNDKDEEEFOFIFRGEGRIEISITJPYUCAKZKG
and I need it like this:
AR,AM,AU,BE,BA,BR,BG,CN,DK,DE,EE,FO,FI,FR,GE,GR,IE,IS,IT,JP,YU,CA,KZ,KG
The length is different in each dataset.
I tried it with format(), stuff() and so on but nothing brought me the result I need.
Thanks in advance
With a little help of a numbers table and for xml path.
-- Sample table
declare #T table
(
Value nvarchar(100)
)
-- Sample data
insert into #T values
('ARAMAU'),
('ARAMAUBEBABRBGCNDKDEEEFOFIFRGEGRIEISITJPYUCAKZKG')
declare #Len int
set #Len = 2;
select stuff(T2.X.value('.', 'nvarchar(max)'), 1, 1, '')
from #T as T1
cross apply (select ','+substring(T1.Value, 1+Number*#Len, #Len)
from Numbers
where Number >= 0 and
Number < len(T1.Value) / #Len
order by Number
for xml path(''), type) as T2(X)
Try on SE-Data
Time to update your resume.
create function DontDoThis (
#string varchar(max),
#count int
)
returns varchar(max)
as
begin
declare #result varchar(max) = ''
declare #token varchar(max) = ''
while DATALENGTH(#string) > 0
begin
select #token = left(#string, #count)
select #string = REPLACE(#string, #token, '')
select #result += #token + case when DATALENGTH(#string) = 0 then '' else ',' end
end
return #result
end
Call:
declare #test varchar(max) = 'ARAMAUBEBABRBGCNDKDEEEFOFIFRGEGRIEISITJPYUCAKZKG'
select dbo.DontDoThis(#test, 2)
gbn's comment is exactly right, if not very diplomatic :) TSQL is a poor language for string manipulation, but if you write a CLR function to do this then you will have the best of both worlds: .NET string functions called from pure TSQL.
I believe this is what QQping is looking for.
-- select .dbo.DelineateEachNth('ARAMAUBEBABRBGCNDKDEEEFOFIFRGEGRIEISITJPYUCAKZKG',2,',')
create function DelineateEachNth
(
#str varchar(max), -- Incoming String to parse
#length int, -- Length of desired segment
#delimiter varchar(100) -- Segment delimiter (comma, tab, line-feed, etc)
)
returns varchar(max)
AS
begin
declare #resultString varchar(max) = ''
-- only set delimiter(s) when lenght of string is longer than desired segment
if LEN(#str) > #length
begin
-- continue as long as there is a remaining string to parse
while len(#str) > 0
begin
-- as long as know we still need to create a segment...
if LEN(#str) > #length
begin
-- build result string from leftmost segment length
set #resultString = #resultString + left(#str, #length) + #delimiter
-- continually shorten result string by current segment
set #str = right(#str, len(#str) - #length)
end
-- as soon as the remaining string is segment length or less,
-- just use the remainder and empty the string to close the loop
else
begin
set #resultString = #resultString + #str
set #str = ''
end
end
end
-- if string is less than segment length, just pass it through
else
begin
set #resultString = #str
end
return #resultString
end
With a little help from Regex
select Wow=
(select case when MatchIndex %2 = 0 and MatchIndex!=0 then ',' + match else match end
from dbo.RegExMatches('[^\n]','ARAMAUBEBABRBGCNDKDEEEFOFIFRGEGRIEISITJPYUCAKZKG',1)
for xml path(''))
I need to store a value with Null which is passed from a parameterized query using VB.net into SSEE 2008 R2.
The value maybe either 'Null' or a blank string "". How can I test for this and properly UPDATE the field in my Stored Procedure?
EDIT: Added declarations.
#ID int,
#currTable varchar(150),
#prev_LangString nvarchar(max),
#brief_Descrip nvarchar(max)
BEGIN
IF #brief_Descrip IS NULL OR #brief_Descrip = 'Null'
SET #brief_Descrip = 'Null';
END
BEGIN
SET #sql = 'UPDATE ' + #currTable + ' SET [date_Changed] = ''' + convert(varchar(20), #submitDate1) + ''', [prev_LangString] = ''' + #prev_LangString + ''', [brief_Descrip] = ''' + #brief_Descrip + '''
WHERE (ID = ' + CAST(#ID as nvarchar(10)) + '); '
EXECUTE(#sql);
END
Thanks for any help on this.
The problem is that you are converting #brief_Descript to a string. This will also fix your injection vulnerability.
BEGIN
IF #brief_Descrip = 'Null'
SET #brief_Descrip = NULL;
END
UPDATE TABLE table_name
SET
date_Changed = convert(varchar(20), #submitDate1),
prev_LangString = #prev_LangString,
brief_Descrip = #brief_Descrip,
WHERE
ID = CAST(#ID as nvarchar(10))
EDIT
The best way to fix this is to convert the string null to DBNull in vb.net, and use a parameterized query with an update statement.
You should not convert the date to a string. Change the column type to a date time.
Another option would be to use a parameterized query. It would even make your problem go away. Like this:
DECLARE #sql NVARCHAR(4000), #params NVARCHAR(4000)
SET #sql = 'UPDATE ' + #currTable + ' SET date_changed = #p0, prev_langstring = #p1, brief_descrip = #p2 WHERE id = #p3'
SET #params = '#p0 VARCHAR(20), #p1 VARCHAR(???), #p2 VARCHAR(???), #p3 NVARCHAR(10)'
DECLARE #sd VARCHAR(20), #sid NVARCHAR(10)
SET #sd = CONVERT(VARCHAR(20), #submiteDate1)
SET #sid = CAST(#ID AS NVARCHAR(10))
EXEC sp_executesql #sql, #params, #p0 = #sd, #p1 = #prev_langstring, #p2 = #brief_descrip, #p3 = #sid
I don't know the datatype of #prev_langstring and of #brief_descrip, hence the VARCHAR(???) you see above; replace it by the real datatype.
You can read about sp_executesql here.
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.