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

Для того, чтобы это сделать, следует создать колонку подзапроса:

111

Пример запроса:

SELECT
        "tbl_Invoice"."Amount" AS "Amount",
        "tbl_Invoice"."OwnerID" AS "OwnerID",
        (SELECT
                SUM("tbl_InvoiceSum"."Amount") AS "Amount"
        FROM
                "tbl_Invoice" AS "tbl_InvoiceSum"
        WHERE("tbl_InvoiceSum"."OwnerID" = "tbl_Invoice"."OwnerID")) AS "AmountSum"
FROM
        "tbl_Invoice" AS "tbl_Invoice"

Пример сервиса в приложении.

Нравится

Поделиться

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

Собственно, подзапросы разные бывают. Чащё даже не сумму вытягивают, а максимальное значение или первое подходящее по критерию (например, адрес контакта нужного типа).
Это настраивается в свойствах поля в подзапросе:
subquery

такой вопрос
есть следующий подзапрос в sq_Opportunity

	(SELECT
		SUM([tbl_Cashflow].[BasicAmount]) AS [BasicAmount]
	FROM
		[dbo].[tbl_Cashflow] AS [_tbl_Cashflow]
	WHERE([tbl_Cashflow].[OpportunityID] = [tbl_Opportunity].[ID] AND
		[tbl_Cashflow].[UseAsPandL] = :True)) AS [TourCreditSum],

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

я так понял оно и не должно подсчитывать (так как итоги подсчитываются SQL-запросом в БД, а не на основе датасета). Верно?
http://www.community.terrasoft.ru/forum/topic/5193

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

Можно сделать view с подзапросом, создать и привязать к нему сервис таблицы и получать значение из неё как из обычной колонки.

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

Добрый день.
Мне нужно создать сложный запрос на подсчет количества свободных продуктов в карточке Продукт в продаже с помощью сервиса Select Query. Теперь подробнее, у меня есть доработанная карточка Продукт в продаже в которой есть стандартные поля Quantity (Количество) и OfferingID (Продукт), а также 2 новых поля StartTime (Время начала бронирования) и StopTime (Время окончания бронирования). С помощью этих полей нужно сформировать запрос, который подсчитывал бы количество всех забронированных в промежуток с Времени начала бронирования по Время окончания бронирования продуктов одного вида при создание новой записи в детали Продукт в продаже в версии Terrasoft 3.3.1.104. Каким образом можно это реализовать?

Нравится

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

Здравствуйте!
Вам будет проще воспользоваться фильтрацией датасета по этим полям, используя фильтры сравнения .
Т.е. создаем два фильтра - (StartDate < Параметр.значение_даты) И (DueDate > Параметр.значение_даты) и далее:

//пролучаем Dataset
var Dataset = Services.GetNewItemByUSI(ds_Dataset);
//фильтруем
ApplyDatasetFilter(Dataset, 'StartDateFilterName', StartDate, true);
ApplyDatasetFilter(Dataset, 'DueDateFilterName', DueDate, true);
Dataset.Open();
var ItemsInDS = Dataset.RecordsCount;

В переменной ItemsInDS и будет количество записей удовлетворяющим условию.

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

Как реализовать в запросе сумму по разнице времени, при чем только для инцидентов по данному сервису, к примеру, в часах?

Это может быть полезным при формировании запроса для отчета FastReport.

Для этого можно использовать DATEDIFF. Пример запроса в MSSQL:

SELECT
        [tbl_Incident].[ID] AS [ID],
        [tbl_Incident].[IncidentNumber] AS [IncidentNumber],
        [tbl_Incident].[ActualDueDate] AS [ActualDueDate],
        [tbl_Incident].[Date] AS [Date],
        [tbl_Incident].[ITServiceID] AS [ITServiceID],
        [tbl_ITService].[Name] AS [ITServiceName],
         DATEDIFF(hh, [tbl_Incident].[CreatedOn], [tbl_Incident].[ActualDueDate]) AS [HoursDuration],
        (SELECT
                DATEDIFF(hh, [tbl_Incident2].[Date], [tbl_Incident2].[ActualDueDate]) AS [Time]
        FROM
                [dbo].[tbl_Incident] AS [tbl_Incident2]
        WHERE([tbl_Incident].[ITServiceID] = [tbl_Incident2].[ITServiceID])) AS [Sum]
FROM
        [dbo].[tbl_Incident] AS [tbl_Incident]
LEFT OUTER JOIN
        [dbo].[tbl_ITService] AS [tbl_ITService] ON [tbl_ITService].[ID] = [tbl_Incident].[ITServiceID]

Как реализовать подобный запрос в Terrasoft?

1. Создаем колонку подзапроса:

111

2. Указываем в блоке FROM колонки подзапроса таблицу, откуда выбираем, и алиас таблицы:

111

3. В блоке Select колонки подзапроса создаем новую колонку типа CustomSQLColumn

111

4. Настраиваем ее следующим образом:

111

5. В блоке WHERE колонки подзапроса создаем фильтр сравнения:

111

6. Фильтр настраиваем следующим образом:

111

7. В колонке Subquery указать суммирование:

111

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

Нравится

Поделиться

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

Добрый день.
Подскажите пожалуйста, где можно посмотреть как создать запрос SelectQuery программно? Особенно интересует полностью программное создание join-ов по нескольким полям.

TerrasoftCRM 3.3.2
Спасибо.

Нравится

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

Здравствуйте, Петр.

Динамически запрос SelectQuery создается приблизительно следующим образом:

var sq = CreateSelectQuery("Common\\" , 'sq_MySelect');
var FromTable = Services.GetNewItemByUSI('tbl_MyTable');
var Select = CreateSelectQuerySelect(sq, FromTable, 'MySelect');
var Join = CreateJoin(Select, LeftField, RightField, ParentJoin);
Join.JoinType = 0;
/*
jtInner = 0x00000000;
jtLeftOuter = 0x00000001;
jtRightOuter = 0x00000002;
jtFullOuter = 0x00000003;
*/
AddColumn(Select, FromTable.Fields('ID'));
AddColumn(Select, FromTable.Fields('Namus'));

