use dbx;
select foo
from db1.dbo.table
join db2.dbo.table on condition
where some_function();
This query consumed lots of CPU and requested a large memory grant. In which database? The information you want simply doesn't exist as a concept. As a human with insight knowledge and with hindsight benefit, you probably would be able to explain why 75% of CPU is due to db1 and 15% is due to db2. But ultimately you just can assign queries to a database. The fact that some (ok, most) queries are 100% contained inside a db does not mean that all query resources can be assigned deterministically to a db.
However, for practical means is relatively simple to automate exactly what you did in your post: inspect the plans and identify all physical access operators locations and use this info to assign the query resources to a DB.
with xmlnamespaces (default 'http://schemas.microsoft.com/sqlserver/2004/07/showplan')
select x.value(N'@NodeId',N'int') as NodeId
, x.value(N'@PhysicalOp', N'sysname') as PhysicalOp
, x.value(N'@LogicalOp', N'sysname') as LogicalOp
, ox.value(N'@Database',N'sysname') as [Database]
, ox.value(N'@Schema',N'sysname') as [Schema]
, ox.value(N'@Table',N'sysname') as [Table]
, ox.value(N'@Index',N'sysname') as [Index]
, ox.value(N'@IndexKind',N'sysname') as [IndexKind]
, x.value(N'@EstimateRows', N'float') as EstimateRows
, x.value(N'@EstimateIO', N'float') as EstimateIO
, x.value(N'@EstimateCPU', N'float') as EstimateCPU
, x.value(N'@AvgRowSize', N'float') as AvgRowSize
, x.value(N'@TableCardinality', N'float') as TableCardinality
, x.value(N'@EstimatedTotalSubtreeCost', N'float') as EstimatedTotalSubtreeCost
, x.value(N'@Parallel', N'tinyint') as DOP
, x.value(N'@EstimateRebinds', N'float') as EstimateRebinds
, x.value(N'@EstimateRewinds', N'float') as EstimateRewinds
, st.*
, pl.query_plan
from sys.dm_exec_query_stats as st
cross apply sys.dm_exec_query_plan (st.plan_handle) as pl
cross apply pl.query_plan.nodes('//RelOp[./*/Object/@Database]') as op(x)
cross apply op.x.nodes('./*/Object') as ob(ox)
DECLARE @sql NVARCHAR(MAX) = N'SELECT ';
SELECT @sql += N'
' + QUOTENAME(c.name)
+ ' = COALESCE(es.' + QUOTENAME(c.name)
+ ', s.' + QUOTENAME(c.name) + '),'
FROM sys.columns AS c
INNER JOIN sys.columns AS c2
ON c.name = c2.name
AND c.[object_id] = OBJECT_ID('dbo.SETTINGS')
AND c2.[object_id] = OBJECT_ID('dbo.EXTENDED_SETTINGS')
AND c.name NOT IN (N'name', N'ID' /* , ... potentially others ... */);
SET @sql += N' s.name, s.ID';
SELECT @sql += N',
s.' + QUOTENAME(c.name)
FROM sys.columns AS c
WHERE [object_id] = OBJECT_ID('dbo.SETTINGS')
AND name NOT IN (N'name', N'ID')
AND NOT EXISTS
(
SELECT 1 FROM sys.columns AS c2
WHERE c2.[object_id] = OBJECT_ID('dbo.EXTENDED_SETTINGS')
AND c2.name = c.name
);
SET @sql += N'
FROM dbo.SETTINGS AS s
INNER JOIN dbo.EXTENDED_SETTINGS AS es
ON s.ID = es.SETTINGS_ID
WHERE es.ID = @extSetId AND s.ID = @settID;';
PRINT @sql;
--EXEC sp_executesql @sql;
Please use table aliases and schema prefixes, and here is why I prefer COALESCE
over ISNULL
. And if those tables really aren't stored as upper case in a case sensitive collation, do you really need all caps? Also, ID
is a horrible and ambiguous name for a column. It should be the same name across the schema; always describing exactly what it is - especially when you're doing lazy dynamic things like this.
Best Answer
You could use a cursor to loop over the databases and store the resultsets in a temporary table
Testing
Result