Здравствуйте. Такая проблема: не фильтруется датасет по CustomSQLColumn.
Попробовал сделать через OnQuickFilter, как советовали в https://community.terrasoft.ru/blogs/4397 , тоже не работает.
Как заставить его работать? В таблице дублировать данные очень нежелательно.
Спасибо.

Нравится

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

Здравствуйте, Ринат!

Если он пустой, значит, он фильтруется. Просто не совпадают значения и содержимое.

Если бы он не фильтровался - тогда бы просто всегда вверху было первое значение.

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

Как альтернативный вариант, оберните CustomSQLColumn в subselect. Есть такая небольшая недокументированная фича. :) Т.е. создаете в SelectQuery подзапрос по таблице, у которой по любому есть только одна запись (лучше создать такую и добавить в нее одну запись). Потом в подзапросе добавляете CustomSQLColumn и реализуете в ней все, что хотите. И все. Далее в ядре подзапрос успешно "оборачивается" и при выполнении сортировки, и фильтраций, и при использовании итогов по нему. Можете проверить этот вариант (просто не во всех версиях он работает).

Через subquery не получилось.
SelectQuery формируется правильно, но по-моему параметр не передаётся в запрос

...
WHERE 
("tbl_Houses"."HouseAddress" || "tbl_Incident"."Flat" like '%' || :pFullAddress_U || '%')

В террасофте параметр FullAddress создан, значение ему присваивается.
Вызывается так:

function grdDataOnQuickFilter(DataGrid, DataField, Value, QuickFilterLikeType, DoFilter, DoQuickFilter) {
	debugger;
	if(DataField.Name == 'FullAddress')
	{	
		var Dataset = DataGrid.DatasetLink.Dataset;
		ApplyDatasetFilter(Dataset, 'FullAddress', Value, true);

Для того, чтобы фильтр отработал, необходимо выполнить запрос, то есть, обновить датасет. То есть, закрыть его и открыть заново.

То есть, после

var Dataset = DataGrid.DatasetLink.Dataset;
ApplyDatasetFilter(Dataset, 'FullAddress', Value, true);

добавить

Dataset.Close();
Dataset.Open();

Всё равно пусто.
Копирую сформированный SelectQuery из дебага, подставляю на место :pFullAddress реальный адрес 'Лермонтова', SelectQuery работает.
Может, мне Пользовательский SQL-фильтр переписать? Как-нибудь параметр по-другому передавать?
Сейчас он такой:

UPPER("tbl_Houses"."HouseAddress" || "tbl_Incident"."Flat") like '%' || :pFullAddress_U || '%'

Ринат, рекомендую Вам посмотреть профайлером.

Что касается фильтра, рекомендую Вам попробовать следующую конструкцию:

UPPER("tbl_Houses"."HouseAddress" || "tbl_Incident"."Flat") LIKE '%' + :pFullAddress_U + '%'

У нас Oracle, плюсы не работают :)

А что в итоге идет в базу данных? Пробовали смотреть запросы профайлером?

Что касается объединения строк, Можете попробовать так:

CONCAT(CONCAT('%', :pFullAddress_U)), '%')