Ознакомиться с программным создание join'ов по нескольким полям можно по ссылке: http://www.community.terrasoft.ua/blogs/5494

Спасибо, Дмитрий, очень помогло.

И еще вопрос. У меня на одном экземпляре MS SQL Server работаю две базы: Terrasoft(TS_Original) и база от другой системы (Warehouse). Есть ли возможность из скрипта базы TS_Original делать запросы на вставку и удаление в базу Warehouse?

Здравствуйте Петр!

Такая возможность существует. Вам нужно создать ADOCommand:

добавление компонента происходит (как и для других сервисов)при щелчке правой кнопкой мыши и выборе его из списка.
Компоненту присваивается осмысленное название, например adc_ExportData.
В окне редактирования сервиса есть такие значения, как Строка соединения(можно создать строку соединения без использования существующих
соединений, созданных в компонентах ADOConnection) и Соединение(можно выбрать созданное ранее посредством сервиса ADOConnection соединение).
В поле Текс SQL прописывается команда, например exec ExportData :GroupID, :GroupID - параметр, добавленный в список параметров сервиса.
В скрипте

//получение сервиса
var Command = GetSingleItemByCode('adc_ExportData');
//где 'adc_ExportData' - название сервиса ADOCommand
// установка параметров
SetParameterValue(Command.Parameters, 'GroupID', GroupID);
//запуск на исполнение
Command.Execute();

Подробнее с детальной инструкцией и примерами, Вы можете ознакомиться по ссылке:
http://www.community.terrasoft.ua/forum/topic/2576

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

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

SELECT
        [c].[ContractNumber] AS [ContractNumber],
        [tbl_Account].[Name] AS [OfficialAccountName],
        (SELECT
                SUM([AllInvoiceAmount2].[Amount]) AS [AllInvoiceAmount2]
        FROM
                [dbo].[tbl_Invoice] AS [AllInvoiceAmount2]
        WHERE([c].[ID] = [AllInvoiceAmount2].[ContractID])) AS [AllInvoiceAmount],
        (SELECT
                SUM([AllCashflowAmount].[Amount]) AS [AllCashflowAmount2]
        FROM
                [dbo].[tbl_Cashflow] AS [AllCashflowAmount]
        WHERE([c].[ID] = [AllCashflowAmount].[ContractID])) AS [AllCashflowAmount],
        (ISNULL((SELECT SUM(IsNULL(Amount,0)) FROM vw_Cashflow cs  WHERE c.ID = cs.ContractID  GROUP BY cs.ContractID),0)
-ISNULL((SELECT SUM(IsNull(Amount,0)) FROM vw_Invoice   i WHERE c.ID = i.ContractID GROUP BY i.ContractID),0)) AS [Ostatok],
        (SELECT SUM(Amount) FROM vw_Invoice i2
WHERE
InvoiceDate = (SELECT top 1 Max(InvoiceDate) FROM vw_Invoice) AND i2.ContractID = c.ID) AS [LastInvoice],
        [tbl_Kladrs].[Name] AS [SubjectName],
        [tbl_Account].[Communication1] AS [Communication1],
        [tbl_Account].[Communication2] AS [Communication2],
        [SubAgent].[Name] AS [SubAgentName],
        [c].[CentrIncomeID] AS [CentrIncomeID],
        [tbl_CentrIncome].[Name] AS [CentrIncomeName]
FROM
        [dbo].[tbl_Contract] AS [c]
LEFT OUTER JOIN
        [dbo].[tbl_Account] AS [tbl_Account] ON [tbl_Account].[ID] = [c].[CustomerID]
LEFT OUTER JOIN
        [dbo].[tbl_Kladrs] AS [tbl_Kladrs] ON [tbl_Kladrs].[ID] = [tbl_Account].[SubjectsID]
LEFT OUTER JOIN
        [dbo].[tbl_Account] AS [SubAgent] ON [SubAgent].[ID] = [c].[SubAgentID]
LEFT OUTER JOIN
        [dbo].[tbl_Kladrs] AS [tbl_CentrIncome] ON [tbl_CentrIncome].[ID] = [c].[CentrIncomeID]
WHERE(ContractTypeID = 'ED7FD7B2-46A7-4583-8FB4-37356272EB8A')

он работает на всех логинах. А при фильтрации
SELECT
        [c].[ContractNumber] AS [ContractNumber],
        [tbl_Account].[Name] AS [OfficialAccountName],
        (SELECT
                SUM([AllInvoiceAmount2].[Amount]) AS [AllInvoiceAmount2]
        FROM
                [dbo].[tbl_Invoice] AS [AllInvoiceAmount2]
        WHERE([c].[ID] = [AllInvoiceAmount2].[ContractID] AND
                [AllInvoiceAmount2].[InvoiceDate] = :DateInvoice)) AS [AllInvoiceAmount],
        (SELECT
                SUM([AllCashflowAmount].[Amount]) AS [AllCashflowAmount2]
        FROM
                [dbo].[tbl_Cashflow] AS [AllCashflowAmount]
        WHERE([c].[ID] = [AllCashflowAmount].[ContractID] AND
                [AllCashflowAmount].[ActualDate] = :DateInvoice)) AS [AllCashflowAmount],
        (ISNULL((SELECT SUM(IsNULL(Amount,0)) FROM vw_Cashflow cs  WHERE c.ID = cs.ContractID AND ActualDate = :DateInvioce GROUP BY cs.ContractID),0)
-ISNULL((SELECT SUM(IsNull(Amount,0)) FROM vw_Invoice   i WHERE InvoiceDate = :DateInvoice AND c.ID = i.ContractID GROUP BY i.ContractID),0)) AS [Ostatok],
        (SELECT SUM(Amount) FROM vw_Invoice i2
WHERE
InvoiceDate = (SELECT top 1 Max(InvoiceDate) FROM vw_Invoice) AND i2.ContractID = c.ID) AS [LastInvoice],
        [tbl_Kladrs].[Name] AS [SubjectName],
        [tbl_Account].[Communication1] AS [Communication1],
        [tbl_Account].[Communication2] AS [Communication2],
        [SubAgent].[Name] AS [SubAgentName]
