tsql - Parameterized SQL with dynamic WHERE column -
i trying write simple filtering, user can input column filter , value. tricky part dynamically choosing column filter.
i've found couple solutions on web , not sure implement. preference lean toward performance instead of maintainability. opinions appreciated.
let's assume have table "t", has 5 varchar columns: "c1", "c2", "c3", "c4", , "c5".
solution 1 - easy way
i use dynamic sql. on lines of:
declare @sql varchar(max) = 'select * t ' + @columnname + ' = ''' + @columnvalue + ''';' exec (@sql);
which come out like:
select * t c1 = 'asdf' ;
i don't want use solution following 2 reasons. i'm including simple point of reference before going down rabbit hole.
- it not guard against sql injection.
- even if parameterize columnvalue, have 5 different execution plans cached each of 5 columns since cannot parameterize @columnname.
solution 2 - or's
could use series of or's 2 parameters. let's say:
@columnname = 'c1' @columnvalue = 'asdf'
then sql become:
select * t (@columnname = 'c1' , c1 = @columnvalue) or (@columnname = 'c2' , c2 = @columnvalue) or (@columnname = 'c3' , c3 = @columnvalue) or (@columnname = 'c4' , c4 = @columnvalue) or (@columnname = 'c5' , c5 = @columnvalue) or (@columnname null , 0 = 0) ;
i try stay away using or when possible. remember reading somewhere suffers performance issues, i'm no dba , can't up. thoughts?
solution 3 - coalesce
this solution relies on having parameter each column. parameters on lines of:
@c1 = 'asdf'; @c2 = null; @c3 = null; @c4 = null; @c5 = null;
sql comes out to:
select * t c1 = coalesce(@c1, c1) , c2 = coalesce(@c2, c2) , c3 = coalesce(@c3, c3) , c4 = coalesce(@c4, c4) , c5 = coalesce(@c5, c5) ;
does have opinion on method implement? i'm leaning towards coalesce, have no hard numbers or experience on matter. maybe there better way of doing things?
the safest way:
declare @sql nvarchar(max) = n'select * dbo.t ' + quotename(@columnname) + ' = @columnvalue;'; exec sp_executesql @sql, n'@columnvalue varchar(255)', @columnvalue;
to guard further sql injection, first check:
if @columnname not in (n'c1',n'c2',n'c3',n'c4',n'c5') begin raiserror('nice try! %s not valid.', 11, 1, @columnname); return; end
or @habo suggested, against sys.columns
catalog view:
if not exists ( select 1 sys.columns name = @columnname , [object_id] = object_id('dbo.t') ) begin raiserror('nice try! %s not valid.', 11, 1, @columnname); return; end
especially when combined optimize ad hoc workloads
, ok have 5 different execution plans - since are, after all, 5 different queries optimize differently based on indexes on different columns, distribution of data within columns, etc.
your or
, coalesce
versions - unless pay compilation hit every single time - stuck using same plan no matter column provided, therefore may work situations, not others. , plan gets not based on what's best parameter sent first.
also, if concerned performance, maybe don't use select *
- if don't need of columns. if do, never know when adds blob or geometry or xml or other expensive column table, , code retrieves though doesn't care it.
Comments
Post a Comment