четверг, 7 июня 2012 г.

MSSQL : Расчет нарастающим итогом, без курсора

Обожаю классические задачи, вот и сегодня наткнулся на одну из них.


Суть была такова:


Сейчас переписываем систему оповещений о результатах выполнения процедур.

Результат кладется в таблицы и с использованием sp_send_dbmail рассылается адресатам.

Но есть одна проблема, рассылаемый объем не может превышать 1000000 байт (ограничение MS SQL).

Ладно ок, SQL не хочет, тогда надо вводить ограничение, а как?


Имперический метод себя не оправдает, так как объем вычисляется по суммарной длине строк подзапроса (про запрос см. MSDN) и ограничение по ширине строки тоже, тогда нам необходимо заранее рассчитать: а сколько байт будет наше сообщение.


Обратившись к статистике и финансовому учету (ага забавно как эти дисциплины помогают решать проблемы), можно вспомнить о таком термине как "нарастающий итог" (google расскажет о методе :) ).


Так давайте решим задачу "нарастающего итога"



Пусть у нас есть таблица только с одной ячейкой самого сообщения, например:


  1. CREATE TABLE msgbodytosend
  2.  (
  3.      mgsstring VARCHAR(max)
  4.  );
* This source code was highlighted with Source Code Highlighter.


Первый вариант - использование одного запроса.


Нам необходимо будет посчитать длины строк и их пронумеровать, в оригинале у нас нет уникального поля.

Дальше используя произведение (INNER JOIN) таблиц и суммирование длин, получаем необходимый результат.


Подзапросы я предпочитаю оборачивать в CTE выражения. На выходе получаем следующий код:
  1. WITH
  2. cte_msgbody AS (
  3.     SELECT row_number() (ORDER BY msgstring) row
  4.         , msgstring
  5.         , len(msgstring) msgstring_len
  6.     )
  7. SELECT
  8.     mb_l.row
  9. ,  mb_l.msgstring
  10. sum(mb_r.msgstring_len) Running_Total
  11. FROM cte_msgbody mb_l
  12. INNER JOIN cte_msgbody mb_r ON mb_l >= mb_r
  13. GROUP BY
  14. mb_l.row
  15.     , mb_l.msgstring
  16. HAVING sum(mb_r.msgstring_len) < 1000000
* This source code was highlighted with Source Code Highlighter.



Второй вариант разбить решение на составные части.

В данном случае мы убираем CTE выражение и делаем предварительный расчет во временной таблице.



  1. -- Удалим временную таблицу если вдруг она использовалась ранее в тексте
  2. IF object_id('tempdb..#msgbody')
  3.     DROP TABLE #msgbody;
  4.  
  5. -- И создадим ее за ново.
  6. CREATE TABLE #msgbody (
  7.     row INT identity(1, 1) PRIMARY KEY NOT NULL
  8.     , msgstring VARCHAR(max) NULL
  9.     , msgstring_len INT NULL
  10.     , running_total INT NULL
  11.     );
  12.  
  13. -- Заполняем таблицу
  14. INSERT INTO #msgbody (
  15.     msgstring
  16.     , msgstring_len
  17.     )
  18. SELECT msgstring
  19.     , len(msgstring)
  20. FROM msgbodytosend
  21.  
  22. -- Теперь сделаем расчет
  23. UPDATE mb
  24. SET running_total = (
  25.         SELECT sum(msgstring_len)
  26.         FROM #msgbody mb_i
  27.         WHERE mb_i.row <= mb.row
  28.         )
  29. FROM #msgbody mb
  30. -- Добавим небольшое ограничение, этот кусок можно улучшить
  31. WHERE (
  32.         SELECT sum(msgstring_len)
  33.         FROM #msgbody mb_i
  34.         WHERE mb_i.row <= mb.row
  35.         ) < 1000000
* This source code was highlighted with Source Code Highlighter.



Вот собственно и все, есть еще вариант решения этой задачи с использованием цикла и курсора, но мне они не кажутся столь интересными.








пятница, 1 июня 2012 г.

C# : Создание таблицы подстановки для элемента управления ComboBox, ListBoxили CheckedListBox в Windows Forms

Источник (rus)
Источник (eng)