Спасибо, но так тоже не работает :(

Не пойму, зачем Вы фильтр накладываете на OnQuickFilter, т.е. после того, как пользователь в гриде уже применил быстрый фильтр. Т.е. датасет уже открылся и уже что-то делать поздно. По моему, мы не туда "копаем". Можете описать задачу детальнее?

Есть такой вот Пользовательский SQL-фильтр в sq_Incidents:

UPPER("tbl_Houses"."HouseAddress" || "tbl_Incident"."Flat") LIKE '%' || :pFullAddress_U || '%'

В датасет он попадает, но датасет по нему не фильтруется, т.к. не всё так просто с этим фильртом, он берёт "адрес дома" из объединённой таблицы и конкатенирует с "квартирой" из основной.
(Сам инцидент привязывается к ID дома, на котором он произошёл, но также важен поиск по полному адресу)
Есть параметр FullAddress (:pFullAddress).

Наверное, всё же придётся дублировать данные в таблице инцидентов...

Просто в сабже Вы писали о CustomSQLColumn и я решил, что Вам нужна возможность фильтрации по такой колонке. Ладно.
1. Согласно Вашего sql-запроса параметр должен называться FullAddress_U, а не FullAddress. Наша engine потом сама добавит при анализе запроса :p и получится :pFullAddress_U.
2. В SQLMonitor-е как запрос целиком отображается (вместе с параметрами и их значениями)?

Вот отрывок трейс-файла оракл, назвал параметр FullAddress_U без :p

*** 2012-01-20 03:58:07.109
CLOSE #139785163541904:c=0,e=13,dep=0,type=3,tim=1327010287108998
PARSE #139785163540784:c=0,e=241,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=1,plh=376016973,tim=1327010287109350
EXEC #139785163540784:c=1000,e=136,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=1,plh=376016973,tim=1327010287109521
FETCH #139785163540784:c=2000,e=2200,p=0,cr=144,cu=0,mis=0,r=1,dep=0,og=1,plh=376016973,tim=1327010287111884
FETCH #139785163540784:c=0,e=150,p=0,cr=0,cu=0,mis=0,r=39,dep=0,og=1,plh=376016973,tim=1327010287114409
CLOSE #139785163540784:c=0,e=11,dep=0,type=3,tim=1327010287399713
=====================
PARSING IN CURSOR #139785163649936 len=3226 dep=0 uid=478 oct=3 lid=479 tim=1327010287400513 hv=2088716702 ad='953aafc0' sqlid='d3dkqjty7ykcy'
SELECT
	"ID",
	"IncidentNumber",
	"AccountID",
	"ActualDueDate",
	"ContactID",
	"Date",
	"DueDate",
	"IncidentTypeID",
	"IncidentTypeName",
	"OfferingID",
	"OwnerID",
	"OwnerName",
	"PriorityID",
	"StatusID",
	"StatusName",
	"Synopsis",
	"StatusIsFinish",
	"ResponseLastDate",
	"ResponseDelay",
	"HouseAddressText",
	"Flat",
	"FullAddress",
	"HouseRegion",
	"HouseRegionName"
FROM (
SELECT 
	"tbl_Incident"."ID" "ID",
	"tbl_Incident"."IncidentNumber" "IncidentNumber",
	"tbl_Incident"."AccountID" "AccountID",
	"tbl_Incident"."ActualDueDate" "ActualDueDate",
	"tbl_Incident"."ContactID" "ContactID",
	"tbl_Incident"."Date" "Date",
	"tbl_Incident"."DueDate" "DueDate",
	"tbl_Incident"."IncidentTypeID" "IncidentTypeID",
	"tbl_IncidentType"."Name" "IncidentTypeName",
	"tbl_Incident"."OfferingID" "OfferingID",
	"tbl_Incident"."OwnerID" "OwnerID",
	"Owner"."Name" "OwnerName",
	"tbl_Incident"."PriorityID" "PriorityID",
	"tbl_Incident"."StatusID" "StatusID",
	"tbl_IncidentStatus"."Name" "StatusName",
	"tbl_Incident"."Synopsis" "Synopsis",
	"tbl_IncidentStatus"."IsFinish" "StatusIsFinish",
	"tbl_Incident"."ResponseLastDate" "ResponseLastDate",
	"tbl_Incident"."ResponseDelay" "ResponseDelay",
	"tbl_Houses"."HouseAddress" "HouseAddressText",
	"tbl_Incident"."Flat" "Flat",
	case when "tbl_Houses"."HouseAddress" like '%(%' 
then
	replace(
	replace("tbl_Houses"."HouseAddress", ')', (case when "tbl_Incident"."Flat" is null or "tbl_Incident"."Flat" = 0 then '' else '-' end) || "tbl_Incident"."Flat" || ')'), '(', 
	(case when "tbl_Incident"."Flat" is null or "tbl_Incident"."Flat" = '0'
	then '' else '-' end) || "tbl_Incident"."Flat" || '('
	)
else 
	("tbl_Houses"."HouseAddress" || (case when "tbl_Incident"."Flat" is null or "tbl_Incident"."Flat" = '0' then '' else '-' end) || "tbl_Incident"."Flat")
end
 "FullAddress",
	"tbl_Houses"."HouseRegion" "HouseRegion",
	(SELECT 
"tbl_Regions"."RegionName" "RegionName"
FROM 
"TS"."tbl_Regions" "tbl_Regions"
WHERE ("tbl_Regions"."ID" = "tbl_Houses"."HouseRegion"))  "HouseRegionName"
FROM 
	"TS"."tbl_Incident" "tbl_Incident"
LEFT OUTER JOIN
	"TS"."tbl_Contact" "Owner" ON "Owner"."ID" = "tbl_Incident"."OwnerID"
LEFT OUTER JOIN
	"TS"."tbl_IncidentType" "tbl_IncidentType" ON "tbl_IncidentType"."ID" = "tbl_Incident"."IncidentTypeID"
LEFT OUTER JOIN
	"TS"."tbl_IncidentStatus" "tbl_IncidentStatus" ON "tbl_IncidentStatus"."ID" = "tbl_Incident"."StatusID"
LEFT OUTER JOIN
	"TS"."tbl_Houses" "tbl_Houses" ON "tbl_Houses"."ID" = "tbl_Incident"."HouseID"
LEFT OUTER JOIN
	"TS"."tbl_CableType" "tbl_CableType" ON "tbl_CableType"."ID" = "tbl_Incident"."CableType"
WHERE ((case when "tbl_Houses"."HouseAddress" like '%(%' 
then
	replace(
	replace("tbl_Houses"."HouseAddress", ')', (case when "tbl_Incident"."Flat" is null or "tbl_Incident"."Flat" = 0 then '' else '-' end) || "tbl_Incident"."Flat" || ')'), '(', 
	(case when "tbl_Incident"."Flat" is null or "tbl_Incident"."Flat" = '0'
	then '' else '-' end) || "tbl_Incident"."Flat" || '('
	)
else 
	("tbl_Houses"."HouseAddress" || (case when "tbl_Incident"."Flat" is null or "tbl_Incident"."Flat" = '0' then '' else '-' end) || "tbl_Incident"."Flat")
end
 LIKE '%' || :pFullAddress5_U || '%'))
ORDER BY
	22 ASC
)
 WHERE ROWNUM <= 40
END OF STMT
PARSE #139785163649936:c=0,e=682,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=1,plh=0,tim=1327010287400512 

Судя по всему, параметр вообще не присваивается.

Хм. Наконец разобрался, как оно работает.
Terrasoft игнорирует любые мои параметры, фильтры, берёт код CustomSQL Column и подставляет (почему-то без UPPER()) его в WHERE, сравнивая с параметром (для которого не забывает сделать UPPER())
A OnQuickFilter срабатывает уже после того, как датасет отфильтрован.

Сейчас получилось сделать с UPPER адресами. Хотя можно уже в гриде их попробовать уменьшить.
Т.е. в Custom SQL Column дописал UPPER(...) и заработало.

А можно как-нибудь сделать, чтобы CustomSQL Column отображался без UPPER, а в фильтре оно проставлялось?
Или отображалось CustomSQL Column, а фильтровалось по другой колонке?

Здравствуйте, Ринат.

Поведение CustomSQL Column определяется бинарными файлами системы, и необходима доработка ядра для того, чтобы изменить их поведение.

Фильтровать по другой колонке действительно можно, но в таком случае Вам необходимо будет это делать непосредственно из кода, а не в запросе.

Здравствуйте Ринат,
Предлагаю другой вариант, создайте View с ID таблицы tbl_Incident, добавьте логику конкатенации. Добавьте эту View в Terrasoft (создайте сервис таблицы, укажите колонки, как в View на базе данных, на вопрос "Создать ли таблицу в БД" ответьте "Нет", проверьте таблицу создав к ней SelectQuary), добавьте таблицу View в запрос, свяжите ее с основной таблицей, после чего попробуйте использовать параметры

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

Здравствуйте, уважаемые коллеги!

Заметил такую штуку. Один и тот же датасет в конфигурации 3.3.2.198 и в 3.4.0.38 ведёт себя по-разному при добавлении быстрого фильтра в реестре. В 3.3.2 всё отлично работает, но в 3.4 реестр остаётся пустым. Профайлер показывает следующее:

в 3.3.2:

exec sp_executesql N'SELECT TOP 40
        [tbl_Document].[ID] AS [ID],
        [tbl_Document].[Code] AS [Code],
        case
        when [tbl_Document].[Description] is null
        then 0
        else 1
end  AS [IsHasDescription]
FROM
        [dbo].[tbl_Document] AS [tbl_Document]
WHERE((case
        when [tbl_Document].[Description] is null
        then 0
        else 1
end  = @P1))'
,N'@P1 int',1

в 3.4.0:

exec sp_executesql N'SELECT TOP 40
        [tbl_Document].[ID] AS [ID],
        [tbl_Document].[Code] AS [Code],
        case
        when [tbl_Document].[Description] is null
        then 0
        else 1
end  AS [IsHasDescription]
FROM
        [dbo].[tbl_Document] AS [tbl_Document]
WHERE((NULL = @P1))'
,N'@P1 int',1

Как тут быть?

Нравится

3 комментария

Анатолий, попробуйте поместить CustomSQL в subselect. Возможно ситуация изменится.

Спасибо, Стас! Помогло! Да, в subselecte ест вполне корректно. Давным-давно Дима Мушенко открыл этот способ обхитрить ядро, но мне не пришло в голову применять этот трюк на 3.4.
Ох, чёрт, значит, при переносе с 3.3 на 3.4 надо все такие колонки в subselect засунуть. Хоть бы их немного оказалось... :smile:

Именно, это Димин способ. :)

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

