Доступ к динамическим группам со стороны SQL Server'a

Добрый день!

Есть задача фоновой обработки данных (через триггеры, джобы), при этом выборка данных для обработки должна браться из некой динамической группы. Вопрос - как это можно сделать? В таблице групп есть поле FilterData. Как я понял, именно здесь и хранится условие выборки. Но, они в специфическом внутреннем формате Terrasoft. Вопрос - как можно на стороне SQL прочитать и распарсить условие фильтра?

Спасибо

Нравится

35 комментариев

Вариант - получаем выборку на основании динамической группы в скрипте TS аналогично как это сделано в разделе рассылка при прикреплении контактов из динамической группы. Потом делаем с ней что угодно - во временную таблицу ее, где триггер подхватит, в хранимку и так далее.
(+) не надо мучаться с парсером фильтра на уровне sql
(-) не будет работать без запуска/какого либо шевеления клиента Terrasoft (который формирует выборку)

Спасибо. Обозначенный Вами вариант заслуживает внимания (и есть таки у меня задача, в решении которой он очнь даже поможет), но, в данном случае необходимость работы в бекграунде (без запуска клиента) - критична :confused:

"bayborodin" написал:работы в бекграунде

Точно не знаю формат FilterData, но наверное ж это xml, который хранится блобом.
Если да и вы хотите именно делать это из SQL Server, то придется в джобе из SQL'a пользовать activeX для работы с xml ... сам никогда не пробовал, но видел код.
Я бы лучше написал какого демона, который в тихую раз в n минут хватал бы данные из FilterData и делал бы все, что надо. К слову, можно даже написать на террасфоте (вызвать окно с параметров, отключить таймаут выплнения скрипта и там уже все крутить), но тут надо учитывать события свала, тоесть наш террасофтный демон может упасть и ничего не сказать, а свое самописное можно подписать на тотальный свал и может какое письмо кому отослать.
Или вот еще вариант. Фиксировать где-то в БД признак, мол "ИДЕТ РАБОТА" с датой и временем(эту дату и время будет обновлять демон при каждой итеррации). Каждый клиент террасофта, на том же таймере что и напоминания, например, шлет легкий запрос на условие дата "ИДЕТ РАБОТА" < текущая - n минут. И если меньше - то каждому пользователю (ну или только админу) сигнализирует мол "ВСЕ УПАЛО, АДМИН, ПОМОГИ!!!11".

А точно ли FilterData - это XML в BLOB? Не могу найти пример, где бы бралось значение FilterData и из него извлекись условия фильтра :(

"bayborodin" написал:пример, где бы бралось значение FilterData и из него извлекись условия фильтра

wnd_ContactInMassMailGridAreaScript, но там хитро и не совсем так - создается объект FilterBuilder и потом им пользуются для фильтрации датасета, то есть непосредственно содержимое из него никто не извлекает и не парсит))

Да... печально как-то :(

"bayborodin" написал:в бекграунде (без запуска клиента) - критична

А если клиент сам будет запускаться? Если сервер с ОС windows, то на нем можно поставить клиент Terrasoft или на какой-то пользовательской машине, которую не выключают, можно запускать.
Была когда-то подобная задача, нужно было делать раз в день групповую операцию над контактами с нетривиальной логикой, а, поскольку эта логика уже была реализована на JScript, то, чтобы долго не переписывать ее на PL/SQL, пользовались планировщиком задач Windows. Раз в день он запускал определенное окно, его, кстати, можно сделать невидимым, а при показе в окне запускался нужный скрипт. Просто работа с фильтрами из этого поля уже реализована в системе, берите и пользуйтесь, а в случае с ХП это придется изобретать заново. Вот пример из wnd_CampaignAudienceGridAreaScript

var FilterBuilder = System.CreateObject('TSObjectLibrary.FiltersBuilder');
var Dataset = GetContactDataset();
FilterBuilder.Dataset = Dataset;
FilterBuilder.FilterDataFieldName = 'FilterData';
FilterBuilder.UseDummyFilter = true;
FilterBuilder.FilterDataset = GroupDataset;
FilterBuilder.Load();
FilterBuilder.ApplyFilter();
Dataset.Open();

Dataset содержит данные, отфильтрованные согласно фильтрам из поля FilterData определенного поля GroupDataset.

Если настройка динамической группы не будет меняться (во всяком случае не часто), то сделайте нужное вью руками и обрабатывайте его на сервере. Да, решение может не очень красивое, но будет работать уже сейчас.

"Раловец Ольга" написал:А если клиент сам будет запускаться? Если сервер с ОС windows, то на нем можно поставить клиент Terrasoft или на какой-то пользовательской машине, которую не выключают, можно запускать.

+ опять же защита от сбоев либо ручками проверять жив ли клиент Terrasoft либо писать того самого демона))