FROM
        [dbo].[tbl_Contract] AS [c]
LEFT OUTER JOIN
        [dbo].[tbl_Account] AS [tbl_Account] ON [tbl_Account].[ID] = [c].[CustomerID]
LEFT OUTER JOIN
        [dbo].[tbl_Kladrs] AS [tbl_Kladrs] ON [tbl_Kladrs].[ID] = [tbl_Account].[SubjectsID]
LEFT OUTER JOIN
        [dbo].[tbl_Account] AS [SubAgent] ON [SubAgent].[ID] = [c].[SubAgentID]
WHERE(ContractTypeID = 'ED7FD7B2-46A7-4583-8FB4-37356272EB8A')

работает только при входе от имени администратора. Не работает именно подсчет разницы
(ISNULL((SELECT SUM(IsNULL(Amount,0)) FROM vw_Cashflow cs  WHERE c.ID = cs.ContractID AND ActualDate = :DateInvioce GROUP BY cs.ContractID),0)
-ISNULL((SELECT SUM(IsNull(Amount,0)) FROM vw_Invoice   i WHERE InvoiceDate = :DateInvoice AND c.ID = i.ContractID GROUP BY i.ContractID),0)) AS [Ostatok]

не могу понять в чем тут может быть проблема.. Он вставляет в колонку Ostatok сумму счетов со знаком минус.

Нравится

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

Николай, очень странно что под пользователями у вас работает, а не выдает ошибку доступа к объекту tbl_Invoice - у вас роздан непосредственно группе public доступ на чтение из таблицы?
А выдает отрицательный остаток под пользователями, потому как они не могут прочитать часть записей, которые "видит" администратор. Хотя конечно странно, что под администратором, считает все корректно. Вы администраторам раздаете дополнительно доступ на чтение всех записей?
Общее замечание к запросу:

(ISNULL((SELECT SUM(IsNULL(Amount,0)) FROM vw_Cashflow cs  WHERE c.ID = cs.ContractID AND ActualDate <= :DateInvioce GROUP BY cs.ContractID),0)
-ISNULL((SELECT SUM(IsNull(Amount,0)) FROM vw_Invoice   i WHERE InvoiceDate <= :DateInvoice AND c.ID = i.ContractID GROUP BY i.ContractID),0)) AS [Ostatok

GROUP BY лишний.

Может я не правильно понял, но как понял. В базе данных, в разрешении таблицы стоит один из пользователей, но по нему тоже не работает.. Разрешение на выборку у пользователя есть. И у этого пользователя есть доступ ко всем записям таблицы tbl_Invoice. Да, много странного) Странно, что при фильтрации по дате не хочет считаться, а если фильтрацию отключить, то все отлично..

Николай, доступ на таблицу вы раздавали специально для этого пользователя? У вас по запросу в одном месте идет обращение к таблицам, в других - к вью. Почему?

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

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

В этом и заключается ответ на ваш вопрос.

Т.е. что то с правами доступа?

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

Запрос выполняется корректно, если отключить все фильтры.. Кстати, сейчас заметил, что под администратором считается не корректно.. Но если отключить фильтры по дате, сделать выборку из вьюшек, то под остальными логинами все отлично считается.)

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

Николай, вам необходимо настраивать весь доступ через раздел Администрирование, и очень-очень редких случаях непосредственно на сервере в БД.

Александр, права на доступ к этим таблицам настроен в разделе Администрирование. Логины на которых проверяется имеют доступ ко всем записям этих таблиц..

Как вы проверили, что логины имеют доступ ко всем записям?

Выборкой из Right таблицы, по количеству)

Николай, это хорошо :) Но там еще есть поле AdminUnitID - вы его учитывали?

Ну это же колонка в которой содержится ID пользователя на которого права раздаются! Я по нему выборку и делал)

Приведите примеры запросов, которыми вы проверяли.

select COUNT(*) from tbl_InvoiceRight where AdminUnit = IDAdminUnit - проверил на сколько записей есть доступ у пользователя. Потом проверил количество счетов)

Николай, я бы так не проверял, а проверял кол-во записей в таблице и вью. И у вас не только Invoice у вас еще и Cashflow есть.

Про Cashflow я не забыл.. Проверю во вью.. Но вероятно все это из за недоступности данных для пользователя?

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

Я уже проверял профайлером, с обоих логинов запрос уходит одинаковый.

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

И вновь продолжу тему запросов...
В скриншотах результаты исполнения одного и того же запроса в TSAdmin и в MS SQL Studio

Разница в общем видна - простой цифрой 1 в select для получения самой поздней по времени записи не обойтись

Из администратора

А вот студия


Я конечно в обход напишу, или из датасета первую запись после сортировки возьму, или вообще хранимку сделаю - но как-то некрасиво
Сервис в аттаче, если что...

Нравится

Поделиться

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

А можно вопрос по самому запросу. Просто интересно
Запрос у вас какой-то хитрый. Group By по первичному ключу ничего не сделает, т.е. вы получите результат как и без Group by.
Или это просто пример такой?

Александр, хочу уточнить: не выполнение

"Александр Кудряшов" написал:запроса в TSAdmin

а предпросмотр запроса в TSAdmin. Выполнение SelectQuery.Open() в скриптах Terrasoft при установленном TOP 1 вернёт одну запись. Поэтому некрасиво, но на реальный результат не влияет.

"Евгений Либин" написал:Group By по первичному ключу ничего не сделает

Евгений, если в сервисе запроса используются агрегатные функции, автоматически устанавливается группировка по остальным полям.

Так даты отсортированы в обратном порядке, и потребности в MAX нет.

Да, в этом с Вами согласен.

"Евгений Либин" написал:Запрос у вас какой-то хитрый. Group By по первичному ключу ничего не сделает, т.е. вы получите результат как и без Group by.
Или это просто пример такой?

