Idempotent QUOTENAME()? - tsql

The QUOTENAME() function in tsql is oft used when building dynamic sql to handle "special" charatcers within names. If one winds up with redundant calls via nested code it falls apart.
declare #name sysname = 'simple_name'
print '#name: ' + #name
print 'QUOTENAME(#name): ' + QUOTENAME(#name)
print 'QUOTENAME(QUOTENAME(#name)): ' + QUOTENAME(QUOTENAME(#name))
The results
#name: simple_name
QUOTENAME(#name): [simple_name]
QUOTENAME(QUOTENAME(#name)): [[simple_name]]]
Other than making sure these sort of chained calls are impossible within a codebase, has anyone a idempotent version of that is safe even for nested calls?

I don't know regex very well, but I'd imagine you could do some kind of #sql scrub before the exec.
I'm sure some kind of regex would be quicker, but here is my test.
DECLARE #name sysname = 'simple_name'
DECLARE #SQLTest nvarchar(max)
SET #SQLTest = 'select QUOTENAME(QUOTENAME(#name)), ''blah'', ''blah'''
SET #SQLTest = Replace(#SQLTest, Substring(#SQLTest, PatIndex('%QUOTENAME(QUOTENAME(%))%', #SQLTest), 20), 'QUOTENAME(')
SET #SQLTest = Replace(#SQLTest, Substring(#SQLTest, PatIndex('%))%', #SQLTest), 2), ')')
--- this might be a safer second replace?
SET #SQLTest = Replace(#SQLTest, Substring(reverse(#SQLTest), PatIndex('%))%EMANETOUQ%', reverse(#SQLTest)), 2), ')')
-
select #SQLTest
exec sp_executesql #SQLTest, N'#name sysname', #name

Related

I need some help understand this line of T-SQL code

I read this line of code in a book but could not fully comprehend its application. I understand this is regarding implicit conversion but don't fully understand the purpose of some of the code.
DECLARE #sql NVARCHAR(
SELECT ISNULL(''5'', 5),
ISNULL(5, ''5''),
COALESCE(''5'', 5),
COALESCE(5, ''5'') ;
' ;
EXEC sp_executesql #sql ;
SELECT column_ordinal,
is_nullable,
system_type_name
FROM master.sys.dm_exec_describe_first_result_set(#sql, NULL, 0) a ;
I'll go out on a limb and guess that it was intended to be executable code that demonstrates the difference between two similar TSQL functions. This example should produce the expected results:
SELECT column_ordinal, is_nullable, system_type_name FROM sys.dm_exec_describe_first_result_set
( N'SELECT ISNULL(''5'', 5), ISNULL(5, ''5''), COALESCE(''5'', 5), COALESCE(5, ''5'');', null, 0);
A SQL Fiddle example is available here.
A careful reading of the documentation for IsNull() and Coalesce(), paying particular attention to the Return Types of each, will explain the results. Data Type Precedence is also illustrative.

T-SQL stored procedure to get data from any table on the server for CSV export (SQL Server 2016)

Answered / Solved.
Long story short, I need a stored procedure that would get the data from a few different views and put it into a .CSV file. Easy enough, but me being me, I decided to write something that could get the data from any table one could potentially desire. I decided to go with 2 procedures in the end:
Loop through a table with all the parameters (catalog, schema, table name, export path/file name etc. etc.) and feed it to 2nd stored procedure (in theory it should make it easier to manage in future, if/when different data needs to be exported). This one is fairly straightforward and doesn't cause any issues.
Pick up the column names (which was surprisingly easy - below in case it helps anyone)
select #SQL = 'insert into Temp_Export_Headers ' +
'select COLUMN_NAME ' +
'from [' + #loc_Source_Database + '].information_schema.columns ' +
'where table_name = ''' + #loc_Source_Table + ''''
and
select #Headers = coalesce(#Headers + ',', '') + convert(varchar, Column_Name)
from Temp_Export_Headers
After that, I want to dump all the data from "actual" table into temp one, which in itself is easy enough, but that's where things start to go downhill for me.
select #SQL =
'drop table if exists TempData ' +
'select * ' +
'into TempData ' +
'from [' + #loc_Source_Database + '].' + #loc_Source_Schema + '.' + #loc_Source_Table + ' with (nolock) '
Select * is just temporary, will probably replace it with a variable later on, for now it can live in this state on dev.
Now I want to loop through TempData and insert things I want (everything at the moment, will add some finesse and where clauses in near future) and put it into yet another temp table that holds all the stuff for actual CSV export.
Is there any way to add a self incrementing column to my TempData without having to look for and get rid of the original PK / Identity? (Different tables will have different values / names for those, making it a bit of a nightmare for someone with my knowledge / experience to loop through in a sensible manner, so I'd just like a simple column starting with 1 and ending with whatever last row number is)
#ShubhamPandey 's answer was exactly what I was after, code below is a product of my tired mind on the verge of madness (It does, however, work)
select #SQL =
'alter table TempData ' +
'add Uni_Count int'
select #SQL2 =
'declare #UniCount int ' +
'select #UniCount = 0 ' +
'update tempdata with (rowlock) ' +
'set #UniCount = Uni_Count = #UniCount + 1'
Both versions execute quicker than select * into without any other manipulation. Something I cannot yet comprehend.
Is there a better / more sensible way of doing this? (My reasoning with the loop - there will potentially be a lot of data for some of the tables / views, with most of them executed daily, plan was to export everything on Sat/Sun when system isn't that busy, and have daily "updates" going from last highest unique id to current.)
Looping was a horrible idea. To illustrate just how bad it was:
Looping through 10k rows meant execution time of 1m 21s.
Not looping through 500k rows resulted in execution time of 56s.
Since you are doing a table creation while insertion, you can always go forward with a statement like:
select #SQL =
'drop table if exists TempData ' +
'select ROW_NUMBER() OVER (<some column name>) AS [Id], * ' +
'into TempData ' +
'from [' + #loc_Source_Database + '].' + #loc_Source_Schema + '.' + #loc_Source_Table + ' with (nolock) '
This would create an auto-incrementing index for you in the TempData table

Dynamic SQL Error with REPLACE Statement

I am trying to create a script that builds a SQL statement dynamically and then executes it.
Here is the string that is built & stored in #strSQL (verified by using PRINT)
UPDATE DNN_RSM_Exams
SET ScoringDates = REPLACE(ScoringDates, '/2015', '/2016')
WHERE SchoolYear = 2015
It executes the variable as follows:
EXECUTE (#strSQL)
However I get the following error:
Conversion failed when converting the nvarchar value 'UPDATE DNN_RSM_Exams SET ScoringDates = REPLACE(ScoringDates, '/' to data type int.
It seems to terminate the EXECUTE when it hits the first forward-slash (/). I tried escaping it using a double slash (//), an open bracket ([), and a backslash (\). None if it worked.
Can anyone help me please?
UPDATE #01: 8/17/15
Sorry if I didn't give enough information initially...
ScoringDates is an NVARCHAR(MAX) field.
The Code that builds the SQL Statement is:
SET #strSQL = 'UPDATE ' + #strTableName +
' SET ScoringDates = REPLACE(ScoringDates, ''/' + LTRIM(RTRIM(STR(#intSchoolYear_CopyFrom + 1))) + ''', ''/' + LTRIM(RTRIM(STR(#intSchoolYear_CopyFrom + 2))) + ''')' +
' WHERE SchoolYear = ' + LTRIM(RTRIM(STR(#intSchoolYear_CopyTo)))
ScoringDates is a string based field that holds data in an INI-like formatted string. I want to change the year portion of ANY date found in the string, but I want to avoid accidental changes of any other numbers that may match. So I am specifically looking to replace "/YYYY" with a different "YYYY" value. The "/" preceding the year value is to ensure that what is being preplaced is the YEAR and not another numeric value within the string.
UPDATE #02: 8/18/15
So I am completely flabbergasted...after banging my head on this script for hours yesterday, I went home defeated. Come in today, start up my PC and run the script again so I can see the error message again...and it worked!
I've never come across this with SQL Management Studio, but it is possible that SQL Management Studio somehow lost it's marbles yesterday and needed a reboot? I thought the SQL was process by the server directly. Could it be that some is processed by the studio first before handing it off to the server and if the studio had "issues" then it would cause strange errors?
In any case, thank you so much guys for your input, I am sorry that it was a wheel spinner. It never occurred to me that a reboot would fix my issue, I just assumed my code was wrong.
You want to replace '/ with ''
So your set statement will be something like...
SET #strSQL = 'UPDATE DNN_RSM_Exams
SET ScoringDates = REPLACE(ScoringDates, ''2015'', ''2016'')
WHERE SchoolYear = 2015'
EDIT:
How is your code different than this below? (I will edit this again and clean it up after. Code just won't fit into comments)
DECLARE #TableName TABLE (ScoringDates VARCHAR(100), SchoolYear INT)
DECLARE #strTableName VARCHAR(MAX)
DECLARE #intSchoolYear_CopyFrom VARCHAR(MAX)
DECLARE #intSchoolYear_CopyTo VARCHAR(MAX)
SET #strTableName = '#TableName'
SET #intSchoolYear_CopyFrom = '2009'
SET #intSchoolYear_CopyTo = '2010'
DECLARE #strSQL VARCHAR(MAX)
SET #strSQL = 'DECLARE #TableName TABLE (ScoringDates VARCHAR(100), SchoolYear INT); UPDATE ' + #strTableName +
' SET ScoringDates = REPLACE(ScoringDates, ''/' + LTRIM(RTRIM(STR(#intSchoolYear_CopyFrom + 1))) + ''', ''/' + LTRIM(RTRIM(STR(#intSchoolYear_CopyFrom + 2))) + ''')' +
' WHERE SchoolYear = ' + LTRIM(RTRIM(STR(#intSchoolYear_CopyTo)))
PRINT #strSQL
EXECUTE (#strSQL)

How to write recursive TSQL replace query?

I am using SSMS 2008 and am trying to write a recursive replace statement. I have a good start on this, but it is not working fully yet. I want to replace every occurrence of XML tags occurring in one column with empty string. So I want to replace the whole range from "<" to ">" for each record. Here is what I have:
DECLARE #I INTEGER
SET #I = 3
while
#I > 0
--(select [note_text] from #TEMP_PN where [note_text] LIKE '%<%')
BEGIN
UPDATE #TEMP_PN
SET [note_text] = replace([note_text],substring([note_text],CHARINDEX('<',[note_text]),CHARINDEX('>',[note_text])),'')
from #TEMP_PN
where [note_text] LIKE '%Microsoft-com%'
SET #I = #I - 1
END
SELECT * FROM #TEMP_PN
The problem with this code is I hardcoded #I to be 3. However, I want to make it continue replacing from "<" to ">" with empty string for each record until there are no more "<" chars. So I tried the commented out line above but this gives me an error on more than one record / subquery. How can I achieve this recursive functionality? Also, my Replace statement above only replaced "<" chars for some records, strangely enough.
I tried your sample code, but it still does not replace all instances of this text per record and for some records it does not replace any text although there is "<" in these records. Here is a record where your script does not replace any substrings. Maybe this is a special character problem?
<DIV class=gc-message-sms-row><SPAN class=gc-message-sms-from>TLS: </SPAN><SPAN class=gc-message-sms-text>Hi Reggie... I'm on my way to Lynn.. see you soon</SPAN> <SPAN class=gc-message-sms-time>3:09 PM </SPAN></DIV>
You were pretty close... the problem is that the SUBSTRING's third parameter is a length, not the position to stop at.
DECLARE #RowsUpdated INT
SET #RowsUpdated = 1
WHILE (#RowsUpdated > 0)
BEGIN
UPDATE #TEMP_PN
SET [note_text] =
REPLACE(
[note_text],
substring(
[note_text],
CHARINDEX('<',[note_text]),
CHARINDEX(
'>',
SUBSTRING([note_text], CHARINDEX('<',[note_text]), 1 + LEN([note_text]) - CHARINDEX('<',[note_text]))
)
),
'')
from #TEMP_PN
where [note_text] LIKE '%Microsoft-com%' and [note_text] like '%<%>%'
SET #RowsUpdated = ##ROWCOUNT
END
SELECT * FROM #TEMP_PN
SECOND EDIT:
OK, I've updated both queries; this code should now handle the leading > before the first tag... which I think could have been the issue.
DECLARE #TestString VARCHAR(MAX)
SELECT #TestString = '><DIV class=gc-message-sms-row><SPAN class=gc-message-sms-from>TLS: </SPAN><SPAN class=gc-message-sms-text>Hi Reggie... I''m on my way to Lynn.. see you soon</SPAN> <SPAN class=gc-message-sms-time>3:09 PM </SPAN></DIV>'
DECLARE #RowsUpdated INT
SET #RowsUpdated = 1
WHILE (#RowsUpdated > 0)
BEGIN
SELECT
#TestString =
REPLACE(
#TestString,
substring(
#TestString,
CHARINDEX('<',#TestString),
CHARINDEX(
'>',
SUBSTRING(#TestString, CHARINDEX('<',#TestString), 1 + LEN(#TestString) - CHARINDEX('<',#TestString))
)
),
'')
WHERE #TestString LIKE '%<%>%'
SET #RowsUpdated = ##ROWCOUNT
END
SELECT #TestString
Could it be because that note doesn't meet the [note_text] LIKE '%Microsoft-com%' criteria?

DESCENDING/ASCENDING Parameter to a stored procedure

I have the following SP
CREATE PROCEDURE GetAllHouses
set #webRegionID = 2
set #sortBy = 'case_no'
set #sortDirection = 'ASC'
AS
BEGIN
Select
tbl_houses.*
from tbl_houses
where
postal in (select zipcode from crm_zipcodes where web_region_id = #webRegionID)
ORDER BY
CASE UPPER(#sortBy)
when 'CASE_NO' then case_no
when 'AREA' then area
when 'FURNISHED' then furnished
when 'TYPE' then [type]
when 'SQUAREFEETS' then squarefeets
when 'BEDROOMS' then bedrooms
when 'LIVINGROOMS' then livingrooms
when 'BATHROOMS' then bathrooms
when 'LEASE_FROM' then lease_from
when 'RENT' then rent
else case_no
END
END
GO
Now everything in that SP works but I want to be able to choose whether I want to sort ASCENDING or DESCENDING.
I really can't fint no solution for that using SQL and can't find anything in google.
As you can see I have the parameter sortDirection and I have tried using it in multiple ways but always with errors... Tried Case Statements, IF statements and so on but it is complicated by the fact that I want to insert a keyword.
Help will be very much appriciated, I have tried must of the things that comes into mind but haven't been able to get it right.
You could use two order by fields:
CASE #sortDir WHEN 'ASC' THEN
CASE UPPER(#sortBy)
...
END
END ASC,
CASE #sortDir WHEN 'DESC' THEN
CASE UPPER(#sortBy)
...
END
END DESC
A CASE will evaluate as NULL if none of the WHEN clauses match, so that causes one of the two fields to evaluate to NULL for every row (not affecting the sort order) and the other has the appropriate direction.
One drawback, though, is that you'd need to duplicate your #sortBy CASE statement. You could achieve the same thing using dynamic SQL with sp_executesql and writing a 'ASC' or 'DESC' literal depending on the parameter.
That code is going to get very unmanageable very quickly as you'll need to double nest your CASE WHEN's... one set for the Column to order by, and nested set for whethers it's ASC or DESC
Might be better to consider using Dynamic SQL here...
DECLARE #sql nvarchar(max)
SET #sql = '
Select
tbl_houses.*
from tbl_houses
where
postal in (select zipcode from crm_zipcodes where web_region_id = ' + #webRegionID + ') ORDER BY '
SET #sql = #sql + ' ' + #sortBy + ' ' + #sortDirection
EXEC (#sql)
You could do it with some dynamic SQL and calling it with an EXEC. Beware SQL injection though if the user has any control over the parameters.
CREATE PROCEDURE GetAllHouses
set #webRegionID = 2
set #sortBy = 'case_no'
set #sortDirection = 'ASC'
AS
BEGIN
DECLARE #dynamicSQL NVARCHAR(MAX)
SET #dynamicSQL =
'
SELECT
tbl_houses.*
FROM
tbl_houses
WHERE
postal
IN
(
SELECT
zipcode
FROM
crm_zipcodes
WHERE
web_region_id = ' + CONVERT(nvarchar(10), #webRegionID) + '
)
ORDER BY
' + #sortBy + ' ' + #sortDirection
EXEC(#dynamicSQL)
END
GO