Тем, кому лень дизайнить сервисы для несложной выборки, посвящается.
Развивая тему, описанную мною здесь, можно получать набор данных, используя сервис, прикрепленный к этому посту (см. вложение), и следующую функцию:

function GetDatasetBySQL(sql) {
    var sq = Services.GetNewItemByUSI('sq_Select');
    sql = sql.replace('select', '');
    var sqlWithoutTop = '';
    if ((sql.indexOf('top') != -1) && (sql.indexOf('top') 10)) {
        sqlWithoutTop = Trim(sql.replace('top', ''));
        sqlWithoutTop = sqlWithoutTop.substring(sqlWithoutTop.indexOf(' '), sqlWithoutTop.length);
    }
    var FindFrom = 'from';
    var FromArray = (sqlWithoutTop == '') ?
        sql.split('from') :sqlWithoutTop.split('from');
    if (FromArray.length > 0) {
        FindFrom = 'from' + FromArray[FromArray.length - 1];
    }
    var SQLColumns = (sqlWithoutTop == '') ?
        Trim(sql.substring(0, sql.indexOf(FindFrom))).split(',') :
        Trim(sqlWithoutTop.substring(0, sqlWithoutTop.indexOf(FindFrom))).split(',');
    var Columns = sq.Items(0).Columns;
    var ColCount = SQLColumns.length;
    for (var i = 0; i ColCount; i++) {
        if (!Assigned(Columns.ItemsByAlias(SQLColumns[i]))) {
            var NewCol = Columns.CreateConstColumn();
            var NewColAlias = SQLColumns[i];
            NewColAlias = (NewColAlias.indexOf(' as') != -1) ?
                NewColAlias.substring(NewColAlias.indexOf(' as') + 3,
                    NewColAlias.length) : NewColAlias;
            NewCol.ColumnAlias = Trim(NewColAlias);
            Columns.Add(NewCol);
        }
    }
    Columns.ItemsByAlias('SQLColumn').SQLText = sql + '/*';
    var ds = sq.Open();
    return ds;
}

