Биты и байты.

Биты и байты.

четверг, 11 сентября 2014 г.

Sharepoint изнутри.


Как выглядит  SharePoint со стороны БД?  Ну как то так..
Для человека который первый раз залез под капот тут не все так очевидно как хотелось бы, поэтому попробуем немного разобраться в этом вопросе.
Предупреждение: Любые изменения в SharePoint через СУБД могут нарушить структуру базы и привести к тому что ваш SharePoint перестанет работать.
Все операции выполняются на свой страх и риск, поэтому перед тем как выполнять в боевой базе необходимо детально проверить скрипты на тестовой.

Если немного приглядеться  можно выделить основные таблицы SharePoint и связи между ними

AllLists
Таблица которая содержит списки (библиотеки) для каждого сайта.
ОСНОВНАЯ таблица содержит мета информацию о каждом элементе в списке.
Содержит информацию обо всех документах (и всех элементах списка) для каждой библиотеки и списка.
DocStreams
Содержит сами документы в BLOB поле Content varbinary (max).
NameValuePair
Индексируемые поля для списков.
AllUserDataJunctions
Содержит множественный выбор значений полей.
ContentTypes 
Содержит описание используемых типов
Features 
 Содержит информацию об активированных дополнениях для каждой коллекции сайтов или сайта.
 Содержит информацию о  всех коллекциях сайтов в базе.
 Содержит информацию о сайтах (webs) в каждой коллекции сайта.
 Содержит информацию о пользователях.
Groups
 Содержит информацию о группах пользователей в каждой коллекции сайтов.
Roles
 Содержит роли (уровни доступа) для каждого сайта.
 Содержит информацию о членах групп.
RoleAssignment
 Содержит информацию обо всех пользователях или группах SharePoint которые присвоены ролям.
SchedSubscriptions
 Содержит информацию обо всех настроенных подписках по расписанию
ImmedSubscriptions
 Таблица содержащая информацию обо всех непосредственных подписках.

1.  Информацию о списках Sharepoint  можно получить из таблицы AllLists
SELECT 
       List.tp_SiteId,
       List.tp_WebId,
       List.tp_ID ,   --идентификатор списка
       tp_BaseType ,  --тип списка: Generic List: 0, Document Library: 1, Discussion Board:3, Survey:4,Issue:5
       tp_ServerTemplate, --ex:100-Generic list,101-Document library,102-Survey,103-Links list,104-Announcements list,105-Contacts list,106-Events list, 107-Tasks list,108-Discussion board,109-Picture library
       list.tp_Title , --название списка
       list.tp_Fields ,  --описание полей в библиотеке
       list.tp_ContentTypes  --описание типов в библиотеке
  FROM alllists LIST WHERE lower(LIST.tp_Title) LIKE  N'%входящие%2011%' and (list.[tp_DeleteTransactionId] = 0x )

Поля tp_Fields, tp_ContentTypes  хранятся в сжатом двоичном виде, чтобы распаковать их достаточно использовать функцию ниже


private static string getXmlFromTpFields(byte[] tpFields)
        {
            using (var memoryStream = new MemoryStream(tpFields))
            {
                // пропускаем первые 14 байтов
                for (var index = 0; index <= 13; index++)
                    memoryStream.ReadByte();

                var deflateStream = new DeflateStream(memoryStream, CompressionMode.Decompress);

                using (var destination = new MemoryStream())
                {
                    deflateStream.CopyTo(destination);

                    var streamReader = new StreamReader(destination);
                    destination.Position = 0;
                    return streamReader.ReadToEnd();
                }
            }
        }


2. Где хранится вся инфа ? Ответ прост  в AllUserData

Для элементов ведется история изменений,поэтому нужно правильно определять версию с которой необходимо работать по полю  
[tp_IsCurrentVersion] = CONVERT(bit,1)  и тип контента по полю tp_ContentTypeId