Иногда полезно отображать данные в удобном для пользователя формате в форме Windows Forms, и при этом сохранять их в формате, требуемом в используемой программе. Например, в бланке заказа продуктов питания могут отображаться элементы меню по их имени в списке элементов. Однако таблица данных регистрации заказа будет содержать уникальные идентификаторы, представляющие продукты питания. В следующей таблице представлен пример хранения и отображения данных бланка заказа продуктов питания.


OrderDetailsTable




OrderIDItemIDQuantity
4085121
4086133


ItemTable




IDName
12Potato
13Chicken


В этом сценарии одна таблица, OrderDetailsTable, содержит фактические сведения, интересующие с точки зрения отображения и сохранения. Однако с целью экономии места они представлены в неудобном для восприятия виде. Другая таблица, ItemTable, включает только данные, связанные с представлением, а именно о том, какой код какому продукту соответствует, данные о фактических заказах продуктов в ней отсутствуют.


ItemTable связывается с элементом управления ComboBox, ListBox или CheckedListBox с помощью трех свойств. Свойство DataSource содержит имя данной таблицы. Свойство DisplayMember содержит столбец данных той таблицы, который необходимо отобразить в элементе управления (название продукта). Свойство ValueMember содержит столбец данных таблицы с сохраняемыми данными (идентификатор).


Таблица OrderDetailsTable связывается с элементом управления с помощью коллекции привязок, доступных через свойство DataBindings. При добавлении объекта привязки в коллекцию свойство элемента управления связывается с конкретным элементом данных (столбцом кодов) в источнике данных (таблице OrderDetailsTable). Когда в элементе управления сделан выбор, в данной таблицей сохраняются вводимые данные.


Создание таблицы подстановок

  1. Добавьте на форму элемент управления ComboBox, ListBox или CheckedListBox.

  2. Произведите подключение к источнику данных.

  3. Установите связь между данными в двух таблицах.

  4. Задайте следующие свойства. Они могут быть заданы в коде или в конструкторе.



    СвойствоПараметр
    DataSourceТаблица, в которой содержатся сведения о том, какому коду соответствует какой элемент. В вышеприведенном сценарии это ItemTable.
    DisplayMemberСтолбец таблицы источника данных, который необходимо отобразить в элементе управления. В вышеприведенном сценарии это "Name" (для задания в коде используйте кавычки).
    ValueMemberСтолбец таблицы источника данных, который содержит сохраняемую информацию. В вышеприведенном сценарии это "ID" (для задания в коде используйте кавычки).

  5. В процедуре вызовите метод Add класса ControlBindingsCollection для привязки свойства SelectedValue элемента управления к таблице, регистрирующей ввод формы. Кроме того, вместо кода это можно сделать в конструкторе с помощью свойства DataBindings элемента управления в окне Свойства. В вышеприведенном сценарии это OrderDetailsTable, а столбец — "ItemID".
    VB
    ListBox1.DataBindings.Add("SelectedValue", OrderDetailsTable, "ItemID")

    C#
    listBox1.DataBindings.Add("SelectedValue", OrderDetailsTable, "ItemID");

    J#
    listBox1.get_DataBindings().Add("SelectedValue", OrderDetailsTable, "ItemID");
















MSSQL : Корректное использование транзакций в хранимых процедурах

Источник

Задача:
Гарантировать корректное использование транзакционного механизма в случаях работы со сложными процедурами.

Результат:
Найденое решение позволяет гарантировать полный откат операций, особенно это актуально для сложных процедур, в которых может происходить вложеный вызов хранимых процедур.

В примере показано использование точки сохранения транзакции для отката изменений, сделанных хранимой процедурой, если выполнение активной транзакции запущено до выполнения хранимой процедуры.



USE AdventureWorks;
GO
IF EXISTS (SELECT name FROM sys.objects
           WHERE name = N'SaveTranExample')
    DROP PROCEDURE SaveTranExample;
GO
CREATE PROCEDURE SaveTranExample
    @InputCandidateID INT
