Is it possibile to count the number of result that a query returns using sp_executesql without executing query?
What I mean:
I have a procedure that gets a sql query in string.
Example:
SELECT KolumnaA FROM Users WHERE KolumnaA > 5
I would like to assign count of how many results this query will return, and store it in a variable, but I do not want to actually execute the query.
I cannot use this solution:
EXECUTE sp_executesql #sql
SET #allCount = ##rowcount
because it returns the query result, in addition to getting the count of returned rows.
Can you somehow generate another query from the above one like this
SELECT count(*) FROM Uzytkownicy WHERE KolumnaA > 5
and then execute that?
In general case...
SELECT COUNT(*) FROM ( <your query> )
...which in your case can be simplified into:
SELECT COUNT(*) FROM Users WHERE KolumnaA > 5
The reason it can't be done cheaper is that there are no hidden "counters" inside the data managed by the DBMS. The DBMS won't even know the total number of rows in the table, let alone the number of rows fulfilling a criteria that is not known in advance (such as KolumnaA > 5).
So, counting requires actually finding the data, so it requires the "real" query. Fortunately, all this happens on the server and only a minute amount of data is transferred to the client (the count itself), so assuming your data is properly indexed it should be pretty fast.
Be careful about consistency though: just because the counting query returned certain count, does not mean that the "real" query will return the same number of rows (in the environment where multiple clients may be modifying the data concurrently).
I think there are two parts to the question, which I will address.
The first part of the question is how to return row counts instead of the query results. This is done using Count(item). Using Count(1) instead of Count(KolumnaA) can be slightly faster, since it just counts the number of rows to be returned, instead of retreiving a specific column.
SELECT Count(1) FROM Users WHERE KolumnaA > 5
The second part is assigning this to a variable. If you need to use sp_executesql, you can do as follows:
Declare #sql varchar(4000)
Declare #allCount int
Set #sql = 'SELECT 1 FROM Users WHERE KolumnaA > 5'
sp_executesql(#sql)
SET #allCount = ##rowcount
Alternatively, you can try to use the sp_executesql output feature:
DECLARE #allCount int
EXEC sp_executesql
N'#allCount = SELECT Count(1) FROM Users WHERE KolumnaA > 5',
'#allCount int OUTPUT',
#allCount OUTPUT
Use:
SELECT COUNT(1) FROM Uzytkownicy WHERE KolumnaA > 5
Yes, it does execute a query. But it doesn't return results other than the number of rows.
Otherwise, I don't see how you can avoid returning results.
It will execute the query but it will return only the count not the actual result.
SELECT count(*) FROM Uzytkownicy WHERE KolumnaA > 5
Here is the result that I have found:
Getting Rowcount within sp_executesql
Basically, re-write your query as follows:
DECLARE #SQL NVARCHAR(1000)
DECLARE #Count INT
SET #SQL = 'SELECT KolumnaA FROM Users WHERE KolumnaA > 5; SELECT #Count = ##ROWCOUNT;'
DECLARE #Params NVARCHAR(100)
SET #Params = '#Count INT OUTPUT'
EXEC sp_executesql #SQL, #Params, #Count = #Count OUTPUT
PRINT #Count --should return the number of rows
Cheers.
Related
If I run this:
DECLARE #sql NVARCHAR(10) = NULL;
EXEC sp_executesql #sql;
SELECT ##ROWCOUNT;
I would expect to get 0, maybe even NULL would make sense. But I don't get either, I get 1. Why is 1 row affected by executing a NULL query? If I pass in a "proper" (non_NULL) query then it works fine.
Background (for those that care): this is from a process that is supposed to generate some dynamic SQL to update one row and ONLY one row. I need to check that 1 row has been affected, not 0 or 2 or more than 2. It worked fine until somehow a NULL SQL statement managed to be generated, and this was seen as a success - oops!
The actual fix will be to check the SQL is non-NULL before running it, and treat a NULL statement the same way as a result other than 1. But I was still curious why it behaved this way.
It's because you're assign NULL to your variable. Statements that make a simple assignment always set the ##ROWCOUNT value to 1.
See the example below. Because management studio can run its own queries on the connection and mess with the ##ROWCOUNT value it starts off selecting an empty result set to ensure the initial ##ROWCOUNT value is zero.
When there is no assignment the SELECT ##ROWCOUNT returns 0 (the initial value has not been modified). Otherwise it returns 1
/*Ensure ##ROWCOUNT starts off at 0*/
SELECT 1 WHERE 1 = 0;
DECLARE #sql NVARCHAR(10);
EXEC sp_executesql #sql;
SELECT ##ROWCOUNT;
GO
/*Ensure ##ROWCOUNT starts off at 0*/
SELECT 1 WHERE 1 = 0;
DECLARE #sql NVARCHAR(10) = NULL;
EXEC sp_executesql #sql;
SELECT ##ROWCOUNT;
You can also try similar with a non zero initial value:
/*Ensure ##ROWCOUNT starts off at 3*/
SELECT 1 UNION SELECT 2 UNION SELECT 3
DECLARE #sql NVARCHAR(10);
EXEC sp_executesql #sql;
SELECT ##ROWCOUNT; --Returns 3
GO
/*Ensure ##ROWCOUNT starts off at 3*/
SELECT 1 UNION SELECT 2 UNION SELECT 3
DECLARE #sql NVARCHAR(10) = NULL;
EXEC sp_executesql #sql;
SELECT ##ROWCOUNT; --Returns 1
Please note that my answer was written purely from my experience of SQL Server Management Studio and does not accurately explain this behaviour. Martin Smith has explained why this isn't true in a comment below.
It looks like sp_executesql doesn't run at all with a null parameter, perhaps as a failsafe.
Try running "SELECT ##ROWCOUNT" alone in a batch and you'll see that it returns 1, regardless of the fact there is no current rowcount. It seems likely that 1 is the default return value and that's why you're seeing this.
Need help to figure ut why the remote query I execute on the server return 0 rows , but the same query returns over 900k rows in the target DB.
the string is less 8000 characters long so I won't post it here. but this is the sctructure basically:
declare #SQL varchar(MAX);
declare #D varchar(15);
declare #Per varchar(15);
declare #NextPer varchar(15);
declare #NextYPer varchar(15);
set #D = N'01-JUN-2019'
set #Per = N'2020004';
set #NextYPer = N'2021004'
set #NextPer = N'2020005'
set #SQL = N' SELECT ...... '
set #SQL = N'select * from openquery ([LK1], "'+#SQL+'")';
execute( #SQL);
print #SQL;
Note: the linked server works and is used on other openqueries with shorter strings successfully. I tried using EXECUTE (#SQL) AT and I still get 0 rows. When i exexute the print output directly on the Oracle DB , the query runs for about 15 min and gives results.
First off, OPENQUERY requires the second parameter to be a query string. A string in SQL Server is written between single quotes. From OPENQUERY documentation:
OPENQUERY ( linked_server ,'query' )
Not only that, the SQL that appears in that string, needs to have any single-quotes that appear in the query to be doubled. Suppose you had SQL you wanted to execute the following query:
SELECT * FROM some_table WHERE name='TT.';
You would write this as:
OPENQUERY(lks,'SELECT * FROM some_table WHERE name=''TT.''')
But if you have this in a dynamic SQL statement, this would become
DECLARE #s VARCHAR(MAX);
SET #s='SELECT * FROM OPENQUERY(lks,''SELECT * FROM some_table WHERE name=''''TT.'''''')';
So that's some explosion of single quotes for even the more trivial SQL queries. Count ye quotes, make sure the query itself is up to snuff (i.e. quotes have been properly doubled).
Thanks all for the input.
The root cause is simply the format of the Date parameter, which didn't run correctly on the linked server.
All I had to do is change my query to use this:
SO_BOOK_DATE < to_date(''#D'' , ''DD-MON-YYYY'')
instead of
SO_BOOK_DATE < ''#D'' .
I have a dynamic query #strQuery which on executing gives a result with lots of column.
I want to insert the result from this dynamic query into a temporary table .
I am doing this because I want to perform some filtering on the temporary table and get required result .
A similar question was asked on previous thread HERE
in which a temporary table is created first and then data inserted using INSERT INTO.
I want to avoid this step due to long list of columns and also the datatypes of fields is not known to me.
select * into #tmh from
exec(#strQuery)
Error Message
Incorrect syntax near the keyword 'exec'.
How to do this ? Is it possible to be done in this way ? If not , please specify some other alternative to get store the result on executing dynamic query into a table.
Thanks.
I have faced this situation before and here is what I did:
DECLARE #strQuery nVarchar(100)
SET #strQuery='SELECT * into [tempdb].[dbo].[temptable] FROM YourTable'
EXECUTE sp_executesql #strQuery
SELECT * FROM [tempdb].[dbo].[temptable]
DROP TABLE [tempdb].[dbo].[temptable]
It works fine. Don't ask me why a FQ table name and not #temptable. I have no idea. It does not work. The only way I could get it working was using [tempdb].[dbo].[temptable]
proceed like this
select t1.name,t1.lastname from(select * from table)t1.
where "select * from table" is your dyanmic query. which will return result which you can use as temp table t1 as given in example .
You can use variables in your current execution context, set by the Dynamic SQL with the OUTPUT option. Sample code below.
DECLARE #Amount AS MONEY
DECLARE #SQL AS NVARCHAR(1000)
SET #Amount = NULL
SET #SQL = ('SELECT #amt=100' )
EXECUTE sp_executeSQL #SQL, N'#amt MONEY OUTPUT', #amt=#Amount OUTPUT
SELECT #Amount
Yes you can make a new dynamic query containing the original query with the insert like this:
declare #strNewQuery varchar(max)
set #strNewQuery ='select * into #tmh from ('+#strQuery+') as t'
exec(#strNewQuery)
I used this to work around - with out dynamic query
This uses a table variable to receive data to procedure
Even joins can be applied to it
select * into #itemPhantom from #tbl_items_upload
select * from #itemPhantom
select #itemPhantom.itemreference from #itemPhantom left join phantom on phantom.name=#itemPhantom.PhantomName
Using SQL Server I am trying to inject a string into a SQL statement based on an if statement, note that am trying to accomplish this inside a stored procedure.
I am currently getting an error for this code:
Declare #topString varchar(240)
IF #topRecords > 0
SET #topString = 'top 500'
ELSE
SET #topString = ''
SELECT #topString * FROM( //incorrect syntax near FROM
SELECT top 500 c.Id as [Customer Id],....
UNION
SELECT top 500 c.Id as [Customer Id],....
)as table1
Order by 1 desc
Edit
if somethingTrue
#whereCondition = '1 = 1 '
else
#whereCondition = branch = #branch
select * from table
where #whereCondition AND etc...
Correct
for injection inside an if statement go with Jodrell
but if you need a dynamic top then go with what was suggested by Kaf.
thanks both for the help!
If you want to decide number of top records depending on #topRecords, you can do it using an INT or BIGINT depending on the number of records needed.
DECLARE #top INT --This is declared as an int here
IF #topRecords > 0
SET #top = 500
ELSE
SET #top = 5000000 --Make it more than records if you need all
--or make it 0 if no records needed.
--#top has to be >=0
--How to use it
SELECT TOP (#top) * FROM YourTable
EDIT: Your question had only a top injection initially. However, If you need more injections (as per your recent question edit) then I would suggest to use a dynamic query as per #Jordell's answer.
You can't inject statement parts as variables like that, however you can change most values for parameters.
Having a stored procedure perform operations that may require different query plans, based on a parameter is a bad idea, the results of this SP could vary wildly based on the value of the #topRecords parameter. You would need to use the RECOMPILE option to warn the query engine, mitigating much of the benefit of SPs. Have you considered just having two stored procedures?
If you want to do it dynamically, you could build the whole statement dynamically, making one big string, then execute that.
You should investigate using sp_executesql to execute the string/VarChar. Then similar queries will benefit from query plan reuse.
As ever Sommarskog is a good reference.
Something like this
DECLARE #topString varchar(240);
DECLARE #statement varchar(max);
IF #topRecords > 0
SET #topString = 'TOP 500';
ELSE
SET #topString = '';
SET #statement = 'SELECT ' + #topString + ' * FROM
(
SELECT TOP 500 c.Id as [Customer Id], ....
UNION
SELECT TOP 500 c.Id as [Customer Id], ....
) table1
ORDER BY 1 DESC'
/* Then execute #statement */
EXEC sp_executesql #statement
If I have a SQL script stored in a variable like this:
DECLARE #SQL VARCHAR(MAX) = 'SELECT * FROM Employees WHERE Age > 80'
How can I tell if #SQL would return any rows if I were to run it?
In effect this:
IF EXISTS(#SQL) print 'yes, there are rows' -- Dummy code -- does not work!
I would like to try this without having to run the script in #SQL, insert that into a table and them count the rows.
Of course you need to run the script. To avoid having to insert the result into a table and count the rows you can use sp_executesql and an output parameter.
DECLARE #Statement NVARCHAR(MAX) = 'SELECT * FROM Employees WHERE Age > 80'
DECLARE #DynSQL NVARCHAR(max) = N'SET #Exists = CASE WHEN EXISTS(' +
#Statement +
N') THEN 1 ELSE 0 END'
DECLARE #Exists bit
EXEC sp_executesql #DynSQL,
N'#Exists bit OUTPUT',
#Exists = #Exists OUTPUT
SELECT #Exists AS [Exists]
While Martin's answer is also valid but can't we just use the ##RowCount after Executing the script? like
DECLARE #q nvarchar(max);
SET #q = 'declare #b int; select * from sys.tables where #b = 5';
EXEC (#q);
If ##RowCount > 0
Print 'Rows > 0';
Else
Print 'Rows = 0';
Note that the query has a variable declaration in it, which obviously cannot be used with Exists()
You can try an out-of-the-box solution.
For example. Keep track of a single variable called emp_over_80. Whenever you add an employee over that age, emp_over_80++. When you remove one, emp_over_80--
At the beginning of each day, run a query to determine the value of emp_over_80 (it may be an employee's birthday). Then, throughout the day, you can refer to emp_over_80 instead of re-running the SQL query.
Other options would be to keep the employee table sorted by age. If the last employee is over 80, then your query will return at least one row.
Now, many might say these are horrible coding practices, and I'd agree with them. But, I don't see another way to magically know the result (even a partial result) of a query before it runs.