tp_ID[int]
Идентификатор записи
tp_GUID[uniqueidentifier]
GUID записи
tp_ListId [uniqueidentifier]
GUID списка. Содержится в поле 'tp_ID' в таблице 'AllLists'
tp_SiteId [uniqueidentifier]
GUID сайта. Содержится в поле  'Id' в таблице 'AllSites'
tp_RowOrdinal [tinyint]
индекс строки в элементе, начинается с 0
tp_ContentTypeID
Тип  элемента, унаследованный. Содержится в таблице ContentType.
tp_Author [int]
Пользователь создавший элемент
tp_Editor [int]
Последний пользователь редактировавший запись
tp_Modified [datetime]
Время изменения записи
tp_Created [datetime]
Время создания записи
tp_DeleteTransactionId [varbinary]
Удаленные элементы в корзине. Для не удалённых элементов содержит  0x
tp_IsCurrentVersion [bit]
Значение отражает текущую версию записи, для среди истории изменений. Для текущей версии элемента содержит значение 1
nvarchar1..64 [nvarchar]
Столбцы navarchar1,navarchar2….navarchar64 stores хранят в себе значения типа nvarchar, всего 64 поля, если не столбец не содержит значение тогда NULL
ntext1..32 [ntext]
Столбцы ntext1 to ntext32 stores хранят в себе значения типа ntext, всего 32 поля, если не столбец не содержит значение тогда NULL
bit1..16[bit]
хранят в себе значения типа bit, всего 16 полей, если не столбец не содержит значение тогда NULL
datetime1..8 [datetime]
хранят в себе значения типа datetime, всего 8 полей, если не столбец не содержит значение тогда NULL
float1..12 [float]
хранят в себе значения типа float, всего 12 полей, если не столбец не содержит значение тогда NULL
int1..16 [int]
хранят в себе значения типа int, всего 16 полей, если не столбец не содержит значение тогда NULL
sql_variant1..8 [sql_variant]
хранят в себе значения типа sql_variant, всего 8 полей, если не столбец не содержит значение тогда NULL

Простой запрос выглядит так по все элементам которые не удалены и не в корзине и последняя версия

SELECT  top 100 DAT.*
FROM [WSS_Content_RBS].dbo.[AllUserData] DAT (nolock)
INNER JOIN [WSS_Content_RBS].dbo.alllists LIST (nolock) ON LIST.tp_ID = DAT.tp_ListId and LIST.tp_SiteId = DAT.tp_SiteId
AND LIST.tp_SiteId = 'CB5AF988-C67A-4605-91EB-68F846A1D596'
AND lower(LIST.tp_Title) LIKE  N'%входящие%[0-9]%' 
WHERE cast(DAT.tp_ContentTypeId as binary(19)) = 0x0120D5200055418DF2944E344D9BB430577FCA нужный тип элемента
    and  DAT.[tp_IsCurrentVersion] = CONVERT(bit,1)   --текущая версия
    and  DAT.[tp_DeleteTransactionId] = 0x   -- не в корзине
    and  DAT.[tp_RowOrdinal]  =-- индекс строки, документ может состоять из нескольких строк
    and  DAT.[tp_CalculatedVersion]  =--0 если версия текущая

Столбец  tp_Columnset [XML] COLUMN_SET FOR ALL_SPARSE_COLUMNS NULL
XML представление всех заполненных разреженных SPARSE  столбцов в таблице AllUserdata
по аналогии с вычисляемыми столбцами физически не хранится в базе, отличается от вычисляемого тем что можно изменять напрямую через UPDATE

SPARSE столбцы это обычные столбцы оптимизированные для хранения пустых значений.


3. Таблица Content Type , SharePoint  Content Type ID GUID's
Данные в таблице AllUserData  разделяются по типам, типы хранятся в  двоичном виде и создаются наследованием от родительского типа.
Наследование выполняется в виде связи строк с разделителем '00′.  Форма записи выглядит так  '0x[baseID]00[GUID (new id)]00[и так далее]'.



В качестве примера наследовать будем от базового типа  0×01. Наследуемый тип будет выглядеть например так  0×010100E0D3BD7003DA22419C52027ABF151.
Созданный новый тип будет начинаться с элемента (0×01) затем идет разделитель '00′ и  GUID присвоенный новому типу.
Чтобы найти родительский тип достаточно разделить строку по разделителю  '00′ и пройти по цепочке наследования.

Полезные запросы:
--найти нужный тип
SELECT * FROM [dbo].[ContentTypes]  t WHERE lower(t.resourcedir) like N'%набор%'

--выбрать элементы по типу
SELECT TOP 100 * FROM AllUserData AS aud WHERE cast(aud.tp_ContentTypeId as binary(19)) = 0x010100A71AE871CC36F34AB8F57FA290225A27


-- найти доччерние типы и в каких списках используется
DECLARE @SiteId UNIQUEIDENTIFIER, @Class BIT,  @ContentTypeId tContentTypeId, @IsFieldId bit
--настраиваем параметры
SELECT  @SiteId = 'CB5AF988-C67A-4605-91EB-68F846A1D596' , @Class = 1 /* Needs always 1 means is a ContentType */ ,  @ContentTypeId = 0x0120D520 , @IsFieldId = 0

