Multiple Assertions using tSQLt - sql-server

I am trying to check whether a table exists or not in the database using AssertObjectExists. Actually I have 10 tables to check whether those tables exists or not. Since the test is verification of existence of tables. I want to put together in one test.
When I keep all assertions in one test, if any of the object assertion fails, the remaining assertions are not executing.
My goal is to check whether the tables are present from a set say 10 tables. And report the list of tables which doesn't exists. I am pasting the sample code below.
ALTER PROCEDURE [Test Tracker].[test TablesExists_01]
AS
BEGIN
-- Verify the existance of each table
EXEC tSQLt.AssertObjectExists #ObjectName = 'auth_user',
#Message = 'Unable to find auth_user Table'
EXEC tSQLt.AssertObjectExists #ObjectName = 'auth_permissions',
#Message = 'Unable to find auth_permissions Table'
EXEC tSQLt.AssertObjectExists #ObjectName = 'auth_groups',
#Message = 'Unable to find auth_groups Table'
END;
Can someone redirect me in right path.
Edit: Solution Given by Brian
IF (NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.Tables WHERE TABLE_NAME = 'auth_user' AND TABLE_SCHEMA = #schema))
SET #errorMessage = #errorMessage + 'Unable to find auth_user' + CHAR(10)
IF (NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.Tables WHERE TABLE_NAME = 'auth_group' AND TABLE_SCHEMA = #schema))
SET #errorMessage = #errorMessage + 'Unable to find auth_group' + CHAR(10)
IF LEN(#errorMessage) = 0
PRINT 'All the Tables in Authentication exists'
ELSE
EXEC tsqlt.Fail #Message = #errorMessage
In the above code CHAR(10) is new line code. I just modified it for having a nice console output

You might try this:
Declare #tableName as varchar(100)
set #tableName = 'auth_user'
IF (EXISTS (SELECT *
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = 'dbo'
AND TABLE_NAME = #tableName ))
BEGIN
--Do Stuff
END
set #tableName = 'auth_permissions'
...
Then just iterate through the rest of the table names. To make it very easy, make this a stored procedure that takes a single string as a parameter that is delimited like :
'auth_user|auth_permission|etc.'
Then you could use a Split function to separate each inbound name into a virtual table you could then cursor through and get the answers to whether the table exist. Thus your stored procedure would be useful in any situation where you wanted to check the exist of 1 to many tables.

I would suggest a similar approach to Brian, but perhaps you can declare a table variable (#Expected) with a single column of the expected table names, then your test can be to select into a second table variable (#Actual) all those entries from INFORMATION_SCHEMA.tables inner joined to #Expected (specifiying schema, etc. in the where clause).
Then, you can use tSQLt.AssetEqualsTable to compare the contents of #Expected with #Actual - if they are the same (all objects exist) then your test will pass, but if not then the test will fail, and all mismatched rows (each indicating a missing object) will show up in the failure message.

Related

Select Current Database Name OR Not Current Database Name in SQL Server for a Table in SQL Server

I have two databases in SQL Server. Let's say they are DB1 and DB2.
When the system starts, the current database is always DB1. BTW, I have to use DB2 for another table.
For the reason, I want to give a table name as a variable like #tablename and want to select a database name for the #tablename. Would it be possible to pull the database name associated with #tablename?
Also, I want to save the database name to a variable like #databasename to print it out.
When I tried to find a database name from the code below, I could get the database name of the table, ExampleTable, among DB1 and DB2.
EXEC sys.sp_msforeachdb
'SELECT ''?'' DatabaseName, name FROM [?].sys.Tables WHERE Name = ''ExampleTable'''
However, I can't go forward to process how to make a code using a variable #table instead of a fixed table name, ExampleTable.
I will use list of tables to input #tablename into the query one by one from the list.
DECLARE #table sysname = 'TableNames';
DECLARE #database_name sysname = 'dbo';
DECLARE #DatabaseName VARCHAR(50)
-- tbl_01 in dbo.DB1
-- tbl_02 in dbo.DB2
-- tbl_03 in dbo.DB1
-- tbl_04 in dbo.DB2
/*
I need the code block
(1) To input a table using #table
(2) To save the database name to a variable like #database_name
EXEC sys.sp_msforeachdb
'SELECT ''?'' DatabaseName, name FROM [?].sys.Tables WHERE Name ='+#table
*/
Please help me to create a script for my work.
The below code contains a set of tools you can use to do what you want to do. I expect your requirements may change a bit as you do this, so that's why I'm giving you the tools first.
These tools are written as simple checks - easy to understand and relatively quick. They currently just do a SELECT 'Yes' if the database exists/etc - but of course you can change that as needed.
DECLARE #DBName_1 nvarchar(100) = N'DB1';
DECLARE #DBName_2 nvarchar(100) = N'DB2';
DECLARE #TableName nvarchar(100) = N'MyTable';
DECLARE #TableNameToCheck nvarchar(200);
-- Is the current database 'DB1'?
IF DB_Name() = #DBName_1 SELECT 'Yes' ELSE SELECT 'No';
-- Check if the database 'DB2' exists
IF DB_ID(#DBName_2) IS NOT NULL SELECT 'Yes' ELSE SELECT 'No';
-- Check if table is in first database
SET #TableNameToCheck = QUOTENAME(#DBName_1) + N'.[dbo].' + QUOTENAME(#TableName);
IF OBJECT_ID(#TableNameToCheck, 'U') IS NOT NULL SELECT 'Yes' ELSE SELECT 'No';
-- Check if table is in second database (note it uses #DBName_2)
SET #TableNameToCheck = QUOTENAME(#DBName_2) + N'.[dbo].' + QUOTENAME(#TableName);
IF OBJECT_ID(#TableNameToCheck, 'U') IS NOT NULL SELECT 'Yes' ELSE SELECT 'No';
Note that you do not actually need the variable #TableNameToCheck - you can just construct it within the OBJECT_ID() function e.g., OBJECT_ID(#DBName_2 + N'.dbo.' + #TableName , 'U'). However, it can be useful to set it first to check/ensure it's correct, and it has infinitesimal impact on performance.
For your actual task (recording whether the table exists in DB1 or DB2) you can do the following
DECLARE #DBName_1 nvarchar(100) = N'DB1';
DECLARE #DBName_2 nvarchar(100) = N'DB2';
DECLARE #TableName nvarchar(100) = N'MyTable';
DECLARE #DatabaseWithTable nvarchar(100);
IF OBJECT_ID(QUOTENAME(#DBName_1) + N'.[dbo].' + QUOTENAME(#TableName), 'U') IS NOT NULL
BEGIN
SET #DatabaseWithTable = #DBName_1;
END
ELSE IF OBJECT_ID(QUOTENAME(#DBName_2) + N'.[dbo].' + QUOTENAME(#TableName), 'U') IS NOT NULL
BEGIN
SET #DatabaseWithTable = #DBName_2;
END
ELSE
BEGIN
SET #DatabaseWithTable = N'Not Found';
END;
SELECT #DatabaseWithTable;
Edit: Added QUOTENAME() as per suggestion/comment from #HABO

Calling dynamic SQL or stored procedure

I'm using a search object query (found on the internet, wish I could give credit to the developer) to search database for the columns needed when I write queries. The output search object query allows me to enter the type of table to look in (partial name) as well as the column name (partial name) I'm trying to find. I've been attempting to modify the search object query so it returns the 1st value (Top 1) it finds. This would help me to easily see at a glance if the column has the particular type of data I'm looking for.
I've attempted to write it both as a stored procedure that I could pass two parameters (partial table and partial column name) and I've also tried using dynamic SQL (my first attempt at using it, so I'm a novice when it comes to use it). I had moderate success with the use of dynamic SQL, but can only get it to produce one result rather than be called multiple times for all the results in my search object output. The code I used is shown here:
-- This is the search object query found on internet
Use masterdb
Select a.name, b.name
From sysobjects a
Inner Join syscolumns b On a.id = b.id
Where b.name like '%Result%'
And a.name like '%Lab%'
Order By a.name, b.name
-- This is a separate query I used to test calling the data with dynamic SQL
DECLARE #value VARCHAR(100), #tablename VARCHAR(100)
SET #value = 'Result'
SET #tablename = 'LabSpecimen'
DECLARE #sqlText NVARCHAR(1000);
SET #sqlText = N'SELECT Top 1 ' + #value + ' FROM testndb.dbo.' + #tablename
EXEC (#sqlText)
If I use the search object query and search for tables that have lab and column names that have result, I might get output like this:
LabMain,ResultID
LabSpecimen,ResultCategory
LabSpecimen,ResultDate
LabSpecimen,Results
I would like to have the search object query pull data from the table in the first column and the column name in the 2nd column and return the first value it finds to give me a sample output for the given column name/table. Output would look like this:
LabMain,ResultID,E201812310001
LabSpecimen,ResultCategory,ExampleCategory
LabSpecimen,ResultDate,20181231
LabSpecimen,Results,34.20
Okay, I really didn't want to have to post an answer to this, but here goes.
So, the first, really-really-huge thing is: SQL Injection. SQL Injection is the #1 security vulnerability for something like a dozen years running, per OWASP. Basically, SQL Injection is where you use dynamic SQL that has any fragment of the sql command being populated by a user. So in the OP's case, this section here:
SET #value = 'Result'
SET #tablename = 'LabSpecimen'
DECLARE #sqlText NVARCHAR(1000);
SET #sqlText = N'SELECT Top 1 ' + #value + ' FROM testndb.dbo.' + #tablename
EXEC (#sqlText)
... if the end incarnation would be that #tableName and #value are populated by the user as part of their search? Then the user can do a 'search' that ends up injecting sql statements that the server runs directly; for a cheap example, imagine this for #value:
3' ; drop table #tableName --
... which would go ahead and drop every table that matches the #tablename you passed in.
Anyway, so, as we go through this problem, we're going to keep SQL Injection in mind at every step.
Problem #1: How to get the tables/columns that match.
You pretty much already nailed this. The only thing missing is to put it into a temp table so that you can loop through it (and limit it down to U-types, since otherwise you'll get stored procs and system tables.) I went ahead and had it also hit the Schema information - that way, if you have tables in different schemas, it'll still be able to get the results.
declare #tableNameFragment varchar(100) -- note: these top 4 lines will eventually
declare #columnNameFragment varchar(100) -- be changed to stored proc args
set #tableNameFragment = 'Performance' -- and populated by the user calling
set #columnNameFragment = 'status' -- the proc (instead of hard-coded.)
declare #entityMatches TABLE (TableName varchar(200), ColName varchar(128))
insert into #entityMatches
Select sch.TABLE_SCHEMA + '.' + sysobj.name as TableName, syscol.name as ColName
From sysobjects sysobj
Join syscolumns syscol On sysobj.id = syscol.id
Join INFORMATION_SCHEMA.TABLES sch on sch.TABLE_NAME = sysobj.name
where sysobj.xtype = 'U'
and (sysobj.name like '%' + isnull(#tableNameFragment,'') + '%')
and (syscol.name like '%' + isnull(#columnNameFragment,'') + '%')
Now, notice that while #tableNameFragment and #columnNameFragment are used, they're not used in a dynamic query. It doesn't matter if the user puts in something malicious into those values
Problem #2 - How to loop through your table
Basically, you're going to need a cursor. I hate cursors, but sometimes (like this one), they're necessary.
Problem #3 - How to actually do a dynamic query and get a result back
This is actually trickier than it looks. You can't do a raw EXEC() for a return value, nor can you simply have the cmd you're executing populating a variable - because EXEC (and SP_ExecuteSql operate in a different context, so they can't populate variables outside in your script.)
You need to use SP_ExecuteSQL, but specify a return variable getting populated by the interior sql command. For example:
declare #sqlCmd nvarchar(max)
declare #dynamicReturn varchar(max)
set #sqlCmd = 'select #retVal=1'
EXEC Sp_executesql #sqlCmd,
N'#retVal varchar(max) output',
#dynamicReturn output
select #dynamicReturn
Problem #4 - How to write your Dynamic command
Here's where things get dicey, since it's where we're using a dynamic SQL command. The important thing here is: you cannot use anything the user provided as an input. Which means, you can't use the variables #tableNameFragment or #columnNameFragment. You can use the values in the #entityMatches table, though. Why? Because the user didn't populate them. They got populated by the data in the sys tables - it doesn't matter if the user puts something nefarious in the input variables, that #entityMatches data simply holds the existing table/column names that match.
Also important: When you're working on code that could be a problem if a future dev down the line tweaks or copies/pastes - you should put comment warnings to illuminate the issue.
So, putting it all together? You'll have something that looks like this:
declare #tableNameFragment varchar(100) -- note: these top 4 lines will eventually
declare #columnNameFragment varchar(100) -- be changed to stored proc args
set #tableNameFragment = 'Performance' -- and populated by the user calling
set #columnNameFragment = 'status' -- the proc (instead of hard-coded.)
declare #entityMatches TABLE (TableName varchar(200), ColName varchar(128))
insert into #entityMatches
Select sch.TABLE_SCHEMA + '.' + sysobj.name as TableName, syscol.name as ColName
From sysobjects sysobj
Join syscolumns syscol On sysobj.id = syscol.id
Join INFORMATION_SCHEMA.TABLES sch on sch.TABLE_NAME = sysobj.name
where sysobj.xtype = 'U'
and (sysobj.name like '%' + isnull(#tableNameFragment,'') + '%')
and (syscol.name like '%' + isnull(#columnNameFragment,'') + '%')
declare #returnResults TABLE (TableName varchar(200), ColName varchar(128), FirstValue varchar(max))
declare Cur Cursor For select TableName,ColName from #entityMatches
declare #cursorTable varchar(200), #cursorColumn varchar(128)
open Cur
fetch Next from cur into #cursorTable,#cursorColumn
while ##FETCH_STATUS = 0
begin
-- Note: the variables #cursorTable, #cursorColumn are NOT user populated
-- but instead are populated from the Sys tables. Because of this,
-- this dynamic sql below is not SQL-Injection vulnerable (the entries
-- are not populated from user entry of any sort.)
-- Be very careful modifying the lines below to make sure you don't
-- introduce a vulnerability.
declare #sqlCmd nvarchar(max)
declare #dynamicReturn varchar(max)
set #sqlCmd = 'select top 1 #retVal=[' + #cursorColumn + '] from ' + #cursorTable
EXEC Sp_executesql #sqlCmd,
N'#retVal varchar(max) output',
#dynamicReturn output
insert into #returnResults values (#cursorTable, #cursorColumn, #dynamicReturn)
fetch Next from cur into #cursorTable,#cursorColumn
End
close cur
deallocate cur
select * from #returnResults
Create a stored procedure like below mention stored procedure.
Get the table and column name from sysobject & syscolumn and add it in hash table on the base of parameter of stored procedure. After that declare a cursor and in loop of cursor create a dynamic query of column and table name and get first row of current column from table of cursor loop. After that execute the query and update the result in the hash table. At the end of lookup select the Record from hash table. Check the below stored procedure. I hope that its helpful for you.
Create procedure Sp_GetSampleData
#TName varchar(200) = ''
as
Select
a.name TableName, b.name ColumnName,
CAST('' as varchar(max)) as SampleValue
into
#Tbl
from
sysobjects a
inner join
syscolumns b on a.id = b.id
where
(#TName='' or a.name = #TName)
order ny
a.name, b.name
declare #TableName varchar(200), #ColumnName varchar(200),
#sqlText nvarchar(max), #Val varchar(max)
declare Cur Cursor For
select TableName, ColumnName
from #Tbl
open Cur
fetch Next from cur into #TableName,#ColumnName
while ##FETCH_STATUS =0
begin
set #sqlText=''
set #Val=''
SET #sqlText = N'SELECT Top 1 #Val=[' + #ColumnName + '] FROM testndb.dbo.' + #TableName
EXEC Sp_executesql
#sqlText,
N'#Val varchar(max) output',
#Val output
print #sqlText
update #Tbl set SampleValue=#Val where TableName=#TableName and ColumnName =#ColumnName
fetch Next from cur into #TableName,#ColumnName
End
close cur
deallocate cur
select * from #Tbl

Call Stored Procedure Repeatedly for version comparison

SSMS: 2008 R2
We are having our software system updated, which may contain an unknown number of undocumented changes to the way data is entered and stored in our database. We have asked for documentation, but only have schema compares for "physical" changes to the database, not the way the data is treated. They may change in the future, but for now we have to assume not.
In order to check that our stored procedures work as expected after the update, we would like to run a sample of procedures using a sample of parameters before and after the update to compare the actual data results. The stored procedures here all take a single Id as the parameter (they are used to make SSRS reports within the software system)
I have set some things up, but I am having problems with my approach and would welcome any suggestions about either a better way to do things, or how to fix my approach. The problem is that an error is returned whenever a called stored procedure uses a temporary table. Here is what I have done:
Made a script to get a random sample of Ids for paramaters (only one table used at the moment - that's fine).
ALTER PROC [dbo].[UpdateValidation_GET_RandomIdSample](#TestSizePercent DECIMAL(6,3))
AS
-- This table is already created and will persist both sides of the update
--CREATE TABLE Live_Companion.dbo.UpdateValidationIds
--( Id INT IDENTITY(1,1)NOT NULL PRIMARY KEY CLUSTERED
-- ,MyTableId NT NULL)
IF #TestSizePercent > 100 RAISERROR('Do you even percent, bro?',16,1)
DECLARE #SQL VARCHAR(255)
TRUNCATE TABLE UpdateValidationIds
SET #SQL =
'INSERT dbo.UpdateValidationIds(Id)
SELECT TOP ' + CONVERT(VARCHAR(10),#TestSizePercent) + ' PERCENT ID FROM Live.dbo.MyTable ORDER BY NEWID()'
EXEC (#SQL)
Made a second script to run a stored procedure for each Id in the table:
ALTER PROC [dbo].[UpdateValidation_GET_ProcedureResultsManyTimes](#Procedure_Name VARCHAR(255))
AS
--DECLARE #Procedure_Name VARCHAR(255) = 'Live_Companion.dbo.MyProc'
DECLARE #ID INT
DECLARE #GET_ID CURSOR
DECLARE #SQL VARCHAR(MAX) = ''
DECLARE #MyTableId INT
DECLARE #FirstRun BIT = 1
SET #GET_ID = CURSOR FOR
SELECT Id FROM Live_Companion.dbo.UpdateValidationIds
WHERE MyTableId IS NOT NULL
OPEN #GET_ID
FETCH NEXT FROM #GET_ID INTO #ID
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #MyTableId = MyTableId FROM Live_Companion.dbo.UpdateValidationIds
WHERE Id = #ID
IF #FirstRun = 1
BEGIN
SET #SQL = 'SELECT * INTO #ProcedureOutput FROM OPENROWSET(''SQLNCLI'',''Server=SQL1;Trusted_Connection=yes;'',''EXEC ' + #Procedure_Name + ' ' + CONVERT(VARCHAR(50),#MyTableId) + ''');'
SET #FirstRun = 0
END
ELSE
BEGIN
SET #SQL = #SQL + '
INSERT #ProcedureOutput SELECT * FROM OPENROWSET(''SQLNCLI'',''Server=SQL1;Trusted_Connection=yes;'',''EXEC ' + #Procedure_Name + ' ' + CONVERT(VARCHAR(50),#MyTableId) + ''');'
END
FETCH NEXT FROM #GET_ID INTO #ID
END
SET #SQL = #SQL + '
SELECT * FROM #ProcedureOutput
DROP TABLE #ProcedureOutput'
EXEC (#SQL)
CLOSE #GET_ID
DEALLOCATE #GET_ID
So now I should be able to execute the second procedure for various stored procedures and output the results to file over a range of Ids, then repeat using the saved (initially random) Ids again after the update and compare the results.
The trouble is, it fails when any of the called procedures use a temporary table:
EDIT:
Error Message returned:
Cannot process the object "EXEC Live_Companion.dbo.MyProc 12345". The
OLE DB provider "SQLNCLI10" for linked server "(null)" indicates that
either the object has no columns or the current user does not have
permissions on that object.
Any suggestions or ideas for how to proceed?

How to check existence of a table from a different sql db?

I have db A and db B. At the beginning of a stored procedure I want to back up all rows from B.mytable to B.mytablebackup. The rest of the stored procedure runs against tables on db A (which gathers data and writes it to B.mytable).
So I check to see if B.mytablebackup exists
IF EXISTS(SELECT 1 FROM B.dbo.mytablebackup)
and if it does, the stored procedure does an
INSERT INTO B..mytablebackup SELECT * FROM B..mytable
If it doesn't exist it does a
SELECT * INTO B..mytablebackup from B..mytable
But when I execute the stored procedure I get the error
There is already an object named 'mytablebackup' in the database
I added a Print statement and execution is taking the "does not exist" branch of the IF.
What am I doing wrong?
For SQL Server, you should use system view sys.tables to check if table exists.
IF EXISTS(SELECT 1 FROM B.sys.tables WHERE name = 'mytablebackup')
OBJECT_ID can be used too:
IF OBJECT_ID('B.dbo.mytablebackup') IS NOT NULL
You can directly check from the given DB,SCHEMA and TABLE parameters (For dynamic database, schema and table use)
DECLARE #targetdatabase NVARCHAR(MAX),
#SchemaName NVARCHAR(MAX),
#TableName NVARCHAR(MAX)
DECLARE #TempTableName NVARCHAR(MAX) = QUOTENAME(#targetdatabase) + '.' +
QUOTENAME(#SchemaName) + '.' + QUOTENAME(#TableName)
IF OBJECT_ID(#TempTableName) IS NULL
BEGIN
PRINT #TempTableName
END

How do I catch a specific exception type from within a stored procedure?

I am writing a stored procedure which iterates over all of the databases on the server and populates a table variable with an aggregate of the data from some of the different databases. Some databases I'm not interested in as they are irrelevant. The problem is when my CURSOR iterates through those databases I don't care about, a SELECT statement is issued on a table that doesn't exist. How can I ignore the Invalid object name exception and continue with my processing?
Edit:
Here is how I was attempting to skip over databases that were irrelevant:
DECLARE db_cursor CURSOR FOR
SELECT name
FROM MASTER.dbo.sysdatabases
WHERE name NOT IN ('master','model','msdb','tempdb')
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #currentDatabaseName
WHILE ##FETCH_STATUS = 0
BEGIN
SET #sql = 'SELECT COUNT(Name) FROM ' + #currentDatabaseName + '.sys.Tables WHERE Name = ''SomeTableICareAbout'''
INSERT INTO #tableSearchResult
EXEC sp_executesql #sql
SET #tableCount = (SELECT COUNT(*) FROM #tableSearchResult WHERE TableCount = 1)
--If the table I care about was found, then do the good stuff
IF #tableCount > 0
...
The problem with this approach is if the executing user (in my case a service account) does not have access to SELECT on the table, then I never know about that error. If the user doesn't have SELECT access, I want that exception to be raised. But, even if the user doesn't have SELECT access, it can SELECT on the sys.Tables view.
You can't catch error 208 directly because it's a name resolution error that is raised at compilation time and before the code is actually executed. The behaviour is documented: see the section called "Errors Unaffected by a TRY…CATCH Construct" for an explanation, and the answers to this question have some interesting comments.
In addition to the 'solution' in the documentation, you can use dynamic SQL; the error will be caught in this example:
begin try
exec('select * from dbo.ThisTableDoesNotExist');
end try
begin catch
select error_number();
end catch;
If you're looping through all databases, there's a good chance you're using dynamic SQL somewhere anyway, so this might suit your case better.
You can catch the error if you are doing it inside a stored procedure (Example documented Here: http://msdn.microsoft.com/en-us/library/ms175976.aspx
Also you can change your dynamic sql to do something like this
SET #sql = '
If Exists(Select Name From ' + #currentDatabaseName + '.sys.Tables
WHERE Name = ''SomeTableICareAbout'')' --+
--Add Whatever the Good Stuff is
EXEC sp_executesql #sql
But checking if the table exists first, instead of doing the select count(1) from the table, will prevent that error from being raised.

Resources