"Евгений Либин" написал:Если настройка динамической группы не будет меняться (во всяком случае не часто), то сделайте нужное вью руками и обрабатывайте его на сервере. Да, решение может не очень красивое, но будет работать уже сейчас.

+1 кстати

а все-таки, известен ли кому способ извечь фильтры из FilterData?

"bayborodin" написал:а все-таки, известен ли кому способ извечь фильтры из FilterData?

Вам это не поможет, т.к. там находится XML для построения SQ в формате Terrasoft CRM. Т.е. парсить этот XML и применять его опять-таки к чему? к SQ_...? он на стороне сервера тоже только в блобе....

Может нужно искать другой путь решения?

"Евгений Либин" написал:Вам это не поможет, т.к. там находится XML для построения SQ в формате Terrasoft CRM. Т.е. парсить этот XML и применять его опять-таки к чему? к SQ_...? он на стороне сервера тоже только в блобе....

ну вот потом вторым шагом генерить блок where для запроса на основании данных из xml sq_***, найдя нужные фильтры по именам из xml из FilterData (и значения параметров и отношений оттуда же)
Путь джедая, тернистый трудный но красивый:smile:

Ок, допустим - выносим функционал на сторну клиента (совершив святотатство и предав свою религию), тогда, если у меня несколько динамических групп, как мне при создании FilterBuilder указать, по какой из динамических групп формировать итоговый DataSet?

"bayborodin" написал:Ок, допустим - выносим функционал на сторну клиента (совершив святотатство и предав свою религию), тогда, если у меня несколько динамических групп, как мне при создании FilterBuilder указать, по какой из динамических групп формировать итоговый DataSet?

Luke, join the dark side... :smile:
А изначально как задумывалось выбирать группу? ID группы - получаем FilterData

FilterBuilder.FilterDataset нужно отфильтровать по id выбранной группы

Спасибо. Буду пробовать

Попробовал вот так:

function ButtonOnClick(Control) {
	var FilterBuilder = System.CreateObject('TSObjectLibrary.FiltersBuilder');
	var Dataset =  Services.GetNewItemByUSI('ds_Account');
	var GroupDataset = GetGroupContactDataset('C8ADE26B-304B-44C8-A657-C9A6A6EC98BF');
	FilterBuilder.Dataset = Dataset;
	FilterBuilder.FilterDataFieldName = 'FilterData';
 	FilterBuilder.UseDummyFilter = true;
 	FilterBuilder.FilterDataset = GroupDataset;
 	FilterBuilder.Load();
 	FilterBuilder.ApplyFilter();
    Dataset.Open();
}
 
function GetGroupContactDataset(GroupID){
	var Dataset = GetGroupDataset('tbl_AccountGroup');
	ApplyDatasetFilter(Dataset, 'OwnerID', Connector.CurrentUser.ContactID,
		true);
	if (GroupID != null) {
		ApplyDatasetFilter(Dataset, 'ID', GroupID, true);
		Dataset.Open();
	}
	return Dataset;
}

получаю пустой dataset :(

"bayborodin" написал:var GroupDataset = GetGroupContactDataset('C8ADE26B-304B-44C8-A657-C9A6A6EC98BF');

Попробуйте взять в фигурные скобки:

var GroupDataset = GetGroupContactDataset('{C8ADE26B-304B-44C8-A657-C9A6A6EC98BF}');

"Лабьяк Олег Игоревич" написал:Попробуйте взять в фигурные скобки:

var GroupDataset = GetGroupContactDataset('{C8ADE26B-304B-44C8-A657-C9A6A6EC98BF}');

Фигурные скобки не помогли. Меня гложат сомнения - если C8ADE26B-304B-44C8-A657-C9A6A6EC98BF - это айдишка динамической группы, правильно ли я делаю?

дааа... шаманство :( Пойду увольняться... или требовать повышения зарплаты

Может, группу создал не текущий пользователь?
Попробуйте не включать фильтр по OwnerID.

"Лабьяк Олег Игоревич" написал:Может, группу создал не текущий пользователь?

Ага, мне такая же идея в голову пришла. Проверил - так и есть. Исправил - GroupDataset теперь не пустой. Но, итоговый Dataset по прежнему пуст - пойду в профилировщик глядеть...

Уважаемые коллеги! Не работайте после 3 часов ночи! Что касается меня, то утром все таинственным образом заработало :)

"bayborodin" написал:Не работайте после 3 часов ночи! Что касается меня, то утром все таинственным образом заработало :)

