Compare the results of a bunch of SQL statements and review results - sql-server

I have a SQL Server table with 2 columns. Column 1 is Query_Name and column 2 is Query_Text. This query_text column contains a semi-simple SQL Select statement. I have 34 rows like this. These Select statements are not necessarily operating on the same SQL server. I want a way where I can run these 34 queries, compare their result, and notify the user via email if certain conditions apply. The email part, I got that. It is a SQL server agent task. But I am lost as to how to run these 34 queries and compare them.
This has to be dynamic. For instance, a stored procedure with 34 variables won't work. This 34 might be 40 tomorrow. I want to run the queries contained in the query_text column every night and compare them and email the user.
Any ideas?
EDIT 1:
The comparison part: It cannot be significantly different. What is significantly different is determined by my boss. For the sake of this question, let us just assume it cannot vary by more than 100. The output of these 34 queries are numbers (Their data type might be nvarchar, but the output is definitely numbers).
EDIT 2:
RUN 1 query that in turn runs all the queries in a given column.
DECLARE #QUERYCOLUMN nvarchar(50)
SET #QUERYCOLUMN = (SELECT QUERY_TEXT FROM TABLE_NAME)
EXEC(#QUERYCOLUMN)
Something in that fashion.

This dumps your queries into a table variable and loops through them executing the text then deleting it from the table variable. You should definately be aware of the dangers of executing dynamic SQL before adopting this or any other approach though.
DECLARE #Name VARCHAR(50), #Query VARCHAR(MAX)
DECLARE #Queries TABLE (Query_Name VARCHAR(50), Query_Text VARCHAR(MAX))
INSERT INTO #Queries(Query_Name, Query_Text)
SELECT Query_Name, Query_Text FROM MY_SOURCE_TABLE
WHILE EXISTS (SELECT 1 FROM #Queries)
BEGIN
SELECT TOP 1 #Name = Query_Name, #Query = Query_Text FROM #Queries
EXEC(#Query)
DELETE FROM #Queries WHERE Query_Name = #Name
END
This method assumes Query_Name is unique in your source data or else the results could be skewed.

This will allow you to iterate through the table and execute the statements contained in each entry.
You will need to alter the statement contained in #sSQL if you wish to output this into a table, but this will work for testing in SSMS for you.
I would suggest you use a simple incrementing ID of sorts (Identity is the easiest) to allow you determine when all entries have been accounted for.
DECLARE #QID INT, #sSQL NVARCHAR(MAX) = ''
SELECT #QID = MIN(QID) FROM [dbo].[QueryComparison]
WHILE #QID IS NOT NULL
BEGIN
SELECT #sSQL = Query_Text FROM [dbo].[QueryComparison] WHERE QID = #QID
EXEC sp_executesql #sSQL
SELECT #QID = MIN(QID) FROM [dbo].[QueryComparison] WHERE QID > #QID
END

Related

Why is the table inside a non-met IF being validated before condition is met, resulting in error if table does not exist?

I am trying to execute a procedure with a parameter, and depending on the value of the parameter, three different IF conditions will be evaluated to verify which query it will execute from a linked server.
But when I execute the query, it seems to be checking if the tables inside all the IF exists before starting the query. And I know that only one of the table exists, that is why I am using the parameter, so it shouldn't fail. but I anyhow get the following error:
Msg 7314, Level 16, State 1, Line 25
The OLE DB provider "Microsoft.ACE.OLEDB.16.0" for linked server "LinkedServer" does not contain the table "D100". The table either does not exist or the current user does not have permissions on that table.
So in this code, assume that the parameter is 300. then I get the message above.
Do you know, if there is a way, to limit the query to do not check all the tables, but only the one where the IF condition will be met?
ALTER PROCEDURE[dbo].[Import_data]
#p1 int = 0
AS
BEGIN
SET NOCOUNT ON;
IF(#p1 = 100)
BEGIN
DROP TABLE IF EXISTS Table1
SELECT [Field1], [Field2], [Field3], [Field4], [Field5], [Field6]
INTO Table1
FROM[LinkedServer]...[D100]
END
IF(#p1 = 200)
BEGIN
DROP TABLE IF EXISTS Table2
SELECT[Field1], [Field2], [Field3], [Field4], [Field5], [Field6]
INTO Table2
FROM[LinkedServer]...[D200]
END
IF(#p1 = 300)
BEGIN
DROP TABLE IF EXISTS Table3
SELECT[Field1], [Field2], [Field3], [Field4], [Field5], [Field6]
INTO Table3
FROM[LinkedServer]...[D300]
END
END
I have tried googling it, but I found mostly workarounds as running a sub procedure, but it is not really a clean solution, I think.
Okay, it seems I that I found the answer. Even with an IF statement, the SQL Server validates the entire query before executing it, so the way to overcome it, is to use a Dynamic SQL Query.
"SQL Server Dynamic SQL is a programming technique that allows you to construct SQL statements dynamically at runtime. It allows you to create more general purpose and flexible SQL statement because the full text of the SQL statements may be unknown at compilation."
This is how the query looks now. so instead of multiple IF statements, the query changes dynamically depending on the parameter.
DECLARE #SQL NVARCHAR(MAX)
SET #SQL = N'DROP TABLE IF EXISTS Table1;
SELECT [Field1]
,[Field2]
,[Field3]
,[Field4]
,[Field5]
,[Field6]
INTO Table1
FROM [LinkedServer]...[D' + CONVERT(nvarchar(3),#p1) + N']'
EXEC sp_executesql #SQL

Generic Table Name Executed in a Loop

I have a db with many companies in it. What I am trying to is, I want to insert data to each company's table's from a different db which also has the same companies and tables in it. I created a loop using a table that contains the company names and in that loop I would like to insert from one db to another by generating the table name in loop. I created a loop which gives me the company names in it but I couldn't find the way to use results to generate the object name. My query is like;
declare #Rowcount int
select #Rowcount=count(*) from [DB_1].[dbo].[Companies];
while( #Rowcount>0)
begin
//Begin Insert Data From DB_1 to DB_2 with sames company names
INSERT INTO [DB_1].[dbo].[**[DB_1].[dbo].[Companies].[CompanyName]**$SomeTable]
([Field_1],
[Field_2])
SELECT
[Field_1],
[Field_2]
FROM [DB_2].[dbo].[**[DB_1].[dbo].[Companies].[CompanyName]**$SomeTable]
//End Insert Data From DB_1 to DB_2 with sames company names
SELECT #Rowcount=#Rowcount-1;
SELECT Name FROM [DB_1].[dbo].[Companies] order by Name desc OFFSET #Rowcount ROWS FETCH NEXT 1 ROWS ONLY;
end
So basically I couldnt find a way to generate [DB_1].[dbo].[Companies].[CompanyName] as a variable to use in insert query.
I will be more than happy if you can show me how to generate the company name in the loop and use it inside the insert query.
Thanks and Regards,
Ararat
Declare a variable which will be contain a table name:
declare #table_name nvarchar(200)
Declare a variable which will contain executing query:
declare #query nvarchar(2000)
In the loop resolve table_name variable. For can suggest two instances:
set #table_name = "some pattern" + sysdatetime()
set #table_name = "some pattern" + #Rowcount
Both ways are good because of its guarantee a unique name for the table.
After that, resolve the #query variable and will execute the query:
...
#query = "INSERT INTO " + #table_name + " ([Field_1],
[Field_2])
SELECT
[Field_1],
[Field_2]
FROM [DB_2].[dbo].[**[DB_1].[dbo].[Companies].[CompanyName]**$SomeTable]
//End Insert Data From DB_1 to DB_2 with sames company names"
exec sp_executesql #query
...
FYI
I wanted to pay your attention to the variable named rowcount able to make a confusion. In MS SQL exists a system variable ##rowcount which returns the number of rows affected by the last statement.
For more information check documentation: https://learn.microsoft.com/en-us/sql/t-sql/functions/rowcount-transact-sql?view=sql-server-2017

How to insert into table the results of a dynamic query when the schema of the result is unknown a priori?

Observe the following simple SQL code:
CREATE TABLE #tmp (...) -- Here comes the schema
INSERT INTO #tmp
EXEC(#Sql) -- The #Sql is a dynamic query generating result with a known schema
All is good, because we know the schema of the result produced by #Sql.
But what if the schema is unknown? In this case I use Powershell to generate a Sql query like that:
SET #Sql = '
SELECT *
INTO ##MySpecialAndUniquelyNamedGlobalTempTable
FROM ($Query) x
'
EXEC(#Sql)
(I omit some details, but the "spirit" of the code is preserved)
And it works fine, except that there is a severe limitation to what $Query can be - it must be a single SELECT statement.
This is not very good for me, I would like to be able to run any Sql script like that. The problem, is that no longer can I concatenate it to FROM (, it must be executed by EXEC or sp_executesql. But then I have no idea how to collect the results into a table, because I have no idea of the schema of that table.
Is it possible in Sql Server 2012?
Motivation: We have many QA databases across different Sql servers and more often than not I find myself running queries on all of them in order to locate the database most likely to yield best results for my tests. Alas, I am only able to run single SELECT statements, which is inconvenient.
We use SP and OPENROWSET for this purpose.
At first create SP based on a query you need, than use OPENROWSET to get data into temp table:
USE Test
DECLARE #sql nvarchar(max),
#query nvarchar(max)
SET #sql = N'Some query'
IF OBJECT_ID(N'SomeSPname') IS NOT NULL DROP PROCEDURE SomeSPname
SET #query =N'
CREATE PROCEDURE SomeSPname
AS
BEGIN
'+#sql+'
END'
EXEC sp_executesql #query
USE tempdb
IF OBJECT_ID(N'#temp') IS NOT NULL DROP TABLE #temp
SELECT *
INTO #temp
FROM OPENROWSET(
'SQLNCLI',
'Server=SERVER\INSTANCE;Database=Test;Trusted_Connection=yes;',
'EXEC dbo.SomeSPname')
SELECT *
FROM #temp

Executing SQL statement stored as a field value

Is it possible to execute a SQL statement that has been stored within a table as text.
I am trying to create a configuration table to drive a large number of SSRS subscriptions and don’t want to code any values directly into the report I want them all driven from a table for maintance.
E.G.
If part of one reports file name will always be the year of the previous month (2013 for example) but this needs to be generated at run time.
Can I stored this as a text field
CAST(YEAR(DATEADD(month, -1, GETDATE())) AS VARCHAR(4))
Then execute it and resolve the result into a SQL query?
If I understand your question correctly, then yes by using dynamic SQL. For example:
DECLARE #SQL NVARCHAR(4000)
DECLARE #YEAR VARCHAR(4)
SET #SQL = 'SELECT #yr = CAST(YEAR(DATEADD(month, -1, GETDATE())) AS VARCHAR(4))'
EXECUTE sp_executesql #SQL, N'#yr VARCHAR(4) OUTPUT', #yr=#YEAR OUTPUT
SELECT #YEAR
...returns 2013 into variable #YEAR.
Here I've hardcoded the query but it is a simple case to build the value of #SQL from a table's column value instead. You can then use the result from this query to build another dynamic query and so on.
Below is a subset of the above showing the SQL being taken from a table instead:
CREATE TABLE Dynamic (id INTEGER, text VARCHAR(4000) )
INSERT Dynamic values (1, 'CAST(YEAR(DATEADD(month, -1, GETDATE())) AS VARCHAR(4))')
SET #SQL = 'SELECT #yr = ' + (SELECT text FROM Dynamic WHERE id = 1)
why couldn't you?
Is it only one line of values per report?
I see 2 choices with SSRS.
1) everything in sql, you do dynamic sql.
declare #sql nvarchar(4000)
select #sql = 'select * from mytable where year = ' mavar'
from tablevalue where uniqueid = blablabla
exec sp_executesql #sql
second possibilty :
You make a first dataset in SSRS getting this value and then you evaluate the value of your condition and send it to a third dataset.
If you have multiple values, you can still imagine to get the value from a first dataset, evaluate them and send this to a subreport.
I don't know your application enought to determine the performance of this.
Until you are in SSRS, i recommand to try to find a solution in SSRS instead of Sql (but it's not a gold rule at all!!!)

T-SQL Dynamic SQL and Temp Tables

It looks like #temptables created using dynamic SQL via the EXECUTE string method have a different scope and can't be referenced by "fixed" SQLs in the same stored procedure.
However, I can reference a temp table created by a dynamic SQL statement in a subsequence dynamic SQL but it seems that a stored procedure does not return a query result to a calling client unless the SQL is fixed.
A simple 2 table scenario:
I have 2 tables. Let's call them Orders and Items. Order has a Primary key of OrderId and Items has a Primary Key of ItemId. Items.OrderId is the foreign key to identify the parent Order. An Order can have 1 to n Items.
I want to be able to provide a very flexible "query builder" type interface to the user to allow the user to select what Items he want to see. The filter criteria can be based on fields from the Items table and/or from the parent Order table. If an Item meets the filter condition including and condition on the parent Order if one exists, the Item should be return in the query as well as the parent Order.
Usually, I suppose, most people would construct a join between the Item table and the parent Order tables. I would like to perform 2 separate queries instead. One to return all of the qualifying Items and the other to return all of the distinct parent Orders. The reason is two fold and you may or may not agree.
The first reason is that I need to query all of the columns in the parent Order table and if I did a single query to join the Orders table to the Items table, I would be repoeating the Order information multiple times. Since there are typically a large number of items per Order, I'd like to avoid this because it would result in much more data being transfered to a fat client. Instead, as mentioned, I would like to return the two tables individually in a dataset and use the two tables within to populate a custom Order and child Items client objects. (I don't know enough about LINQ or Entity Framework yet. I build my objects by hand). The second reason I would like to return two tables instead of one is because I already have another procedure that returns all of the Items for a given OrderId along with the parent Order and I would like to use the same 2-table approach so that I could reuse the client code to populate my custom Order and Client objects from the 2 datatables returned.
What I was hoping to do was this:
Construct a dynamic SQL string on the Client which joins the orders table to the Items table and filters appropriate on each table as specified by the custom filter created on the Winform fat-client app. The SQL build on the client would have looked something like this:
TempSQL = "
INSERT INTO #ItemsToQuery
OrderId, ItemsId
FROM
Orders, Items
WHERE
Orders.OrderID = Items.OrderId AND
/* Some unpredictable Order filters go here */
AND
/* Some unpredictable Items filters go here */
"
Then, I would call a stored procedure,
CREATE PROCEDURE GetItemsAndOrders(#tempSql as text)
Execute (#tempSQL) --to create the #ItemsToQuery table
SELECT * FROM Items WHERE Items.ItemId IN (SELECT ItemId FROM #ItemsToQuery)
SELECT * FROM Orders WHERE Orders.OrderId IN (SELECT DISTINCT OrderId FROM #ItemsToQuery)
The problem with this approach is that #ItemsToQuery table, since it was created by dynamic SQL, is inaccessible from the following 2 static SQLs and if I change the static SQLs to dynamic, no results are passed back to the fat client.
3 around come to mind but I'm look for a better one:
1) The first SQL could be performed by executing the dynamically constructed SQL from the client. The results could then be passed as a table to a modified version of the above stored procedure. I am familiar with passing table data as XML. If I did this, the stored proc could then insert the data into a temporary table using a static SQL that, because it was created by dynamic SQL, could then be queried without issue. (I could also investigate into passing the new Table type param instead of XML.) However, I would like to avoid passing up potentially large lists to a stored procedure.
2) I could perform all the queries from the client.
The first would be something like this:
SELECT Items.* FROM Orders, Items WHERE Order.OrderId = Items.OrderId AND (dynamic filter)
SELECT Orders.* FROM Orders, Items WHERE Order.OrderId = Items.OrderId AND (dynamic filter)
This still provides me with the ability to reuse my client sided object-population code because the Orders and Items continue to be returned in two different tables.
I have a feeling to, that I might have some options using a Table data type within my stored proc, but that is also new to me and I would appreciate a little bit of spoon feeding on that one.
If you even scanned this far in what I wrote, I am surprised, but if so, I woul dappreciate any of your thoughts on how to accomplish this best.
You first need to create your table first then it will be available in the dynamic SQL.
This works:
CREATE TABLE #temp3 (id INT)
EXEC ('insert #temp3 values(1)')
SELECT *
FROM #temp3
This will not work:
EXEC (
'create table #temp2 (id int)
insert #temp2 values(1)'
)
SELECT *
FROM #temp2
In other words:
Create temp table
Execute proc
Select from temp table
Here is complete example:
CREATE PROC prTest2 #var VARCHAR(100)
AS
EXEC (#var)
GO
CREATE TABLE #temp (id INT)
EXEC prTest2 'insert #temp values(1)'
SELECT *
FROM #temp
1st Method - Enclose multiple statements in the same Dynamic SQL Call:
DECLARE #DynamicQuery NVARCHAR(MAX)
SET #DynamicQuery = 'Select * into #temp from (select * from tablename) alias
select * from #temp
drop table #temp'
EXEC sp_executesql #DynamicQuery
2nd Method - Use Global Temp Table:
(Careful, you need to take extra care of global variable.)
IF OBJECT_ID('tempdb..##temp2') IS NULL
BEGIN
EXEC (
'create table ##temp2 (id int)
insert ##temp2 values(1)'
)
SELECT *
FROM ##temp2
END
Don't forget to delete ##temp2 object manually once your done with it:
IF (OBJECT_ID('tempdb..##temp2') IS NOT NULL)
BEGIN
DROP Table ##temp2
END
Note: Don't use this method 2 if you don't know the full structure on database.
I had the same issue that #Muflix mentioned. When you don't know the columns being returned, or they are being generated dynamically, what I've done is create a global table with a unique id, then delete it when I'm done with it, this looks something like what's shown below:
DECLARE #DynamicSQL NVARCHAR(MAX)
DECLARE #DynamicTable VARCHAR(255) = 'DynamicTempTable_' + CONVERT(VARCHAR(36), NEWID())
DECLARE #DynamicColumns NVARCHAR(MAX)
--Get "#DynamicColumns", example: SET #DynamicColumns = '[Column1], [Column2]'
SET #DynamicSQL = 'SELECT ' + #DynamicColumns + ' INTO [##' + #DynamicTable + ']' +
' FROM [dbo].[TableXYZ]'
EXEC sp_executesql #DynamicSQL
SET #DynamicSQL = 'IF OBJECT_ID(''tempdb..##' + #DynamicTable + ''' , ''U'') IS NOT NULL ' +
' BEGIN DROP TABLE [##' + #DynamicTable + '] END'
EXEC sp_executesql #DynamicSQL
Certainly not the best solution, but this seems to work for me.
I would strongly suggest you have a read through http://www.sommarskog.se/arrays-in-sql-2005.html
Personally I like the approach of passing a comma delimited text list, then parsing it with text to table function and joining to it. The temp table approach can work if you create it first in the connection. But it feel a bit messier.
Result sets from dynamic SQL are returned to the client. I have done this quite a lot.
You're right about issues with sharing data through temp tables and variables and things like that between the SQL and the dynamic SQL it generates.
I think in trying to get your temp table working, you have probably got some things confused, because you can definitely get data from a SP which executes dynamic SQL:
USE SandBox
GO
CREATE PROCEDURE usp_DynTest(#table_type AS VARCHAR(255))
AS
BEGIN
DECLARE #sql AS VARCHAR(MAX) = 'SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = ''' + #table_type + ''''
EXEC (#sql)
END
GO
EXEC usp_DynTest 'BASE TABLE'
GO
EXEC usp_DynTest 'VIEW'
GO
DROP PROCEDURE usp_DynTest
GO
Also:
USE SandBox
GO
CREATE PROCEDURE usp_DynTest(#table_type AS VARCHAR(255))
AS
BEGIN
DECLARE #sql AS VARCHAR(MAX) = 'SELECT * INTO #temp FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = ''' + #table_type + '''; SELECT * FROM #temp;'
EXEC (#sql)
END
GO
EXEC usp_DynTest 'BASE TABLE'
GO
EXEC usp_DynTest 'VIEW'
GO
DROP PROCEDURE usp_DynTest
GO

Resources