--найти все дочерние ContentTypeId
select ContentTypeId,Scope,[Version],ResourceDir,SolutionId,IsFromFeature
from  ContentTypes  WITH (INDEX=ContentTypes_SiteClassCTId, FORCESEEK)
where SiteId = @SiteId AND Class = @Class AND
ContentTypeId <= (@ContentTypeId + 0xFF) AND ContentTypeId > @ContentTypeId

--в каком списке используется этот ContentTypeId
select W.FullUrl as Url ,L.tp_Title as ListTitle
from ContentTypeUsage WITH (INDEX=ContentTypeUsage_SiteIsFClassCTIdWeb, FORCESEEK)
Join Lists L on L.tp_ID = ContentTypeUsage.ListId
Join Webs W on W.Id = ContentTypeUsage.WebId
Where ContentTypeUsage.SiteId = @SiteId AND ((@IsFieldId IS NULL AND IsFieldId IS NULL) OR @IsFieldId=IsFieldId) AND
Class = @Class AND
ContentTypeId <= (@ContentTypeId + 0xFF) AND
ContentTypeId >= @ContentTypeId

Также можно узнать типы через PowerShell, следующим скриптом
1
$site = Get-SPSite http://mysite.myfarm.com

2
$web = $site.RootWeb

3
ForEach($ctype in $web.ContentTypes){write-host $ctype.Name": "$ctype.ID}

Ниже результат работы скрипта

4. Супер мега индексы в Sharepoint - Таблица NameValuePair
Проиндексировав столбец в Sharepoint можно легко увидеть как заполняется таблица NameValuePair[_{SQLCollation}] индексируемыми полями. Давайте взглянем на схему таблицы.


SiteId: GUID сайта
WebId: GUID подсайта
ListId: GUID листа
ItemId: Идентификатор tp_id из AllUserData
Level: уровень элемента
FieldId: GUID поля
Value: Значение столбца

Что важно знать, каждое значение индексируемого поля хранится  этой таблице  и должно быть обновлено вместе с таблицей AllUserdata.
На каждое индексируемое поле создается одна запись если 3 поля индексируются создается 3 записи и тд

Так можно посмотреть список проиндексированных столбцов
SELECT al.tp_Title ,  nvp.FieldId , COUNT(*)
FROM NameValuePair AS nvp INNER JOIN alllists al ON  nvp.SiteId = al.tp_SiteId and nvp.WebId = al.tp_WebId AND nvp.ListId = al.tp_ID
GROUP BY al.tp_Title , nvp.FieldId
ORDER BY al.tp_Title


5. Где и как хранятся сами документы.
В таблице AllDocs хранятся описания связанных документов, сами документы хранятся в таблице

SELECT top 100 UserData.* , t1.* FROM  AllUserData AS UserData 
 INNER LOOP JOIN AllDocs AS t1
            ON (UserData.[tp_RowOrdinal] = 0 )
            AND ( t1.SiteId=UserData.tp_SiteId )
            AND (t1.SiteId = 'CB5AF988-C67A-4605-91EB-68F846A1D596' )
            AND (t1.ParentId = UserData.tp_ParentId)
            AND (t1.Id = UserData.tp_DocId)
            AND ((UserData.tp_Level = 1 OR  UserData.tp_Level =255 ) )
            AND (t1.Level = UserData.tp_Level)
            AND ( UserData.[tp_IsCurrentVersion] = CONVERT(bit, 1) )
            AND (UserData.[tp_CalculatedVersion] = 0 )
            AND (UserData.[tp_DeleteTransactionId] = 0x )
            AND (t1.[DeleteTransactionId] = 0x )

В таблице DocStreams хранятся сами документы в двоичном виде в поле Content
SELECT * FROM DocStreams d
WHERE d.SiteId = 'CB5AF988-C67A-4605-91EB-68F846A1D596'
  and d.DocId = 'C97A9C4C-E37B-492A-BD92-11A7290EE942' --Идентификатор документа tp_DocId из Alluserdata


Чтобы экспортировать документ в файл можно воспользоваться утилитой командной строки bcp, подробнее тут

bcp "SELECT Content FROM AllDocStreams WHERE ID = '05AE4037-782E-4BA6-A924-FA3EEDA0DCCE'"
queryout Sean_Excell_file.xlsx -S ServerName -T
-d My_Content_Database -f Path_To_Format_File.fmt