Защита от полуночников:) встроенная охрана труда и соблюдение ТК

"bayborodin" написал:Ок, допустим - выносим функционал на сторну клиента (совершив святотатство и предав

Вот не надо тут неправославных действий. Вот тут говорят, что FilterData - это таки xml. Собственно какая проблема разбирать xml и формировать нужное условие для выборки?

"Доленко Юрий" написал:Вот не надо тут неправославных действий. Вот тут говорят, что FilterData - это таки xml. Собственно какая проблема разбирать xml и формировать нужное условие для выборки?

Юрий, не стоит так категорично говорить.
Вот пример одной динамической группы к sq_Account

<?xml version="1.0" encoding="UTF-8"?> <FiltersBuilderRootNode Type="FiltersBuilderItems"><Item Type="LookupFiltersBuilderItem" DataFieldName="AccountTypeID"><Item Value="{E3E1E8B8-3C90-49DB-B7D6-0970CDF889CA}"></Item><DisplayFiledValues><Item Value="Terrasoft" UID="243891E27BCB4A399C5BBC5C10F42529"></Item></DisplayFiledValues></Item></FiltersBuilderRootNode> 

Сам sq_Account приводить не буду т.к. он очень большой, вы его можете посмотреть в своей конфигурации.
Если у вас получится объединить эти два XML-а и получить из них правильный SQL-запрос (при этом не используя объекты Terrasoft) то тогда вам РЕСПЕКТ.

Теоретически можно сделать всё (или почти всё) но это не всегда оправдывает понесенные затраты (человеческие, временные финансовые....)

"Евгений Либин" написал:Теоретически можно сделать всё (или почти всё) но это не всегда оправдывает понесенные затраты (человеческие, временные финансовые....)

+1 об этом собственно и спорим.
Кстати, вспомнила еще один случай, когда в карточке продукта находился FilterBuilder, данные из которого помимо стандартного поля сохранялись еще и в текстовое поле в виде условия Where SQL-запроса, а потом использовались в ХП, добавлялись к select конкатенацией. Если выбрали этот путь, наверное было все же быстрее.

"Евгений Либин" написал:Сам sq_Account приводить не буду т.к. он очень большой, вы его можете посмотреть в своей конфигурации.
Если у вас получится объединить эти два XML-а и получить из них правильный SQL-запрос

Я может задачи до конца не понял. А зачем нам вообще sq_Account?
Из узлов Item вытянули три атрибута - сформировали условие - выбрали что надо из tbl_Account по этому условию.
Тут еще ж вопрос надежности, нагрузки, отказоустойчивости и т.п.

"Раловец Ольга" написал:Кстати, вспомнила еще один случай, когда в карточке продукта находился FilterBuilder, данные из которого помимо стандартного поля сохранялись еще и в текстовое поле в виде условия Where SQL-запроса, а потом использовались в ХП, добавлялись к select конкатенацией.

Ольга, если Вас не затруднит, можно об этом подробней? Похоже, это то, что я искал!

Дело в том, что в sq_Account идет выборка не только из tbl_Account, фильтр может содержать условия не связанные напрямую с tbl_Account

"Евгений Либин" написал:Дело в том, что в sq_Account идет выборка не только из tbl_Account, фильтр может содержать условия не связанные напрямую с tbl_Account

хм.. не подумал, действительно )

Интересное обсуждение.
Предлагаю такое решение: просто посмотреть какой запрос пойдет в БД после применения фильтра с FilterBilder.

function ButtonOnClick(Control) {
        var FilterBuilder = System.CreateObject('TSObjectLibrary.FiltersBuilder');
        var Dataset =  Services.GetNewItemByUSI('ds_Account');
        var GroupDataset = GetGroupContactDataset('C8ADE26B-304B-44C8-A657-C9A6A6EC98BF');
        FilterBuilder.Dataset = Dataset;
        FilterBuilder.FilterDataFieldName = 'FilterData';
        FilterBuilder.UseDummyFilter = true;
        FilterBuilder.FilterDataset = GroupDataset;
        FilterBuilder.Load();
        FilterBuilder.ApplyFilter();
        // Определить текст запроса
 
Dataset.Open();
}

