im stuck with some "simple" sql query.
I want to query all DB's and get all sql-users back.
first i used the sp_foreachdb, but than read its not supported and also the output is not the best for further handling...
DECLARE #command varchar(1000)
SELECT #command = 'USE [?]
IF DB_ID(''?'') > 4
SELECT ''?'' as DBName, * FROM sysusers AS SU
Left Join sys.server_principals AS SP
ON SU.sid = SP.sid
WHERE SP.type = ''S''
ORDER BY SU.name'
EXEC sp_MSforeachdb #command
Now i get bit further with nested queries and UNION.
But now i get all users and not only the sql users (not system-users)
DECLARE #Sql NVARCHAR(MAX) = NULL;
SELECT #Sql = COALESCE(#Sql + ' UNION ALL ' + CHAR(13) + CHAR(10), '' ) + 'SELECT ''' + DB.[name] + ''' AS dbname, SU.[name] COLLATE Latin1_General_CI_AS AS Username
FROM ' + QUOTENAME([name]) + '.sys.sysusers AS SU'
--INNER Join sys.server_principals AS SP'
--ON SU.sid = SP.sid
--WHERE SP.type = ''S''
--ORDER BY SU.name'
FROM master.sys.databases as DB
WHERE [database_id] > 4 AND [state] = 0;
SET #Sql = ' ' + #Sql;
--PRINT #Sql;
EXECUTE ( #Sql )
As soon as i add the commented lines back, there is an error with the "next to union statement"...
Can anyone show me how to correct use a "for each database" query? :)
Thx alot.
Related
I need to create a query that is executed on all databases of my SQL server instance. An additional constraint is, that the query should only be executed on databases that contain a special table with a special column. Background is that in some databases the special table does (not) have the special column.
Based on this solution, what I have until now is a query that executes only on databases that contain a certain table.
SELECT *
FROM sys.databases
WHERE DATABASEPROPERTY(name, 'IsSingleUser') = 0
AND HAS_DBACCESS(name) = 1
AND state_desc = 'ONLINE'
AND CASE WHEN state_desc = 'ONLINE'
THEN OBJECT_ID(QUOTENAME(name) + '.[dbo].[CERTAIN_TABLE]', 'U')
END IS NOT NULL
However, what is still missing is a constraint that the query should only select databases where the table CERTAIN_TABLE has a specific column. How can this be achieved?
When i want to loop through all databases, i do a loop like the following. Its easy to follow:
DECLARE #dbs TABLE ( dbName NVARCHAR(100) )
DECLARE #results TABLE ( resultName NVARCHAR(100) )
INSERT INTO #dbs
SELECT name FROM sys.databases
DECLARE #current NVARCHAR(100)
WHILE (SELECT COUNT(*) FROM #dbs) > 0
BEGIN
SET #current = (SELECT TOP 1 dbName FROM #dbs)
INSERT INTO #results
EXEC
(
'IF EXISTS(SELECT 1 FROM "' + #current + '".INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = ''Target_Table_Name'' AND COLUMN_NAME = ''Target_Column_Name'')
BEGIN
--table and column exists, execute query here
SELECT ''' + #current + '''
END'
)
DELETE FROM #dbs
WHERE dbName = #current
END
SELECT * FROM #results
You are going to need either some looping or dynamic sql for this. I really dislike loops so here is how you could do this with dynamic sql.
declare #TableName sysname = 'CERTAIN_TABLE'
, #ColumnName sysname = 'CERTAIN_COLUMN'
declare #SQL nvarchar(max) = ''
select #SQL = #SQL + 'select DatabaseName = ''' + db.name + ''' from ' + QUOTENAME(db.name) + '.sys.tables t join ' + QUOTENAME(db.name) + '.sys.columns c on c.object_id = t.object_id where t.name = ''' + QUOTENAME(#TableName) + ''' and c.name = ''' + QUOTENAME(#ColumnName) + '''' + char(10) + 'UNION ALL '
from sys.databases db
where db.state_desc = 'ONLINE'
order by db.name
select #SQL = substring(#SQL, 0, len(#SQL) - 9)
select #SQL
--uncomment the line below when you are comfortable the query generated is correct
--exec sp_executesql #SQL
Apologies, for a convoluted question - I'm not a dba. Is there a simple script I can run that can list all the stored procedures I have on SQL Server, grouped by database and list them with the input and output parameters that go with the stored procedures.
I'm writing a similar script outside of SQL, to do the same for a language calling the stored procedures, so I find if there are conflicts in a legacy application.
Use the following script:
DECLARE #CurrentRowID INT
,#CurrentDatabase SYSNAME;
DECLARE #DynamicSQL NVARCHAR(MAX);
IF OBJECT_ID('tempdb..##DataSource') IS NOT NULL
BEGIN
DROP TABLE ##DataSource;
END;
CREATE TABLE ##DataSource
(
[database] SYSNAME
,[procedure] SYSNAME
,[parameter] SYSNAME
,[is_output] BIT
);
DECLARE #DataBases TABLE
(
[RowID] INT IDENTITY(1,1)
,[database] SYSNAME
);
INSERT INTO #DataBases ([database])
SELECT [name]
FROM [sys].[databases];
WHILE EXISTS(SELECT 1 FROM #DataBases)
BEGIN
SELECT TOP 1 #CurrentRowID = [RowID]
,#CurrentDatabase = [database]
FROM #DataBases;
SET #DynamicSQL = N'INSERT INTO ##DataSource
SELECT ''' + #CurrentDatabase + ''' AS [database]
,PR.[name]
,P.[name]
,P.[is_output]
FROM [' + #CurrentDatabase + '].[sys].[procedures] PR
INNER JOIN [' + #CurrentDatabase + '].[sys].[parameters] P
ON PR.[object_id] = P.[object_id]'
EXEC sp_executesql #DynamicSQL;
DELETE FROM #DataBases
WHERE [RowID] = #CurrentRowID;
END;
SELECT *
FROM ##DataSource
Of course, you can filter some of the databases, or add more columns from the sys.procedures dmv like system type for example.
Run a loop/cursor on sys.databases and inside the loop, USE and run the following query and keep taking union:
SELECT pr.name [Procedure], par.name Parameter, CASE WHEN is_output = 1 THEN 'Output Parameter' ELSE 'Input Parameter' END [ParameterType]
FROM sys.parameters par
INNER JOIN sys.procedures pr ON pr.object_id = par.object_id
ORDER BY [Procedure], [ParameterType]
This will give you all procedures and their dependent parameters.
declare #sql nvarchar(max) = ''
set #sql = #sql + N'union all
select '''+ quotename(d.name) + N''' as db, s.name as sname, p.name as p.name, r. name as paramname, r.is_output as paramoutput
from '''+ quotename(d.name) + N'''.sys.procedures p
join sys.parameters r on p.object_id = r.object_id
join sys.schemas s on p.schema_id = s.schema_id'
from sys.databases d
-- where d.name like ....
order by d.name
set #sql = stuff#sql, 1, 10, N'')
exec sp_executesql #sql
-- not tested
I got the 'Select permission denied' error for some of my tables in a db.
In MS SQL Server 2012, setting 'Select' Permission on 1 table is easy. I just right click on the table > Properties > Permissions > Select the User/Role and tick the 'Select' Permission in the 'Explicit' tab below.
But I want to do that for over a 100 tables. Is there a better way to do it?
Without while loop:
DECLARE #Sql NVARCHAR(MAX)
SET #Sql = ''
select #Sql = #Sql +
'grant select on '+
s.name + '.' + o.name + ' to [domain\user] go' + CHAR(10)
from sys.objects o
join sys.schemas s on o.schema_id = s.schema_id
where o.type = 'U' and s.name = 'dbo'
PRINT #Sql
You should get the list of tables you want from sys.objects, then loop through and execute grant on each table. Here's an example:
select
s.name + '.' + o.name schemaQualifiedTableName,
row_number() over (order by o.name) Id
into #tables
from sys.objects o
join sys.schemas s on o.schema_id = s.schema_id
where o.type = 'U' and s.name = 'dbo'
declare #counter int
set #counter = 1
while exists(select * from #tables where id = #counter)
begin
declare #sql as varchar(4000)
select #sql = 'grant select on ' + schemaQualifiedTableName + ' to [domain\user] go' from #tables where id = #counter
print #sql
--exec (#sql)
set #counter = #counter + 1
end
The Quick and Dirty way would be creating a new database role and granting it select to the dbo squema (assuming that those tables are in the dbo scheme). Of course it's not the best security practice, a better practice would be granting that role to each table it needs and then include users in the role.
Before going to any solution you should define what level of security do you want to keep.
Hope this helps.
I'm running the below stored procedure sp_MSforeachdb with a simple command. My question is how to limit the result to show only the databases that have at least 1 record satisfying the command:
Here's my stored procedure:
EXECUTE master.sys.sp_MSforeachdb 'USE [?];
IF (EXISTS (SELECT *
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = ''Tabs''))
BEGIN
SELECT ''?'' as dbname,T.TabName, T.TabPath
FROM Tabs T
WHERE T.TabID IN (
SELECT Distinct TM.TabID
FROM TabModules TM
WHERE mID IN (
...
)
)
ORDER BY T.TabName
END
'
Any ideas how I can modify the sp so that it doesn't display the databases that have empty results (see image)?
Well, first, stop using sp_MSforEachDb. Oh, the problems (if you want proof, see here).
How about:
DECLARE #cmd NVARCHAR(MAX) = N'', #sql NVARCHAR(MAX) = N'';
SELECT #cmd += N'IF EXISTS (SELECT 1 FROM '
+ QUOTENAME(name) + '.sys.tables WHERE name = N''Tabs'')
SET #sql += N''UNION ALL
SELECT ''''' + name + ''''',T.TabName
FROM ' + QUOTENAME(name) + '.dbo.Tabs AS T
WHERE EXISTS
(
SELECT 1 FROM ' + QUOTENAME(name) + '.dbo.TabModules AS TM
WHERE TM.TabID = T.TabID
AND TM.mID IN -- this should probably be exists too
(
...
)
)
'''
FROM sys.databases
WHERE state = 0 -- assume you only want online databases
AND database_id > 4; -- assume you don't want system dbs
EXEC sp_executesql #cmd, N'#sql NVARCHAR(MAX) OUTPUT', #sql OUTPUT;
SET #sql = STUFF(#sql, 1, 10, '') + N' ORDER BY TabName;';
PRINT #sql; -- this will appear truncated, but trust me, it is not truncated
-- EXEC sp_executesql #sql;
If you really want some unknown, arbitrary number of separate resultsets, the change is simple.
DECLARE #cmd NVARCHAR(MAX) = N'', #sql NVARCHAR(MAX) = N'';
SELECT #cmd += N'IF EXISTS (SELECT 1 FROM '
+ QUOTENAME(name) + '.sys.tables WHERE name = N''Tabs'')
SET #sql += N''SELECT ''''' + name + ''''',T.TabName
FROM ' + QUOTENAME(name) + '.dbo.Tabs AS T
WHERE EXISTS
(
SELECT 1 FROM ' + QUOTENAME(name) + '.dbo.TabModules AS TM
WHERE TM.TabID = T.TabID
AND TM.mID IN -- this should probably be exists too
(
...
)
)
ORDER BY T.TabName;
'';'
FROM sys.databases
WHERE state = 0 -- assume you only want online databases
--AND database_id > 4; -- assume you don't want system dbs
EXEC sp_executesql #cmd, N'#sql NVARCHAR(MAX) OUTPUT', #sql OUTPUT;
PRINT #sql; -- this will appear truncated, but trust me, it is not truncated
-- EXEC sp_executesql #sql;
You basically need another IF to only run the select if data exists.
Here's what i did to test.
On DB1:
create table mytesttable(a int)
insert mytesttable values(1)
On DB2:
create table mytesttable(a int)
So you want DB1 to return results, but DB2 not to. you can use the following sql:
EXECUTE master.sys.sp_MSforeachdb 'USE [?];
IF (EXISTS (SELECT *
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = ''mytesttable''))
BEGIN
IF EXISTS (SELECT 1 FROM mytesttable) BEGIN
SELECT ''?'' as dbname,T.A
FROM mytesttable AS T
END
END
'
This only returns:
db1, 1
This is what I have so far to find all tables with more than 100 rows:
SELECT sc.name +'.'+ ta.name TableName
,SUM(pa.rows) RowCnt
FROM sys.tables ta
INNER JOIN sys.partitions pa
ON pa.OBJECT_ID = ta.OBJECT_ID
INNER JOIN sys.schemas sc
ON ta.schema_id = sc.schema_id
WHERE ta.is_ms_shipped = 0 AND pa.index_id IN (1,0) AND pa.rows >100
GROUP BY sc.name,ta.name,pa.rows
ORDER BY TABLENAME
Is there something similar where I can go through the database to find out a specific row data for a column within a table?
For example:
Where c.name = GUITARS and GUTARS = 'Fender'
Edit:
I do not have CREATE PROCEDURE permission OR CREATE TABLE
Just looking for any specific data under a certain column name, doesn't matter if it returns a lot of rows.
I dont know whether my solutions works for you or not. But instead of that i would use the query below to get all possible tables having guitar in any combination .
Select t.name, c.name
from sys.columns c
inner join sys.tables t
on c.object_id=t.object_id
Where c.name like '%guitar%'
Suppose it will give 20-25 tables depending on the no of tables and usage of guitar columns. You can see the resultset and can almost know your usable tables.
Now search for Fenders in your guessed items list.
I am saying so as I am working on maintenance of an erp app and it has 6000+ tables and 13000+ procedures. So whenever I need to find out the related tables , i just use the same trick and it works.
This is a simple stored procedure which can search through all the
data in the SQL Server database tables. Also this has capability to
search in the selected tables if the table names are specified with
comma separated values. This has a capability to generate the SQL
alone without executing the SQL. Enclosing the script version of it
also.
Parameters and usage:
#Tablenames -- Provide a single table name or multiple table name
with comma separated.
If left blank, it will check for all the tables in the database
#SearchStr -- Provide the search string. Use the '%' to coin the
search.
EX : X%--- will give data starting with X
%X--- will give data ending with X
%X%--- will give data containing X
#GenerateSQLOnly -- Provide 1 if you only want to generate the SQL
statements without searching the database.
By default it is 0 and it will search.
IF OBJECT_ID('SP_SearchTables','P') IS NOT NULL
DROP PROCEDURE SP_SearchTables
GO
CREATE PROCEDURE SP_SearchTables
#Tablenames VARCHAR(500)
,#SearchStr NVARCHAR(60)
,#GenerateSQLOnly Bit = 0
AS
SET NOCOUNT ON
DECLARE #MatchFound BIT
SELECT #MatchFound = 0
DECLARE #CheckTableNames Table
(
Tablename sysname
)
DECLARE #SQLTbl TABLE
(
Tablename SYSNAME
,WHEREClause VARCHAR(MAX)
,SQLStatement VARCHAR(MAX)
,Execstatus BIT
)
DECLARE #sql VARCHAR(MAX)
DECLARE #tmpTblname sysname
DECLARE #ErrMsg VARCHAR(100)
IF LTRIM(RTRIM(#Tablenames)) IN ('' ,'%')
BEGIN
INSERT INTO #CheckTableNames
SELECT Name
FROM sys.tables
END
ELSE
BEGIN
SELECT #sql = 'SELECT ''' + REPLACE(#Tablenames,',',''' UNION SELECT ''') + ''''
INSERT INTO #CheckTableNames
EXEC(#sql)
END
IF NOT EXISTS(SELECT 1 FROM #CheckTableNames)
BEGIN
SELECT #ErrMsg = 'No tables are found in this database ' + DB_NAME() + ' for the specified filter'
PRINT #ErrMsg
RETURN
END
INSERT INTO #SQLTbl
( Tablename,WHEREClause)
SELECT QUOTENAME(SCh.name) + '.' + QUOTENAME(ST.NAME),
(
SELECT '[' + SC.name + ']' + ' LIKE ''' + #SearchStr + ''' OR ' + CHAR(10)
FROM SYS.columns SC
JOIN SYS.types STy
ON STy.system_type_id = SC.system_type_id
AND STy.user_type_id =SC.user_type_id
WHERE STY.name in ('varchar','char','nvarchar','nchar')
AND SC.object_id = ST.object_id
ORDER BY SC.name
FOR XML PATH('')
)
FROM SYS.tables ST
JOIN #CheckTableNames chktbls
ON chktbls.Tablename = ST.name
JOIN SYS.schemas SCh
ON ST.schema_id = SCh.schema_id
WHERE ST.name <> 'SearchTMP'
GROUP BY ST.object_id, QUOTENAME(SCh.name) + '.' + QUOTENAME(ST.NAME) ;
UPDATE #SQLTbl
SET SQLStatement = 'SELECT * INTO SearchTMP FROM ' + Tablename + ' WHERE ' + substring(WHEREClause,1,len(WHEREClause)-5)
DELETE FROM #SQLTbl
WHERE WHEREClause IS NULL
WHILE EXISTS (SELECT 1 FROM #SQLTbl WHERE ISNULL(Execstatus ,0) = 0)
BEGIN
SELECT TOP 1 #tmpTblname = Tablename , #sql = SQLStatement
FROM #SQLTbl
WHERE ISNULL(Execstatus ,0) = 0
IF #GenerateSQLOnly = 0
BEGIN
IF OBJECT_ID('SearchTMP','U') IS NOT NULL
DROP TABLE SearchTMP
EXEC (#SQL)
IF EXISTS(SELECT 1 FROM SearchTMP)
BEGIN
SELECT Tablename=#tmpTblname,* FROM SearchTMP
SELECT #MatchFound = 1
END
END
ELSE
BEGIN
PRINT REPLICATE('-',100)
PRINT #tmpTblname
PRINT REPLICATE('-',100)
PRINT replace(#sql,'INTO SearchTMP','')
END
UPDATE #SQLTbl
SET Execstatus = 1
WHERE Tablename = #tmpTblname
END
IF #MatchFound = 0
BEGIN
SELECT #ErrMsg = 'No Matches are found in this database ' + DB_NAME() + ' for the specified filter'
PRINT #ErrMsg
RETURN
END
SET NOCOUNT OFF
go
By Sorna Kumar Muthuraj
Something I use at work really flexible useful piece of code.
If you don't have permissions to create procs just declare the variables being used in this script and execute script as ad-hoc query
DECLARE #Tablenames VARCHAR(500) = 'Table_Name'
DECLARE #SearchStr NVARCHAR(60) = 'Data_LookingFor'
DECLARE #GenerateSQLOnly Bit = 0
Your DBA really doesn't trust you does he :) Anyway, I have tweaked the code a little bit more making use of temp table rather than the Table variables, maybe this will work for you:
DECLARE #Tablenames VARCHAR(500) = 'Table_name'
DECLARE #SearchStr NVARCHAR(60) = 'Serach_String'
DECLARE #GenerateSQLOnly Bit = 0
SET NOCOUNT ON
DECLARE #MatchFound BIT
SELECT #MatchFound = 0
IF OBJECT_ID('tempdb..#CheckTableNames') IS NOT NULL
DROP TABLE #CheckTableNames
CREATE Table #CheckTableNames
(
Tablename sysname
)
IF OBJECT_ID('tempdb..#SQLTbl') IS NOT NULL
DROP TABLE #SQLTbl
CREATE TABLE #SQLTbl
(
Tablename SYSNAME
,WHEREClause VARCHAR(MAX)
,SQLStatement VARCHAR(MAX)
,Execstatus BIT
)
DECLARE #sql VARCHAR(MAX)
DECLARE #tmpTblname sysname
DECLARE #ErrMsg VARCHAR(100)
IF LTRIM(RTRIM(#Tablenames)) IN ('' ,'%')
BEGIN
INSERT INTO #CheckTableNames
SELECT Name
FROM sys.tables
END
ELSE
BEGIN
SELECT #sql = 'SELECT ''' + REPLACE(#Tablenames,',',''' UNION SELECT ''') + ''''
INSERT INTO #CheckTableNames
EXEC(#sql)
END
IF NOT EXISTS(SELECT 1 FROM #CheckTableNames)
BEGIN
SELECT #ErrMsg = 'No tables are found in this database ' + DB_NAME() + ' for the specified filter'
PRINT #ErrMsg
RETURN
END
INSERT INTO #SQLTbl
( Tablename,WHEREClause)
SELECT QUOTENAME(SCh.name) + '.' + QUOTENAME(ST.NAME),
(
SELECT '[' + SC.name + ']' + ' LIKE ''' + #SearchStr + ''' OR ' + CHAR(10)
FROM SYS.columns SC
JOIN SYS.types STy
ON STy.system_type_id = SC.system_type_id
AND STy.user_type_id =SC.user_type_id
WHERE STY.name in ('varchar','char','nvarchar','nchar')
AND SC.object_id = ST.object_id
ORDER BY SC.name
FOR XML PATH('')
)
FROM SYS.tables ST
JOIN #CheckTableNames chktbls
ON chktbls.Tablename = ST.name
JOIN SYS.schemas SCh
ON ST.schema_id = SCh.schema_id
WHERE ST.name <> 'SearchTMP'
GROUP BY ST.object_id, QUOTENAME(SCh.name) + '.' + QUOTENAME(ST.NAME) ;
UPDATE #SQLTbl
SET SQLStatement = 'SELECT * INTO SearchTMP FROM ' + Tablename + ' WHERE ' + substring(WHEREClause,1,len(WHEREClause)-5)
DELETE FROM #SQLTbl
WHERE WHEREClause IS NULL
WHILE EXISTS (SELECT 1 FROM #SQLTbl WHERE ISNULL(Execstatus ,0) = 0)
BEGIN
SELECT TOP 1 #tmpTblname = Tablename , #sql = SQLStatement
FROM #SQLTbl
WHERE ISNULL(Execstatus ,0) = 0
IF #GenerateSQLOnly = 0
BEGIN
IF OBJECT_ID('SearchTMP','U') IS NOT NULL
DROP TABLE SearchTMP
EXEC (#SQL)
IF EXISTS(SELECT 1 FROM SearchTMP)
BEGIN
SELECT Tablename = #tmpTblname,* FROM SearchTMP
SELECT #MatchFound = 1
END
END
ELSE
BEGIN
PRINT REPLICATE('-',100)
PRINT #tmpTblname
PRINT REPLICATE('-',100)
PRINT replace(#sql,'INTO SearchTMP','')
END
UPDATE #SQLTbl
SET Execstatus = 1
WHERE Tablename = #tmpTblname
END
IF #MatchFound = 0
BEGIN
SELECT #ErrMsg = 'No Matches are found in this database ' + DB_NAME() + ' for the specified filter'
PRINT #ErrMsg
RETURN
END
Please see my answer to How do I find a value anywhere in a SQL Server Database? where I provide a script to search all tables in a database.
A pseudo-code description of this would be be select * from * where any like 'foo'
It also allows you to search specific column names using standard like syntax, e.g. %guitar% to search column names that have the word "guitar" in them.
It runs ad-hoc, so you do not have to create a stored procedure, but you do need access to information_schema.
I use this script in SQL 2000 and up almost daily in my DB development work.