Использовать эту функцию можно так:
var Prefix = (Connector.CurrentUser.IsAdmin) ? 'tbl_' : 'vw_';
var sql =
        "select Name " +
        "from " + Prefix  + "Contact " +
        "where not AccountID is null " +
        "group by Name " +
        "having not Name like '%test%' " +
        "order by Name asc ";
var Dataset = GetDatasetBySQL(sql);
Log.Write(1, Dataset('Name'));

Поддерживается использование агрегатных функций, только в этом случае нужно обязательно дать полям выборки алиасы:
var Prefix = (Connector.CurrentUser.IsAdmin) ? 'tbl_' : 'vw_';
var sql =
        "select count(Name) as test " +
        "from " + Prefix  + "Contact ";
var Dataset = GetDatasetBySQL(sql);
Log.Write(1, Dataset('test'));

"+": экономия времени, не нужно особо ничего дизайнить.
"-": не предусмотрены сложные конструкции запросов с подзапросами, с exists-фильтрами и т.д. Поэтому рекомендую использовать в более простых случаях (или же доработать функцию своими силами).

Нравится

Поделиться

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

Стас, чего-то сбилось форматирование...
1. Я не увидел где ты работаешь с фильтрами Where, сортировкой Order by и группировкой Group By.
2. Непонятно почему ты создаешь Const колонки, а не General.
А так идея хорошая, сам когда-то порывался сделать :) , но учитываю сложность написания sql - парсера, пока забросил эту затею.