Это сгенерил TSAdmin, я б такое не придумал:wink:

"Евгений Либин" написал:Так даты отсортированы в обратном порядке, и потребности в MAX нет.

Вы правы, нет потребности, погорячился, перестраховался. Минус один мне:sad:

"Лабьяк Олег Игоревич" написал:Выполнение SelectQuery.Open() в скриптах Terrasoft при установленном TOP 1 вернёт одну запись. Поэтому некрасиво, но на реальный результат не влияет.

Не соглашусь. Я тоже так думал:)

var Query = Services.GetNewItemByUSI('sq_ActInDocument');
var Dataset = Query.Open();

Результат виден здесь

Поэтому верно будет только в случае создания нового сервиса Dataset на основании данного запроса и обращении именно к нему Dataset.Open() - тогда все замечательно, одна запись.

"Александр Кудряшов" написал:Вы правы, нет потребности, погорячился, перестраховался. Минус один мне

Я сам, когда заработаюсь так что глаза из орбит лезут, такого накручу... что на утро приходится всё "оптимизировать":lol:

"Александр Кудряшов" написал:Не соглашусь. Я тоже так думал:)

По поводу RecordsCount - оно посчиталось в результате запроса вида select count() .. и ваши условия.
При чем вероятней всего оно считается как раз на get методе этого свойства.
PageRecordsCount - у вас как раз 1. То есть все так как сказал Олег.
Просто видимо TOP 1, при формирования запроса, вставляется толи в последнюю очередь то ли ..

А нет.. у вас по 1 записи на страницу и страниц 13 как и RecordsCount. Значит что-то не так, да..

"Доленко Юрий" написал:А нет.. у вас по 1 записи на страницу и страниц 13 как и RecordsCount. Значит что-то не так, да..

Вот и я когда экземпляр датасета в скрипте получил, удивился:)
Сервис датасет, подчеркну, работает как надо - только не всегда хочется им пользоваться...

[update] Разобрался.
Интересный момент - профайлер показывает абсолютно идентичный запрос! И при Dataset.Open() и при Query.Open() идет такой запрос

SELECT TOP 1
	[tbl_Document].[ID] AS [ID],
	CAST(CONVERT(VARCHAR(8), [tbl_Document].[Date], 112) AS DATETIME) AS [Date]
FROM
	[dbo].[tbl_Document] AS [tbl_Document]
ORDER BY
	2 DESC

Но в случае query запись возвращается в получаемый датасет одна и та же, но столько раз, сколько их всего (как при select .... без TOP 1 - получается dataset c несколькими одинаковыми записями) а при query - одна:smile:

Так что в общем все работает, получается, но нужно при написании кода учитывать этот нюанс.

А вы явно не указывали FetchRecordsCount = 1 ?
Если нет, то попахивает костылем в ядре )

"Доленко Юрий" написал:А вы явно не указывали FetchRecordsCount = 1 ?
Если нет, то попахивает костылем в ядре )

Не пробовал... долго писал апдейт к прошлому своему же комментарию - нашел решение:smile:

Кстати, к Администратору - а не надо ли нам опцию сделать такую, что нельзя редактировать свой комментарий если на него уже ответили? сейчас идею заведу

"Доленко Юрий" написал:А вы явно не указывали FetchRecordsCount = 1 ?

Думаю, не особо поможет. Поскольку

"Александр Кудряшов" написал:Сервис датасет, подчеркну, работает как надо - только не всегда хочется им пользоваться...

а для SelectQuery нет возможности установить значение свойству FetchRecordsCount по причине его отсутствия. Когда же выполнится var Dataset = SelectQuery.Open(), изменять это свойство уже поздно.

вопрос снят.

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

Эх, а вот не сделать же в администраторе left join T1 on T1.A=T2.B and T1.C = 'Value'...
И в блок where это условие не перетащить - пресловутая "галочка" для фильтра сравнения должна быть проставлена и в запросах глобально используемых типа sq_Document уже нельзя такие вольности допускать.
Снова делаем вьюху на сервере с выборкой по нужному условию, цепляем ее как сервис таблицы и используем в join... громоздко:cry:

Нравится

Поделиться

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

Александр, можно при создании экземпляра запроса динамически создавать дополнительные условия и даже менять логический оператор между ними.

Пока решил через view - динамически лень-матушка не позволила, хотя идея хорошая. Если б экземпляр нужен был единожды - для определенного типа документа надо хитро подтянуть значения нескольких полей...
Пока писал вспомнил про вычисляемое поле в датасете - может быть потом переделаю, заодно посмотрю что быстрее функционирует:smile:

Если надумаете переделывать :) вот как у меня было:

function AddCompareFilterToJoin(Filters, Code, Field1, Field2, CompareOperatorType) {
	var CompareFilter = Filters.CreateCompareFilter();
	Filters.Add(CompareFilter);
	CompareFilter.Code = Code;
	if (!IsEmptyValue(CompareOperatorType)) {
		CompareFilter.CompareOperator = CompareOperatorType;	
	}
	var TestExpression = CompareFilter.CreateFieldFilterExpression();
	var ValueExpression = CompareFilter.CreateFieldFilterExpression();
	TestExpression.Field = Field1;
	ValueExpression.Field = Field2;
	CompareFilter.TestExpression = TestExpression;
	CompareFilter.ValueExpression = ValueExpression;
	return CompareFilter;
}

Вызов:

