SQL Server number of pages in a table - sql-server

I'm trying to calculate how many pages a single table has. For this i end up with different queries
SELECT SUM(si.dpages)
FROM sys.sysindexes si
INNER JOIN sys.tables st on si.id = st.object_id
WHERE st.name = 'job'
SELECT SUM(page_count)
FROM sys.dm_db_index_physical_stats (NULL, NULL, NULL, NULL, NULL) s
WHERE
s.database_id = db_id()
and s.object_id = OBJECT_ID('job')
SELECT SUM(used_page_count)
FROM sys.dm_db_partition_stats
WHERE object_id = OBJECT_ID('job');
dbcc ind
(
'wellnew'
,'dbo.job'
,-1
);
And these are the results:
16681
16681
16771
(16771 row(s) affected)
Now, which one is right? Or what's the correct way to get that information?
There is quite a similar question here it's even using a query similar tom mine, but i have 4 queries that in theory are all correct but they return different results
Thanks in advance
Henrry

Related

SQL Query to get data from various databases

I wrote the below query to pull the data from different databases. I have created two temp tables to pull the data from two different databases and finally a select statement from the original database to join all the tables. My query is getting executed but not getting any data.(Report is blank). I tried executing the two temp tables separately. it is giving the correct data. But when I execute the whole query, the result is blank. Below is the query. Please help.
"set fmtonly off
use GODSDB
IF object_id('tempdb..#CISIS_Call_Log') IS NOT NULL DROP TABLE #CISIS_Call_Log
select *
into #CISIS_Call_Log
from OPENQUERY (CSISDB,
'select
ccl.ContractOID,
ccl.db_insertdate,
ccl.ContractCallLogStatusIdentifier,
ccl.db_UpdateDate,
ccp.ContractCallLogPurposeOID,
ccp.ContractCallLogPurposeIdentifier,
ccp.Description
from csisdb.dbo.ContractCallLog CCL
inner join csisdb.dbo.ContractCallLogPurpose CCP on ccl.ContractCallLogPurposeIdentifier = ccp.ContractCallLogPurposeIdentifier
where JurisdictionShortIdentifier = ''ON''
AND ContractCallLogStatusIdentifier IN (''DNR'', ''NR'')
')
IF object_id('tempdb..#CMS_Campaign') IS NOT NULL DROP TABLE #CMS_Campaign
select *
into #CMS_Campaign
from OPENQUERY (BA_GBASSTOCMS, '
Select
SystemSourceIdentifier,
ContractOID,
OfferSentDate,
CampaignOfferTypeIdentifier,
CampaignContractStatusIdentifier,
CampaignContractStatusUpdateDate,
DeclineDate,
CampaignOfferOID,
CampaignOID,
CampaignStartDate,
CampaignEndDate,
Jurisdiction,
CampaignDescription
from CMS.dbo.vw_CampaignInfo
where Jurisdiction = ''ON''
and CampaignOfferTypeIdentifier = ''REN''
')
select mp.CommodityTypeIdentifier as Commodity
,c.RtlrContractIdentifier as ContractID
,cs.ContractStatusIdentifier as ContractStatus
,c.SigningDate
,cf.StartDate as FlowStartDate
,cf.EndDate as FlowEndDate
,datediff(day, getdate(), c.RenewalDate) as RemainingDays
,c.RenewalDate
,l.ContractCallLogStatusIdentifier as CallLogType
,Substring (l.Description, 1, 20) as CallPurpose
,l.db_insertDate as CallLogDate
,cms.CampaignOfferOID as OfferID
,cms.CampaignContractStatusIdentifier as OfferStatus
,cms.CampaignContractStatusUpdateDate as StatusChangeDate
,cms.DeclineDate
from Contract c
inner join contractstate cs on cs.contractoid = c.ContractOID
and cs.ContractStatusIdentifier in ('ERA', 'FLW')
and datediff(day, getdate(), c.RenewalDate) > 60
inner join SiteIdentification si on si.SiteOID = c.SiteOID
inner join MarketParticipant mp on mp.MarketParticipantOID = si.MarketParticipantOID
inner join Market m on m.MarketOID = mp.MarketOID
inner join Jurisdiction j on j.JurisdictionOID = m.JurisdictionOID
and j.CountryCode = 'CA'
and j.ProvinceOrStateCode = 'ON'
inner join ContractFlow cf on cf.ContractOID = c.ContractOID
inner join #CISIS_Call_Log l on convert(varchar(15), l.ContractOID) = c.RtlrContractIdentifier
inner join #CMS_Campaign cms on convert(varchar(15), cms.ContractOID) = c.RtlrContractIdentifier
set fmtonly on"
IF the data in each temp table is verified, then:
Try a smaller, less complex, query to test your temp tables with. Also try them using a LEFT join as well e.g.:
select
c.RtlrContractIdentifier as ContractID
, c.SigningDate
, datediff(day, getdate(), c.RenewalDate) as RemainingDays
, c.RenewalDate
, l.ContractCallLogStatusIdentifier as CallLogType
, Substring (l.Description, 1, 20) as CallPurpose
, l.db_insertDate as CallLogDate
, cms.CampaignOfferOID as OfferID
, cms.CampaignContractStatusIdentifier as OfferStatus
, cms.CampaignContractStatusUpdateDate as StatusChangeDate
, cms.DeclineDate
from Contract c
LEFT join #CISIS_Call_Log l on convert(varchar(15), l.ContractOID) = c.RtlrContractIdentifier
LEFT join #CMS_Campaign cms on convert(varchar(15), cms.ContractOID) = c.RtlrContractIdentifier
Does this return data? Does it return data from both joined tables?
If neither temp table is returning data then those join conditions need to be changed.
If both temp tables do return data from that query, then try INNER joins. If that still works, then add back more joins (one at a time) until you identify the join that causes the overall fault.
Without data for every table it just isn't possible for us to pinpoint the exact reason for a NULL result. Only you can, so you need to trouble-shoot the problem one step at a time.

Visual Studio database schema compare is very slow

We've been using Visual Studio 2017's SQL Server Schema Comparison for all our (SQL Server 2016) migrations and deployments.
However, recently, it has become very slow, taking hours to process. If we uncheck the "Tables" object, it goes quickly. But when tables are checked, it is stuck on "Initializing comparison..." for ages.
I've not been able to find anything online that has helped us. Any ideas?
Initializing comparison...
What seemed to work for us is that if you excluded tables (in Schema Compare Options --> Object types --> Application-scoped -- > Tables), it runs quickly.
After its runs initially, you can compare again with tables selected, and its fine.
With the exception of this, the Visual Studio database Schema Compare is an awesome tool.
You can use MSSQL Server Management Studio's own comparison or use one of these tools:
https://www.agile-code.com/blog/choose-your-sql-server-schema-comparison-tool/
I had very similar problem and I was able to find a solution.
Query starting with SELECT * FROM (SELECT SCHEMA_NAME([o].[schema_id]) AS [SchemaName], from [sys].[spatial_indexes] and other tables had suboptimal plan and was running for hours, causing VS to timeout and retry.
Plan guide included below solved the problem for me.
Because VS sends all queries in one batch during 1st attempt to get results plan guide will not kick in. After query times out, VS will retry this query in its own batch and plan guide will be applied.
To speed up the process even more and don't wait for time out you can kill VS session when it is stuck on this query and force retry.
EXEC sp_create_plan_guide
#name = N'VS Schema Comp Spatial Idxs',
#stmt = N'SELECT * FROM (
SELECT
SCHEMA_NAME([o].[schema_id]) AS [SchemaName],
[si].[object_id] AS [ColumnSourceId],
[o].[name] AS [ColumnSourceName],
[o].[type] AS [ColumnSourceType],
[ic].[column_id] AS [ColumnId],
[c].[name] AS [ColumnName],
[si].[index_id] AS [IndexId],
[si].[name] AS [IndexName],
[ds].[type] AS [DataspaceType],
[ds].[data_space_id] AS [DataspaceId],
[ds].[name] AS [DataspaceName],
[si].[fill_factor] AS [FillFactor],
[si].[is_padded] AS [IsPadded],
[si].[is_disabled] AS [IsDisabled],
[si].[allow_page_locks] AS [DoAllowPageLocks],
[si].[allow_row_locks] AS [DoAllowRowLocks],
[sit].[cells_per_object] AS [CellsPerObject],
[sit].[bounding_box_xmin] AS [XMin],
[sit].[bounding_box_xmax] AS [XMax],
[sit].[bounding_box_ymin] AS [YMin],
[sit].[bounding_box_ymax] AS [YMax],
[sit].[level_1_grid] AS [Level1Grid],
[sit].[level_2_grid] AS [Level2Grid],
[sit].[level_3_grid] AS [Level3Grid],
[sit].[level_4_grid] AS [Level4Grid],
[sit].[tessellation_scheme] AS [TessellationScheme],
[s].[no_recompute] AS [NoRecomputeStatistics],
[p].[data_compression] AS [DataCompressionId],
CONVERT(bit, CASE WHEN [ti].[data_space_id] = [ds].[data_space_id] THEN 1 ELSE 0 END)
AS [EqualsParentDataSpace]
FROM
[sys].[spatial_indexes] AS [si] WITH (NOLOCK)
INNER JOIN [sys].[objects] AS [o] WITH (NOLOCK) ON [si].[object_id] = [o].[object_id]
INNER JOIN [sys].[spatial_index_tessellations] [sit] WITH (NOLOCK) ON [si].[object_id] = [sit].[object_id] AND [si].[index_id] = [sit].[index_id]
INNER JOIN [sys].[data_spaces] AS [ds] WITH (NOLOCK) ON [ds].[data_space_id] = [si].[data_space_id]
INNER JOIN [sys].[index_columns] AS [ic] WITH (NOLOCK) ON [si].[object_id] = [ic].[object_id] AND [si].[index_id] = [ic].[index_id]
INNER JOIN [sys].[columns] AS [c] WITH (NOLOCK) ON [si].[object_id] = [c].[object_id] AND [ic].[column_id] = [c].[column_id]
INNER JOIN [sys].[objects] AS [o2] WITH (NOLOCK) ON [o2].[parent_object_id] = [si].[object_id]
INNER JOIN [sys].[stats] AS [s] WITH (NOLOCK) ON [o2].[object_id] = [s].[object_id] AND [s].[name] = [si].[name]
INNER JOIN [sys].[partitions] AS [p] WITH (NOLOCK) ON [p].[object_id] = [o2].[object_id] AND [p].[partition_number] = 1
LEFT JOIN [sys].[indexes] AS [ti] WITH (NOLOCK) ON [o].[object_id] = [ti].[object_id]
LEFT JOIN [sys].[tables] AS [t] WITH (NOLOCK) ON [t].[object_id] = [si].[object_id]
WHERE [si].[is_hypothetical] = 0
AND [ti].[index_id] < 2
AND OBJECTPROPERTY([o].[object_id], N''IsSystemTable'') = 0
AND ([t].[is_filetable] = 0 OR [t].[is_filetable] IS NULL)
AND ([o].[is_ms_shipped] = 0 AND NOT EXISTS (SELECT *
FROM [sys].[extended_properties]
WHERE [major_id] = [o].[object_id]
AND [minor_id] = 0
AND [class] = 1
AND [name] = N''microsoft_database_tools_support''
))
) AS [_results];
',
#type = N'SQL',
#module_or_batch = NULL,
#params = NULL,
#hints = N'OPTION (FORCE ORDER)';

Returning rows by users that aren't blocked sql server

I'm currently implementing blocking functionality into my web application. The user will have the option to block other users.
My current table set up is as follows:
CREATE TABLE [User].[Block_List]
(
[Id] [bigint] IDENTITY(1,1) NOT NULL,
[UserId] [bigint] NOT NULL, -- Person that I have blocked
[BlockedById] [bigint] NOT NULL, -- Person who requested the block
[DateBlocked] [datetime] NOT NULL,
[DateUnblocked] [datetime] NULL
)
Now inside my stored procedure I'm joining this table to filter out the people that I've blocked as shown here:
SELECT
p.Id [SenderId],
p.Username,
COUNT(mr.RecipientId) [TotalMessages],
COUNT(CASE WHEN mr.ReadDate IS NULL THEN 1 END) AS [NewMessages],
up.PhotoId,
p.LastLoggedIn,
p.LoggedIn,
MAX(m.SentDate)[EmailDate]
FROM
[User].[User_Profile] p
JOIN
[MailBox].[Message] m ON p.Id = m.SenderId
JOIN
[MailBox].[MessageRecipient] mr ON m.Id = mr.MessageId
LEFT JOIN
[User].[User_Photos] up ON p.Id = up.UserId AND up.isProfilePic = 1
LEFT JOIN
[User].[Block_List] b ON p.Id = b.UserId
WHERE
mr.RecipientId = 1 --and mr.DeletedDate is null
AND (b.BlockedById = 1 and p.Id != b.UserId) -- Checking blocked table
GROUP BY
p.id, p.Username, mr.RecipientId, up.PhotoId, p.LastLoggedIn, p.LoggedIn
I have hard coded the BlockedbyId to 1 as that's my current Id, unfortunately this doesn't return any rows when I would expect it to return at least one row, reason being I have two records and I've only blocked one of them.
When I execute the stored procedure above without the blocking where clause I get the following:
In my blocked table I have blocked the user with the Id of 2 as shown here:
When I add the blocking where clause back in I would expect to see the record for UserId 21 but unfortunately I don't get any records returned.
Any help would be great
The following part
LEFT JOIN [User].[Block_List] b on p.Id = b.UserId
WHERE mr.RecipientId = 1 --and mr.DeletedDate is null
AND (b.BlockedById = 1 and p.Id != b.UserId) -- Checking blocked table
should be replaced by
WHERE mr.RecipientId = 1
AND m.SenderId NOT IN (SELECT UserId FROM [User].[Block_List] WHERE BlockedById = 1)
Especially if you want to count I would be very careful when joining a table, because you might end up having too many combinations if not correctly filtered out afterwards, using the where clause when possible is safer in my opinion except if you really want to join and use the values.
You have some mutually exclusive conditions set here. Your join says
p.Id = b.UserId
However the where clause also says
p.Id != b.UserId
So when you combine them you will get no rows.

Need help implementing a Full Outer Join in MS Access

I'm having trouble getting a query to work properly in Access. I need a full outer join on dbo_cardpurchases and dbo_vendors so that all all vendors will appear in the query regardless of whether a purchase was made at that vendor. But Access doesn't support full outer joins. How else can I do this?
SELECT dbo_vendors.name,
Iif([fundingsourceid] = 10, [amount], "") AS Credit,
Iif(( [fundingsourceid] = 2 )
OR ( [fundingsourceid] = 3 ), [amount], "") AS EBT,
Iif([fundingsourceid] = 4, [amount], "") AS [Match],
dbo_cardpurchases.updateddate,
dbo_markets.marketid
FROM (((dbo_cardpurchases
LEFT JOIN dbo_vendors
ON dbo_cardpurchases.vendorid = dbo_vendors.vendorid)
LEFT JOIN dbo_cardfundings
ON dbo_cardpurchases.cardfundingid =
dbo_cardfundings.cardfundingid)
INNER JOIN dbo_marketevents
ON dbo_cardpurchases.marketeventid =
dbo_marketevents.marketeventid)
INNER JOIN dbo_markets
ON dbo_marketevents.marketid = dbo_markets.marketid
ORDER BY dbo_vendors.name;
As mentioned in the Wikipedia article on joins here, for sample tables
[employee]
LastName DepartmentID
---------- ------------
Heisenberg 33
Jones 33
Rafferty 31
Robinson 34
Smith 34
Williams NULL
and [department]
DepartmentID DepartmentName
------------ --------------
31 Sales
33 Engineering
34 Clerical
35 Marketing
the full outer join
SELECT *
FROM employee FULL OUTER JOIN department
ON employee.DepartmentID = department.DepartmentID;
can be emulated using a UNION ALL of three SELECT statements. So, in Access you could do
SELECT dbo_employee.LastName, dbo_employee.DepartmentID,
dbo_department.DepartmentName, dbo_department.DepartmentID
FROM dbo_employee
INNER JOIN dbo_department ON dbo_employee.DepartmentID = dbo_department.DepartmentID
UNION ALL
SELECT dbo_employee.LastName, dbo_employee.DepartmentID,
NULL, NULL
FROM dbo_employee
WHERE NOT EXISTS (
SELECT * FROM dbo_department
WHERE dbo_employee.DepartmentID = dbo_department.DepartmentID)
UNION ALL
SELECT NULL, NULL,
dbo_department.DepartmentName, dbo_department.DepartmentID
FROM dbo_department
WHERE NOT EXISTS (
SELECT * FROM dbo_employee
WHERE dbo_employee.DepartmentID = dbo_department.DepartmentID)
However, since you are using linked tables into SQL Server you can just use an Access pass-through query and perform a "real" FULL OUTER JOIN using T-SQL:
Pass-through queries always produce recordsets that are not updateable, but a native Access query against linked tables that uses UNION ALL is going to produce a recordset that is not updatable anyway, so why not take advantage of the speed and simplicity of just using SQL Server to run the query?

How do I monitor and find unused indexes in SQL database

I would like to monitor index usage for an SQL database in order to find unused indexes and then drop them. How can I monitor index usage most efficiently? Which scripts could be useful?
I'm aware of this question about identifying unused objects, but this applies only to the current run of the SQL server. I would like to monitor index usage over a period of time.
This is an interesting question. I've been working on this same question over the past week. There is a system table called dm_db_index_usage_stats that contains usage statistics on indexes.
Indexes That Never Appear in the Usage Statistics Table
However, many indexes never appear in this table at all. The query David Andres posted lists all indexes for this case. I've updated it a little bit to ignore primary keys, which probably shouldn't be deleted, even if they aren't ever used. I also joined on the dm_db_index_physical_stats table to get other information, including Page Count, Total Index Size, and the Fragmentation Percentage. An interesting note is that indexes that are returned by this query don't seem to show up in the SQL Report for Index Usage Statistics.
DECLARE #dbid INT
SELECT #dbid = DB_ID(DB_NAME())
SELECT Databases.Name AS [Database],
Objects.NAME AS [Table],
Indexes.NAME AS [Index],
Indexes.INDEX_ID,
PhysicalStats.page_count as [Page Count],
CONVERT(decimal(18,2), PhysicalStats.page_count * 8 / 1024.0) AS [Total Index Size (MB)],
CONVERT(decimal(18,2), PhysicalStats.avg_fragmentation_in_percent) AS [Fragmentation (%)]
FROM SYS.INDEXES Indexes
INNER JOIN SYS.OBJECTS Objects ON Indexes.OBJECT_ID = Objects.OBJECT_ID
LEFT JOIN sys.dm_db_index_physical_stats(#dbid, null, null, null, null) PhysicalStats
on PhysicalStats.object_id = Indexes.object_id and PhysicalStats.index_id = indexes.index_id
INNER JOIN sys.databases Databases
ON Databases.database_id = PhysicalStats.database_id
WHERE OBJECTPROPERTY(Objects.OBJECT_ID,'IsUserTable') = 1
AND Indexes.type = 2 -- Nonclustered indexes
AND Indexes.INDEX_ID NOT IN (
SELECT UsageStats.INDEX_ID
FROM SYS.DM_DB_INDEX_USAGE_STATS UsageStats
WHERE UsageStats.OBJECT_ID = Indexes.OBJECT_ID
AND Indexes.INDEX_ID = UsageStats.INDEX_ID
AND DATABASE_ID = #dbid)
ORDER BY PhysicalStats.page_count DESC,
Objects.NAME,
Indexes.INDEX_ID,
Indexes.NAME ASC
Indexes That Do Appear in the Usage Statistics Table, But Are Never Used
There are other indexes that do appear in the dm_db_index_usage_stats table, but which have never been used for user seeks, scans, or lookups. This query will identify indexes that fall into this category. Incidentally, unlike the indexes returned from the other query, the indexes returned in this query can be verified on the SQL Report by Index Usage Statistics.
I added a Minimum Page Count that allows me to initially focus on and remove unused indexes that are taking up a lot of storage.
DECLARE #MinimumPageCount int
SET #MinimumPageCount = 500
SELECT Databases.name AS [Database],
Indexes.name AS [Index],
Objects.Name AS [Table],
PhysicalStats.page_count as [Page Count],
CONVERT(decimal(18,2), PhysicalStats.page_count * 8 / 1024.0) AS [Total Index Size (MB)],
CONVERT(decimal(18,2), PhysicalStats.avg_fragmentation_in_percent) AS [Fragmentation (%)],
ParititionStats.row_count AS [Row Count],
CONVERT(decimal(18,2), (PhysicalStats.page_count * 8.0 * 1024) / ParititionStats.row_count) AS [Index Size/Row (Bytes)]
FROM sys.dm_db_index_usage_stats UsageStats
INNER JOIN sys.indexes Indexes
ON Indexes.index_id = UsageStats.index_id
AND Indexes.object_id = UsageStats.object_id
INNER JOIN sys.objects Objects
ON Objects.object_id = UsageStats.object_id
INNER JOIN SYS.databases Databases
ON Databases.database_id = UsageStats.database_id
INNER JOIN sys.dm_db_index_physical_stats (DB_ID(), NULL, NULL, NULL, NULL) AS PhysicalStats
ON PhysicalStats.index_id = UsageStats.Index_id
and PhysicalStats.object_id = UsageStats.object_id
INNER JOIN SYS.dm_db_partition_stats ParititionStats
ON ParititionStats.index_id = UsageStats.index_id
and ParititionStats.object_id = UsageStats.object_id
WHERE UsageStats.user_scans = 0
AND UsageStats.user_seeks = 0
AND UsageStats.user_lookups = 0
AND PhysicalStats.page_count > #MinimumPageCount -- ignore indexes with less than 500 pages of memory
AND Indexes.type_desc != 'CLUSTERED' -- Exclude primary keys, which should not be removed
ORDER BY [Page Count] DESC
I hope this helps.
Final Thought
Of course, once indexes are identified as candidates for removal, careful consideration should still be employed to make sure it's a good decision to do so.
For more information, see Identifying Unused Indexes in a SQL Server Database
Currently (as of SQL Server 2005 - 2008) the SQL index stats information is only kept in memory and so you have to do some of the work yourself if you would like to have that persisted across restarts and database detaches.
What I usually do, is I create a job that runs every day and takes a snapshot of the information found in the sys.dm_db_index_usage_stats table, into a custom table that I create for the database in question.
This seems to work pretty well until a future version of SQL which will support persistent index usage stats.
Pulled this puppy off of http://blog.sqlauthority.com/2008/02/11/sql-server-2005-find-unused-indexes-of-current-database/. Note that this works for 2005 and above. The key is the JOIN to the SYS.DM_DB_INDEX_USAGE_STATS system table.
USE AdventureWorks
GO
DECLARE #dbid INT
SELECT #dbid = DB_ID(DB_NAME())
SELECT OBJECTNAME = OBJECT_NAME(I.OBJECT_ID),
INDEXNAME = I.NAME,
I.INDEX_ID
FROM SYS.INDEXES I
JOIN SYS.OBJECTS O ON I.OBJECT_ID = O.OBJECT_ID
WHERE OBJECTPROPERTY(O.OBJECT_ID,'IsUserTable') = 1
AND I.INDEX_ID NOT IN (
SELECT S.INDEX_ID
FROM SYS.DM_DB_INDEX_USAGE_STATS S
WHERE S.OBJECT_ID = I.OBJECT_ID
AND I.INDEX_ID = S.INDEX_ID
AND DATABASE_ID = #dbid)
ORDER BY OBJECTNAME,
I.INDEX_ID,
INDEXNAME ASC
GO
I tweaked John Pasquet's queries here: Identifying Unused Indexes in a SQL Server Database to return indexes used 10 or less times, unioned the results that aren't in the usage stats tables, exclude heap indexes and unique constraints or primary key indexes, and finally to exclude indexes with zero pages.
Be careful with the results of this query – it's best to use in production where indexes are actually getting used the way you would expect. If you query on a database with rebuilt or dropped/recreated indexes or on a recent database backup you could get false positives (indexes that normally would get used but aren't because of special circumstances). Not safe to use in test or dev environments to decide whether to drop indexes. As Narnian says, this query just identifies candidates for removal for your careful consideration.
USE [DatabaseName]
DECLARE #MinimumPageCount int
SET #MinimumPageCount = 500
DECLARE #dbid INT
SELECT #dbid = DB_ID(DB_NAME())
-- GET UNUSED INDEXES THAT APPEAR IN THE INDEX USAGE STATS TABLE
SELECT
Databases.name AS [Database]
,object_name(Indexes.object_id) AS [Table]
,Indexes.name AS [Index]
,PhysicalStats.page_count as [Page Count]
,CONVERT(decimal(18,2), PhysicalStats.page_count * 8 / 1024.0) AS [Total Index Size (MB)]
,CONVERT(decimal(18,2), PhysicalStats.avg_fragmentation_in_percent) AS [Fragmentation (%)]
,ParititionStats.row_count AS [Row Count]
,CONVERT(decimal(18,2), (PhysicalStats.page_count * 8.0 * 1024) / ParititionStats.row_count) AS [Index Size Per Row (Bytes)]
,1 AS [Appears In Usage Stats Table]
FROM sys.dm_db_index_usage_stats UsageStats
INNER JOIN sys.indexes Indexes
ON Indexes.index_id = UsageStats.index_id AND Indexes.object_id = UsageStats.object_id
INNER JOIN SYS.databases Databases
ON Databases.database_id = UsageStats.database_id
INNER JOIN sys.dm_db_index_physical_stats (DB_ID(),NULL,NULL,NULL,NULL) AS PhysicalStats
ON PhysicalStats.index_id = UsageStats.Index_id AND PhysicalStats.object_id = UsageStats.object_id
INNER JOIN SYS.dm_db_partition_stats ParititionStats
ON ParititionStats.index_id = UsageStats.index_id AND ParititionStats.object_id = UsageStats.object_id
WHERE
UsageStats.user_scans <= 10
AND UsageStats.user_seeks <= 10
AND UsageStats.user_lookups <= 10
-- exclude heap indexes
AND Indexes.name IS NOT NULL
-- ignore indexes with less than a certain number of pages of memory
AND PhysicalStats.page_count > #MinimumPageCount
-- Exclude primary keys, which should not be removed
AND Indexes.is_primary_key = 0
-- ignore unique constraints - those shouldn't be removed
AND Indexes.is_unique_constraint = 0
AND Indexes.is_unique = 0
UNION ALL
(
-- GET UNUSED INDEXES THAT DO **NOT** APPEAR IN THE INDEX USAGE STATS TABLE
SELECT
Databases.Name AS [Database]
,Objects.NAME AS [Table]
,Indexes.NAME AS [Index]
,PhysicalStats.page_count as [Page Count]
,CONVERT(decimal(18,2), PhysicalStats.page_count * 8 / 1024.0) AS [Total Index Size (MB)]
,CONVERT(decimal(18,2), PhysicalStats.avg_fragmentation_in_percent) AS [Fragmentation (%)]
,-1 AS [Row Count]
,-1 AS [Index Size Per Row (Bytes)]
,0 AS [Appears In Usage Stats Table]
FROM SYS.INDEXES Indexes
INNER JOIN SYS.OBJECTS Objects
ON Indexes.OBJECT_ID = Objects.OBJECT_ID
LEFT JOIN sys.dm_db_index_physical_stats(#dbid, null, null, null, null) PhysicalStats
ON PhysicalStats.object_id = Indexes.object_id AND PhysicalStats.index_id = indexes.index_id
INNER JOIN sys.databases Databases
ON Databases.database_id = PhysicalStats.database_id
WHERE
Objects.type = 'U' -- Is User Table
-- exclude heap indexes
AND Indexes.name IS NOT NULL
-- exclude empty tables
AND PhysicalStats.page_count <> 0
-- Exclude primary keys, which should not be removed
AND Indexes.is_primary_key = 0
-- ignore unique constraints - those shouldn't be removed
AND Indexes.is_unique_constraint = 0
AND Indexes.is_unique = 0
AND Indexes.INDEX_ID NOT IN
(
SELECT UsageStats.INDEX_ID
FROM SYS.DM_DB_INDEX_USAGE_STATS UsageStats
WHERE
UsageStats.OBJECT_ID = Indexes.OBJECT_ID
AND Indexes.INDEX_ID = UsageStats.INDEX_ID
AND DATABASE_ID = #dbid
)
)
ORDER BY [Table] ASC, [Total Index Size (MB)] DESC
You should take a look at Brent Ozars sp_BlitzIndex. This stored procedure lists among others unsused indexes. It lists the disorders in a report. For each entry an URL is given which explains what to look for and how to handle the issue.

Resources