AS
    -- Detect if the procedure was called
    -- from an active transaction and save
    -- that for later use.
    -- In the procedure, @TranCounter = 0
    -- means there was no active transaction
    -- and the procedure started one.
    -- @TranCounter > 0 means an active
    -- transaction was started before the 
    -- procedure was called.
    DECLARE @TranCounter INT;
    SET @TranCounter = @@TRANCOUNT;
    IF @TranCounter > 0
        -- Procedure called when there is
        -- an active transaction.
        -- Create a savepoint to be able
        -- to roll back only the work done
        -- in the procedure if there is an
        -- error.
        SAVE TRANSACTION ProcedureSave;
    ELSE
        -- Procedure must start its own
        -- transaction.
        BEGIN TRANSACTION;
    -- Modify database.
    BEGIN TRY
        DELETE HumanResources.JobCandidate
            WHERE JobCandidateID = @InputCandidateID;
        -- Get here if no errors; must commit
        -- any transaction started in the
        -- procedure, but not commit a transaction
        -- started before the transaction was called.
        IF @TranCounter = 0
            -- @TranCounter = 0 means no transaction was
            -- started before the procedure was called.
            -- The procedure must commit the transaction
            -- it started.
            COMMIT TRANSACTION;
    END TRY
    BEGIN CATCH
        -- An error occurred; must determine
        -- which type of rollback will roll
        -- back only the work done in the
        -- procedure.
        IF @TranCounter = 0
            -- Transaction started in procedure.
            -- Roll back complete transaction.
            ROLLBACK TRANSACTION;
        ELSE
            -- Transaction started before procedure
            -- called, do not roll back modifications
            -- made before the procedure was called.
            IF XACT_STATE() <> -1
                -- If the transaction is still valid, just
                -- roll back to the savepoint set at the
                -- start of the stored procedure.
                ROLLBACK TRANSACTION ProcedureSave;
                -- If the transaction is uncommitable, a
                -- rollback to the savepoint is not allowed
                -- because the savepoint rollback writes to
                -- the log. Just return to the caller, which
                -- should roll back the outer transaction.

        -- After the appropriate rollback, echo error
        -- information to the caller.
        DECLARE @ErrorMessage NVARCHAR(4000);
        DECLARE @ErrorSeverity INT;
        DECLARE @ErrorState INT;

        SELECT @ErrorMessage = ERROR_MESSAGE();
        SELECT @ErrorSeverity = ERROR_SEVERITY();
        SELECT @ErrorState = ERROR_STATE();

        RAISERROR (@ErrorMessage, -- Message text.
                   @ErrorSeverity, -- Severity.
                   @ErrorState -- State.
                   );
    END CATCH

четверг, 24 мая 2012 г.

Конвертирование видео G2M3/G2M4

Нашел наконец рецепт, позволяющий сконверитировать видео gotomeeting
для просмотра на linux системах.
Проблема этого рецепта в том что он (рецепт) под windows.
Суть следующая:

В комплект GoToMeeting изначально входит необходимый инструментарий
для кодирования в формат Windows Media.

Но начнем по порядку.

Допустим к вам попал файл abcdef.wmv - например запись видео
конференции (основное использование встречавшееся мне), и кодированное
в G2M3/G2M4 формате.

Если Вас будет интересовать только возмжность просмотра под Windows, то
предлагаю ограничиться первым пунктом, иначе читать все:


Шаг 0(ноль).

Запустить Windows. (Ну так пост же для Линуксоидов :) )


Шаг 1.

Необходимо скачать и установить кодек, это можно сделать посетив
страницу www.gotomeeting.com/codec
По сути жмите кнопку "Install GoToMeeting Codec" и следуйте инструкциям.

Шаг 2.

После установки открываете содержимое директории (папки) C:\Program
Files\Citrix\GoToMeeting\723
, где 723 - номер установленной версии
GoToMeeting, у вас это может быть другое число. В этой папке должен находиться файл g2mtranscoder.exe.
Этот файлик и есть наше "волшебное" средство, которое и позволит
преобразовать файл из G2M3 в честный Windows Media формат.

Шаг 3.

Чтобы не засорять папку с кодеком, копируем из него 2 файла в
отдельный каталог, нам будут нужны g2mtranscoder.exe и g2m.dll.
Я предлагаю "мусорить" в родном каталоге, "C:\Documents and
Settings\<Ваш Юзернэйм>\Мои документы\Мои видеозаписи\"


Шаг 4.

Копируем в этот же каталог нашу видео запись.

ВНИМАНИЕ!!! Рекомендую работать с копией, а не с оригиналом, так как
файл будет перезаписан!!!

Шаг 5.