var RelationMembersSQ = RelationDiagram.RelationMembersSQ;
	var RelationTable = RelationMembersSQ.Items(0).Joins.ItemsByLeftTableAlias('tbl_Relation');
	if (RelationTable.Filters.Count == 0) {
		ContactAIDField = RelationTable.LeftField.ParentFields.ItemsByName('ContactAID');
		ContactBIDField = RelationTable.LeftField.ParentFields.ItemsByName('ContactBID');
		AccountBIDField = RelationTable.LeftField.ParentFields.ItemsByName('AccountBID');
		RelationTable.LogicalOperator = lotOr;
		RelationTable.Filters.LogicalOperator = lotOr;
		var RelationMembersTable = RelationMembersSQ.Items(0).FromTable;
		AccountIDField = RelationMembersTable.Fields.ItemsByName('AccountID');
		ContactIDField = RelationMembersTable.Fields.ItemsByName('ContactID');
		AddCompareFilterToJoin(RelationTable.Filters, 'ContactAID', ContactAIDField, ContactIDField);
		AddCompareFilterToJoin(RelationTable.Filters, 'ContactBID', ContactBIDField, ContactIDField);
		AddCompareFilterToJoin(RelationTable.Filters, 'AccountBID', AccountBIDField, AccountIDField);
	}
Показать все комментарии

Здравствуйте,
подскажите как решить задачу: по разделу Проекты надо сделать отчет (лучше Excel, поскольку его удобно фильтровать). В Карточке Проекта есть дополнительное пользовательское поле "Номер Машины". Проекты содержат определенный набор типовых работ, при этом даты начала и даты завершения 4х работ совпадают для всех проектов, у которых номер машины одинаковый. Надо вывести в отчет сгруппированные данные: "Номер машины", "Проект" + некоторые данные из карточки Проекта, а также названия этих 4х работ с датами начала и завершения. Сейчас я смогла сделать так: у меня выводятся в одну строку "номер машины" + информация о проекте, а в детали запроса название необходимых 4х работ и даты. Но получается, что эта деталь дублируется столько раз, сколько есть проектов. Это не удобно пользователю. Может, есть способ как решить мою задачу по-другому?

Нравится

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

Виктория, честно говоря, в данном контексте постановки задачи у меня нет других вариантов решения. Исходя из Вашего описания, фактически эти записи дублями не являются. Это абсолютно разные работы, хотя и с одинаковыми названиями и датами. Поэтому ни один механизм определения дублирующихся записей здесь не поможет.

Если бы речь шла об отчёте FastReport или формированием отчёта путём создания "вручную" в скрипте, то можно использовать два независимых источника данных, первый из которых содержал бы информацию о проектах, а второй - информацию о работах одного из проектов из первого источника. В FastReport можно было бы добавить два объекта MasterData, которые бы ссылались на эти источники. Но в базовом функционале пользовательских запросов нет возможности построить отчёт, который использует два практически независимых запроса. Необходимо решать эту задачу другими методами (FastReport или скриптами).

Да, спасибо. Я так и думал, будем создавать Fastreport.

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

Добрый день.

Я создала новое поле в сервисе sq_Task 'Колонка с типом SQL'. Там написала запрос:

case when isnull((SELECT acc.MyColumn FROM tbl_Account acc
 WHERE tbl_Task.AccountID = acc.ID), '') = '' then ''
else
isnull((SELECT StringValue FROM tbl_SystemSetting WHERE Code = 'MyCode'), '')
 + isnull((SELECT cast(acc.MyColumn AS nvarchar(max)) FROM tbl_Account acc
 WHERE tbl_Task.AccountID = acc.ID ), '')
 end

в датасете добавила поле типа справочник и указала там эту колонку, оставив поле "источник" пустым.
Теперь при открытии карточки задачи пользователем бз админских прав у меня вылетает ошибка
"The Select permission was denied on the object 'tbl_Account', database 'CRM', achema 'dbo'"

Что-то с правами не так?

Нравится

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

Kat, да проблема с правами. Если говорить, не вдаваясь сильно глубоко, то Вы должны заменять названия таблиц tbl_Account, tbl_Task на vw_Account, vw_Task если конфигурация работает под пользователем, а не администратором.

Александр, спасибо!
поменяла в своем запросе tbl_Account на vw_Account

Kat, Вы решили проблему частично :)
Под пользователем с правами администратора могут быть нюансы, поэтому Вам необходимо добавить логику перед открытием DataSet - заменять или нет таблицы на вью в БД.

Все работает и под админом корректно сейчас.
На будущее: нужно прямо текст запроса менять перед открытием? Или есть способы попроще?

"Kat" написал:Все работает и под админом корректно сейчас.

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

"Kat" написал:На будущее: нужно прямо текст запроса менять перед открытием? Или есть способы попроще?

По как что способа проще нет. Да и куда уж проще:)

Есть другая проблема. При открытии карточки (только при создании записи не админом), которая на датасет ссылается, где я запрос написала, вылетает ошибка
"Ошибка выполнения метода 'wnd_MyWindowEditOnPrepare'. OLE error 80020102"
Когда отключаю поле с запросом в сервисе, тогда ошибки нет.

Kat, Вы не проверяли с помощью профайлера, какой запрос посылается на сервер при открытии карточки пользователем? Проверьте, пожалуйста, и попробуйте выполнить этот запрос в SQL Management Studio (или Query Analyzer).

Да, проверила. запроса вообще нет при открытии карточки.
При этом заметила следующее: если зайти в раздел и сразу нажать Добавить, тогда ошибки нет. Затем, если открою и закрою какую-нибудь задачу, а после этого пытаюсь добавить новую, тогда тоже ошибки нет.

Так сообщение возникает при открытии карточки, или при попытке сохранения? Отладчик запускается, или просто возникает окно лога?

Отладчик появляется и потом лог. Это при попытке открыть карточку новой задачи.

Можете сказать, в каком скрипте и каком месте останавливается отладчик?

Да.вот:

function ProcessBaseDBEditOnPrepare(Window, BaseDBEdit){
	try {
		var Datalink = Window.ComponentsByName('dlData');
		InitializeDBEdit(Datalink, BaseDBEdit, Window);
		if (BaseDBEdit.RecordID == GUID_NULL) {
		    if (BaseDBEdit.IsCopy){
		        CopyRecord(BaseDBEdit, Window);
			} else {
			    AppendRecord(BaseDBEdit, Window);
			}
		} else {
			if (!EditRecord(BaseDBEdit, Window)) {
				return;
			}
		}
		var btnOK = Window.ComponentsByName('btnOK');
		EnableOKButtonByRights(btnOK, BaseDBEdit);
	} finally {/// дебагер кидает сюда
		Window.Attributes('WindowWasPrepared') = true;
	}
}