Вот так

SQText = Connector.DBEngine.GetSelectQuerySQLText(Dataset.SelectQuery);

"bayborodin" написал:Ольга, если Вас не затруднит, можно об этом подробней? Похоже, это то, что я искал!

Есть небольшое отличие. У меня по кнопке OK записывалось все, а у Вас в поле FilterData сохраняется по кнопке "Сохранить" FilterBuildera, не уверена, что можно в ее событие влезть, чтобы сохранить фильтр в виде текста в тот же момент. Но смысл тот же, что и обсуждался выше. Брался экземпляр запроса, к нему применялись фильтры и у него из GetSelectQuerySQLText вырезался нужный текст, но тут еще могут быть нюансы с псевдонимами, еще попытаюсь повспоминать детали.

Развивая мысль Ольги, удалось реализовать такой подход:
Для интересующих нас групп разделов (tbl_<Имя раздела>Group) добавляем поле FilterSQL типа строка Unicode, размер 4000 (Для некоторых запросов SQL 4000 может и не хватить, но больше сделать не дает :smile:)
Это же поле должно быть добавлено в Common\Details\Groups\sq_ItemGroup и в Common\Details\Groups\ds_ItemGroup (здесь также не забыть указать размер 4000)

В сервисе scr_BaseWorkspace изменяем функцию SaveFilters (добавляем блок try)

function SaveFilters() {
	var FilterDataset = fbcFilters.FilterDatasetLink.Dataset;
	if (!CurrentGroupIsFiltered(FilterDataset)) {		
		return; 
	}     
	FilterDataset.Edit();
 
	try  {
			var mdSQL = mf_GetDynamicFilterSQLText(Connector.DBEngine.GetSelectQuerySQLText(fbcFilters.DatasetLink.Dataset.SelectQuery));
			FilterDataset.Values('FilterSQL') = mdSQL;
		}
	catch (e)
		{
// Здесь произвольным образом обрабатываем ошибку -2147418113. Можно вообще оставить catch пустым 
			System.MessageDialog("Ошибочка вышла....\n"+e.number+'\n'+e.description, mdtConfirmation, mdbYes, 0);
			Log.Write(1, "Не удалось записать значение поля FilterSQL для сервиса " + FilterDataset.USI);
		}
	fbcFilters.Save();
	FilterDataset.Post();
}

В scr_BaseWorkspace добавляем служебные функции:

function mf_GetDynamicFilterSQLText(DatasetLinkSelectQuery) {
	var re = /:\w*\b/mg; // Поиск строк параметров типа :Parameter
	var strParameters = DatasetLinkSelectQuery.match(re);
	if (strParameters!=null) {
		for (var j = 0; j < strParameters.length; j++)
			{
				DatasetLinkSelectQuery = DatasetLinkSelectQuery.replace(strParameters[j], mf_GetParameterValue)
			}
	}
	return DatasetLinkSelectQuery;
}
 
function mf_GetParameterValue(parName) {
	var SelectQuery = fbcFilters.DatasetLink.Dataset.SelectQuery;	
	var Parameter = SelectQuery.Parameters.ItemsByName(parName.substr(1, parName.length - 1));
	switch (Parameter.DataType)
		{
			case (pdtInteger) : return Parameter.ValAsInt;
			case (pdtFloat) : return Parameter.ValAsFloat;
			case (pdtBoolean) : return Parameter.ValAsInt;
			case (pdtGUID): return Parameter.ValAsGUID;
			case (pdtFunction) : return Parameter.ValAsFunction;
			case (pdtString) : 
			case (pdtDateTime) : 
			case (pdtUnicodeString) : return "'" + Parameter.ValAsStr + "'";
			default : return Parameter.Value;
		}
}

Теперь при сохранении динамической группы, текст запроса SQL будет в виде простого текста сохраняться в поле FilterSQL.
Для созданных ранее динамических групп, надо один раз нажать кнопку "Применить и сохранить".
Можно еще добавить проверку длины полученного SQL запроса, и в случае если она превышает 4000, ничего не записывать.

Показать все комментарии