how to find duplicate indexes in sql server [duplicate] - sql-server-2008

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

Related

how to use the column in a temporary table for the following update in MSQL

I have a MySQL database.
I want to update a column(in my case title column in bms_title table) in a table using the values from concat columns in other tables.
SELECT * FROM(SELECT distinct t.id, t.title as Textbook,
GROUP_CONCAT(concat(ci.discipline_code, ci.code, " (" , ci.type , ")") SEPARATOR ', ') as CourseCode FROM
tms_local.bms_material m,
tms_local.bms_title t,
tms_local.bms_course c,
tms_local.bms_courseinfo ci
where t.id > 1 AND t.id = m.book_id
and c.id = m.course_id
and ci.id = c.id
and isbn != 'NA'
GROUP BY t.id) AS temporary_table;
UPDATE tms_local.bms_title
SET tms_local.bms_title.thumbnail = temporary_table.CourseCode
WHERE tms_local.bms_title.title=temporary_table.Textbook;
But I got the error: Unknow temporary_table.Textbook in where clause.
How could I update the tms_local.bms_title.thumbnail column using CourseCode column from the selected table?
enter image description here
I have tried
CREATE TEMPORARY TABLE IF NOT EXISTS temporary_table AS (SELECT distinct t.id, t.title as Textbook,
GROUP_CONCAT(concat(ci.discipline_code, ci.code, " (" , ci.type , ")") SEPARATOR ', ') as CourseCode FROM
tms_local.bms_material m,
tms_local.bms_title t,
tms_local.bms_course c,
tms_local.bms_courseinfo ci
where t.id > 1 AND t.id = m.book_id
and c.id = m.course_id
and ci.id = c.id
and isbn != 'NA'
GROUP BY t.id);
UPDATE tms_local.bms_title
SET tms_local.bms_title.thumbnail = temporary_table.CourseCode
WHERE tms_local.bms_title.title=temporary_table.Textbook;
But got the same error.
you need to join the select statement.
As seen below:
UPDATE tms_local.bms_title t0
INNER JOIN
(SELECT
*
FROM
(SELECT DISTINCT
t.id,
t.title AS Textbook,
GROUP_CONCAT(CONCAT(ci.discipline_code, ci.code, ' (', ci.type, ')')
SEPARATOR ', ') AS CourseCode
FROM
tms_local.bms_material m, tms_local.bms_title t, tms_local.bms_course c, tms_local.bms_courseinfo ci
WHERE
t.id > 1 AND t.id = m.book_id
AND c.id = m.course_id
AND ci.id = c.id
AND isbn != 'NA'
GROUP BY t.id) AS temporary_table) t1 ON t0.title = t1.Textbook
SET
t0.thumbnail = t1.ourseCode;
Your temporary_table is being lost between the first statement and the second.
I find the WITH ... AS SQL structure to be helpful to get these together and is far more readable:
WITH temporary_table AS(
SELECT *
FROM(SELECT distinct t.id,
t.title as Textbook,
GROUP_CONCAT(concat(ci.discipline_code,
ci.code,
" (" ,
ci.type ,
")")
SEPARATOR ', '
) as CourseCode
FROM tms_local.bms_material m,
tms_local.bms_title t,
tms_local.bms_course c,
tms_local.bms_courseinfo ci
WHERE t.id > 1 AND t.id = m.book_id
and c.id = m.course_id
and ci.id = c.id
and isbn != 'NA'
GROUP BY t.id)
UPDATE tms_local.bms_title
SET tms_local.bms_title.thumbnail = temporary_table.CourseCode
WHERE tms_local.bms_title.title=temporary_table.Textbook;

UPDATE - SELECT - MYSQL #1093 - You can't specify target table 'temp1' for update in FROM clause

I can't find solution to correct this big query, I always receive an error from database.
I have tre tables and I need to make changes on some attribute on some condition:
UPDATE o36t_orders as temp1,
mytable as temp2
SET
temp1.bonifico = 1,
temp2.ultimo = 1
WHERE
temp1.id_order IN (
SELECT
id_order
FROM o36t_orders
LEFT JOIN o36t_address ON (o36t_address.id_address = o36t_orders.id_address_delivery)
LEFT JOIN mytable ON (
mytable.Causale = CONCAT(
o36t_address.lastname,
' ',
o36t_address.firstname
)
)
WHERE
o36t_orders.bonifico <> 1
)
AND temp2.id IN (
SELECT
id
FROM o36t_orders
LEFT JOIN o36t_address ON (o36t_address.id_address = o36t_orders.id_address_delivery)
LEFT JOIN mytable ON (
mytable.Causale = CONCAT(
o36t_address.lastname,
' ',
o36t_address.firstname
)
)
WHERE
o36t_orders.bonifico <> 1
)
Since the subqueries of the 2 IN clauses are identical (except the retuned column), I think that you can do what you want by a straight inner join of the 2 tables and that subquery (returning both columns):
UPDATE o36t_orders temp1
INNER JOIN (
SELECT
id, id_order
FROM o36t_orders
LEFT JOIN o36t_address ON (o36t_address.id_address = o36t_orders.id_address_delivery)
LEFT JOIN mytable ON (
mytable.Causale = CONCAT(
o36t_address.lastname,
' ',
o36t_address.firstname
)
)
WHERE
o36t_orders.bonifico <> 1
) t ON t.id_order = temp1.id_order
INNER JOIN mytable temp2 ON temp2.id = t.id
SET
temp1.bonifico = 1,
temp2.ultimo = 1

table valued function xml reader is causing performance in SQL Server

Could you please help me to change the script with substring or any other alternative way to improve the query.
I am not a developer it would be great if you can provide a sample script from the below script. the table valued function cost 49% in actual execution plan of the query and runs for 7 minutes.
Thank you in advance.
LEFT OUTER JOIN
(SELECT
pp2.patient_id,
Foodallergy= STUFF((SELECT ',' + cd.description
FROM [OHCP_OHProblemList].[problemlist].[problems_v] pp
INNER JOIN [OHCP_OHCLINICAL].[CodeSet].[CodeSet] CS ON cs.identifier = pp.problem_name_code_set
INNER JOIN ohcp_ohclinical.codeset.CodeDefinition cd ON (cd.code = pp.problem_name_code AND cd.codesetid = cs.id)
WHERE patient_id = pp2.patient_id
AND pp.last_update_action NOT IN ('DELETE', 'CLOSE')
AND pp.adr_class_code IN (3,4)
AND cd.description != 'Other'
ORDER BY [description]
FOR XML PATH ('')), 1, 1, ''),
description = STUFF((SELECT N',' + ' Other:' + pp.problem_name_freetext_desc
FROM [OHCP_OHProblemList].[problemlist].[problems_v] pp
INNER JOIN [OHCP_OHCLINICAL].[CodeSet].[CodeSet] CS on cs.identifier = pp.problem_name_code_set
INNER JOIN ohcp_ohclinical.codeset.CodeDefinition cd ON (cd.code = pp.problem_name_code AND cd.codesetid = cs.id)
WHERE patient_id = pp2.patient_id
AND pp.last_update_action not in ('DELETE', 'CLOSE')
AND pp.adr_class_code in (3, 4)
FOR XML PATH('')), 1, 1, '')
FROM
[OHCP_OHProblemList].[problemlist].[problems_v] pp2
GROUP BY
pp2.patient_id) AS problem ON problem.patient_id = externalpatientid.externalpatientid

No Join Predicate Warning - Why Does this query never complete/timeout?

Here is my query that I get a "No Join Predicate" warning:
select DISTINCT d.* from Device d , Company c1
WHERE d.CustomerID = c1.CompanyID AND c1.CompanyNumber in
(SELECT * FROM [dbo].[Split] ('56257100', ','))
OR
EXISTS ( select * from (SELECT * FROM [dbo].[Split] ('American', ',')) as s
WHERE d.CustomerID = c1.CompanyID AND c1.CompanyName like '%' + s.items + '%' )
Essentially I had two queries, and I want to join them with an OR (get the results from both). I believe it's trying to join the two queries as a Cartesian product, which isn't what I want.
If I separate it as 2 queries and UNION the results, it is fast. This however makes me re-structure my dynamic SQL quite a bit. I'd like to keep it formatted where I can have an OR there, or an AND if need be.
The parenthesis are incorrect. They should be as follows so that it knows d is the same across the OR
select DISTINCT d.* from Device d , Company c1
WHERE d.CustomerID = c1.CompanyID AND
(
c1.CompanyNumber in (SELECT * FROM [dbo].[Split] ('56257100', ','))
OR
EXISTS ( select * from (SELECT * FROM [dbo].[Split] ('American', ',')) as s
WHERE d.CustomerID = c1.CompanyID AND c1.CompanyName like '%' + s.items + '%' )
)

More than one Case in same query are generating more than one row

My problem is actually I have multiple tables and I'm using two case statements to generate one column for ARV1 and one for ICA1, but I need the results are generated in the same row. When I usecase, generate the two columns but the values are displayed in two rows. What am I missing?
the thing is, i have an invoice the table OINV and have the table INV5 that is the table with the Holding Taxes, i need to put on the same row the invoice with all the Holding Taxes in different columns that are applying on it, thanks
this is the example tables
CREATE TABLE Invoice
(
Id INT, InvoiceNumber VARCHAR(10), Total INT
)
INSERT INTO Invoice
VALUES
(1,'200000',100),
(2,'200001',200),
(3,'200002',500),
(4,'200003',700),
(5,'200004',200),
(6,'200005',100),
(7,'200006',300)
CREATE TABLE HoldingTaxes
(
Id INT, HoldingTaxCode VARCHAR(10),HoldedAmount INT)
)
INSERT INTO HoldingTaxes
VALUES
(1,'ARV1',20),
(1,'ARV2',30),
(1,'ARV3',35),
(2,'ICA1',20),
(2,'ARV1',10),
(1,'ICA3',50)
I want a query that returns something like this:
InvoiceNumber Total ARV1 ARV2 ARV3 ICA1 ICA2 ICA3
200000 100 20 30 35 null null 50
This is what i am trying to do with my real tables
SELECT T0.DocNum [No. Factura],
CASE
WHEN t5.WTCode ='ARV1' and (t5.U_Ret_ML <>0 AND t5.U_Ret_ML is not null)
THEN 'Perro1'
else NULL
end AS ARV1
,
CASE
WHEN t5.WTCode ='ICA1' and (t5.U_Ret_ML <>0 AND t5.U_Ret_ML is not null)
THEN 'Perro2'
else NULL
end AS ICA1
FROM OINV T0
INNER JOIN INV1 T1 ON T0.DocEntry = T1.DocEntry
INNER JOIN OSLP T4 ON T0.SlpCode = T4.SlpCode
INNER JOIN OITM T2 ON T1.ItemCode = T2.ItemCode
INNER JOIN OITW T3 ON T2.ItemCode = T3.ItemCode
INNER JOIN INV5 T5 ON T5.AbsEntry = T0.DocEntry
WHERE T1.WhsCode = T3.WhsCode`enter code here`
GROUP BY T0.DocNum,T0.DocDate,T0.DocTotal, T0.GrosProfit, T4.SlpName,T5.WTCODE,t5.U_Ret_ML
Alternative way :
SELECT inv.InvoiceNumber,inv.Total,[ARV1],[ARV2],[ARV3],[ICA1],[ICA2],[ICA3]
FROM INVOICE inv
JOIN(
SELECT id,[ARV1],[ARV2],[ARV3],[ICA1],[ICA2],[ICA3]
FROM
(SELECT * FROM HoldingTaxes ) t1
PIVOT(SUM(HoldedAmount) for HoldingTaxCode in
([ARV1],[ARV2],[ARV3],[ICA1],[ICA2],[ICA3])) t2
) ht
ON inv.id =ht.id
sql fiddle :http://sqlfiddle.com/#!3/ea3a4/10
I would use PIVOT to solve this - as demonstrated in the SQL Fiddle:
SELECT EmpName
,CASE WHEN ARV1 > 0 THEN 'PERRO1' ELSE NULL END
,CASE WHEN ARV2 > 0 THEN 'PERRO2' ELSE NULL END
,CASE WHEN ICA1 > 0 THEN 'PERRO3' ELSE NULL END
FROM (SELECT t0.EmpName, t1.Name, t1.Value
FROM Table0 t0 INNER JOIN Table1 t1 ON t0.Id = t1.Id ) as src
PIVOT (
MAX(src.Value)
FOR src.Name IN ([ARV1],[ARV2],[ICA1])) as p
http://sqlfiddle.com/#!3/a6ff0/2
If your having issues and willing to share your structure, I can put this into a a closer match to what you are using on SQL Fiddle.
Edit:
Based on the update you gave, here is the fiddle I created with the pivot.
http://sqlfiddle.com/#!3/47511/4
SELECT * FROM
(SELECT InvoiceNumber = Invoice.InvoiceNumber
,Total = Invoice.Total
,HoldingTaxCode = HoldingTaxes.HoldingTaxCode
,HoldedAmount = HoldingTaxes.HoldedAmount
FROM Invoice
LEFT JOIN HoldingTaxes
ON Invoice.Id = HoldingTaxes.Id) PivotSource
PIVOT
(
SUM(HoldedAmount) FOR HoldingTaxCode IN(ARV1, ARV2, ARV3, ICA1, ICA2, ICA3)
) PivotData
ORDER BY InvoiceNumber
The two CASE expressions employ the same logic. Only the value in t5.WTCode is different. Since that column is unlikely to have the value ARV1 and ICA1 at the same time you'll always get a NULL in at least one of the computed columns in any row.
You are putting different case statements for 2 different values. Therefore for each row one of them would be valid and other one null, which is what you are getting.
This is how i resolved my issue, thanks to everyone for the help
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(t5.WTCode)
FROM INV5 T5
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT Factura, Fecha, SN, TotalFacturaSinImpuesto, Total$Descuento, Total$Impuesto, Total$Retenciones, Total$Factura, CostoTotal$Factura, Margen$Factura, Costo$PromedioInventario, Margen$Inventario, NombreVendedor, ' + #cols + ' from
(
select T0.DocNum [Factura], T0.DocDate [Fecha], T0.CardName [SN],SUM(T1.LineTotal) [TotalFacturaSinImpuesto], T0.DiscSum [Total$Descuento], T0.VatSum [Total$Impuesto],(T0.WTSum*-1) [Total$Retenciones],t5.WTCode [CodigoRetencion],t5.U_Ret_ML [CantidadRetenida],T0.DocTotal [Total$Factura],SUM(T1.GrossBuyPr*T1.Quantity) [CostoTotal$Factura], T0.GrosProfit [Margen$Factura],SUM(T1.Quantity*T3.AvgPrice) [Costo$PromedioInventario],(SUM(T1.LineTotal*T1.Quantity))-(SUM(T1.Quantity*T3.AvgPrice)) [Margen$Inventario], T4.SlpName [NombreVendedor]
FROM OINV T0
INNER JOIN INV1 T1 ON T0.DocEntry = T1.DocEntry
INNER JOIN OSLP T4 ON T0.SlpCode = T4.SlpCode
INNER JOIN OITM T2 ON T1.ItemCode = T2.ItemCode
INNER JOIN OITW T3 ON T2.ItemCode = T3.ItemCode
INNER JOIN INV5 T5 ON T5.AbsEntry = T0.DocEntry
WHERE T1.WhsCode = T3.WhsCode
GROUP BY T0.DocNum,T0.DocDate, T0.CardName,T0.BaseAmnt, T0.DiscSum, T0.VatSum, T0.WTSum,T0.DocTotal, T0.GrosProfit, T4.SlpName,T5.WTCODE,T5.U_RET_ML
) x
pivot
(
sum(CantidadRetenida)
for CodigoRetencion in (' + #cols + ')
) p '
execute(#query)