Открываем терминал (коммандную строку).
Для этого нажимаем Win+R, в окне "выполнить" вводим cmd и нажимаем ENTER.

Шаг 6.

Переходим в каталог "C:\Documents and Settings\<Ваш Юзернэйм>\Мои
документы\Мои видеозаписи\"

Для этого выполним команду
cd "C:\Documents and Settings\<Ваш Юзернэйм>\Мои документы\Мои видеозаписи\"


Шаг 7.

Запускаем процесс конвертирования, выполнив команду
ВНИМАНИЕ!!! Исходный файл будет перезаписан!!!

g2mtranscoder.exe source=abcdef.wmv

Команда выполнится мгновенно, но это еще не все :)


Шаг 8.

Не пугаться, и не ругаться, а проверить, висит ли в Диспетчере Задач
Windows процесс g2mtranscoder.exe
Дело в том что программа запускается в фоновом режиме, и начинает
"кушать" процессорное время, и не много памяти 200-300 Мб
Эти признаки указывают на то что у нас "процесс пошел".


Шаг 9.

Дождаться завершения g2mtranscoder.exe и проверить новый файл abcdef.wmv


Дальше можно любым конвертером продолжить преобразование файла
например в кодек THEORA.

понедельник, 21 мая 2012 г.

Linux : Установка BloGTK на Ubuntu 10.04

Сегодня представлю вольный перевод статьи найденной в сети, на этот раз о хорошем инструменте для блогов типа BlogSpot.com. Рецепт подходит для ubuntu 10.04, которой и сам пользуюсь до сих пор.

BloGTK installation on Ubuntu 10.04