Если не получится можно скриптом ниже
sp_configure 'show advanced options', 1;
GO
RECONFIGURE;
GO
sp_configure 'Ole Automation Procedures', 1;
GO
RECONFIGURE;
GO
--Now that you have access to the file system you can run the following script. Extracting files from database:

--DECLARING THE CURSOR, THIS IS THE ITEMS YOU WANT TO RUN THE EXTRACTING ON
DECLARE CURSOR_Images CURSOR FOR (SELECT Id    FROM [dbo].[AllDocs] t1 where t1.LeafName = 'TT499964.pdf')

--DECLARE THE TYPE OF THE COLUMN YOU SELECTED ABOVE
DECLARE @ImageID uniqueidentifier;

--START THE CURSOR AND RUN THROUGH ALL THE ITEMS IN CURSOR_Images
OPEN CURSOR_Images
FETCH NEXT FROM CURSOR_Images INTO @ImageID
WHILE (@@FETCH_STATUS <> -1)
BEGIN
  --DECLARE THE VARIABLE THAT WILL KEEP THE BINARY DATA
  DECLARE @ImageData varbinary(MAX);
  --SELECT THE BINARY DATA AND SET IT TO @ImageData.  THE BINARY DATA FOR ALLDOCS ARE LOCATED IN ALLDOCSTREAMS
  --AND THE ID IS THE SAME AS IN ALLDOCS
  SELECT @ImageData = (SELECT TOP 1 CONVERT(varbinary(MAX), Content, 1) FROM [dbo].[AllDocStreams] WHERE Id = @ImageID ORDER BY InternalVersion ASC);

  --GET THE LOCATION OF THE DIRECTORY THE FILES WAS SAVED IN AND CHANGE REPLACE THE / WITH \ TO BE USED IN FILESYSTEM
  DECLARE @DIRPATH NVARCHAR(MAX);
  SET @DIRPATH = REPLACE((SELECT  DirName FROM [dbo].[AllDocs]  WHERE Id = @ImageID),'/','\');

  --SET THE PATH
  DECLARE @Path nvarchar(1024);
  SELECT @Path = 'C:\Export\' + @DIRPATH + '\';

  --CREATE THE DIRECTORIES
  EXEC master.dbo.xp_create_subdir @Path;

  --GET THE FILE NAME OF THE FILE FROM LEAFNAME
  DECLARE @Filename NVARCHAR(1024);
  SELECT @Filename = (SELECT LeafName FROM [dbo].[AllDocs] WHERE id = @ImageID);

  --SET THE FULL PATH FOR WHERE THE FILES WILL BE STORED
  DECLARE @FullPathToOutputFile NVARCHAR(2048);
  SELECT @FullPathToOutputFile = @Path + '\' + @Filename;

  --SAVE THE FILE TO THE FILE SYSTEM
  DECLARE @ObjectToken INT
  EXEC sp_OACreate 'ADODB.Stream', @ObjectToken OUTPUT;
  EXEC sp_OASetProperty @ObjectToken, 'TYPE', 1;
  EXEC sp_OAMethod @ObjectToken, 'OPEN';
  EXEC sp_OAMethod @ObjectToken, 'WRITE', NULL, @ImageData;
  EXEC sp_OAMethod @ObjectToken, 'SaveToFile', NULL, @FullPathToOutputFile, 2;
  EXEC sp_OAMethod @ObjectToken, 'Close';
  EXEC sp_OADestroy @ObjectToken;

  --LOOP TO THE NEXT ENTRY IN THE CURSOR
  FETCH NEXT FROM CURSOR_Images INTO @ImageID
END
CLOSE CURSOR_Images
DEALLOCATE CURSOR_Images


5. Запрос для представления в общем виде выглядит так

SELECT
   DISTINCT t10.[tp_Ordinal],
   t6.[tp_ID] AS c21,
   t11.[nvarchar10] AS c21c18,
   t9.*
FROM
   (
      SELECT
      TOP(@NUMROWS)
       *
      FROM
      --таблица индексируемых полей для списка
      NameValuePair AS NVP
   INNER LOOP JOIN
      --основная запись по элементу списка
      AllUserData AS UserData ON UserData... = NVP...  -- (AND NVP.SiteId = @SITEID AND NVP.ListId = @LISTID  AND NVP.FieldId = @L7)
      --информация по документу
      INNER LOOP JOIN AllDocs AS t1 ON t1... = UserData...  --t1.Id = UserData.tp_DocId
      --раскрываем значения полей
      LEFT OUTER LOOP JOIN AllUserData AS t2  ON t2...  = UserData...  --UserData.[tp_CheckoutUserId]=t2.[tp_ID]
      LEFT OUTER LOOP JOIN AllUserData AS t3  ON t3...  = UserData...  --UserData.[int2]=t3.[tp_ID]
      LEFT OUTER LOOP JOIN AllUserData AS t4  ON t4...  = UserData..   --UserData.[int8]=t4.[tp_ID]
      LEFT OUTER LOOP JOIN AllUserData AS t5  ON t5...  = UserData..   --UserData.[int6]=t5.[tp_ID]
      LEFT OUTER LOOP JOIN AllUserData AS t7  ON t7...  = UserData..   --UserData.[int7]=t7.[tp_ID]
      LEFT OUTER LOOP JOIN AllUserData AS t8  ON t8...  = UserData..   --UserData.[int13]=t8.[tp_ID]
      WHERE 
        --фильтры по пользователям, спискам и тд
     ) AS t9   
     
      --раскрываем множественные значения  для документа                             
      LEFT OUTER JOIN AllUserDataJunctions AS t10  ON t10... = t9... -- t9.[tp_DocId] = t10.[tp_DocId]
      LEFT OUTER JOIN AllUserDataJunctions AS t6   ON t6... =  t9...  AND (t10.[tp_Ordinal] = t6.[tp_Ordinal])  AND (t6.[tp_FieldId] = @L12)
      --получаем названия для множественных значений
      LEFT OUTER LOOP JOIN AllUserData AS t11 ON ... t11.[tp_Id] = t6.[tp_ID]
ORDER BY  t9.tp_Modified  ASC , t9.tp_ID  ASC , t10.[tp_Ordinal]  ASC

Получить его можно с помощью профайлера достаточно   выставить фильтр по GUID этого списка
SELECT  LIST.tp_ID , list.tp_Title FROM alllists LIST WHERE lower(LIST.tp_Title) LIKE  N'%входящие%2011%'

И открыть страницу SharePoint с нужным списком, в профайлере получим требуемый динамический запрос exec sp_executesql, который при желании можно отформатировать и придать ему читабельный вид

5. Полезные запросы

1) Query to get all the top level site collections
SELECT SiteId AS SiteGuid, Id AS WebGuid, FullUrl AS Url, Title, Author, TimeCreated
FROM dbo.Webs
WHERE (ParentWebId IS NULL)

