Получить ›901 строку со связанного сервера SQL Server 2008 с Active Directory

В SQL Server 2008 (версия 10.0.4000) я создал сервер, связанный с сервером Active Directory.

Этот запрос:

select  TOP 901 *
from  openquery(adsisca, '
select  givenName,
                sn,
                sAMAccountName          
from    ''LDAP://10.1.2.3:389''
where   objectCategory = ''Person''
        and
        objectClass = ''InetOrgPerson''
')

работает.

Однако изменение запроса и попытка получить 902 строки не приводят к следующему:

select  TOP 902 *
    from  openquery(adsisca, '
    select  givenName,
                    sn,
                    sAMAccountName          
    from    ''LDAP://10.1.2.3:389''
    where   objectCategory = ''Person''
            and
            objectClass = ''InetOrgPerson''
    ')

Ошибка:

Msg 7330, уровень 16, состояние 2, строка 1 Не удалось получить строку от поставщика OLE DB «ADSDSOObject» для связанного сервера «adsisca».

Я встречал и другие случаи, когда люди обсуждали ту же проблему на форумах, и они никогда ее не исправляли, а просто работали над ее решением, например, написав несколько представлений и объединив их вместе.

Есть ли более элегантное исправление, есть ли параметр, который я могу где-нибудь изменить, чтобы получить более 901 строки?


person bgs264    schedule 14.04.2011    source источник
comment
Я не знаю, есть ли ссылка, но в текущем поисковом запросе Active-Directory никогда не отвечает более чем на 1000 записей, и, возможно, существует более ограничительное значение из-за объема. С этой точки зрения каталог не похож на базу данных.   -  person JPBlanc    schedule 15.04.2011


Ответы (10)


Используйте union, чтобы обойти ограничение.

нравится :

select  TOP 901 *
from  openquery(adsisca, '
select  givenName,
                sn,
                sAMAccountName          
from    ''LDAP://10.1.2.3:389''
where   objectCategory = ''Person''
        and
        objectClass = ''InetOrgPerson''
        and
        sAMAccountName < ''m''
')
union
select  TOP 901 *
from  openquery(adsisca, '
select  givenName,
                sn,
                sAMAccountName          
from    ''LDAP://10.1.2.3:389''
where   objectCategory = ''Person''
        and
        objectClass = ''InetOrgPerson''
        and
        sAMAccountName >= ''m''
')
person Magnus Reuter    schedule 13.12.2013

Я знаю, что это старый пост, но у меня тоже были те же проблемы, и я изучил предложенное выше решение. (В основном, используя кучу меньших выборок с изменяющимися критериями, чтобы вести обратный отсчет строк) Я просто вырезал немного другую версию и объединил их все в Db View. Меня не смущала эта штука MaxPageSize - кажется, слишком много усилий.

IF NOT EXISTS(SELECT 1 FROM sys.servers WHERE name = 'ADSI') 
    EXEC sp_addlinkedserver 'ADSI', 'Active Directory Services 2.5', 'ADSDSOObject', 'adsdatasource'

-- Create a database view from unions of smaller selects. The max 901 records thing in AD forces us to do this.
DECLARE @queryFormat VARCHAR(MAX) = '
SELECT * FROM OPENQUERY(ADSI, ''
    SELECT userPrincipalName, samAccountName, telephoneNumber, mail, middleName, givenName, sn, displayName, distinguishedName
    FROM ''''LDAP://OU=Users, OU=ABC, DC=XYZ, DC=local''''
    WHERE objectClass = ''''User'''' AND objectCategory = ''''Person'''' AND samAccountName = ''''#p0'''''')';

DECLARE @sql VARCHAR(MAX) = 'CREATE VIEW [AdView] AS ';
DECLARE @asciiValue INT = ASCII('A');
DECLARE @asciiEnd INT = ASCII('Z');
WHILE @asciiValue <= @asciiEnd BEGIN 
    SET @sql = @sql + replace(@queryFormat, '#p0', CHAR(@asciiValue) + '*');
    IF @asciiValue < @asciiEnd  SET @sql = @sql + ' UNION ALL ';
    SET @asciiValue = @asciiValue + 1;
END
--PRINT @sql;

-- the 'live' view of the active directory data.
IF OBJECT_ID('[AdView]') IS NOT NULL DROP VIEW [AdView]
EXEC(@sql);

-- a 'snapshot' of the active directory data, for faster selects. you could execute this on a schedule to keep up to date.
IF OBJECT_ID('[AdTable]', 'U') IS NOT NULL DROP TABLE [AdTable]
SELECT * INTO [AdTable] FROM [AdView]
person Ross Oliver    schedule 10.09.2013
comment
Работа в 2008 году для меня стала очарованием. - person Portekoi; 14.11.2014

Вам необходимо изменить параметр MaxPageSize в Active Directory. Для этого вам нужно использовать Ntdsutil.exe, который вы можете ввести в команде запуска, затем выполните следующие действия.

  1. В командной строке Ntdsutil.exe введите LDAP policies и нажмите клавишу ВВОД.
  2. В командной строке политики LDAP введите Set MaxPageSize to 2000. -> Или любое число, которое вы хотите
  3. Чтобы просмотреть изменения, введите Show Values
  4. Для сохранения изменений наберитеCommit Changes
  5. Чтобы выйти, введите q
person Raymund    schedule 26.04.2011
comment
Спасибо, я не хочу менять размер страницы, так как это имеет другие последствия. С .NET я могу просто сказать ему, каков размер страницы, и он работает. Я действительно ищу способ установить размер страницы в моем SQL-запросе. (Извините за поздний ответ, меня не было) - person bgs264; 06.05.2011

Проблема

Ошибка:

Msg 7330, уровень 16, состояние 2, строка 1 Не удалось получить строку от поставщика OLE DB «ADSDSOObject» для связанного сервера «adsisca».

Я встречал и другие случаи, когда люди обсуждали ту же проблему на форумах, и они никогда ее не исправляли,> просто работали над этим, создавая несколько представлений и объединяя их, например.

Есть ли более элегантное исправление, есть ли параметр, который я могу где-нибудь изменить, чтобы получить более 901> строк?

Решение

Я только что решил ту же проблему, с которой столкнулся я, без каких-либо изменений настроек Active Directory (и я успешно могу получить около 50 тысяч логинов из AD, и не пропустить, чтобы получить одну учетную запись для входа из доменов AD):

Вам нужно обойти ограничение запроса ADSI, перебирая символы атрибутов. См. Решение здесь: http://www.sqlservercentral.com/Forums/Topic231658-54-1.aspx#bm1249991

Ошибка была устранена записью SELECT TOP 901 ... ВМЕСТО ТОЛЬКО SELECT.

Эта проблема возникла у меня после миграции базы данных с 2005 на 2008, потому что в SQL Server 2008 существует ограничение в 901 строку, которое было 1000 в SQL Server 2005 (разница в том, что нам нужно написать select TOP 901, который не был требуется в SQL Server 2005, иначе программа выйдет из строя с ошибкой)

person Mazhar Ehsan    schedule 23.04.2013

Мне не очень понравился запах других вариантов, размещенных здесь, потому что с большим доменом вполне возможно иметь более 901 учетную запись, начинающуюся с одной и той же первой буквы - особенно если вы просматриваете учетные записи компьютеров, которые, вероятно, следуют некоторым систематическим соглашение об именах с использованием той же первой буквы ...

Я немного поигрался и обнаружил, что если вы закажете открытый запрос через uSNCreated и поместите предложение TOP 901 во внешний запрос, он не взорвется.

Итак, вот мой SQL, который извлекает ВСЕ объекты активного каталога (компьютеры, контроллеры домена, пользователи и контакты) во временную таблицу кусками по 901 записи и дает вам некоторую полезную информацию по каждому объекту.

CREATE TABLE #ADData(
    Login           NVARCHAR(256)
    ,CommonName     NVARCHAR(256)
    ,GivenName      NVARCHAR(256)
    ,FamilyName     NVARCHAR(256)   
    ,DisplayName    NVARCHAR(256)
    ,Title          NVARCHAR(256)
    ,Department     NVARCHAR(256)
    ,Location       NVARCHAR(256)
    ,Info           NVARCHAR(256)
    ,LastLogin      BIGINT
    ,flags          INT
    ,Email          NVARCHAR(256)
    ,Phone          NVARCHAR(256)   
    ,Mobile         NVARCHAR(256)
    ,Quickdial      NVARCHAR(256)
    , usnCreated    INT
)

DECLARE @Query      VARCHAR (2000)
DECLARE @Filter     VARCHAR(200)
DECLARE @Rowcount   INT

select @Filter =''

WHILE ISNULL(@rowcount,901)  = 901 BEGIN

    SELECT @Query = '
    SELECT top 901
            Login           = SamAccountName
            , CommonName    = cn
            , GivenName
            , FamilyName    = sn    
            , DisplayName
            , Title
            , Department
            , Location      = physicalDeliveryOfficeName
            , Info
            , LastLogin     = CAST(LastLogon AS bigint)
            , flags         = CAST (UserAccountControl as int)
            , Email         = mail
            , Phone         = telephoneNumber
            , Mobile        = mobile
            , QuickDial     = Pager
            , usnCreated
        FROM OPENROWSET(''ADSDSOObject'', '''', ''
                SELECT cn, givenName, sn, userAccountControl, lastLogon, displayName, samaccountname, 
                title,  department, physicalDeliveryOfficeName, info, mail, telephoneNumber, mobile, pager, usncreated
            FROM ''''LDAP://[ldap-query-string]'''' 
            WHERE objectClass=''''Person''''
            AND objectClass = ''''User''''
            ' + @filter + '
            ORDER BY usnCreated'')'             
    INSERT INTO #ADData EXEC (@Query) 
    SELECT @Rowcount = @@ROWCOUNT
    SELECT @Filter = 'and usnCreated > '+ LTRIM(STR((SELECT MAX(usnCreated) FROM #ADData)))

END

SELECT LOGIN            
        , CommonName    
        , GivenName
        , FamilyName
        , DisplayName
        , Title         
        , Department
        , Location      
        , Email         
        , Phone         
        , QuickDial     
        , Mobile        
        , Info          
        , Disabled      = CASE WHEN CAST (flags AS INT) & 2 > 0 THEN 'Y' ELSE NULL END 
        , Locked        = CASE WHEN CAST (flags AS INT) & 16  > 0 THEN 'Y' ELSE NULL END 
        , NoPwdExpiry   = CASE WHEN CAST (flags AS INT) & 65536  > 0 THEN 'Y' ELSE NULL END 
        , LastLogin     = CASE WHEN ISNULL(CAST (LastLogin AS BIGINT),0) = 0 THEN NULL ELSE 
                            DATEADD(ms, (CAST (LastLogin AS BIGINT) / CAST(10000 AS BIGINT)) % 86400000,
                            DATEADD(day, CAST (LastLogin AS BIGINT) / CAST(864000000000 AS BIGINT) - 109207, 0)) END 
        , Type = CASE WHEN flags  & 512 = 512 THEN 'user' 
                    WHEN flags IS NULL THEN 'contact' 
                    WHEN flags & 4096 = 4096 THEN 'computer'
                    WHEN flags & 532480 = 532480 THEN 'computer (DC)' END
FROM #ADData
ORDER BY Login

DROP TABLE #ADData
person John Sinclair    schedule 27.03.2017
comment
Но uSNCreated не может быть уникальным. Можно ли вместо этого разделить результат с помощью objectGUID? - person dlh; 20.11.2017

Мне нужно изменить параметр MaxTempTableSize в Active Directory. Для этого вам нужно использовать Ntdsutil.exe, который вы можете ввести в команде запуска, затем выполните следующие действия.

At the Ntdsutil.exe command prompt, type LDAP policies, and then press ENTER.
At the LDAP policy command prompt, type Set MaxTempTableSize to 2000. -> Or any number you want
To view the changes, type Show Values
To save the changes, typeCommit Changes
To quit, type q
person Edison Troncoso    schedule 25.04.2014

Эта версия решения имеет дело с ситуациями, когда количество пользователей, начинающихся с указанного символа, все еще> 901. Она использует главную процедуру, вызывающую другую хранимую процедуру.

-- This procedure pulls a subset of LDAP users

CREATE PROC [dbo].[Select_LDAP_Rows]
(
    @MyChar CHAR(1)
)
AS
BEGIN

--DECLARE @MyChar CHAR(1) = 'A';

DECLARE @queryFormat VARCHAR(MAX) = '
    SELECT * FROM OPENQUERY(ADSI, ''
        SELECT displayName, telephoneNumber, mail, mobile, facsimileTelephoneNumber
        FROM ''''LDAP://OU=PHC,dc=MyCompany,dc=org''''
        WHERE objectClass = ''''User'''' AND objectCategory = ''''Person'''' AND displayName = ''''' + @MyChar + '#p0'''''')
    ';

DECLARE @sql VARCHAR(MAX) = 'CREATE VIEW [AdView] AS ';
DECLARE @asciiValue INT = ASCII('A');
DECLARE @asciiEnd INT = ASCII('Z');

WHILE @asciiValue <= @asciiEnd BEGIN 
    SET @sql = @sql + replace(@queryFormat, '#p0', CHAR(@asciiValue) + '*');
    IF @asciiValue < @asciiEnd  SET @sql = @sql + ' UNION ALL ';
    SET @asciiValue = @asciiValue + 1;
END

--PRINT @sql;

-- the 'live' view of the active directory data.
IF OBJECT_ID('v_ADView') IS NOT NULL DROP VIEW v_ADView

EXEC(@sql);

-- ADTable holds a 'snapshot' of the active directory data.
IF OBJECT_ID('[Users_AD]', 'U') IS NULL
    SELECT DisplayName, TelephoneNumber AS Phone, Mail, Mobile, FacsimileTelephoneNumber AS Fax 
    INTO [Users_AD] FROM v_ADView;
ELSE
    INSERT INTO [Users_AD] 
    SELECT DisplayName, TelephoneNumber AS Phone, Mail, Mobile, FacsimileTelephoneNumber AS Fax 
    FROM v_ADView;

END


GO


-- By calling Select_LDAP_Rows with a separate character each time,
-- build up a table containing all the LDAP data for each user.

ALTER PROC [dbo].[Select_LDAP_Rows_Master]
AS

BEGIN

-- ADTable holds a 'snapshot' of the active directory data.
IF OBJECT_ID('[AdTable]', 'U') IS NOT NULL DROP TABLE [AdTable];

-- Create a database view from unions of smaller selects. The max 901 records thing in AD forces us to do this.

DECLARE @sql VARCHAR(200)
DECLARE @asciiValue INT = ASCII('A');
DECLARE @asciiEnd INT = ASCII('Z');

-- Create a view of the active directory data and insert to table Users_AD
WHILE @asciiValue <= @asciiEnd BEGIN 

    SET @sql = 'EXEC dbo.Select_LDAP_Rows ' + CHAR(@asciiValue) + ' ';
    SET @asciiValue = @asciiValue + 1;

    --PRINT @sql;

    EXEC(@sql);

END

END
person Tailspinner    schedule 01.05.2015

Мне нравится вариант союза самый лучший и простой.

выберите TOP 901 * из openquery (adsisca, 'выберите givenName, sn, sAMAccountName
из' 'LDAP: //10.1.2.3: 389' ', где objectCategory =' 'Person' 'и objectClass =' ​​'InetOrgPerson' 'и sAMAccountName ‹'' m '' ') union выберите TOP 901 * из openquery (adsisca,' выберите givenName, sn, sAMAccountName
из '' LDAP: //10.1.2.3: 389 '' 'где objectCategory =' 'Person' 'и objectClass =' ​​'InetOrgPerson' 'и sAMAccountName> =' 'm' '')

person user6232480    schedule 13.06.2017

Я так оценил ответ Джона Синклера, что выбрал высшую форму лести - подражание. Вот мое изложение его решения. Вместо того, чтобы объявлять соединение ADSI LDAP с каждым запросом в OpenRowSet, я выбрал метод OpenQuery:

DECLARE @DomainFQDN VARCHAR(50) = '<your.domain.FQDN>';

IF OBJECT_ID('tempdb..#ADData') IS NOT NULL
  DROP TABLE #ADData;

-- Query AD for all known user accounts
CREATE TABLE #ADData(
    lanId               NVARCHAR(256),
    firstName           NVARCHAR(256),
    lastName            NVARCHAR(256),
    email               NVARCHAR(256),
    costcenter          NVARCHAR(256),  --Our AD implementation uses the optional extensionAttributes, defining 1 as cost center
    mobile              NVARCHAR(256),  --In @Query below, the name of this column is the same as the LDAP returned parameter, so no equate is applied in the query
    country             NVARCHAR(256),
    usnCreated          BIGINT         --uSNCreated is an INT64 object type
);

--Define the AD LDAP connection
IF NOT EXISTS(SELECT 1 FROM sys.servers WHERE name = 'ADSI') 
 EXEC master.dbo.sp_addlinkedserver
    @server = N'ADSI', 
    @srvproduct = N'Active Directory Services',
    @provider = N'ADsDSOObject', 
    @datasrc = @DomainFQDN;

DECLARE @Rowcount int;
DECLARE @LastCreatedFilter VARCHAR(200) = '';
DECLARE @ADrecordsToReturn smallint = 901;  --AD will not return more than 901 records per query (changed from 1000 at some point). You can set it to any value smaller to control the 'pagesize' of returned results

--Loop mechanics:
-- - 1st loop: @Rowcount will be NULL but we need to looping to commence, thus ISNULL function
-- - Intermediate loops: Rowcount will equal the max number of requested records, indicating there may be more to query from AD
--SELECT @LastCreatedFilter = 'AND usnCreated = ''''<yourvalue>'''''; --Used during debugging to iniate the loop at a certain value
--DECLARE @TestStop int = 1;  -- @TestStop is a debug option to halt processing. It needs to be commented in or out at 3 places
WHILE ISNULL(@Rowcount,@ADrecordsToReturn) = @ADrecordsToReturn --AND @TestStop < 4  --Un-comment the three @TestStop lines to run a reduced sample query of AD, dictated by the value provided on this line (# of loops to process before stopping)
 BEGIN

    DECLARE @Query VARCHAR (2000) = 
     '
        SELECT TOP ' + CONVERT(varchar(10),@ADrecordsToReturn) + '
            lanId               = SamAccountName,
            firstName           = GivenName,
            lastName            = sn,
            email               = mail,
            bsbcc               = extensionAttribute1,
            mobile,
            country             = c,
            usnCreated
         FROM OpenQuery
          (
            ADSI,
            ''
                SELECT SamAccountName, GivenName, sn, mail, extensionAttribute1, mobile, c, usnCreated
                 FROM ''''LDAP://' + @DomainFQDN + '''''
                 WHERE objectCategory = ''''Person''''
                 AND objectClass = ''''user''''
                 ' + @LastCreatedFilter + '
                 ORDER BY usnCreated
            ''
          )
     ';  
    INSERT INTO #ADData EXEC (@Query);
    SELECT @Rowcount = @@ROWCOUNT;
    SELECT @LastCreatedFilter = 'AND usnCreated > ' + LTRIM(STR((SELECT MAX(usnCreated) FROM #ADData)));
--PRINT @LastCreatedFilter;  --While debugging, used to determine progress
--SET @TestStop = @TestStop + 1;  -- @TestStop is a debug option to halt processing. It needs to be commented in or out at 3 places
 END;

EXEC master.dbo.sp_dropserver 'ADSI';

--Do something with the results...
SELECT lanId, email, costcenter, mobile, country, usnCreated FROM #ADData order by lanId;
person Barnabya    schedule 09.10.2017
comment
Таким образом, я обнаружил что-то очень странное, используя вышеуказанное решение: оно отлично работало с моим содержимым prod AD, но вылетало при чтении моего тестового AD. Затем, когда я попытался устранить неполадки, я обнаружил, что значения, возвращаемые для usnCreated запросом TSQL, были последовательными и не соответствовали значениям usnCreated, возвращаемым Get-ADUser. - person Barnabya; 11.10.2017
comment
Оказывается, решение Джона было неверным в одном отношении - uSNCreated из AD является значением INT64, поэтому требуется тип данных SQL 'BIGINT'. Мой код был исправлен и дополнен дополнительными комментариями и параметрами отладки. - person Barnabya; 11.10.2017

Все решения, основанные на атрибуте sAMAccountName, недействительны.
Если вам нужно быть уверенным, что вы получаете все результаты, чем вы должны построить запрос по-другому.
Атрибут sAMAccountName может содержать символы, отличные от букв / цифр, в начале строки атрибута.
Другая проблема заключается в том, что вам нужно добавить другую логику для обработки ситуации, когда результат для одного конкретного символа в первой позиции дает более 901 строки - например, из-за особого соглашения об именах.

Нашел другое более точное решение. AD можно «разбить» на разделы с помощью атрибута objectguid.

Этот фильтр

(&(objectclass=user)(!(objectclass=computer))
(objectguid<=\0f\ff\ff\ff\ff\ff\ff\ff\ff\ff\ff\ff\ff\ff\ff\ff\ff\ff\ff\ff)
(objectguid>=\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00))

приведет к созданию списка всех учетных записей пользователей с атрибутом объекта guid от
0x00000000000000000000000000000000 до 0x0fffffffffffffffffffffffffffffffff

следующий запрос в диапазоне будет от 0x10000000000000000000000000000000 до 0x1fffffffffffffffffffffffffffffffff ...

Всего с 16 запросами (объединение всех) вы можете прочитать всю AD, содержащую в общей сложности около 14000 записей. Для большего количества записей вам необходимо настроить поля, но это проще сделать, чем работать с символом на 2-й / 3-й / ... позиции sAMAccountName.

На objectguid хорошо то, что он генерируется случайным образом, так что приведенная выше схема разделения дает очень близкое количество строк для каждого из каждых 16 запросов (в моем случае у меня около 8500 учетных записей пользователей, и каждый запрос дает мне результат +/- 500 строк).

person jarabizna    schedule 04.02.2021