I have a database which holds data for several LDAP directories, it is bulk refreshed on a schedule and maintenance is run immediately after so that a series of interactive reports can be executed. The reports are programmatically generated and often join
between the collections of directory data, for example, objects in LDAP directory 1 may have an attribute typefoo whose value matches objects in LDAP directory 2 with an attribute type ofbar with the same value. The two tables of interest are as follows:
CREATE TABLE [DirectoryObjects] (
[Id] int NOT NULL IDENTITY,
[DistinguishedName] nvarchar(832) NOT NULL,
[DirectoryConfigurationId] int NOT NULL,
CONSTRAINT [PK_DirectoryObjects] PRIMARY KEY ([Id]),
CONSTRAINT [FK_DirectoryConfigurationId]
FOREIGN KEY
([DirectoryConfigurationId])
REFERENCES
[DirectoryConfigurations] ([Id]) ON DELETE CASCADE
);
CREATE TABLE [DirectoryAttributes] (
[Id] int NOT NULL IDENTITY,
[Name] nvarchar(128) NOT NULL,
[Value] nvarchar(832) NULL,
[DirectoryObjectId] int NOT NULL,
CONSTRAINT [PK_DirectoryAttributes] PRIMARY KEY ([Id]),
CONSTRAINT [FK_DirectoryObjectId]
FOREIGN KEY
([DirectoryObjectId])
REFERENCES
[DirectoryObjects] ([Id]) ON DELETE CASCADE
);
I have the following initial attempt at setting up indexes:
CREATE NONCLUSTERED INDEX [IX_DirectoryObjects_DirectoryConfigurationId] ON [dbo].[DirectoryObjects]
(
[DirectoryConfigurationId] ASC
) ON [PRIMARY]
GO
CREATE NONCLUSTERED INDEX [IX_DirectoryObjects_Id_DirectoryConfigurationId] ON [dbo].[DirectoryObjects]
(
[Id] ASC,
[DirectoryConfigurationId] ASC
) ON [PRIMARY]
GO
CREATE UNIQUE NONCLUSTERED INDEX [IX_DirectoryObjects_DirectoryConfigurationId_DistinguishedName] ON [dbo].[DirectoryObjects]
(
[DirectoryConfigurationId] ASC,
[DistinguishedName] ASC
) ON [PRIMARY]
GO
CREATE NONCLUSTERED INDEX [IX_DirectoryAttributes_DirectoryObjectId__Name_Value] ON [dbo].[DirectoryAttributes]
(
[DirectoryObjectId] ASC
)
INCLUDE([Name],[Value]) ON [PRIMARY]
GO
CREATE NONCLUSTERED INDEX [IX_DirectoryAttributes_DirectoryObjectId] ON [dbo].[DirectoryAttributes]
(
[DirectoryObjectId] ASC
) ON [PRIMARY]
GO
USE [PSU_DCVALIDATION2]
GO
CREATE NONCLUSTERED INDEX [IX_DirectoryAttributes_Name__Value_DirectoryObjectId] ON [dbo].[DirectoryAttributes]
(
[Name] ASC
)
INCLUDE([Value],[DirectoryObjectId]) ON [PRIMARY]
GO
Essentially a table for the long ids of each object in each directory, and a table for the object attribute key/value data.
The following query is programmatically generated:
DECLARE @P_T0 INT = 1;
DECLARE @P_T0_C0 NVARCHAR(128) = N'sAMAccountName';
DECLARE @P_T0_C1 NVARCHAR(128) = N'sAMAccountType';
DECLARE @P_T0_C2 NVARCHAR(128) = N'userAccountControl';
DECLARE @P_T0_C3 NVARCHAR(128) = N'lastLogonTimestamp';
DECLARE @P_T0_C4 NVARCHAR(128) = N'givenName';
DECLARE @P_T0_C5 NVARCHAR(128) = N'sn';
DECLARE @P_T1 INT = 2;
DECLARE @P_T1_C0 NVARCHAR(128) = N'alternateID';
DECLARE @P_T1_C1 NVARCHAR(128) = N'description';
DECLARE @PageOffset INT = 0;
DECLARE @PageSize INT = 1000;
WITH [T0] ([C0],[C1],[C2],[C3],[C4],[C5]) AS
(
SELECT [DA0].[Value],[DA1].[Value],[DA2].[Value],[DA3].[Value],[DA4].[Value],[DA5].[Value]
FROM [DirectoryObjects]
LEFT JOIN [DirectoryAttributes] [DA0] ON [DirectoryObjects].[Id]=[DA0].[DirectoryObjectId] AND [DA0].[Name]=@P_T0_C0
LEFT JOIN [DirectoryAttributes] [DA1] ON [DirectoryObjects].[Id]=[DA1].[DirectoryObjectId] AND [DA1].[Name]=@P_T0_C1
LEFT JOIN [DirectoryAttributes] [DA2] ON [DirectoryObjects].[Id]=[DA2].[DirectoryObjectId] AND [DA2].[Name]=@P_T0_C2
LEFT JOIN [DirectoryAttributes] [DA3] ON [DirectoryObjects].[Id]=[DA3].[DirectoryObjectId] AND [DA3].[Name]=@P_T0_C3
LEFT JOIN [DirectoryAttributes] [DA4] ON [DirectoryObjects].[Id]=[DA4].[DirectoryObjectId] AND [DA4].[Name]=@P_T0_C4
LEFT JOIN [DirectoryAttributes] [DA5] ON [DirectoryObjects].[Id]=[DA5].[DirectoryObjectId] AND [DA5].[Name]=@P_T0_C5
WHERE [DirectoryObjects].[DirectoryConfigurationId]=@P_T0
),
[T1] ([C0],[C1]) AS
(
SELECT [DA0].[Value],[DA1].[Value]
FROM [DirectoryObjects]
LEFT JOIN [DirectoryAttributes] [DA0] ON [DirectoryObjects].[Id]=[DA0].[DirectoryObjectId] AND [DA0].[Name]=@P_T1_C0
LEFT JOIN [DirectoryAttributes] [DA1] ON [DirectoryObjects].[Id]=[DA1].[DirectoryObjectId] AND [DA1].[Name]=@P_T1_C1
WHERE [DirectoryObjects].[DirectoryConfigurationId]=@P_T1
)
-- SELECT [T0].[C0] P0,[T0].[C1] P1,[T0].[C2] P2,[T0].[C3] P3,[T0].[C4] P4,[T0].[C5] P5,[T1].[C1] P6
SELECT COUNT (*)
FROM [T0]
LEFT JOIN [T1] ON [T0].[C0]=[T1].[C0]
WHERE NOT ([T0].[C0] IS NULL AND [T0].[C1] IS NULL AND [T0].[C2] IS NULL AND [T0].[C3] IS NULL AND [T0].[C4] IS NULL AND [T0].[C5] IS NULL AND [T1].[C1] IS NULL)
--ORDER BY [P0] -- DESC
--OFFSET @PageOffset ROWS FETCH NEXT @PageSize ROWS ONLY
With the current indexes, the count query works reasonably quick.
Reversing the comments so the result query produces inconsistent results. If DESC is enabled, the result set is returned immediately whereas it takes some time. Further, removing the order by and paging returns the entire result set even quicker?
What can be done to improve this? I have the option to both create/remove indexes and statistics and refactor the code for a more efficient query.
Thanks!