I wanted to create something that would be as non-invasive as possible. I didn’t want to force a person to create my procedure in his/her own database. And then I noticed SQL will allow you to create a temporary stored procedure, just by putting a ‘#’ in front of the procedure’s name. Great! Looked like the answer.
But then I ran up against, of all things, collation problems. I was using AdventureWorks as a database to test out my procedure. Its collation is Latin1_General_CI_AS. However the collation of my server is SQL_Latin1_General_CP1_CI_AS. This created some unexpected bumps in the road.
You can see some of these potholes for yourself. First find out tempdb’s collation (which is inherited from the server’s collation when tempdb is created):
select databasepropertyex('tempdb','collation')Now create a test database with a collation different from tempdb (I’m creating one below with a case-sensitive accent-insensitive collation just to be really contrarian) and (this is important!) make this new test database your current database:
--My system returns SQL_Latin1_General_CP1_CI_AS
if db_id('BSTest') is not null drop database BSTestNow, with BSTest as our current database context, try creating a laughably simple temporary procedure:
go
create database BSTest collate Latin1_General_CS_AI
go
use BSTest
go
create procedure #TempProc1You will get the following error: Cannot resolve the collation conflict between "Latin1_General_CS_AI" and "SQL_Latin1_General_CP1_CI_AS" in the equal to operation. (Your collation names may differ).
as
declare @var nchar(1)
set @var='X'
if @var='X' print 'Duh!'
Apparently there’s a problem comparing the variable @var to the constant of 'X'. It seems that @var inherits the collation of tempdb (since we’re creating the procedure there), but the constant inherits the collation of the current database context that we’re in (BSTest). They are incompatible.
Well, I guess that kinda sorta in a weird way makes a teensy-weensy bit of sense. I could certainly re-code the IF statement like so and it would work:
if @var = '' collate SQL_Latin1_General_CP1_CI_AS print 'Duh!'But I can only do that because I know exactly what tempdb’s collation is. Alternately I can use database_default to convert @var to the collation of the current database’s (BSTest’s) collation:
if @var collate database_default = 'X' print 'Duh!'Ultimately, for whatever reason, I decided to take care of it by doing something like the following instead:
create procedure #TempProc1Okay, that worked. But then I ran up against something like this:
as
declare @var nchar(1)
,@emptystring nchar(1)
set @var=''
set @emptystring=''
if @var=@emptystring print 'Duh!'
create procedure #TempProc2The same type of collation error occurs! It appears that not all NULLs are created equal. The NULL in the code above is apparently a NULL with BSTest’s collation.
as
declare @var nchar(1)
if @var is null print 'Duh!'
Swell. Now what? Well, how about the following? Seems straightforward enough:
create procedure #TempProc2Another error? Jeez! Now come on! What’s wrong with that? I’m comparing apples to apples, aren’t I? I get the same kind of collation error message, but now it says: Cannot resolve the collation conflict between ... in the is not operation. Huh? What IS NOT operation are they talking about?
as
declare @var nchar(1)
,@emptystring nchar(1)
set @emptystring=''
if coalesce(@var,@emptystring)=@emptystring print 'Duh!'
I find out (thanks to my pal Hunchback at the MSDN T-SQL Forum) that SQL converts COALESCE to a CASE function behind the scenes. You can see this if you look at a query plan for the following:
select coalesce(Name,'') from sys.databasesIf you look at the Properties of the Compute Scalar icon in the plan, you will see that it is essentially computing the following:
CASE WHEN [Name] IS NOT NULL THEN [Name] ELSE '' ENDSo that explains why the collation error message has a problem with an IS NOT operation. Luckily, if I use ISNULL instead of COALESCE, everything finally works fine. Whew!
Finally I had problems mixing variables and constants in SELECT commands:
create procedure #TempProc3Again, the above throws an error on both SELECT commands, citing a collation conflict. I ended up solving this kind of problem by adding COLLATE database_default to the SELECT column:
as
declare @var nchar(1)
set @var='X'
select 'Prefix'+@var
select 'Row#1' union all select @var
create procedure #TempProc3Note that I can specify the COLLATE clause in any of the SELECTs of a UNIONed query. It doesn’t matter which one. Just being specific about the collation in one SELECT controls the collation of the entire UNIONed query. Again, note that the database_default here is the database that has context (i.e. BSTest).
as
declare @var nchar(1)
set @var='X'
select 'Prefix'+@var collate database_default
select 'Row#1' collate database_default union all select @var
You can see all of this demonstrated here:
create procedure #TempProc4So I think the lesson here is to use temporary procedures with caution… or use them if you’re a glutton for punishment. Are they worth it? They seem to be a big pain in the posterior, but this whole experience did make me learn something. I hope you learned something too.
as
declare @var nchar(1)
set @var='X'
--Create 4 temp files
select StringConstant='Whatever' into #Junk1
select StringVar=@var into #Junk2
select StringCombo='Whatever'+@var collate database_default into #Junk3
select StringUnion='Whatever' collate database_default
into #Junk4
union all
select @var
--Now let's look at what collation was created for the columns
--and compare them to the collations of our databases
select Name,Collation_Name
from tempdb.sys.columns
where Name like 'String%'
union all
select 'tempdb',databasepropertyex('tempdb','collation')
union all
select db_name(),databasepropertyex(db_name(),'collation')
go
exec #TempProc4
/*
Name Collation_Name
-------------- ----------------------------
StringCombo Latin1_General_CS_AI
StringConstant Latin1_General_CS_AI
StringUnion Latin1_General_CS_AI
StringVar SQL_Latin1_General_CP1_CI_AS
tempdb SQL_Latin1_General_CP1_CI_AS
bstest Latin1_General_CS_AI
*/
Great start Brad! Keep the good stuff coming!
ReplyDelete--Plamen
Thanks, Plamen! I'll do my best to keep it interesting.
ReplyDeleteBrad, a couple comments:
ReplyDelete1) Only post once per day. Queue the rest. This makes it more likely to post for longer, spend more time on each post (making a higher quality post), not burning you out, and focuses. I just read both CROSS APPLY posts as one because they were posted so closely together. Ideally, each should be appreciated on its own.
2) Can we submit challenges to you? Or is that what the MS forums are for? :)
Thanks for the tips, Brian... most appreciated.
ReplyDeleteDo you have a challenge? I'm game.
Thanx Brad!
ReplyDeleteI like your posts, so i want this blog to be very successful. Selfish reasons of course. :)
My challenge was posted in the T-SQL forums. http://social.msdn.microsoft.com/Forums/en-US/transactsql/thread/2dd0a0bc-54b5-4a69-bbef-50418a05ec4d
After going through it in my head a few times, i've come to the conclusion that it is not possible. As it is confusing, though, i'm not sure i explained the question well enough.
If you'd like to look at it and give me your opinion, i'd much appreciate it. Though, as it stands right now, i'm going to be implementing it a stored PROCEDURE, with multiple queries. *sniff*, i like my all-in-one VIEW solution (used elsewhere).
If you're willing to take a look at it, i could help explain it further, or i'd email you the actual query in our application (which would require a bit more explanation). Or whatever for of communication your like.
Ouch... my head hurts.
ReplyDeleteI'm not sure this can be accomplished in one single query, recursive or not.
But I think you can do it with a recursive FUNCTION (kind of like a factorial works, if you know what I mean).
I'd have to think more on it.
--Brad
@Brad
ReplyDeleteHeh. I keep having to remind myself what i am doing. Worst part is, i designed those TABLEs. :)
Hmmm recursive FUNCTION, i didn't think of that at all. Currently, i'm most of the way through a stored PROCEDURE that uses a temp TABLE to store the results of calculations that don't rely on others (or rely on those already calculated). Unfortunately, every time i concentrate on it for too long something comes up and i lose my train of thought. But, i'm almost done.
Using a recursive FUNCTION, if possible, would allow a VIEW to be based upon it, which is something i'd really like to do. I'll have to think about to see if that makes sense. Well, if i can make sense of it.
If you are interested enough and you'd like me to explain it further, please let me know.
@Brad
ReplyDeleteJust wanted to add.
The confusing part of the problem is that it is a two-level grouping, where the second group can include the first group. Thus, the top-to-bottom approach does not work.
The obvious solution is to go bottom-to-top. That is, have a recursive CTE create a calculation-chain for each calculation, identifying each a level. So, if there is a level 2, level 1 relies on it, and so on. Then, process each calculation/level from the highest to lowest level.
That ought to work, but, if the second level includes more than one calculation, there is no way to have a CTE reference a record other than the one prior to it. For example, if calculation 1 has a set that uses the results of calculation 2 and calculation 3, even if processed 3 then 2 then 1, calculation 1 can get to the results of calculation 2 because it is the prior record. But calculation 3 is out of bounds. The branches are not handled well.
That is why i am using a temp TABLE.
Brad, bored of posting already?
ReplyDeleteI have an idea. CTEs can be UPDATEd, DELETEd, and so on. Confusing topic, i say. Want to try to tackle it?
No I'm not bored... our family is on vacation for 2 weeks... and yes, I would like to tackle that subject when I get back... you read my mind... that was something I was considering.
ReplyDeleteExcellent Brad.
ReplyDeleteEnjoy the summer. :)
ALTER DATABASE test2 -- put your database name here
ReplyDeleteCOLLATE Latin1_General_CS_AS -- replace with whatever collation you need
@Anonymous:
ReplyDeleteSure, you can change the collation of the database... but are you going to blindly do that on a Production database? Or on TempDB?
The point of this blog post was to get around having to do that... and the fact that TEMPDB can have a collation different from the USE database... I had a need to write generalized code to handle that collation difference without hard-coding a SPECIFIC collation.
I require you to thank for your period of this radiant read!!! I definately value every last bit of it and I have you bookmarked to take a gander at new stuff of your blog an outright need read blog!!!! collated printing meaning
ReplyDeleteI am genuinely thankful to the holder of this site page who has shared this splendid section at this place cheap 24 hours printing services near me
ReplyDeleteThe issue you we included above is certainly regard proficient for anyone to work out. the possibility of your article is substantial and It will realize a positive way. data entry professional
ReplyDeleteThe issue you we included above is certainly regard proficient for anyone to work out. the possibility of your article is substantial and It will realize a positive way. splashback colours
ReplyDeleteI have been looking to discover a solace or powerful method to finish this procedure and I think this is the most reasonable approach to do it viably. 먹튀검증
ReplyDeleteThought Media is a top mobile application development company. A team of the best mobile app designers and developers ready to grow your business fast. Work with leading mobile application developers today! mobile application development
ReplyDeleteThought Media is a top WordPress Web Design company providing web development businesses thrive on! Industry leading WordPress Experts and website designers that are skilled in SEO search engine optimization of your website to generate more leads and sales for your business. wordpress web design
ReplyDeleteThis is extremely exceptionally decent post you shared, I like the post, a debt of gratitude is in order for sharing.. End Of Tenancy Cleaning Gainsborough
ReplyDeleteI have been looking to discover a solace or powerful method to finish this procedure and I think this is the most reasonable approach to do it viably. Puerto Viejo Talamanca Zip Line Canopy
ReplyDeleteThis post is genuinely incredible. I really like this post. It is remarkable among different posts that I ve read in a long time. You shake for this alright post. I genuinely welcome it! new products
ReplyDeleteYour online diaries propel more each else volume is so captivating further serviceable It chooses me happen for pull back repeat. I will in a blaze grab your reinforce to stay instructed of any updates. satta matka
ReplyDeleteYour online diaries propel more each else volume is so captivating further serviceable It chooses me happen for pull back repeat. I will in a blaze grab your reinforce to stay instructed of any updates. buy magic mushrooms
ReplyDeleteYour online diaries propel more each else volume is so captivating further serviceable It chooses me happen for pull back repeat. I will in a blaze grab your reinforce to stay instructed of any updates. Moonboys
ReplyDeleteAn obligation of appreciation is all together for the better than average blog. It was amazingly useful for me. I m playful I found this blog. Thankful to you for offering to us,I too reliably increase some new helpful learning from your post. matka
ReplyDeleteI no uncertainty esteeming each and every bit of it. It is an amazing site and superior to anything normal give. I need to grateful. Marvelous work! Every one of you complete an unfathomable blog, and have some extraordinary substance. Keep doing stunning. satta king
ReplyDeleteI no uncertainty esteeming each and every bit of it. It is an amazing site and superior to anything normal give. I need to grateful. Marvelous work! Every one of you complete an unfathomable blog, and have some extraordinary substance. Keep doing stunning. satta
ReplyDeleteIncredible tips and straightforward. This will be exceptionally helpful for me when I get an opportunity to begin my blog. Río Cuarto Volcanic Lagoon
ReplyDeleteIncredible tips and straightforward. This will be exceptionally helpful for me when I get an opportunity to begin my blog. San Fernando Waterfall Costa Rica
ReplyDeleteI have a mission that I'm quite recently chipping away at, and I have been at the post for such data Costa Rica Snorkeling Tour
ReplyDelete