Установив BloGTK на Ubuntu 10.04 из Центра приложений, я попробовал запустить программу из меню (Приложения -> Интернет), но оно не запустилось :(. Я видел только на панели задач, что окно под названием "Запуск BloGTK" появляется, но ничего не отображается. Тогда я попытался запустить его из терминала, и это то, что произошло:


  1. $ blogtk
  2. Traceback (most recent call last):
  3. File "/usr/bin/blogtk", line 14, in <module>
  4. import gtkhtml2
  5. ImportError: No module named gtkhtml2

Видим, что отсутствует модуль Python, я попытался установить его вручную, но его не оказалось в репозитариях ubuntu:


$ sudo apt-get install python-gtkhtml2
[sudo] password for sorokin: 
Чтение списков пакетов... Готово
Построение дерева зависимостей       
Чтение информации о состоянии... Готово
Пакет python-gtkhtml2 недоступен, но упомянут в списке зависимостей другого пакета.
Это может означать, что пакет отсутствует, устарел, или доступен из источников, не упомянутых в sources.list
E: Для пакета python-gtkhtml2 не найдены кандидаты на установку

Затем я обнаружил, что BloGTK есть на Launchpad PPA, и попытался установить его оттуда:


$ sudo apt-get remove blogtk
$ sudo apt-add-repository ppa:jayreding/ppa
$ sudo apt-get update
$ sudo apt-get install blogtk

Затем я запустил вновь установленную программу (теперь он назывался blogtk2), и он открыл правильно.



p.s. Хочу добавить, клиент мне вполне понравился, но это по сути просто html редактор с подсветкой синтаксиса, ему очень сильно не хватает возможности добавления изображений, с загрузкой их в Piassa. Но это я могу сделать и через web-клиент.

суббота, 19 мая 2012 г.

MSSQL : Удаление не цифровых символов в строке

Всем привет, Сегодня хочу поделиться результатом изысканий по классической задаче, удаление всех не нужных символов. Если мы говорили бы о функциональном программировании, то тут все просто, например так:
  1. CREATE FUNCTION dbo.RemoveChars(@Input varchar(1000))
  2.     RETURNS VARCHAR(1000)
  3. BEGIN
  4.     DECLARE @pos INT
  5.     SET @Pos = PATINDEX('%[^0-9]%',@Input)
  6.     WHILE @Pos > 0
  7.     BEGIN
  8.         SET @Input = STUFF(@Input,@pos,1,'')
  9.         SET @Pos = PATINDEX('%[^0-9]%',@Input)
  10.     END
  11.     RETURN @Input
  12. END
Но, так как мы находимся в среде, SQL в моем случае MSSQL, то было найти решение не использующее скалярную функцию. Такое решение было найдено тут (http://social.msdn.microsoft.com/Forums/en-US/transactsql/thread/06eb934c-4082-4d12-ab9b-5fbe68855061/) Вот текст изначального примера:
  1. DECLARE @test TABLE(x VARCHAR(20));
  2. INSERT INTO @test (x)
  3.     SELECT '+91 (876) 098 6789' UNION ALL
  4.     SELECT '1-567-987-7655' UNION ALL
  5.     SELECT 'FR5-5105';
  6. WITH
  7.     Num1 (n) AS (SELECT 1 UNION ALL SELECT 1),
  8.     Num2 (n) AS (SELECT 1 FROM Num1 AS X, Num1 AS Y),
  9.     Num3 (n) AS (SELECT 1 FROM Num2 AS X, Num2 AS Y),
  10.     Num4 (n) AS (SELECT 1 FROM Num3 AS X, Num3 AS Y),
  11.     Nums (n) AS (SELECT ROW_NUMBER() OVER(ORDER BY n) FROM Num4),
  12.     Cleaner AS (
  13.         SELECT x
  14.         ,   (SELECT
  15.                 CASE
  16.                     WHEN SUBSTRING(x, n, 1) NOT LIKE '[^0-9]'
  17.                         THEN SUBSTRING(x, n, 1)
  18.                     ELSE ''
  19.                 END + ''
  20.             FROM Nums
  21.             WHERE n <= LEN(x)
  22.             FOR XML PATH('')) AS x_clean
  23.         FROM @test)
  24. SELECT x, x_clean
  25. FROM Cleaner;
  1. /*
  2. x x_clean
  3. -------------------- -------------
  4. +91 (876) 098 6789 918760986789
  5. 1-567-987-7655 15679877655
  6. FR5-5105 55105
  7. */
На мой взгляд просто замечательный подход, но что нам делать если такая задача будет повторяться?! Не переписывать же под каждую задачу этот кусок (мне надо было 8 полей очистить в одном единственном запросе) И тут на помощь приходят табличные функции
  1. CREATE FUNCTION fnt_RemoveNonNumericChars (@x varchar(50))
  2.     RETURN TABLE
  3. AS
  4. RETURN (
  5.     WITH
  6.         Num1 (n) AS (SELECT 1 UNION ALL SELECT 1),
  7.         Num2 (n) AS (SELECT 1 FROM Num1 AS X, Num1 AS Y),
  8.         Num3 (n) AS (SELECT 1 FROM Num2 AS X, Num2 AS Y),
  9.         Num4 (n) AS (SELECT 1 FROM Num3 AS X, Num3 AS Y),
  10.         Nums (n) AS (SELECT ROW_NUMBER() OVER(ORDER BY n) FROM Num4),
  11.         Cleaner AS (
  12. SELECT @x [x]
  13. ,    (SELECT
  14.         CASE
  15.             WHEN SUBSTRING(@x, n, 1) NOT LIKE '[^0-9]'
  16.                 THEN SUBSTRING(@x, n, 1)
  17.             ELSE ''
  18.         END + ''
  19.     FROM Nums
  20.     WHERE n <= LEN(@x)
  21.     FOR XML PATH('')) AS x_clean
  22.     )
  23. SELECT x, x_clean
  24. FROM Cleaner;
  25. )
И вот собственно вариант использования:
  1. DECLARE @test TABLE(x VARCHAR(20));
  2. INSERT INTO @test (x)
  3.     SELECT '+91 (876) 098 6789' UNION ALL
  4.     SELECT '1-567-987-7655' UNION ALL
  5.     SELECT 'FR5-5105';
  6. SELECT
  7.     t.x
  8. ,   vv.x
  9. ,   vv.x_clean
  10. FROM @test t
  11.     CROSS APPLY fnt_RemoveNonNumericChars(t.x) vv
И результат выполнения:
  1. t.x vv.x vv.x_clean
  2. -------------------- -------------------- -------------
  3. +91 (876) 098 6789 +91 (876) 098 6789 918760986789
  4. 1-567-987-7655 1-567-987-7655 15679877655
  5. FR5-5105 FR5-5105 55105
ВАЖНО!!! Если будете использовать вариант с XML имейте в виду, порядок цифр может перемешаться, наткнулся на эту проблему буквально на следующий день.
В принципе мне не очень нравится текущий вариант нумерации, но это уже лирика, ее легко можно поменять, это позволит сэкономить еще несколько тактов, особенно на больших числах.

четверг, 29 сентября 2011 г.

MSSQL : LDAP запрос.

Всем добрый день.
Рано или поздно вам возможно придется столкнуться с запросом к ActiveDirectory.
Собственно сам рецепт запроса прост:

Либо так:
  1. SELECT primaryGroupID,
  2.        primaryGroupToken,
  3.        employeeid,
  4.        sAMAccountName,
  5.        [name],
  6.        adspath,
  7.        objectGUID,
  8.        info
  9. FROM OPENQUERY (ADSI, 'SELECT name,adspath,objectGUID,info,primaryGroupID,primaryGroupToken,employeeid, sAMAccountName FROM ''LDAP://DC=MYDOMAIN,DC=RU'' WHERE objectCategory=''Group'' ORDER BY samAccountname');


Либо так:
  1. SELECT primaryGroupID,
  2.        primaryGroupToken,
  3.        employeeid,
  4.        sAMAccountName,
  5.        [name],
  6.        adspath,
  7.        objectGUID,
  8.        info
  9. FROM OPENQUERY (ADSI, N'<LDAP://DC=MORTON,DC=RU>;(&(objectClass=User)(objectCategory=Person)(|(sAMAccountName=a*)(sAMAccountName=b*)(sAMAccountName=c*)));primaryGroupID, primaryGroupToken,employeeid, sAMAccountName, name, adspath, objectGUID, info');
  10.  
  11.  


Но что делать если у вас в каталоге количество записей много? Хотя бы пара тысяч. Вот тут поможет мой рецептик, до нельзя простой.

  1.  
  2. declare @samAccountname varchar(64)
  3. declare @query varchar(4000)
  4. declare @rc int
  5.  
  6. if (object_id('tempdb..#usersgrps') is not null) drop table #usersgrps
  7.  
  8.         CREATE TABLE #usersgrps (
  9.             id INT IDENTITY,
  10.             primaryGroupID INT ,
  11.             primaryGroupToken INT ,
  12.             employeeid VARCHAR (15) ,
  13.             sAMAccountName VARCHAR (64) ,
  14.             username VARCHAR (1000),
  15.             adspath VARCHAR (1000),
  16.             isprocessed INT ,
  17.             sid VARBINARY (50),
  18.             info VARCHAR (200) ,
  19.             isgroup BIT
  20.         );
  21.  
  22. INSERT #usersgrps (primaryGroupID, primaryGroupToken, employeeid, sAMAccountName, username, adspath, isprocessed, sid, info, isgroup)
  23. SELECT TOP 900 primaryGroupID,
  24.                primaryGroupToken,
  25.                employeeid,
  26.                sAMAccountName,
  27.                [dbo].[f_string_ad_clear]([name]),
  28.                [dbo].[f_string_ad_clear](adspath),
  29.                0,
  30.                objectGUID,
  31.                [dbo].[f_string_ad_clear](info),
  32.                0
  33. FROM OPENQUERY(ADSI,
  34.         'SELECT name,adspath,objectGUID,info,primaryGroupID,primaryGroupToken,employeeid, sAMAccountName FROM ''LDAP://DC=MYDOMAIN,DC=RU'' WHERE objectCategory=''User'' ORDER BY samAccountname')
  35.  
  36. set @rc = @@rowcount
  37.  
  38. select @samAccountname = max(sAMAccountName)
  39. from #usersgrps
  40. where isgroup = 0
  41.  
  42. select @query = 'SELECT name,adspath,objectGUID,info,primaryGroupID,primaryGroupToken,employeeid, sAMAccountName FROM ''''LDAP://DC=MYDOMAIN,DC=RU'''' WHERE objectCategory=''''User'''' and samAccountname > '''''+@samAccountname+''''' ORDER BY samAccountname'
  43. print @query
  44. select @query = 'SELECT TOP 900 primaryGroupID, primaryGroupToken, employeeid, sAMAccountName, [dbo].[f_string_ad_clear]([name]), [dbo].[f_string_ad_clear](adspath), 0, objectGUID, [dbo].[f_string_ad_clear](info), 0 FROM OPENQUERY(ADSI, ''' + @query + ''')'
  45. print @query
  46.  
  47. while @rc > 0
  48.     begin
  49.         INSERT #usersgrps (primaryGroupID, primaryGroupToken, employeeid, sAMAccountName, username, adspath, isprocessed, sid, info, isgroup)
  50.         exec (@query )
  51.         set @rc = @@rowcount
  52.  
  53.         select @samAccountname = max(sAMAccountName)
  54.         from #usersgrps
  55.         where isgroup = 0
  56.  
  57.         select @query = 'SELECT name,adspath,objectGUID,info,primaryGroupID,primaryGroupToken,employeeid, sAMAccountName FROM ''''LDAP://DC=MYDOMAIN,DC=RU'''' WHERE objectCategory=''''User'''' and samAccountname > '''''+@samAccountname+''''' ORDER BY samAccountname'
  58.         print @query
  59.         select @query = 'SELECT TOP 900 primaryGroupID, primaryGroupToken, employeeid, sAMAccountName, [dbo].[f_string_ad_clear]([name]), [dbo].[f_string_ad_clear](adspath), 0, objectGUID, [dbo].[f_string_ad_clear](info), 0 FROM OPENQUERY(ADSI, ''' + @query + ''')'
  60.         print @query
  61.     end
  62.  
  63.  
  64.  
  65. INSERT #usersgrps (primaryGroupID, primaryGroupToken, employeeid, sAMAccountName, username, adspath, isprocessed, sid, info, isgroup)
  66. SELECT TOP 900 primaryGroupID,
  67.                primaryGroupToken,
  68.                employeeid,
  69.                sAMAccountName,
  70.                [dbo].[f_string_ad_clear]([name]),
  71.                [dbo].[f_string_ad_clear](adspath),
  72.                0,
  73.                objectGUID,
  74.                [dbo].[f_string_ad_clear](info),
  75.                1
  76. FROM OPENQUERY(ADSI,
  77.         'SELECT name,adspath,objectGUID,info,primaryGroupID,primaryGroupToken,employeeid, sAMAccountName FROM ''LDAP://DC=MYDOMAIN,DC=RU'' WHERE objectCategory=''Group'' ORDER BY samAccountname')
  78.  
  79. set @rc = @@rowcount
  80.  
  81. select @samAccountname = max(sAMAccountName)
  82. from #usersgrps
  83. where isgroup = 1
  84.  
  85. select @query = 'SELECT name,adspath,objectGUID,info,primaryGroupID,primaryGroupToken,employeeid, sAMAccountName FROM ''''LDAP://DC=MYDOMAIN,DC=RU'''' WHERE objectCategory=''''Group'''' and samAccountname > '''''+@samAccountname+''''' ORDER BY samAccountname'
  86. print @query
  87. select @query = 'SELECT TOP 900 primaryGroupID, primaryGroupToken, employeeid, sAMAccountName, [dbo].[f_string_ad_clear]([name]), [dbo].[f_string_ad_clear](adspath), 0, objectGUID, [dbo].[f_string_ad_clear](info), 1 FROM OPENQUERY(ADSI, ''' + @query + ''')'
  88. print @query
  89.  
  90. while @rc > 0
  91.     begin
  92.         INSERT #usersgrps (primaryGroupID, primaryGroupToken, employeeid, sAMAccountName, username, adspath, isprocessed, sid, info, isgroup)
  93.         exec (@query )
  94.         set @rc = @@rowcount
  95.  
  96.         select @samAccountname = max(sAMAccountName)
  97.         from #usersgrps
  98.         where isgroup = 1
  99.  
  100.         select @query = 'SELECT name,adspath,objectGUID,info,primaryGroupID,primaryGroupToken,employeeid, sAMAccountName FROM ''''LDAP://DC=MYDOMAIN,DC=RU'''' WHERE objectCategory=''''Group'''' and samAccountname > '''''+@samAccountname+''''' ORDER BY samAccountname'
  101.         print @query
  102.         select @query = 'SELECT TOP 900 primaryGroupID, primaryGroupToken, employeeid, sAMAccountName, [dbo].[f_string_ad_clear]([name]), [dbo].[f_string_ad_clear](adspath), 0, objectGUID, [dbo].[f_string_ad_clear](info), 1 FROM OPENQUERY(ADSI, ''' + @query + ''')'
  103.         print @query
  104.     end
  105.  
  106. select * from #usersgrps