Если в отладчике переместиться выше, то можно отследить следующее. Мы попадаем в:

function EditRecord(BaseDBEdit, Window) {
	var Dataset = BaseDBEdit.Dataset;
	OpenDatasetWithRecordID(Dataset, BaseDBEdit.RecordID);
	if (IsDatasetEmpty(Dataset)) {
	    var Message = "Запись удалена";
	    if (!Connector.CurrentUser.IsAdmin) {
	        Message += ' ' + "или" + ' ' +
				"текущий пользователь не имеет достаточно прав для чтения записи";
		}
		ShowWarningDialog(Message);
		var btnOK = Window.ComponentsByName('btnOK');
		EnableControl(btnOK, false);
		return false;//проваливаемся до сюда
	}	
	Dataset.Edit();
	SetEditWindowCaption(BaseDBEdit, Window);
	return true;
}

Попадаем сюда, потому что BaseDBEdit.RecordID не нулевой, хотя задачу только создаем.

Kat, попробуйте добавить строчку debugger; сразу после try {. Потом перезапустите Terrasoft и добавьте новую запись. После того, как отладчик остановится на строке c debugger, пройдитесь дальше в ручном режиме и определите, где на самом деле происходит сбой.

Проваливаюсь в:

function AppendRecord(BaseDBEdit, Window) {
	var Dataset = BaseDBEdit.Dataset;
	var Attributes = Window.Attributes;
	var DoDisableEvents = !Attributes('DoNotDisableEvents');
	BaseDBEdit.RecordID = Connector.GenGUID();
	Dataset.DisableGettingDisplayValues();
	try {
	Dataset.Append();
	if (DoDisableEvents) {
		Dataset.DisableEvents();
	}
	try {
		Dataset.ValAsGUID('ID') = BaseDBEdit.RecordID;
		Window.Attributes('RecordID') = BaseDBEdit.RecordID;
		if ((!IsEmptyValue(BaseDBEdit.ParentItemID)) &&
			(!IsEmptyValue(BaseDBEdit.ParentItemFieldName))) {
			var DataField = Dataset.DataFields(BaseDBEdit.ParentItemFieldName);
			if (Assigned(DataField)) {
				DataField.Value = BaseDBEdit.ParentItemID;
			}
		}
		SetDefaultValues(BaseDBEdit);
	} finally {
		if (DoDisableEvents) {
			Dataset.EnableEvents();
		}
	}
	} finally {
		Dataset.EnableGettingDisplayValues();//падает здесь
	}
	SetEditWindowCaption(BaseDBEdit, Window);
}

Думаю, дело в

"Kat" написал:в датасете добавила поле типа справочник и указала там эту колонку, оставив поле "источник" пустым.

Ядро пытается определить колонку для отображения для этого поля, но у него ничего не получается, так как датасет не указан.

Попробуйте удалить из датасета поле типа "Справочник" и добавить обычное текстовое поле.

гениально) спасибо огромное за потраченное время!!!

Добрый день у меня та же ошибка. В SelectQuery есть колонка подзапроса, в которой есть колонка с текстом SQL (если ее отключить все работает). Ошибка вылетает только при открытии существующих записей и только если пользователь не администратор. Принажатии на кнопку "добавить" все работает.Вот скриншот. Как это можно обойти.

Михаил, думаю, Вы можете (поскольку всё-равно используете CustomSQL-колонку) внести все фильтры и используемые таблицы внутрь SQL-текста, не используя подзапрос (то есть, использовать CustomSQL-колонку непосредственно в основном запросе). А дальше - как уже было описано ранее: либо

"Kat" написал:поменяла в своем запросе

все tbl_ на vw_, либо

"Осауленко Александр" написал:Вам необходимо добавить логику перед открытием DataSet - заменять или нет таблицы на вью в БД

Лучше, конечно, второй вариант.

Спасибо за совет. Так и сделаю!

Я сделал, как Вы говорили, но под не-админом все равно эта колонка не работает (та же ошибка). На событии BeforeOpen заменяю значение свойства колонки SQLText из

(SELECT	SUM([Invoice].[Amount]) - SUM([Invoice].[PaymentAmount]) AS [DebtorAmount]
	FROM
		[dbo].[tbl_IssueInApplication] AS [IssueInApplication]
	LEFT OUTER JOIN
		[dbo].[tbl_Issue] AS [Issue] ON [Issue].[ID] = [IssueInApplication].[IssueID]
	LEFT OUTER JOIN
		[dbo].[tbl_OfferingInApplication] AS [OfferingInApplication] ON [OfferingInApplication].[ID] = [IssueInApplication].[OfferingInApplicationID]
	LEFT OUTER JOIN
		[dbo].[tbl_Application] AS [Application] ON [Application].[ID] = [OfferingInApplication].[ApplicationID]
	LEFT OUTER JOIN
		[dbo].[tbl_Invoice] AS [Invoice] ON [Invoice].[ApplicationID] = [Application].[ID]
	WHERE([IssueInApplication].[IsPlaced] = :IsPlaced AND
		[Application].[CustomerID] = [tbl_Account].[ID] AND
		[Issue].[StatusID] = :IssueIsClosed AND
		([Invoice].[BillStatusID] = :InvoiceWaiting OR
		[Invoice].[BillStatusID] = :InvoicePartPayed)))

на

