Related
I have a little issue with the ORDER BY statement.
Here is my query (sorry I know is a little bit long, but I alredy cut a lot of the code)
(SELECT AssAttrezzi.ID, AssBombole.matricola, CONCAT(AssBombole.matricola, ' - ', Bombole.nome) AS Descrizione
FROM AssAttrezzi INNER JOIN
AssBombole
ON AssBombole.matricola = AssAttrezzi.attrezzoID INNER JOIN
Bombole
ON Bombole.ID = AssBombole.tipoBombolaID
WHERE AssAttrezzi.cantiereID=1
) UNION
(SELECT AssAttrezzi.ID, Saldatrici.matricola,CONCAT(Saldatrici.matricola, ' - ', Saldatrici.nome) AS Descrizione
FROM AssAttrezzi INNER JOIN
Saldatrici
ON Saldatrici.matricola = AssAttrezzi.attrezzoID
WHERE AssAttrezzi.cantiereID = 1
) UNION
(SELECT AssAttrezzi.ID, AssBanchiGlad.idAtrezzo, CONCAT(AssBanchiGlad.idAtrezzo, ' - ', BanchiGladiator.nome) AS Descrizione
FROM AssAttrezzi INNER JOIN
AssBanchiGlad
ON AssBanchiGlad.idAtrezzo = AssAttrezzi.attrezzoID INNER JOIN
BanchiGladiator
ON BanchiGladiator.ID = AssBanchiGlad.bancoID
WHERE AssAttrezzi.cantiereID = 1
) ORDER BY LEN(matricola), matricola
The problem is when I put ORDER BY at the end of each SELECT It gives me error:
Incorrect syntax near UNION
But when I try to put the ORDER BY at the end of all it gives me another error:
If the ORDER BY statement includes the UNION, INTERSECT or EXCEPT operator, the elements of the instruction must be specified in the selection list.
What? I would like to order by *.matricola basically matricola for me is the important one.
Try this:
(
SELECT AssAttrezzi.ID,
AssBombole.matricola,
CONCAT(AssBombole.matricola, ' - ', Bombole.nome) AS Descrizione,
LEN(AssBombole.matricola) AS matricola_len
FROM AssAttrezzi
INNER JOIN AssBombole ON AssBombole.matricola = AssAttrezzi.attrezzoID
INNER JOIN Bombole ON Bombole.ID = AssBombole.tipoBombolaID
WHERE AssAttrezzi.cantiereID=1
)
UNION
(
SELECT AssAttrezzi.ID,
Saldatrici.matricola,
CONCAT(Saldatrici.matricola, ' - ', Saldatrici.nome) AS Descrizione,
LEN(Saldatrici.matricola) AS matricola_len
FROM AssAttrezzi
INNER JOIN Saldatrici ON Saldatrici.matricola = AssAttrezzi.attrezzoID
WHERE AssAttrezzi.cantiereID = 1
)
UNION
(
SELECT AssAttrezzi.ID,
AssBanchiGlad.idAtrezzo,
CONCAT(AssBanchiGlad.idAtrezzo, ' - ', BanchiGladiator.nome) AS Descrizione,
LEN(AssBanchiGlad.idAtrezzo) AS matricola_len
FROM AssAttrezzi
INNER JOIN AssBanchiGlad ON AssBanchiGlad.idAtrezzo = AssAttrezzi.attrezzoID
INNER JOIN BanchiGladiator ON BanchiGladiator.ID = AssBanchiGlad.bancoID
WHERE AssAttrezzi.cantiereID = 1
)
ORDER BY LEN(matricola),
matricola
You can use custom sort :
SELECT . . . , 1 AS ID
UNION
SELECT . . . , 2
UNION
SELECT . . . , 3
ORDER BY ID;
You can't add separate order by clause when you use UNION/UNION ALL
EDIT :
SELECT AssAttrezzi.ID, AssBombole.matricola,
CONCAT(AssBombole.matricola, ' - ', Bombole.nome) AS Descrizione,
LEN(matricola) AS SortLen
FROM AssAttrezzi INNER JOIN
AssBombole
ON AssBombole.matricola = AssAttrezzi.attrezzoID INNER JOIN
Bombole
ON Bombole.ID = AssBombole.tipoBombolaID
WHERE AssAttrezzi.cantiereID = 1
UNION
SELECT AssAttrezzi.ID, Saldatrici.matricola,
CONCAT(Saldatrici.matricola, ' - ', Saldatrici.nome) AS Descrizione,
LEN(matricola)
FROM AssAttrezzi INNER JOIN
Saldatrici
ON Saldatrici.matricola = AssAttrezzi.attrezzoID
WHERE AssAttrezzi.cantiereID = 1
UNION
SELECT AssAttrezzi.ID, AssBanchiGlad.idAtrezzo,
CONCAT(AssBanchiGlad.idAtrezzo, ' - ', BanchiGladiator.nome) AS Descrizione,
LEN(matricola)
FROM AssAttrezzi INNER JOIN
AssBanchiGlad
ON AssBanchiGlad.idAtrezzo = AssAttrezzi.attrezzoID INNER JOIN
BanchiGladiator
ON BanchiGladiator.ID = AssBanchiGlad.bancoID
WHERE AssAttrezzi.cantiereID = 1
ORDER BY SortLen, matricola;
I have the following complex query which builds a list of columns dynamically before passing them to a second query (with a pivot) and executing it.
I have produced a report in Visual Studio that passes a parameter (#Event) for the user to select an event they want a report to run on. In order for the #Event to be in scope with the #SQL statement it is concatenated as
'+ #Event +'
however when I then run the statement I get a conversion failed error
Conversion failed when converting the varchar value
if I hardcode the values for #Event the query runs correctly.
What have I done wrong that wont let this run?
thanks
DECLARE #sql AS varchar(max)
DECLARE #pivot_list AS varchar(max)
DECLARE #select_list AS varchar(max)
SELECT #pivot_list = COALESCE(#pivot_list + ', ', '') + '[' + CONVERT(varchar, PIVOT_CODE) + ']'
,#select_list = COALESCE(#select_list + ', ', '') + '[' + CONVERT(varchar, PIVOT_CODE) + '] AS [' + CONVERT(varchar, PIVOT_NAME) + ']'
FROM (
SELECT DISTINCT PIVOT_CODE, PIVOT_NAME
FROM (
SELECT s.SESSION_REF AS PIVOT_CODE, s.name as PIVOT_NAME
FROM DELEGATE as d
INNER JOIN DELEGATE_SESSION as ds on d.DELEGATE_REF=ds.DELEGATE_REF
INNER JOIN SESSION as s on ds.SESSION_REF=s.SESSION_REF
where ds.NO_DELEGATES=1 and d.EVENT_REF=#Event
) as rows
) AS PIVOT_CODES
SET #sql = '
;WITH p AS (
select d.DELEGATE_REF as REF, s.SESSION_REF AS PIVOT_CODE, d.name, ds.NO_DELEGATES
, o.COMPANY_NAME as ''Org'', l7.LOOKUP_FULL_DESC as ''Org_Member'', l8.LOOKUP_FULL_DESC as ''Org_Type''
, e.NAME as ''Event'', CAST(d.code AS INTEGER) as Delegate_No, d.member_ref as ''Record_No''
, i.SURNAME, i.FORENAMES, l6.LOOKUP_FULL_DESC as ''Status'', l4.LOOKUP_FULL_DESC as ''Membership'', i_fee.LABEL_NAME as ''Feepayer''
, CAST(d.COMMENT AS NVARCHAR(100)) as ''Comments'', l1.LOOKUP_FULL_DESC as ''Delegate_Type''
, e1.EMAIL_ADDRESS as ''Email''
--, CASE WHEN e1.type = 1236 then e1.EMAIL_ADDRESS WHEN e2.type = 1240 then e2.EMAIL_ADDRESS WHEN e3.type = 1197 then e3.EMAIL_ADDRESS ELSE NULL END as ''Email''
, d.mailing_different, l.ADDRESS1 as ''Mail1'', l.ADDRESS2 as ''Mail2'', l.ADDRESS3 as ''Mail3'', l.TOWN as ''Mail4'', l.POSTCODE as ''Mail5'', l.COUNTRY as ''Mail6''
, d.invoice_different, l2.ADDRESS1 as ''Inv1'', l2.ADDRESS2 as ''Inv2'', l2.ADDRESS3 as ''Inv3'', l2.TOWN as ''Inv4'', l2.POSTCODE as ''Inv5'', l2.COUNTRY as ''Inv6''
, d.TOTAL_AMOUNT, l5.LOOKUP_FULL_DESC as ''Pay''
, CAST(dq1.comments AS NVARCHAR(100)) as ''Q_B_Name'', CAST(dq2.comments AS NVARCHAR(100)) as ''Q_B_Inst'', a.DESCRIPTION as ''Q_Food'', CAST(dq3.comments AS NVARCHAR(100)) as ''Q_Food_Comment'', a2.DESCRIPTION as ''Q_Special'', CAST(dq4.comments AS NVARCHAR(100)) as ''Q_Special_Comment'', CAST(dq5.comments AS NVARCHAR(100)) as ''Q_Twitter'', CAST(dq6.comments AS NVARCHAR(100)) as ''Q_Number'', CAST(dq7.comments AS NVARCHAR(100)) as ''Q_School''
, l3.LOOKUP_FULL_DESC as ''Delegate_Status'', ev.DESCRIPTION as ''Session_Rate'', ev2.DESCRIPTION as ''Rate''
FROM DELEGATE as d
INNER JOIN EVENT as e on d.EVENT_REF=e.EVENT_REF
INNER JOIN DELEGATE_SESSION as ds on d.DELEGATE_REF=ds.DELEGATE_REF
INNER JOIN EVENT_RATE as ev on ds.EVENT_RATE_REF=ev.EVENT_RATE_REF
INNER JOIN SESSION as s on ds.SESSION_REF=s.SESSION_REF
INNER JOIN DELEGATE_SESSION as ds2 on d.DELEGATE_REF=ds2.DELEGATE_REF
INNER JOIN EVENT_RATE as ev2 on ds2.EVENT_RATE_REF=ev2.EVENT_RATE_REF
INNER JOIN EVENT as event2 on ev2.EVENT_REF=event2.EVENT_REF
LEFT JOIN LOCATION as l on d.MAILING_LOCATION=l.LOCATION_REF
LEFT JOIN LOCATION as l2 on d.INVOICE_LOCATION=l2.LOCATION_REF
INNER JOIN MEMBER as m on d.MEMBER_REF=m.MEMBER_REF
LEFT JOIN INDIVIDUAL as i on m.INDIVIDUAL_REF=i.INDIVIDUAL_REF
LEFT JOIN CONTACT as c on i.INDIVIDUAL_REF=c.INDIVIDUAL_REF and c.MAIN_ORGANISATION=''Y''
LEFT JOIN ORGANISATION as o on c.ORGANISATION_REF=o.ORGANISATION_REF and c.MAIN_ORGANISATION=''Y''
LEFT JOIN MEMBER as m2 on o.ORGANISATION_REF=m2.ORGANISATION_REF
LEFT JOIN LOOKUP as l7 on m2.MEMBER_STATUS=l7.LOOKUP_REF
LEFT JOIN ATTRIBUTE as at3 on o.ORGANISATION_REF=at3.ORGANISATION_REF and at3.CODE_TYPE=206 --School type
LEFT JOIN LOOKUP as l8 on at3.ATTR_CODE_REF=l8.LOOKUP_REF
LEFT JOIN LOOKUP as l1 on d.TYPE=l1.LOOKUP_REF
LEFT JOIN LOOKUP as l3 on d.STATUS=l3.LOOKUP_REF
LEFT JOIN LOOKUP as l6 on m.MEMBER_STATUS=l6.LOOKUP_REF
LEFT JOIN LOOKUP as l4 on m.MEMBER_CLASS=l4.LOOKUP_REF
LEFT JOIN LOOKUP as l5 on d.PAY_METHOD=l5.LOOKUP_REF
LEFT JOIN INDIVIDUAL as i_fee on d.FEEPAYING_MEMBER=i_fee.INDIVIDUAL_REF
--LEFT JOIN EMAIL as e1 on d.INDIVIDUAL_REF=e1.INDIVIDUAL_REF and (e1.MAIN_EMAIL=''Y'' and (e1.type=1236))
--LEFT JOIN EMAIL as e2 on d.INDIVIDUAL_REF=e2.INDIVIDUAL_REF and (e2.MAIN_EMAIL=''Y'' and (e2.type=1240))
--LEFT JOIN EMAIL as e3 on d.INDIVIDUAL_REF=e3.INDIVIDUAL_REF and (e3.MAIN_EMAIL=''Y'' and (e3.type=1197))
--LEFT JOIN EMAIL as e4 on d.INDIVIDUAL_REF=e3.INDIVIDUAL_REF and (e3.MAIN_EMAIL=''Y'' and (e3.type=2976))
LEFT JOIN EMAIL as e1 on i.INDIVIDUAL_REF=e1.INDIVIDUAL_REF and e1.MAIN_EMAIL=''Y'' and (e1.type NOT IN (2232,1241,1242,1106,3220,2612))
LEFT JOIN DELEGATE_QUESTION as dq1 on d.DELEGATE_REF=dq1.DELEGATE_REF and dq1.question=2054
LEFT JOIN DELEGATE_QUESTION as dq2 on d.DELEGATE_REF=dq2.DELEGATE_REF and dq2.question=2055
LEFT JOIN DELEGATE_QUESTION as dq3 on d.DELEGATE_REF=dq3.DELEGATE_REF and dq3.question=1620
LEFT JOIN DELEGATE_QUESTION as dq4 on d.DELEGATE_REF=dq4.DELEGATE_REF and dq4.question=1621
LEFT JOIN DELEGATE_QUESTION as dq5 on d.DELEGATE_REF=dq5.DELEGATE_REF and dq5.question=2626
LEFT JOIN DELEGATE_QUESTION as dq6 on d.DELEGATE_REF=dq6.DELEGATE_REF and dq6.question=3155
LEFT JOIN DELEGATE_QUESTION as dq7 on d.DELEGATE_REF=dq7.DELEGATE_REF and dq7.question=2979
LEFT JOIN ANSWER as a on dq3.ANSWER_REF=a.ANSWER_REF
LEFT JOIN ANSWER as a2 on dq4.ANSWER_REF=a2.ANSWER_REF
where ds.NO_DELEGATES=1 and d.EVENT_REF='+ #Event +'
)
SELECT event, delegate_no, record_no, Org, Org_Member, Org_Type, name, surname,forenames,status,membership,feepayer
, comments, Delegate_Type
, Email
, mailing_different, Mail1, Mail2, Mail3, Mail4, Mail5, Mail6, invoice_different, Inv1, Inv2, Inv3, Inv4, Inv5, Inv6
, total_amount, pay
, Q_B_Name, Q_B_Inst, Q_Food, Q_Food_Comment, Q_Special, Q_Special_Comment, Q_Twitter, Q_Number, Q_School, Delegate_Status, Rate
, ' + #select_list + '
FROM p
PIVOT (
MAX(Session_Rate)
FOR PIVOT_CODE IN (
' + #pivot_list + '
)
) AS pvt
'
EXEC (#sql)
Don't concatenate your values, use parametrised SQL. Concatenating like that is bad, as it leaves you open to injection.
Change your WHERE clause to d.EVENT_REF= #dEvent and then change your EXEC to:
EXEC sp_executesql #SQL, N'#dEvent int', #dEvent = #Event;
Note, I have guess yoru datatype, as I can't see a DECLARE in your provided SQL.
Also, instead of using code like '[' + CONVERT(varchar, PIVOT_CODE) + ']' use QUOTENAME(PIVOT_CODE). That'll cope with what ever value is passed far better than the former. For example, if someone (silly enough) ever creates an object with a ] in the name, then '[' + CONVERT(varchar, PIVOT_CODE) + ']' would fail.
Helpful tip: When writing Dyanmic SQL, format it as well. it'll make things far easier to debug. Just because your SQL is dynamic doesn't mean you should forget simple things like good use of whitespace and linebreaks.
I am using the following query to inner join my two tables.
select a.*
from AllUK a
join AllCompanies b
on replace(a.Address1 + a.Postcode,' ','') = replace(b.street + b.Postcode,' ','')
I want to return the rest of the records in the AllUK table, which is not returned by the inner join.
try this......
select a.*
from AllUK a
left join AllCompanies b
on replace(a.Address1 + a.Postcode,' ','') = replace(b.street + b.Postcode,' ','')
where replace(b.street + b.Postcode,' ','') is null
This is pretty much the definition of the EXCEPT action:
select a.*
from AllUK a
EXCEPT
select a.*
from AllUK a
join AllCompanies b
on replace(a.Address1 + a.Postcode,' ','') = replace(b.street + b.Postcode,' ','')
I like to stick to IN and NOT IN commands to make it simple to read.
Select a.*
from AllUk a
where a.ID not in (
select a.id
from AllUK a
join AllCompanies b
on replace(a.Address1 + a.Postcode,' ','') = replace(b.street + b.Postcode,' ',''))
My set up is as thus: I have three tables-
Students (StudentID, FirstName, LastName etc.),
StudentSemesters(StudentID,SemID etc.), and
Semesters(SemID, Year)
My requirement is to, get the details for each student but only for their last semester. Logically this implies the semester with the highest year number. I cannot seem to get the Query right. For simplicity 'Year' is just an integer (e.g. 2000, 1998). Below is the current query that I have been stuck at for some time:
SELECT dbo.Student.LastName + ' , ' + dbo.Student.FirstName AS Student, dbo.Student.Defence1Date, dbo.Student.Defence2Date, COUNT(StudentSemesters_1.SemID)
AS SemesterCount, dbo.Student.EntrySemester + ' - ' +
(SELECT dbo.StudentSemesters.SemID
FROM dbo.StudentSemesters INNER JOIN
dbo.ListSemesters ON dbo.StudentSemesters.SemID = dbo.ListSemesters.SemID
WHERE (dbo.Student.StudentCode = dbo.StudentSemesters.StudentCode)
GROUP BY dbo.StudentSemesters.SemID, dbo.ListSemesters.Year
HAVING (dbo.ListSemesters.Year = MAX(dbo.ListSemesters.Year))) AS Expr1
FROM dbo.Student INNER JOIN
dbo.StudentSemesters AS StudentSemesters_1 ON dbo.Student.StudentCode = StudentSemesters_1.StudentCode
GROUP BY dbo.Student.LastName, dbo.Student.FirstName, dbo.Student.Defence1Date, dbo.Student.Defence2Date, dbo.Student.EntrySemester,
dbo.Student.StudentCode
You can do this to get the details for only the last semester for each student:
SELECT
s.LastName + ' , ' + s.FirstName AS Student,
s.Defence1Date,
s.EntrySemester + ' - ' + s1.SemId AS Expr1
FROM dbo.Student AS s
INNER JOIN dbo.StudentSemesters AS S1 ON s.StudentCode = S1.StudentCode
INNER JOIN dbo.ListSemesters AS lm ON lm.SemID = s1.SemId
INNER JOIN
(
SELECT SemId, MAX(Year) AS MaxYear
FROM dbo.StudentSemesters
GROUP BY SemId
) AS s2 ON s2.semId = lm.SemId AND ls.Year = s2.MaxYear;
However, if you're using SQL Server 2005+, you can use the window function to do so:
WITH CTE
AS
(
SELECT
s.LastName + ' , ' + s.FirstName AS Student,
s.Defence1Date,
s.EntrySemester + ' - ' + s1.SemId AS Expr1,
ROW_NUMBER() OVER(PARTITION BY s1.SemId
ORDER BY Year DESC) AS RowNumber
FROM dbo.Student AS s
INNER JOIN dbo.StudentSemesters AS S1 ON s.StudentCode = S1.StudentCode
INNER JOIN dbo.ListSemesters AS lm ON lm.SemID = s1.SemId
)
SELECT
Student,
Defence1Date,
Expr1,
FROM CTE
WHERE RN = 1;
But this won't get the SemesterCount, but you might be able to make it like this (this is just a guess):
SELECT
s.LastName + ' , ' + s.FirstName AS Student,
s.Defence1Date,
s1.SemCount,
s.EntrySemester + ' - ' + s1.SemId AS Expr1
FROM dbo.Student AS s
INNER JOIN
(
SELECT StudentCode, COUNT(SemId) AS SemCount
FROM dbo.StudentSemesters
GROUP BY StudentCode
) AS S1 ON s.StudentCode = S1.StudentCode
INNER JOIN dbo.ListSemesters AS lm ON lm.SemID = s1.SemId
INNER JOIN
(
SELECT SemId, MAX(Year) AS MaxYear
FROM dbo.StudentSemesters
GROUP BY SemId
) AS s2 ON s2.semId = lm.SemId AND ls.Year = s2.MaxYear;
Is anyone aware of a T-SQL script that can detect redundant indexes across an entire database? An example of a redundant index in a table would be as follows:
Index 1: 'ColumnA', 'ColumnB', 'ColumnC'
Index 2: 'ColumnA', 'ColumnB'
Ignoring other considerations, such as the width of columns and covering indexes, Index 2 would be redundant.
Thanks.
There are situations where the redundancy doesn't hold. For example, say ColumnC was a huuge field, but you'd sometimes have to retrieve it quickly. Your index 1 would not require a key lookup for:
select ColumnC from YourTable where ColumnnA = 12
On the other hand index 2 is much smaller, so it can be read in memory for queries that require an index scan:
select * from YourTable where ColumnnA like '%hello%'
So they're not really redundant.
If you're not convinced by my above argument, you can find "redundant" indexes like:
;with ind as (
select a.object_id
, a.index_id
, cast(col_list.list as varchar(max)) as list
from (
select distinct object_id
, index_id
from sys.index_columns
) a
cross apply
(
select cast(column_id as varchar(16)) + ',' as [text()]
from sys.index_columns b
where a.object_id = b.object_id
and a.index_id = b.index_id
for xml path(''), type
) col_list (list)
)
select object_name(a.object_id) as TableName
, asi.name as FatherIndex
, bsi.name as RedundantIndex
from ind a
join sys.sysindexes asi
on asi.id = a.object_id
and asi.indid = a.index_id
join ind b
on a.object_id = b.object_id
and a.object_id = b.object_id
and len(a.list) > len(b.list)
and left(a.list, LEN(b.list)) = b.list
join sys.sysindexes bsi
on bsi.id = b.object_id
and bsi.indid = b.index_id
Bring cake for your users in case performance decreases "unexpectedly" :-)
Inspired by Paul Nielsen, I wrote this query to find/distinguish:
Duplicates (ignoring include order)
Redundant (different include columns)
Overlapping (different index columns)
And also record their usage
(One might also want to use is_descending_key, but I don't need it.)
WITH IndexColumns AS
(
SELECT I.object_id AS TableObjectId, OBJECT_SCHEMA_NAME(I.object_id) + '.' + OBJECT_NAME(I.object_id) AS TableName, I.index_id AS IndexId, I.name AS IndexName
, (IndexUsage.user_seeks + IndexUsage.user_scans + IndexUsage.user_lookups) AS IndexUsage
, IndexUsage.user_updates AS IndexUpdates
, (SELECT CASE is_included_column WHEN 1 THEN NULL ELSE column_id END AS [data()]
FROM sys.index_columns AS IndexColumns
WHERE IndexColumns.object_id = I.object_id
AND IndexColumns.index_id = I.index_id
ORDER BY index_column_id, column_id
FOR XML PATH('')
) AS ConcIndexColumnNrs
,(SELECT CASE is_included_column WHEN 1 THEN NULL ELSE COL_NAME(I.object_id, column_id) END AS [data()]
FROM sys.index_columns AS IndexColumns
WHERE IndexColumns.object_id = I.object_id
AND IndexColumns.index_id = I.index_id
ORDER BY index_column_id, column_id
FOR XML PATH('')
) AS ConcIndexColumnNames
,(SELECT CASE is_included_column WHEN 1 THEN column_id ELSE NULL END AS [data()]
FROM sys.index_columns AS IndexColumns
WHERE IndexColumns.object_id = I.object_id
AND IndexColumns.index_id = I.index_id
ORDER BY column_id
FOR XML PATH('')
) AS ConcIncludeColumnNrs
,(SELECT CASE is_included_column WHEN 1 THEN COL_NAME(I.object_id, column_id) ELSE NULL END AS [data()]
FROM sys.index_columns AS IndexColumns
WHERE IndexColumns.object_id = I.object_id
AND IndexColumns.index_id = I.index_id
ORDER BY column_id
FOR XML PATH('')
) AS ConcIncludeColumnNames
FROM sys.indexes AS I
LEFT OUTER JOIN sys.dm_db_index_usage_stats AS IndexUsage
ON IndexUsage.object_id = I.object_id
AND IndexUsage.index_id = I.index_id
AND IndexUsage.Database_id = db_id()
)
SELECT
C1.TableName
, C1.IndexName AS 'Index1'
, C2.IndexName AS 'Index2'
, CASE WHEN (C1.ConcIndexColumnNrs = C2.ConcIndexColumnNrs) AND (C1.ConcIncludeColumnNrs = C2.ConcIncludeColumnNrs) THEN 'Exact duplicate'
WHEN (C1.ConcIndexColumnNrs = C2.ConcIndexColumnNrs) THEN 'Different includes'
ELSE 'Overlapping columns' END
-- , C1.ConcIndexColumnNrs
-- , C2.ConcIndexColumnNrs
, C1.ConcIndexColumnNames
, C2.ConcIndexColumnNames
-- , C1.ConcIncludeColumnNrs
-- , C2.ConcIncludeColumnNrs
, C1.ConcIncludeColumnNames
, C2.ConcIncludeColumnNames
, C1.IndexUsage
, C2.IndexUsage
, C1.IndexUpdates
, C2.IndexUpdates
, 'DROP INDEX ' + C2.IndexName + ' ON ' + C2.TableName AS Drop2
, 'DROP INDEX ' + C1.IndexName + ' ON ' + C1.TableName AS Drop1
FROM IndexColumns AS C1
INNER JOIN IndexColumns AS C2
ON (C1.TableObjectId = C2.TableObjectId)
AND (
-- exact: show lower IndexId as 1
(C1.IndexId < C2.IndexId
AND C1.ConcIndexColumnNrs = C2.ConcIndexColumnNrs
AND C1.ConcIncludeColumnNrs = C2.ConcIncludeColumnNrs)
-- different includes: show longer include as 1
OR (C1.ConcIndexColumnNrs = C2.ConcIndexColumnNrs
AND LEN(C1.ConcIncludeColumnNrs) > LEN(C2.ConcIncludeColumnNrs))
-- overlapping: show longer index as 1
OR (C1.IndexId <> C2.IndexId
AND C1.ConcIndexColumnNrs <> C2.ConcIndexColumnNrs
AND C1.ConcIndexColumnNrs like C2.ConcIndexColumnNrs + ' %')
)
ORDER BY C1.TableName, C1.ConcIndexColumnNrs
I created the following query that gives me a lot of good information to identify duplicate and near-duplicate indexes. It also includes other information like how many pages of memory an index takes, which allows me to give a higher priority to larger indexes. It shows what columns are indexed and what columns are included, so I can see if there are two indexes that are almost identical with only slight variations in the included columns.
WITH IndexSummary AS
(
SELECT DISTINCT sys.objects.name AS [Table Name],
sys.indexes.name AS [Index Name],
SUBSTRING((SELECT ', ' + sys.columns.Name as [text()]
FROM sys.columns
INNER JOIN sys.index_columns
ON sys.index_columns.column_id = sys.columns.column_id
AND sys.index_columns.object_id = sys.columns.object_id
WHERE sys.index_columns.index_id = sys.indexes.index_id
AND sys.index_columns.object_id = sys.indexes.object_id
AND sys.index_columns.is_included_column = 0
ORDER BY sys.columns.name
FOR XML Path('')), 2, 10000) AS [Indexed Column Names],
ISNULL(SUBSTRING((SELECT ', ' + sys.columns.Name as [text()]
FROM sys.columns
INNER JOIN sys.index_columns
ON sys.index_columns.column_id = sys.columns.column_id
AND sys.index_columns.object_id = sys.columns.object_id
WHERE sys.index_columns.index_id = sys.indexes.index_id
AND sys.index_columns.object_id = sys.indexes.object_id
AND sys.index_columns.is_included_column = 1
ORDER BY sys.columns.name
FOR XML Path('')), 2, 10000), '') AS [Included Column Names],
sys.indexes.index_id, sys.indexes.object_id
FROM sys.indexes
INNER JOIN SYS.index_columns
ON sys.indexes.index_id = SYS.index_columns.index_id
AND sys.indexes.object_id = sys.index_columns.object_id
INNER JOIN sys.objects
ON sys.OBJECTS.object_id = SYS.indexES.object_id
WHERE sys.objects.type = 'U'
)
SELECT IndexSummary.[Table Name],
IndexSummary.[Index Name],
IndexSummary.[Indexed Column Names],
IndexSummary.[Included Column Names],
PhysicalStats.page_count as [Page Count],
CONVERT(decimal(18,2), PhysicalStats.page_count * 8 / 1024.0) AS [Size (MB)],
CONVERT(decimal(18,2), PhysicalStats.avg_fragmentation_in_percent) AS [Fragment %]
FROM IndexSummary
INNER JOIN sys.dm_db_index_physical_stats (DB_ID(), NULL, NULL, NULL, NULL)
AS PhysicalStats
ON PhysicalStats.index_id = IndexSummary.index_id
AND PhysicalStats.object_id = IndexSummary.object_id
WHERE (SELECT COUNT(*) as Computed
FROM IndexSummary Summary2
WHERE Summary2.[Table Name] = IndexSummary.[Table Name]
AND Summary2.[Indexed Column Names] = IndexSummary.[Indexed Column Names]) > 1
ORDER BY [Table Name], [Index Name], [Indexed Column Names], [Included Column Names]
Results of the query look like this:
Table Name Index Indexed Cols Included Cols Pages Size (MB) Frag %
My_Table Indx_1 Col1 Col2, Col3 123 0.96 8.94
My_Table Indx_2 Col1 Col2, Col3 123 0.96 8.94
Complete Description
For the complete explanation see Identifying Duplicate or Redundant Indexes in SQL Server.
Try the script below to show Unused Indexes, hope it helps
/****************************************************************
Description: Script to show Unused Indexes using DMVs
****************************************************************/
SELECT TOP 100
o.name AS ObjectName
, i.name AS IndexName
, i.index_id AS IndexID
, dm_ius.user_seeks AS UserSeek
, dm_ius.user_scans AS UserScans
, dm_ius.user_lookups AS UserLookups
, dm_ius.user_updates AS UserUpdates
, p.TableRows
, 'DROP INDEX ' + QUOTENAME(i.name)
+ ' ON ' + QUOTENAME(s.name) + '.' + QUOTENAME(OBJECT_NAME(dm_ius.object_id)) as 'drop statement'
FROM sys.dm_db_index_usage_stats dm_ius
INNER JOIN sys.indexes i ON i.index_id = dm_ius.index_id AND dm_ius.object_id = i.object_id
INNER JOIN sys.objects o on dm_ius.object_id = o.object_id
INNER JOIN sys.schemas s on o.schema_id = s.schema_id
INNER JOIN (SELECT SUM(p.rows) TableRows, p.index_id, p.object_id
FROM sys.partitions p GROUP BY p.index_id, p.object_id) p
ON p.index_id = dm_ius.index_id AND dm_ius.object_id = p.object_id
WHERE OBJECTPROPERTY(dm_ius.object_id,'IsUserTable') = 1
AND dm_ius.database_id = DB_ID()
AND i.type_desc = 'nonclustered'
AND i.is_primary_key = 0
AND i.is_unique_constraint = 0
ORDER BY (dm_ius.user_seeks + dm_ius.user_scans + dm_ius.user_lookups) ASC
GO
I was just reading some MSDN blogs, noticed a script to do this and remembered this question.
I haven't bothered testing it side by side with Andomar's to see if one has any particular benefit over the other.
One amendment I would likely make to both though would be to take into account the size of both indexes when assessing redundancy.
Edit:
Also see Kimberley Tripp's post on Removing duplicate indexes