How can I skip errors and continue running the SQL-query? - sql-server

I am writing a piece of code with the intention of searching through all databases in the server for a certain table name, however I ran into some trouble since I do not have permission to read/access all databases in the server.
I am wondering if there is a way for the query to advance if one statement is not available due to security permissions (i.e. if I have ten databases in a particular server with no access to the fourth, I would want it to run 1-2-3 and then 5-6-7-8-9-10 with returned results).
I have tried using TRY-CATCH, but I cannot seem to get the code to bypass the initial problem which is stopping when the security permissions are not available.
declare #tabell varchar(254) = 'JE' -- table name which is supposed to be
--found.
-- STEP 1: lists all available databases in the server with a row number.
drop table if exists #steg1 select name, row_number() over (order by
name) as rownumber into #steg1 from sys.databases
-- STEP 2: generates code for all databases in order to identify those
--with the table name #tabell.
drop table if exists #steg2 select 1 Ordn,'use '+name+' drop table if
exists #hitta select * into #hitta from sys.tables where name =
'''+ltrim(#tabell)+'''' as script into #steg2 from #steg1 a
where rownumber =1
union
select 2 Ordn, 'use '+name+' insert into #hitta select * from sys.tables
where name = '''+ltrim(#tabell)+'''' from #steg1 a
where rownumber >1
union
select 3 Ordn,'select * from #hitta' as x
-- STEP 3: concatenate the generated code into a single string.
declare #string varchar(max)
select #string = concat(#string + ' ', '')+ script from #steg2
drop table if exists #steg3 select #string as string into #steg3
-- STEP 4: exec the code concatenated in the previous step.
declare #cmd varchar(max)
begin
set #cmd = (select string from #steg3)
exec (#cmd)
end
Getting the error message: Msg 916, level 14, state 1, stating that the user cannot access the database under the current security context.

I managed to solve my issue using has_dbaccess(database), below you can see how I incorporated it into the code.
declare #tabell varchar(254) = 'JE' -- table name which is
supposed to be found.
-- STEP 1: lists all available databases in the server with a row number.
drop table if exists #steg1 select name, row_number() over (order by name) as rownumber
into #steg1 from (SELECT name, has_dbaccess(name) access FROM sys.databases) a where
access = 1
-- STEP 2: generates code for all databases in order to identify those with the table
--name #tabell.
drop table if exists #steg2 select 1 Ordn,'use '+name+' drop table if exists #hitta
select name as [Table], cast('''+name+'''as varchar(max)) as [Databas] into #hitta from
sys.tables where name = '''+ltrim(#tabell)+'''' as script into #steg2 from #steg1 a
where rownumber =1
union
select 2 Ordn, 'use '+name+' insert into #hitta select name as [Table],
cast('''+name+'''as varchar(max)) as [Databas] from sys.tables where name =
'''+ltrim(#tabell)+'''' from #steg1 a
where rownumber >1
union
select 3 Ordn,'select * from #hitta' as x
-- STEP 3: concatenate the generated code into a single string.
declare #string varchar(max)
select #string = concat(#string + ' ', '')+ script from #steg2
drop table if exists #steg3 select #string as string into #steg3
-- STEP 4: exec the code concatenated in the previous step.
declare #cmd varchar(max)
begin
set #cmd = (select string from #steg3)
exec (#cmd)
end

Related

How to get only the columns that have at least one non-null value in a table existing in SQL server

I have a table named Product with following data for instance:
p_id
p_name
p_cat
1
shirt
null
2
null
null
3
cap
null
Suppose I don't know numbre of rows and columns in the table as well as I don't know which columns are compltely null (no non-null value in all of its rows). How to write a query to retrieve just the columns that have atleast one non-null value in its rows. My approach is as follows but not getting a corret output:
select
column_name
into #TempColumns
from information_schema.columns
where
table_name = 'Product'
and table_schema = 'DDB'
declare #CurrentColumn nvarchar(max) = '', #IsNull bit, #NonNullCols nvarchar(max) = ''
declare Cur cursor for
select column_name from #TempColumns
open Cur
while 1=1
begin
fetch next from Cur into #CurrentColumn
select #IsNull = case when count(#CurrentColumn) > 0 then 0 else 1 end
from Product
if #IsNull = 1
begin
set #NonNullCols = #NonNullCols + ',' + #CurrentColumn
end
if ##fetch_status <> 0 break
end
close Cur
deallocate Cur
select #NonNullCols as NullColumns
drop table #TempColumns
If there is any other approach or correction in my above (T-SQL) query. Thanks in advance.
First I just create a temporary table to store all the column names avaialbe in the Product table. Then I looped in this temporary table and feteched each row and checked it on the product table whether the column is comptely null or not using the count() function. The condition sets the bit variable 1 if the column is completely null and then that particular column name is stored in anothe variable which is then retrieved as null columns.
Here is a conceptual example for you.
It is using SQL Server XML and XQuery powers without dynamic SQL and cursors/loops.
The algorithm is very simple.
When we are converting each row into XML, columns that hold NULL value are missing from the XML.
SQL
USE tempdb;
GO
DROP TABLE IF EXISTS #tmpTable;
CREATE TABLE #tmpTable (
client_id int,
client_name varchar(500),
client_surname varchar(500),
city varchar(500),
state varchar(500));
INSERT #tmpTable VALUES
(1,'Miriam',NULL,'Las Vegas',NULL),
(2,'Astrid',NULL,'Chicago',NULL),
(3,'David',NULL,'Phoenix',NULL),
(4,'Hiroki',NULL,'Orlando',NULL);
SELECT DISTINCT x.value('local-name(.)', 'SYSNAME') AS NotNULLColumns
FROM #tmpTable AS t
CROSS APPLY (SELECT t.* FOR XML PATH(''), TYPE, ROOT('root')) AS t1(c)
CROSS APPLY c.nodes('/root/*') AS t2(x);
SQL #2
To handle edge cases.
SELECT DISTINCT x.value('local-name(.)', 'SYSNAME') AS NotNULLColumns
FROM #tmpTable AS t
CROSS APPLY (SELECT t.* FOR XML RAW, ELEMENTS, BINARY BASE64, TYPE, ROOT('root')) AS t1(c)
CROSS APPLY c.nodes('/root/row/*') AS t2(x);
Output
NotNULLColumns
city
client_id
client_name

Creating a Query for each row in a SQL Table

I have a schema in my SQL table out of which some table has a time value stamp (same column name 'timestamp' in all the tables in the schema) and I need to create a new table which will give the latest time stamp for each such table. I have achieved a part which will give me a table with 2 columns, one the table name column and another column which gives the query for each table which if runs will give me the latest timeStamp for each table in table name Column. The script I used is as follows and I show 3 rows as an example:
WITH CTE AS
(
SELECT
CONCAT(schema_name(t.schema_id), '.',t.name) AS table_name,
c.name AS 'time_stamp'
FROM
sys.tables t
INNER JOIN
sys.columns c ON c.object_id = t.object_id
WHERE
schema_name(t.schema_id) = 'PROD'
AND c.name = 'timestamp'
)
SELECT table_name, time_stamp
INTO #TEMP_TABLE
FROM CTE
DECLARE #i int = 1, #c int = (SELECT COUNT(*) FROM #TEMP_TABLE)
DECLARE #Result TABLE
(
tName varchar(500),
tStamp varchar(500)
)
WHILE (#i <= #c)
BEGIN
INSERT INTO #Result
SELECT
table_name,
'SELECT MAX('+ time_stamp +') FROM ' + table_name
FROM #TEMP_TABLE;
SET #i = #i + 1
END
DROP TABLE #TEMP_TABLE
SELECT * FROM #RESULT
When I run this script I get the following table (3 rows shown as an illustration)
My output (O)
tName tStamp
-----------------------------------------------------------
PROD.table_A SELECT MAX(time_stamp) FROM PROD.table_A
PROD.table_B SELECT MAX(time_stamp) FROM PROD.table_B
PROD.table_C SELECT MAX(time_stamp) FROM PROD.table_C
However what I want is the value of the query in the tStamp column and not the query string. So actually the output table should look like (say assuming the query in each of the above rows in column tStamp. I put in some max values as an example when we run each query in tStamp column)
My final expected output (F)
tName tStamp
------------------------------------------
PROD.table_A 2021-10-12 14:20:56.000
PROD.table_B 2021-11-01 19:04:35.000
PROD.table_C 2021-10-23 08:07:12.000
I am in a limbo at this stage not sure, how to get the table F from table O. So I will really appreciate any help. If it can be possible to tweak something which I am doing to get directly the output table F or if we can work on the table O to get to table F anything can help.
Thanks in advance.
If this is a one shot thing, I would consider just using a macro (vim, excel) to generate the query text for each table using your CTE results and then paste it back in and run.
If not, you could consider some of the suggestions for dynamic sql in this article: [https://www.mssqltips.com/sqlservertip/1160/execute-dynamic-sql-commands-in-sql-server/][1]

Select on a table with 2 possible structures

I'm trying to write a query that will select data from a table. due to different versions of the database, there are 2 possible structures for the source table, where the newer version has 2 more fields than the old one.
I've tried identifying the older structure and replacing the columns with NULL and also tried writing 2 separate queries with and IF statement directing to the correct one. Neither of these solutions work and in both cases it seems that the SQL engine is failing on validating these 2 columns.
Examples of my attempted solutions:
IF NOT EXISTS (SELECT *
FROM sys.objects
WHERE object_id = Object_id(N'[dbo].[Test2]')
AND type IN ( N'U' ))
BEGIN
CREATE TABLE [dbo].[test2]
(
[id] [INT] IDENTITY(1, 1) NOT NULL,
[statusid] [INT] NULL
)
END
go
DECLARE #Flag INT = 0
IF EXISTS(SELECT 1
FROM sys.columns
WHERE NAME = N'TestId'
AND object_id = Object_id(N'dbo.Test2'))
SET #Flag = 1
--Solution #1
IF #Flag = 1
SELECT id,
statusid,
testid
FROM dbo.test2
ELSE
SELECT id,
statusid
FROM dbo.test2
--Solution #2
SELECT id,
statusid,
CASE
WHEN #Flag = 1 THEN testid
ELSE NULL
END AS TestId
FROM dbo.test2
you can use Dynamic SQL and generate the query accordingly depends on value of #flag
declare #sql nvarchar(max)
select #sql = N'select id, statusid, '
+ case when #flag = 1 then 'testid' else 'NULL' end + ' as testid'
+ ' from dbo.test2'
print #sql
exec sp_executesql #sql
But it will not be that easy to code and maintain Dynamic Query if you have a complex query.

query/script/stored procedure which will return a single table

I have the following query. can someone help rapping this in a cursor.
IF OBJECT_ID('tempdb.[dbo].[#Results]') IS NOT NULL
DROP TABLE [dbo].[#Results]
GO
CREATE TABLE [dbo].[#Results] (
[DatabaseName] VARCHAR(128) NULL,
[DatabaseVersion] VARCHAR(128) NULL,
[DateChangedOn] DATETIME NULL)
EXEC sp_msForEachDb '
IF EXISTS (SELECT * FROM [?].sys.objects WHERE NAME = ''stdVersions'')
BEGIN
INSERT INTO #Results
SELECT
''?'' AS [DatabaseName],
v.[DatabaseVersion],
l.[DateChangedOn]
FROM [?].dbo.stdVersions v
CROSS JOIN [?].dbo.stdChangeLog l
END'
SELECT * FROM #Results ORDER BY DateChangedOn
the query should return list of all database names in the server and within the databases return the databaseversion column and DateChangeOn Column. All the databases in the server contain tables named stdVersions and stdChangeLog of which the stdVersion table have a single row of DatabaseVersion comes and stdChangeLog table have a single row of DateChangedOn.
Below is an example of creating a cursor on the #Results table.
DECLARE #DatabaseName sysname
, #DatabaseVersion sysname
, #DateChangedOn datetime;
DECLARE DatabaseList CURSOR LOCAL FAST_FORWARD
FOR
SELECT DatabaseName
, DatabaseVersion
, DateChangedOn
FROM #Results
ORDER BY DateChangedOn;
OPEN DatabaseList;
WHILE 1 = 1
BEGIN
FETCH NEXT FROM DatabaseList INTO
#DatabaseName
, #DatabaseVersion
, #DateChangedOn;
IF ##FETCH_STATUS = -1 BREAK;
--process row here
END;
CLOSE DatabaseList;
DEALLOCATE DatabaseList;
You mention that the "std" tables exist in all databases. If these tables does not include system databases, change the IF statement in the script below to exclude those:
IF EXISTS (SELECT * FROM [?].sys.objects WHERE NAME = ''stdVersions'')
AND [?] NOT IN(N'master', N'model', N'tempdb', N'msdb', N'SSISDB')

How to store a multiple or a list of values returned from sp_executesql?

UPDATE : This is what I did -
set #dyn_sql = '
select
#UserName=UserName
from
(
select
E.ID as EmployeeID,
E.UserName as Username
from
Leaderboard K
inner join Employee E on K.EmployeeId = E.Id
inner join INFO KD on KD.EmployeeId=E.Id
where E.CompanyId=4
) as d1'
DECLARE #leaderboards TABLE
(
UserName varchar(50)
)
set #params='#Employee_Id int, #UserName varchar(200) OUTPUT'
INSERT INTO #leaderboards (UserName)
EXEC sp_executesql #dyn_sql, #params,#EmployeeId=#Employee_Id OUTPUT,#UserName = #User_Name OUTPUT
SELECT * from #leaderboards
But this is not returning records although if I see the query is right and returns records..
Hi all, I am executing a dynamic sql statement using sp_executesql and this is what I am doing currently -
EXEC sp_executesql #dyn_sql, #params,#EmployeeId=#Employee_Id OUTPUT,#UserName = #User_Name OUTPUT
SELECT #Employee_Id AS EmployeeId,#User_Name AS UserName
But the above only gets me single value when I am getting a list of records if I run the dynamic sql query individually.How can I store the list of values returned by execution of my dynamic sql ?
and returns records..and returns records..But this
Via a temp table.
[Update]
declare #sql nvarchar(max)
set #sql = '
select
E.ID as EmployeeID, --doesn't really matter how you name them, it's the order that matters
E.UserName as Username -- and this order should match the order of columns in the insert statement
from
Leaderboard K
inner join Employee E on K.EmployeeId = E.Id
inner join INFO KD on KD.EmployeeId=E.Id
where E.CompanyId=4
'
DECLARE #LeaderBoard TABLE
(
EmployeeId int,
UserName varchar(50)
)
INSERT INTO #LeaderBoard (EmployeeId, UserName)
exec sp_executesql #sql
select * from #LeaderBoard
--Hurray, we made it!
You can insert the results of EXEC sp_executesql into a temp table or table variable.
DECLARE #t TABLE
(
a INT,
b INT
)
INSERT INTO #t (a,b)
EXEC sp_executesql N'SELECT 1, 2 UNION SELECT 3, 4 '
Or alternatively the dynamic SQL can access a temp table declared in the parent scope (but any temp table created in the dynamic SQL itself will be out of scope as soon as the execution finishes)

Resources