Стас, можно попробовать парсер от mssql - MSSQLParser.dll (это ActiveX библиотека), он правда очень простой, но "допилить" его можно, он и под Oracle работает. Он разбирает sql на составляющие - пример
И я еще не очень уверен, что его можно просто так ставить клиентам... но попробовать можно :)

>>1. Я не увидел где ты работаешь с фильтрами Where, сортировкой Order by и группировкой Group By.
>>2. Непонятно почему ты создаешь Const колонки, а не General.

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

По поводу парсера - попробую.

>>не очень уверен, что его можно просто так ставить клиентам
Это чисто "проектный" функционал, поскольку вся ответственность на разработчике и его "прямых" руках. А так, почему бы и нет... :)

Не понял :) Ты парсишь текст запроса и строишь SelectQuery, так вот не видно где ты фильтры добавляешь.... Или это только набросок и скрипт так сказать для "затравки"? :)

Нет, это вся функция. Попробую объяснить:
- сначала проверяю наличие top-а
- далее нахожу ту часть, которая идет после from-а.
Все это нужно просто для того, чтобы получить список колонок (т.е. секцию select-а).
А потом в первую CustomSQL-колонку прописываю весь запрос целиком, со всеми группировками, сортировками, фильтрами. И строю const-колонки для того, чтобы можно было корректно что-то вообще получать из датасета. В итоге получается запрос вида:

SELECT
	 Name as test from tbl_Contact where not AccountID is null group by Name having not Name like '%test%' order by Name asc /* AS [SQLColumn],
	*/
if 1=0
select getdate() AS [Fake],
	N'' AS [test]
FROM
	[dbo].[tbl_AccountGroup] AS [tbl_AccountGroup]

Помнишь такое? Это ж твоя была когда-то идея... :)

Естесственно, что такой запрос нельзя использовать полноценно, я имею в виду для редактирования, вставки, удаления данных. Только для выборки.
Просто часто лень создавать сервисы для довольно простых выборок. :)

"S.Kalishenko" написал:Саня, использую твой же "хитрый" способ, который ты показывал для получения данных из хранимок.

Та да )) я вот только увидел строчку

Columns.ItemsByAlias('SQLColumn').SQLText = sql +  '/*';

сразу понятно стало ;).

Олег Лабьяк,
разработчик,
3-я линия Службы поддержки Terrasoft.

Теперь понял... я было подумал, что ты написал простенький парсер sql...

Забыл прикрепить вложение. :(
Сейчас уже сообщение полноценное.

По-моему есть что-то порочное в том, чтобы по тексту строить SelectQuery, из которого потом где-то в ядре получается текст.

Вы не правы, намного проще написать в конфигурации, что-то типа:

var selectQuery = ConvertToSelectQuery("select Name from Table1");

которое, потом скрипт преобразует в универсальный формат, который уже будет выполняться на 3-х СУБД.

Стас, в прикрепленном сервисе SQL-запроса не хватает следующего SQL-кода для поля Fake:

*/
IF 1=0
SELECT getdate()

Прикрепил исправленный запрос.

Еще столкнулся с тем, что, когда я передаю в функцию SQL-запрос вида:

select distinct OwnerID
from tbl_OrderOffering

у меня в результате формируется датасет с именем поля 'distinct OwnerID'.

Чтобы формировалось правильное имя поля OwnerID, нужно заменить строчку в функции GetDatasetBySQL(sql)

var NewColAlias = SQLColumns[i];

на строчку

var NewColAlias = SQLColumns[i].replace('distinct', '');

Можно учесть и другие варианты кроме слова distinct.

Стас, спасибо за ценный материал.
Вопрос: проводилось ли исследование предложенного способа на ресурсоемкость? Имеется в виду время создания сервиса и выполнения запроса, по сравнению с традиционным задизайненным запросом в SelectQuery?

>>Кошкаров Андрей
Спасибо, Андрей, за Ваши наблюдения и комментарии. Извините за задержку с ответом, был в отпуске. Действительно, в этом варианте получения датасета не учтен ряд опций, таких как distinct и другие. В принципе, об этом я и предупреждал: "не предусмотрены сложные конструкции запросов с подзапросами, с exists-фильтрами и т.д. Поэтому рекомендую использовать в более простых случаях (или же доработать функцию своими силами)". :)