(SELECT	SUM([Invoice].[Amount]) - SUM([Invoice].[PaymentAmount]) AS [DebtorAmount]
	FROM
		[dbo].[tbl_IssueInApplication] AS [IssueInApplication]
	LEFT OUTER JOIN
		[dbo].[vw_Issue] AS [Issue] ON [Issue].[ID] = [IssueInApplication].[IssueID]
	LEFT OUTER JOIN
		[dbo].[tbl_OfferingInApplication] AS [OfferingInApplication] ON [OfferingInApplication].[ID] = [IssueInApplication].[OfferingInApplicationID]
	LEFT OUTER JOIN
		[dbo].[vw_Application] AS [Application] ON [Application].[ID] = [OfferingInApplication].[ApplicationID]
	LEFT OUTER JOIN
		[dbo].[vw_Invoice] AS [Invoice] ON [Invoice].[ApplicationID] = [Application].[ID]
	WHERE([IssueInApplication].[IsPlaced] = :IsPlaced AND
		[Application].[CustomerID] = [tbl_Account].[ID] AND
		[Issue].[StatusID] = :IssueIsClosed AND
		([Invoice].[BillStatusID] = :InvoiceWaiting OR
		[Invoice].[BillStatusID] = :InvoicePartPayed)))

в строке

[Application].[CustomerID] = [tbl_Account].[ID] AND

[tbl_Account].[ID] берется из основного селекта.

Также есть еще одна проблема(номер ошибки тот же): не возможно удалить контрагента, если он указан в каком-то контакте. Воспроизводится даже под админом и даже если я эту колонку удаляю или пишу в ней просто 1

Михаил, у Вас в запросе используются как минимум две таблицы (tbl_IssueInApplication и tbl_OfferingInApplication). Они администрируются по записям? Вы смотрели профайлером, какой запрос посылается на сервер при открытии датасета? Предоставьте, пожалуйста, текст запроса и полный текст сообщения об ошибке.

По первой ошибке (открытие карточки редактирования контрагента не админом):
Текст запроса с профайлера:

exec sp_executesql N'SELECT
	[tbl_Account].[ID] AS [ID],
	[tbl_Account].[Name] AS [Name],
	[tbl_Account].[OfficialAccountName] AS [OfficialAccountName],
	[tbl_Account].[AnnualRevenue] AS [AnnualRevenue],
	[tbl_Account].[EmployeesNumber] AS [EmployeesNumber],
	[tbl_Account].[Address] AS [Address],
	[tbl_Account].[AddressTypeID] AS [AddressTypeID],
	[tbl_Account].[Communication1] AS [Communication1],
	[tbl_Account].[Communication1TypeID] AS [Communication1TypeID],
	[tbl_Account].[Communication2] AS [Communication2],
	[tbl_Account].[Communication2TypeID] AS [Communication2TypeID],
	[tbl_Account].[Communication3] AS [Communication3],
	[tbl_Account].[Communication3TypeID] AS [Communication3TypeID],
	[tbl_Account].[Communication4] AS [Communication4],
	[tbl_Account].[Communication4TypeID] AS [Communication4TypeID],
	[tbl_Account].[Communication5] AS [Communication5],
	[tbl_Account].[Communication5TypeID] AS [Communication5TypeID],
	[tbl_City].[Name] AS [CityName],
	[tbl_Account].[CityID] AS [CityID],
	[tbl_Account].[ZIP] AS [ZIP],
	[tbl_Contact].[Name] AS [PrimaryContactName],
	[tbl_Account].[PrimaryContactID] AS [PrimaryContactID],
	[tbl_Country].[Name] AS [CountryName],
	[tbl_Account].[CountryID] AS [CountryID],
	[tbl_State].[Name] AS [StateName],
	[tbl_Account].[StateID] AS [StateID],
	[tbl_Territory].[Name] AS [TerritoryName],
	[tbl_Account].[TerritoryID] AS [TerritoryID],
	[Owner].[Name] AS [OwnerName],
	[tbl_Account].[OwnerID] AS [OwnerID],
	[tbl_Account].[ActivityID] AS [ActivityID],
	[tbl_Activity].[Name] AS [ActivityName],
	[tbl_Account].[FieldID] AS [FieldID],
	[tbl_Field].[Name] AS [FieldName],
	[tbl_Account].[AccountTypeID] AS [AccountTypeID],
	[tbl_AccountType].[Name] AS [AccountTypeName],
	[tbl_AddressType].[Name] AS [AddressTypeName],
	[CommunicationType1].[Name] AS [Communication1TypeName],
	[CommunicationType2].[Name] AS [Communication2TypeName],
	[CommunicationType3].[Name] AS [Communication3TypeName],
	[CommunicationType4].[Name] AS [Communication4TypeName],
	[CommunicationType5].[Name] AS [Communication5TypeName],
	[tbl_Account].[Code] AS [Code],
	[tbl_Account].[TaxRegistrationCode] AS [TaxRegistrationCode],
	[tbl_Account].[CreatedOn] AS [CreatedOn],
	[tbl_Account].[CreatedByID] AS [CreatedByID],
	[CreatedBy].[Name] AS [CreatedByName],
	[tbl_Account].[ModifiedOn] AS [ModifiedOn],
	[tbl_Account].[ModifiedByID] AS [ModifiedByID],
	[ModifiedBy].[Name] AS [ModifiedByName],
	[tbl_Job].[NameOf] AS [JobNameOf],
	(SELECT SUM([Invoice].[Amount]) - SUM([Invoice].[PaymentAmount]) AS [DebtorAmount]	FROM [dbo].[tbl_IssueInApplication] AS [IssueInApplication]	LEFT OUTER JOIN [dbo].[vw_Issue] AS [Issue] ON [Issue].[ID] = [IssueInApplication].[IssueID] LEFT OUTER JOIN [dbo].[tbl_OfferingInApplication] AS [OfferingInApplication] ON [OfferingInApplication].[ID] = [IssueInApplication].[OfferingInApplicationID] LEFT OUTER JOIN [dbo].[vw_Application] AS [Application] ON [Application].[ID] = [OfferingInApplication].[ApplicationID] LEFT OUTER JOIN [dbo].[vw_Invoice] AS [Invoice] ON [Invoice].[ApplicationID] = [Application].[ID] WHERE([IssueInApplication].[IsPlaced] = @P1 AND [Application].[CustomerID] = [tbl_Account].[ID] AND [Issue].[StatusID] = @P2 AND ([Invoice].[BillStatusID] = @P3 OR [Invoice].[BillStatusID] = @P4))) AS [DebtorAmount]