2) Query to get all the child sites in a site collection
SELECT SiteId AS SiteGuid, Id AS WebGuid, FullUrl AS Url, Title, Author, TimeCreated
FROM dbo.Webs
WHERE (NOT (ParentWebId IS NULL))

3) Query to get all the SharePoint groups in a site collection
SELECT dbo.Webs.SiteId, dbo.Webs.Id, dbo.Webs.FullUrl, dbo.Webs.Title, dbo.Groups.ID AS Expr1, 
dbo.Groups.Title AS Expr2, dbo.Groups.Description
FROM dbo.Groups INNER JOIN
dbo.Webs ON dbo.Groups.SiteId = dbo.Webs.SiteId

4) Query to get all the users in a site collection
SELECT dbo.Webs.SiteId, dbo.Webs.Id, dbo.Webs.FullUrl, dbo.Webs.Title, dbo.UserInfo.tp_ID, 
dbo.UserInfo.tp_DomainGroup, dbo.UserInfo.tp_SiteAdmin, dbo.UserInfo.tp_Title, dbo.UserInfo.tp_Email
FROM dbo.UserInfo INNER JOIN
dbo.Webs ON dbo.UserInfo.tp_SiteID = dbo.Webs.SiteId

5) Query to get all the members of the SharePoint Groups
(i) All the members in all the SharePoint Groups in a web application.
SELECT dbo.Groups.ID, dbo.Groups.Title, dbo.UserInfo.tp_Title, dbo.UserInfo.tp_Login
FROM dbo.GroupMembership INNER JOIN
dbo.Groups ON dbo.GroupMembership.SiteId = dbo.Groups.SiteId INNER JOIN
dbo.UserInfo ON dbo.GroupMembership.MemberId = dbo.UserInfo.tp_ID