>>Гамора Дмитрий
Дима, такого анализа я не делал, так как основная идея была не быстродействие получения экземпляра, а простота работы метода.

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

Как многие знают, есть такое ограничение на использование CustomSQLColumn-а: датафилд, полученный по нему, нельзя фильтровать (как быстрым фильтром, так и фильтром из FilterBuilder-а), что значительно ограничивает нас при реализации сложных запросов с использованием функций и хранимых процедур. Но недавно случайно один из разработчиков (Дмитрий Мушенко - страна должна знать своих героев :)) обнаружил, что в случае, если завернуть CustomSQLColumn в subselect, то все будет нормально работать. Т.е. датасет, полученный по запросу вида

select
    ID as ID,
    Amount1 - Amount2 as Difference
from tbl_MyTable

где Amount1 - Amount2 - это CustomSQLColumn,
не будет фильтроваться по датафилду Difference

Но если видоизменить запрос на

select 
    ID as ID,
    (select  top 1
         m.Amount1 - m.Amount2 
     from tbl_MyTable m
     where m.ID = tbl_MyTable) as Difference

from tbl_MyTable

то все будет работать замечательно.

Прим.: Единственный неприятный момент, что удалось обнаружить, - эта фича не работает со строковыми полями. Т.е. если Вы в таком поле получаете дату, числовое значение или значение из справочника (GUID-ное значение), все будет фильтроваться нормально. Если же строку - фильтрация работать не будет.

Нравится

Поделиться

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

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

Есть еще несколько путей решения такой задачи:

  • чтобы обеспечить работу быстрого фильтра, можно создать в SelectQuery CustomSQLFilter с нужным условием и вручную применять его в обработчике событий OnQuickFilter соответствующего DataGrid - самый правильный и безотказный вариант, правда доступный только в более поздних версиях
  • можно в реальной таблице создать вычисляемое поле (для случаев, когда все данные для вычисления берутся из одной таблицы и когда такое позволяет СУБД - MS SQl Server точно позволяет, насчет других не уверен, но скорее всего у них тоже с этим не будет проблем), а затем добавить его в сервис таблицы без сохранения структуры (в принципе, такой вариант я бы посоветовал вместо первого для тех случаев, когда возникает реальная необходимость в оптимизации запроса)

>>чтобы обеспечить работу быстрого фильтра, можно создать в SelectQuery CustomSQLFilter с нужным условием и вручную применять его в обработчике событий OnQuickFilter соответствующего DataGrid - самый правильный и безотказный вариант, правда доступный только в более поздних версиях

Да, можно. Только кода больше нужно писать.

>>можно в реальной таблице создать вычисляемое поле

Только для случаев, когда данные находятся в одной таблице. К сожалению, такие случаи очень редки.

>>Можно просто использовать View:

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

Можно подробнее про OnQuickFilter?
Я создал фильтр, вызываю его в этом обработчике, но датасет не фильтруется (пустой).

OnQuickFilter - метод, который срабатывает, когда пользователь применяет быстрый фильтр в реестре (выбирает в контекстном меню "Быстрый фильтр" - на англ. "Quick Filter"). Т.е. он не срабатывает, когда Вы применяете любой произвольный фильтр на датасете.

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

