I want to be able to run a snippet of my SQL query by selecting it and pressing F5. Issue is, if that selection contains a variable name I get an error: Must declare the scalar variable "#variableName".. Is there anyway to resolve this? I want variableName to be the value it would otherwise be had I run the whole statement at that moment in time that I've selected...
Sample of full code:
DECLARE #cat INT;
SET #cat = 2;
SELECT * FROM TableName
WHERE ColumnName = #cat;
Sample of my selection that I want to run without including declaration/set lines:
SELECT * FROM TableName
WHERE ColumnName = #cat;
Probably not possible but I figured it'd be worth a shot.
P.S. I'm a SQL noobie so if I'm missing something obvious let me know!
I understand where you are coming from. This is a snippet in a long procedure or something and naturally you want to keep the declarations at the top, which I agree with. In this case, when you are testing, the only real way to circumvent this is to re-declare it and set it at the top of your snippet. Then, when you are running the entire batch of code just comment out this line. Otherwise you'd have to wrap the snippet in a try / catch block to try and catch compile errors which is tricky.
Also, this is usually how I've seen people put a select * from someWorkTable to test results along the way. Then it's commented out when the batch is ran.
Left click on table name, right click design and you are able to view the datatype of the column name.
if it is nvarchar..
DECLARE #cat INT;
SET #cat = 2;
Convert(varchar, #cat)
SELECT * FROM TableName
WHERE ColumnName = #cat;
OR
directly declare
DECLARE #Cat VARCHAR(3)
SET #cat = '2'
SELECT * FROM TableName
WHERE ColumnName = #cat;
Hope this helps
You can simulate your variable as declared in a select query with an alias result. Then you can just use alias.column as part of your join...
SELECT
TN.*
FROM
( select '2' as TmpColumn ) tmpAlias
JOIN TableName TN
on tmpAlias.TmpColumn = TN.ColumnName
this way, no "scalar" variable is required, but not as practical as a simple parameterized query using a direct WHERE clause. Additionally, you could use the alias.column throughout in case you had other tables relations, etc or even additional "variables" you wanted to apply in your query.
Related
Is it possible to test for a column before selecting it within a select statement?
This may be rough for me to explain, I have actually had to teach myself dynamic SQL over the past 4 months. I am using a dynamically generated parameter (#TableName) to store individual tables within a loop (apologize for the vagueness, but the details aren't relevant).
I then want to be able to be able to conditionally select a column from the table (I will not know if each table has certain columns). I have figured out how to check for a column outside of a select statement...
SET #SQLQuery2 = 'Select #OPFolderIDColumnCheck = Column_Name From INFORMATION_SCHEMA.COLUMNS Where Table_Name = #TABLENAME And Column_Name = ''OP__FolderID'''
SET #ParameterDefinition2 = N'#TABLENAME VARCHAR(100), #OPFolderIDColumnCheck VARCHAR(100) OUTPUT'
EXECUTE SP_EXECUTESQL #SQLQuery2, #ParameterDefinition2, #TABLENAME, #OPFolderIDColumnCheck OUTPUT
IF #OPFolderIDColumnCheck IS NULL
BEGIN
SET #OP__FOLDERID = NULL
END
ELSE
IF #OPFolderIDColumnCheck IS NOT NULL
BEGIN
...etc
but id like to be able to do it inside of a select statement. Is there a way to check and see if OP__FOLDERID exists in the table?
Id like to be able to do something like this:
SELECT IF 'OP__FOLDERID' EXISTS IN [TABLE] THEN 'OP__FOLDERID' FROM [TABLE]
Thank you for any help or direction you can offer.
I'm afraid there isn't any direct way to do this within a SELECT statement at all. You can determine if a column exists in a table, however, and construct your dynamic SQL accordingly. To do this, use something like this:
IF COL_LENGTH('schemaName.tableName', 'columnName') IS NOT NULL
BEGIN
-- Column Exists
END
You could then set a variable as a flag, and the code to construct the dynamic SQL would construct the expression with/without the column, as desired. Another approach would be to use a string value, and set it to the column name if it is present (perhaps with a prefix or suffix comma, as appropriate to the expression). This would allow you to save writing conditionals in the expression building, and would be particularly helpful where you have more than one or two of these maybe-columns in a dynamic expression.
DECLARE #a varchar(max);
set #a ='''a'' OR Name like ''%a'';';
--Why the below query not working
Select TOP 10 * FROM Member where Name = #a
-- The query below was executed to make sure that the query above
being constructed properly
print 'SQL: Select TOP 10 * FROM Member where Name ='+ #a
--SQL: Select TOP 10 * FROM Member where Name ='a' OR Name like '%a';
Correct me if im wrong, SQL injection wont work in Stored Procedure is due to some precompiled factor but the above scenario was tested in query statement instead of Stored Procedure. Why still not working?
I'm not sure why you think that would work. #a is a varchar variable, so Select TOP 10 * FROM Member where Name = #a finds rows where Name is equal to the value of that variable.
If you want SQL-Server to take the value of #a and insert it into the query as code, then you need to use sp_executesql (analogous to eval in languages like Bash and Python and JavaScript):
EXECUTE sp_executesql 'Select TOP 10 * FROM Member where Name = ' + #a
SQL Injection occurs when data is confused for and interpreted as code.
This does not happen in your scenario since parameter or variable values are not directly interpreted as code - they're only at risk of being interpreted as code if you construct new code by combining strings and these parameter/variable values and then pass the entire constructed string to the system and ask it to interpret the entire string as code - via exec, sp_executesql or other such means.
Look there is no name ending with 'a'. Try like
Select TOP 10 * FROM Member where Name ='a' OR Name like '%a%'
Updated
Microsoft handle SQL injection for SQL parameters.
I was wondering if it is possible to set a declared variable to a return value from a select result? Something like:
#WatchedSeconds
SET #WatchedSeconds = 200
DECLARE #SelectedVideo int
SET #SelectedVideo = (SELECT TOP 1 * FROM Video v WHERE v.VideoID = 12)
IF #SelectedVideo IS NOT NULL
BEGIN
IF #SelectedVideo.VideoLength = #WatchedSeconds
BEGIN
--DO SOMETHING
END
IF #SelectedVideo.SomeOtherColumn = #SomethingElse
BEGIN
END
END
It's for using some information from the SELECT result multiple places in a Stored Procedure.
I know that I can set a variable to e.g, a integer, and set it to the selected result, if it returns a integer, e.g:
DECLARE #VideoSeconds int
SET #VideoSeconds = (SELECT v.Length FROM Video v WHERE v.VideoID = #VideoID)
This way I have to make multiple variables, and multiple SELECT calls if I need to use more values from the Video result. And that's what I want to avoid.
You can do this simply by running:
SELECT #videoSeconds = v.Length FROM Video v WHERE v.VideoID = #VideoID
so as to not add the SET part.
Also, you must make sure that only 1 row is being returned by the query, otherwise it will generate an error.
You can try something like
(declare variables first...)
SELECT TOP 1 #var1=col1, #var2=col2, #var3=col3, [...] FROM YourTable WHERE YourFilter
EDIT: All together this seems not to be the best approach... With SQL you should not think in values and single rows but rather in result sets (set based programming). Your thinking leads to many tiny selects, while loops, cursors and all this stuff one should avoid.
You can store the results in a temporary table or table variable:
SELECT TOP 1 *
INTO #SelectedVideo
FROM Video v
WHERE v.VideoID = 12;
Then you can assign values from the table later in your code. Something like:
IF ( (SELECT VideoLength FROM #SelectedVideo) = #WatchedSeconds)
However, for your particular example, if you have an index on video(VideoId), then there is little to be gained performance-wise from using a temporary table.
If what you're trying to get is similar to returning a dataset in a procedural language (so you can type something like Result.Field1 = 'Test') then I don't think this is possible. You'll just need to declare multiple variables and make the SELECT call as
SELECT TOP 1 #var1=col1, #var2=col2, #var3=col3, [...] FROM YourTable WHERE YourFilter
as #Shnugo suggests
The 'dataset' equivalent structure in SQL is cursors, but they require variables to be set up as well, so there's no benefit there.
I've used dynamic SQL for many tasks and continuously run into the same problem: Printing values of variables used inside the Dynamic T-SQL statement.
EG:
Declare #SQL nvarchar(max), #Params nvarchar(max), #DebugMode bit, #Foobar int
select #DebugMode=1,#Foobar=364556423
set #SQL='Select #Foobar'
set #Params=N'#Foobar int'
if #DebugMode=1 print #SQL
exec sp_executeSQL #SQL,#Params
,#Foobar=#Foobar
The print results of the above code are simply "Select #Foobar". Is there any way to dynamically print the values & variable names of the sql being executed? Or when doing the print, replace parameters with their actual values so the SQL is re-runnable?
I have played with creating a function or two to accomplish something similar, but with data type conversions, pattern matching truncation issues, and non-dynamic solutions. I'm curious how other developers solve this issue without manually printing each and every variable manually.
I dont believe the evaluated statement is available, meaning your example query 'Select #FooBar' is never persisted anywhere as 'Select 364556243'
Even in a profiler trace you would see the statement hit the cache as '(#Foobar int)select #foobar'
This makes sense, since a big benefit of using sp_executesql is that it is able to cache the statement in a reliable form without variables evaluated, otherwise if it replaced the variables and executed that statement we would just see the execution plan bloat.
updated: Here's a step in right direction:
All of this could be cleaned up and wrapped in a nice function, with inputs (#Statement, #ParamDef, #ParamVal) and would return the "prepared" statement. I'll leave some of that as an exercise for you, but please post back when you improve it!
Uses split function from here link
set nocount on;
declare #Statement varchar(100), -- the raw sql statement
#ParamDef varchar(100), -- the raw param definition
#ParamVal xml -- the ParamName -to- ParamValue mapping as xml
-- the internal params:
declare #YakId int,
#Date datetime
select #YakId = 99,
#Date = getdate();
select #Statement = 'Select * from dbo.Yak where YakId = #YakId and CreatedOn > #Date;',
#ParamDef = '#YakId int, #Date datetime';
-- you need to construct this xml manually... maybe use a table var to clean this up
set #ParamVal = ( select *
from ( select '#YakId', cast(#YakId as varchar(max)) union all
select '#Date', cast(#Date as varchar(max))
) d (Name, Val)
for xml path('Parameter'), root('root')
)
-- do the work
declare #pStage table (pName varchar(100), pType varchar(25), pVal varchar(100));
;with
c_p (p)
as ( select replace(ltrim(rtrim(s)), ' ', '.')
from dbo.Split(',', #ParamDef)d
),
c_s (pName, pType)
as ( select parsename(p, 2), parsename(p, 1)
from c_p
),
c_v (pName, pVal)
as ( select p.n.value('Name[1]', 'varchar(100)'),
p.n.value('Val[1]', 'varchar(100)')
from #ParamVal.nodes('root/Parameter')p(n)
)
insert into #pStage
select s.pName, s.pType, case when s.pType = 'datetime' then quotename(v.pVal, '''') else v.pVal end -- expand this case to deal with other types
from c_s s
join c_v v on
s.pName = v.pName
-- replace pName with pValue in statement
select #Statement = replace(#Statement, pName, isnull(pVal, 'null'))
from #pStage
where charindex(pName, #Statement) > 0;
print #Statement;
On the topic of how most people do it, I will only speak to what I do:
Create a test script that will run the procedure using a wide range of valid and invalid input. If the parameter is an integer, I will send it '4' (instead of 4), but I'll only try 1 oddball string value like 'agd'.
Run the values against a data set of representative size and data value distribution for what I'm doing. Use your favorite data generation tool (there are several good ones on the market) to speed this up.
I'm generally debugging like this on a more ad hoc basis, so collecting the results from the SSMS results window is as far as I need to take it.
The best way I can think of is to capture the query as it comes across the wire using a SQL Trace. If you place something unique in your query string (as a comment), it is very easy to apply a filter for it in the trace so that you don't capture more than you need.
However, it isn't all peaches & cream.
This is only suitable for a Dev environment, maybe QA, depending on how rigid your shop is.
If the query takes a long time to run, you can mitigate that by adding "TOP 1", "WHERE 1=2", or a similar limiting clause to the query string if #DebugMode = 1. Otherwise, you could end up waiting a while for it to finish each time.
For long queries where you can't add something the query string only for debug mode, you could capture the command text in a StmtStarted event, then cancel the query as soon as you have the command.
If the query is an INSERT/UPDATE/DELETE, you will need to force a rollback if #DebugMode = 1 and you don't want the change to occur. In the event you're not currently using an explicit transaction, doing that would be extra overhead.
Should you go this route, there is some automation you can achieve to make life easier. You can create a template for the trace creation and start/stop actions. You can log the results to a file or table and process the command text from there programatically.
I need to make a SELECT with a call of a stored procedure in the WHERE clause.
It should be something like that....
SELECT distinct top 10 i.x, d.droit
FROM v_droit d, v_info i
WHERE d.nomdroit='yy'
AND i.id<>2
AND (select val from (exec up_droits(i.x, d.droit)) <>3
But it does not work...
Any idea?
Don't say to replace the stored procedure with a function because is not possible to use the existing code in a function. So the function is not a valid option. I really need to be able to use a stored procedure
This is achieved by first executing the stored procedure, capturing the output into a #temp table or a #tabel variable, then running your query against the table. Something like this:
declare #droits_table (val ,... );
insert into #droits_table
exec up_droits(param, param);
SELECT distinct top 10 i.x, d.droit FROM v_droit d, v_info i WHERE d.nomdroit='yy' AND i.id<>2 AND (select val from #droits) <>3
Of course this will not work for you because the up_droits needs the i.x and d.droit parameters from the query. This indicates that your stored procedure should probably be a a view or table valued function.
Sorry but, make it a table valued function rather than stored procedure.
Eg:
Scalar - SELECT id, name FROM test WHERE id < (SELECT dbo.mytestfunction())
Table - SELECT id, name FROM test WHERE id = (SELECT col1 from dbo.mytestfunction())
You can't. The content of the WHERE clause must be a search expression.
Is the reason that the code doesn't work as a function because it modifies some data? If so, then you're out of luck, functions used in where clauses must be immutable.
If the stored procedure doesn't modify any data, you may be able to wrap it inside of a function.
If you are on SQL Server I don't think you can do what you propose.
But one thing you can do is build dynamic queries, but be careful doing it because they open up many interesting problemareas.
The syntax is :
EXEC #<query>
But anotherthing you can do, which is probably much better for you, is to make the up_droits function deliver it's results in a temp table, if you select into a #table it is temporary for the duration of your function/procedure scope
declare procedure up_droits() as
select val .. into #temp
So what you do is create a procedure
create procedure Top10FromDroit
begin
exec up_droits
SELECT distinct top 10 i.x, d.droit FROM v_droit d, v_info i WHERE d.nomdroit='yy' AND i.id2 AND (select val from (#temp) 3
Hopefully that will give you the results you want to achieve.
If at first you don't succeed, code around it^^
Could anyone of you explain reasons for executing dynamic SQl inside stored procedure. I know very few situations when you need them - but really very few. 99.9% (or 999 of a 1000) of execute strings could be rewritten as normal sql statements with parameters.
The very same is with Selects that have functions inside select or where clauses.
Try to think about your sets of data, not about procedural ways how to solve it.