(ii)All the members in each SharePoint Group in a web application.
SELECT dbo.Groups.ID, dbo.Groups.Title, dbo.UserInfo.tp_Title, dbo.UserInfo.tp_Login
FROM dbo.GroupMembership with (nolock) 
INNER JOIN dbo.Groups with (nolock) 
ON dbo.GroupMembership.GroupId = Groups.ID 
INNER JOIN dbo.UserInfo 
ON dbo.GroupMembership.MemberId = dbo.UserInfo.tp_ID

6) Query to get all the sites where a specific feature is activated
SELECT dbo.Webs.Id AS WebGuid, dbo.Webs.Title AS WebTitle, dbo.Webs.FullUrl AS WebUrl, dbo.Features.FeatureId, 
dbo.Features.TimeActivated
FROM dbo.Features INNER JOIN
dbo.Webs ON dbo.Features.SiteId = dbo.Webs.SiteId AND dbo.Features.WebId = dbo.Webs.Id
WHERE (dbo.Features.FeatureId = '00DGEA71-E2FE-42de-9DF3-A44065BE0104')

7) Query to get all the users assigned to roles
SELECT dbo.Webs.Id, dbo.Webs.Title, dbo.Webs.FullUrl, dbo.Roles.RoleId, dbo.Roles.Title AS RoleTitle, 
dbo.UserInfo.tp_Title, dbo.UserInfo.tp_Login
FROM dbo.RoleAssignment INNER JOIN
dbo.Roles ON dbo.RoleAssignment.SiteId = dbo.Roles.SiteId AND 
dbo.RoleAssignment.RoleId = dbo.Roles.RoleId INNER JOIN
dbo.Webs ON dbo.Roles.SiteId = dbo.Webs.SiteId AND dbo.Roles.WebId = dbo.Webs.Id INNER JOIN
dbo.UserInfo ON dbo.RoleAssignment.PrincipalId = dbo.UserInfo.tp_ID

8) Query to get all the SharePoint groups assigned to roles
SELECT dbo.Webs.Id, dbo.Webs.Title, dbo.Webs.FullUrl, dbo.Roles.RoleId, dbo.Roles.Title AS RoleTitle, 
dbo.Groups.Title AS GroupName
FROM dbo.RoleAssignment INNER JOIN
dbo.Roles ON dbo.RoleAssignment.SiteId = dbo.Roles.SiteId AND 
dbo.RoleAssignment.RoleId = dbo.Roles.RoleId INNER JOIN
dbo.Webs ON dbo.Roles.SiteId = dbo.Webs.SiteId AND dbo.Roles.WebId = dbo.Webs.Id INNER JOIN
dbo.Groups ON dbo.RoleAssignment.SiteId = dbo.Groups.SiteId AND 
dbo.RoleAssignment.PrincipalId = dbo.Groups.ID

9)количество используемых терминов
SELECT  g.Name    AS GroupName ,s.Name TermSetName,  m.[TermSetId] , s.UniqueId,
        COUNT( DISTINCT m.[TermId]) CNT
FROM [dbo].[ECMUsedTerms] m
LEFT JOIN [dbo].[ECMTerm] t ON t.Id = m.TermId AND t.PartitionId = m.PartitionId
LEFT JOIN  [dbo].[ECMTermSet] s (NOLOCK) ON s.UniqueId = m.TermSetId and m.PartitionId = s.PartitionId
LEFT OUTER JOIN dbo.ECMGroup g (NOLOCK)  ON  g.Id = s.GroupId AND g.PartitionId = s.PartitionId
WHERE t.IsDeleted = 0 AND t.MergedIdList IS NULL

GROUP BY g.Name, s.Name ,  m.[TermSetId]  , s.UniqueId

6. Получить GUID всех полей можно из файла manifest.xml на сервере SharePoint.

P.S: Для изучения структуры SharePoint очень пригодится утилита SharePoint Manager
После запуска приложение автоматически найдет локальную ферму и подключится к ней.
Программа позволяет просматривать каждый сайт вашей локальной фермы и все свойства объектов. Также можно изменять значения некоторых свойств. Важно, что все сделанные изменения приступают в силу после нажатия на кнопку Сохранить.
Кроме просмотра и изменения свойств для некоторых типов объектов (поля, типы контента и т.д.) можно посмотреть их схему (рис. 2)
Также можно активировать и деактивировать возможности (Features).
Еще один способ применения – понимание структуры фермы SharePoint.
К недостаткам можно отнести отсутствие возможности подключения к удаленной ферме.

About