FROM
	[dbo].[vw_Account] AS [tbl_Account]
LEFT OUTER JOIN
	[dbo].[vw_Contact] AS [tbl_Contact] ON [tbl_Contact].[ID] = [tbl_Account].[PrimaryContactID]
LEFT OUTER JOIN
	[dbo].[tbl_Territory] AS [tbl_Territory] ON [tbl_Territory].[ID] = [tbl_Account].[TerritoryID]
LEFT OUTER JOIN
	[dbo].[vw_Contact] AS [Owner] ON [Owner].[ID] = [tbl_Account].[OwnerID]
LEFT OUTER JOIN
	[dbo].[tbl_City] AS [tbl_City] ON [tbl_City].[ID] = [tbl_Account].[CityID]
LEFT OUTER JOIN
	[dbo].[tbl_State] AS [tbl_State] ON [tbl_State].[ID] = [tbl_Account].[StateID]
LEFT OUTER JOIN
	[dbo].[tbl_Country] AS [tbl_Country] ON [tbl_Country].[ID] = [tbl_Account].[CountryID]
LEFT OUTER JOIN
	[dbo].[tbl_Activity] AS [tbl_Activity] ON [tbl_Activity].[ID] = [tbl_Account].[ActivityID]
LEFT OUTER JOIN
	[dbo].[tbl_Field] AS [tbl_Field] ON [tbl_Field].[ID] = [tbl_Account].[FieldID]
LEFT OUTER JOIN
	[dbo].[tbl_AccountType] AS [tbl_AccountType] ON [tbl_AccountType].[ID] = [tbl_Account].[AccountTypeID]
LEFT OUTER JOIN
	[dbo].[vw_Contact] AS [CreatedBy] ON [CreatedBy].[ID] = [tbl_Account].[CreatedByID]
LEFT OUTER JOIN
	[dbo].[vw_Contact] AS [ModifiedBy] ON [ModifiedBy].[ID] = [tbl_Account].[ModifiedByID]
LEFT OUTER JOIN
	[dbo].[tbl_AddressType] AS [tbl_AddressType] ON [tbl_AddressType].[ID] = [tbl_Account].[AddressTypeID]
LEFT OUTER JOIN
	[dbo].[tbl_CommunicationType] AS [CommunicationType1] ON [CommunicationType1].[ID] = [tbl_Account].[Communication1TypeID]
LEFT OUTER JOIN
	[dbo].[tbl_CommunicationType] AS [CommunicationType2] ON [CommunicationType2].[ID] = [tbl_Account].[Communication2TypeID]
LEFT OUTER JOIN
	[dbo].[tbl_CommunicationType] AS [CommunicationType3] ON [CommunicationType3].[ID] = [tbl_Account].[Communication3TypeID]
LEFT OUTER JOIN
	[dbo].[tbl_CommunicationType] AS [CommunicationType4] ON [CommunicationType4].[ID] = [tbl_Account].[Communication4TypeID]
LEFT OUTER JOIN
	[dbo].[tbl_CommunicationType] AS [CommunicationType5] ON [CommunicationType5].[ID] = [tbl_Account].[Communication5TypeID]
LEFT OUTER JOIN
	[dbo].[tbl_Job] AS [tbl_Job] ON [tbl_Job].[ID] = [tbl_Contact].[JobID]
WHERE([tbl_Account].[ID] = @P5)',N'@P1 int,@P2 varchar(8000),@P3 varchar(8000),@P4 varchar(8000),@P5 varchar(8000)',1,'{E78E618C-956B-4D7E-9F6B-53955E31ADB5}','{89E2E62C-E2B0-47E6-A183-B774ED20CDE8}','{E64C1967-77EE-46DD-97D5-C0101067F6FA}','{A0EAB21C-2BB6-4B50-B4D0-90091A091736}'

Таблицы tbl_IssueInApplication и tbl_OfferingInApplication не администрируются по записям.
Скриншот ошибки в файле "Ошибка 1".

Другая ошибка - при открытии окна отображения связанных записей.Воспроизводится не только в контрагентах, а и в других разделах. Я сначала подумал, что это из за 3 пробелов в названии базы данных, но сделал копию без пробелов и ошибка не пропала(. Скриншот в файле "Ошибка 2".

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

Уже все нормально :smile:. Клиент просто в правах доступа к полям таблиц поставил уровень "Запрет" на поля, которые вытягивались в CustomSQL. Спасибо за помощь.
А ошибка при удалении связанных записей куда то сама исчезла :smile:

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

Не могу найти таблицу логгирования в пользовательских запросах.

Хотелось бы на ее основании быстро сделать отчеты в Excel и графики для анализа усердности работы менеджеров в период экспериментальной эксплуатации.

Как получить tbl_DatabaseLog для запросов?

Нравится

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

Таблица логирования создается автоматически, когда в сервисе таблицы устанавливается признак "Отслеживать изменения". Для включения логирования по запросам необходимо открыть меню "Файл"->"Настройки"->"Настройка журнала изменений БД". В верхнем реестре появившегося окна нажать кнопку "Добавить" и по коду найти таблицу tbl_Query ("Запрос"). Потом выбрать поля этой таблицы, по которым Вы хотите отслеживать изменения.

Наверно, я Вас не совсем правильно понял. Дело в том, что при выборе таблицы для построения запроса отбрасываются все таблицы, код которых заканчивается на "Log". Чтобы включить в запрос таблицу tbl_DatabaseLog, необходимо в запрос sq_ReportService добавить такой UNION:

UNION ALL
SELECT
	[tbl_Service].[ID] AS [ID],
	[tbl_Service].[Caption] AS [Caption],
	[tbl_Service].[Code] AS [Code],
	[tbl_Service].[Path] AS [Path]
FROM
	[dbo].[tbl_Service] AS [tbl_Service]
WHERE([tbl_Service].[Code] = :DatabaseLogCode)

где параметр :DatabaseLogCode - строковый параметр, значение которого равно tbl_DatabaseLog. Фильтр должен быть всегда включен.

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