Порой возникает необходимость в использовании CustomSQLColumn: не всякий запрос можно построить с помощью обычных сервисов. Ввести небольшое условие, или какой-нибудь расчет, для которого писать «хранимку» нецелесообразно, а вписать его в CustomSQLColumn удобно. Скажем, выводить крестик, если два других поля равны, а если нет – выводить пробел. Особенно в отчётах часто возникает необходимость в поле этого типа. И вот, отчёт создан, отлажен, и вдруг, тестируя уже под пользователем, выясняется, что администрируемые таблицы ему не видны. Поминаются недобрыми словами склероз и забывчивость, когда возникают ошибки вроде «Ошибка открытия источника данных "". Оригинальное сообщение об ошибке: ORA-01031: insufficient privileges», или что-то подобное. Вместо администрируемых таблиц необходимо ставить представления, т.е. view.

Помня об этой особенности, обычно используют в тексте CustomSQLColumn указание на представление, а не на таблицу. Тогда для пользователей-администраторов следует делать подмену view на table. По методу, предложенному Андреем Громовым, на событие BeforeDatasetOpen выполняются проверки:

// Пример для CustomSQLFilter
function ds_OfferingAllowRedemptionOnDatasetBeforeOpen(Dataset, DoOpen) {
        if (Connector.CurrentUser.IsAdmin) {
                var SelectQuery = Dataset.SelectQuery;
                var Filters = SelectQuery.Items(0).Filters;
                var TablePrefix = 'tbl_';
                var Filter;
                var Count = Filters.Count;
                for(var i = 0; i Count; i++) {
                        Filter = Filters.Items(i);
                        if (Filter.FilterType == ftCustomSQL) {
                                Filter.SQLText = Filter.SQLText.replace(/vw_/g, TablePrefix);
                        }
                }
        }
}  

// Пример для CustomSQLColumn
function ds_OfferingOnDatasetBeforeOpen(Dataset, DoOpen) {
        if (Connector.CurrentUser.IsAdmin) {
                var SelectQuery = Dataset.SelectQuery;
                var Select = SelectQuery.Items(0);
                var TablePrefix = 'tbl_';
                var Columns = Select.Columns;
                var Column;
                var Count = Columns.Count;
                for(var i = 0; i Count; i++) {
                        Column = Columns.Items(i);
                        if (Column.ColumnType == ctSQLText) {
                                Column.SQLText = Column.SQLText.replace(/vw_/g, TablePrefix);
                        }
                }
        }
}

Метод замечательно работает, когда CustomSQLColumnы содержат указания на таблицы разделов, администрируемость которых всем известна (вроде tbl_Account или tbl_Contact). Но как быть, когда администриуются не только разделы, но и некоторые детали? Да ещё (как это иногда бывает), деталь сделали администрируемой уже после того, как был создан отчёт. Или во время разработки сервиса SelectQuery просто забыли об этой особенности, и при создании CustomSQLColumn-ов использовали имена таблиц, а не представлений. Отлавливать появившиеся ошибки в куче специфических отчётов, SelectQuery которых содержат десятки CustomSQLColumn было бы крайне медленным и неэффективным занятием. К тому же, ручная правка может наплодить новых ошибок, в чём я очень быстро убедился.

Предлагаемые выше методы оказались неприменимы. Пришлось разработать свой. Нужна была автоматическая проверка доступности всех таблиц, используемых в CustomSQLColumn-ах, и их автоматическая замена. Первоначально было необходимо просто выяснить, из-за каких именно CustomSQLColumn и упоминающихся в них таблиц происходит «свал». Зная, какие именно CustomSQLColumn-ы вызывают ошибку, было куда легче её исправить. Но впоследствии задача была усложнена: раз машина знает, из-за каких таблиц и полей SelectQuery не отработает, так пусть сама их и заменит на представления. После остаётся только передать предлагаемой функции SelectQuery перед открытием: все возможные ошибки, связанные с появлением представлений вместо таблиц будут исправлены автоматически. Функция работает на удивление быстро, и благодаря ей можно пользоваться CustomSQLColumn-ами без опасений, что какая-то из таблиц под пользователем превратится в представление.
Способы, которыми осуществляется проверка, возможно, не самые оптимальные, но подход, полагаю, правильный:

function FindUnsufficientTables(SQ, IsNeededReplace){
//Функция ищет недоступные таблицы и представления в CustomeSQLColumnах
//и в зависимости от аргумента IsNeededReplace выводит их список в лог (false)
//или автоматически производит замену (true)
//В качестве первого аргумента используется Select Query, где требуется провести поиск/замену
       
        var TablesHash = {};//Ключ  - имя таблицы. Значение - имя колонки, через запятую.  
        // Символы ограничителей имени таблицы:
        var QuoteLeft = '"';//Для Оракла - ", для MSSQL - [
        var QuoteRight = '"';//Для Оракла - ", для MSSQL - ]       

        // Также их можно определить автоматически:
        /*
        if (Connector.DBEngine.DBEngineTypeCode == 'Oracle'){
                var QuoteLeft = '"';
                var QuoteRight = '"';
        } else {
                var QuoteLeft = '[';
                var QuoteRight = ']';
        }
        */


        var Prefix = Connector.CurrentUser.IsAdmin?('vw_'):('tbl_');
        var ReplacePrefix = Connector.CurrentUser.IsAdmin?('tbl_'):('vw_');
       
        var Select = SQ.Items(0);
    var Columns = Select.Columns;
    var Column;
    var Count = Columns.Count;
    for(var i = 0; i Count; i++) {
                Column = Columns.Items(i);
                if (Column.ColumnType == ctSQLText) { // ctSQLText  - константа из scr_SysEnums
                        var TablesDraftSplit = Column.SQLText.split(QuoteLeft + Prefix);
                        for (var j = 1; j TablesDraftSplit.length; j++){
                                var TablesSplit = TablesDraftSplit[j].split(QuoteLeft);
                                if (Select.Joins.ItemsByLeftTableAlias(Prefix + TablesSplit[0]) == null &&
                                        Select.FromTableAlias != Prefix + TablesSplit[0]){
                                //Если таблица не входит в число алиасов join-oв и основной таблицы
                                        if (!!TablesHash[TablesSplit[0]]){
                                                if (TablesHash[TablesSplit[0]].indexOf(Column.ColumnAlias) 0 ){
                                                //Добавляем название колонки только если такого ещё нет.
                                                        TablesHash[TablesSplit[0]] = TablesHash[TablesSplit[0]] + Column.ColumnAlias + ', ';
                                                }
                                        } else {
                                                TablesHash[TablesSplit[0]] = Column.ColumnAlias +', ';
                                        }
                                }
                        }
                }
        }              

        //TablesHash содержит список всех используемых таблиц.
        //Удаляем лишнюю запятую:

    for (var key in TablesHash){
            TablesHash[key] = TablesHash[key].substr(0, TablesHash[key].length - 2);
    }

        //Проверяем доступность каждой таблицы:
        for (var key in TablesHash){
                //здесь используется проверка "в лоб", можно ли читать из этой таблицы.
                //Наверняка существует более грамотный вариант.
                var SQL = 'select * from ' + QuoteLeft + Prefix + key + QuoteRight;
                try {
                        Connector.DBEngine.ExecuteCustomSQL(SQL, System.EmptyValue);
                } catch (e) {
                        if (IsNeededReplace){
                                var RegExpString = QuoteLeft + Prefix + key + QuoteRight;
                                var Reg = new RegExp(RegExpString, "g");
                                var ColAliasSplit = TablesHash[key].split(', ');
                                var ColAliasSplitLength = ColAliasSplit.length;
                                for (var i = 0; i ColAliasSplitLength; i++){
                                //для каждого перечисленного поля
                                        SQ.Columns.ItemsByAlias(ColAliasSplit[i]).SQLText =
                                                SQ.Columns.ItemsByAlias(ColAliasSplit[i]).SQLText.replace(Reg,
                                                QuoteLeft + ReplacePrefix + key + QuoteRight);
                                }
                        } else {
                                Log.Write(1, 'Недоступна таблица ' + Prefix + key +
                                        ' (используется в колонках ' + TablesHash[key] + ')');
                        }
                }
        }      
}

Нравится

Поделиться

1 комментарий

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

Так можно же проверить возможность чтения из таблицы по-честному. Метод у Connector.CurrentUser если есть TableGroup у таблицы.

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