|
22.01.2016, 08:07 | #1 |
Дмитрий Ерин
|
SimpleQueryBuilder - простой Х++ конструктор читабельных T-SQL запросов
Предлагаю на суд общественности реализацию наглядного построителя текстовых запросов к внешним БД.
Преамбула Наверняка многим приходилось сталкиваться с задачей формирования внешних SQL-запросов, и наверняка мало кто назовет такую задачу приятной. Особенно, если запрос длинный, "многоэтажный", а еще хуже - если приходится многократно дорабатывать такой хардкод. Разбираться во всех этих strfmt(...) (иногда вложенных) с десятками параметров - муторно. Того и гляди накосячишь. Для пущей безопасности этого процесса в Аксапте есть семейство классов SQLBuilder*. Но на мой взгляд, код, написанный с его помощью, тоже получается громоздким. В итоге появилась идея, набросок которой я тут публикую. Что умеет SimpleQueryBuilder (SQB)?
Основные плюсы
Минусы
Во вложении - проект с набором классов и интерфейсов (у интерфейсов префикс SQB_*). Примеры использования - в классе SQBTutorial. Делалось и проверялось на AX2009, в 2012 скорее всего тоже заработает. PS. Еще точнее - изначально делалось даже на Java для других нужд, а потом любопытство заставило попробовать адаптировать под Аксапту) |
|
|
За это сообщение автора поблагодарили: mazzy (10), macklakov (10), Logger (10), S.Kuskov (10). |
22.01.2016, 10:07 | #2 |
Участник
|
А вы не пробовали внутри генерить аксаптовский Query ?
Мне кажется было бы удобно. Запрос писать в виде select но потом на выходе получать Query или QueryRun, который можно передать дальше в другие методы для дальнейшей модификации или на форме использовать с возможностью добавлять фильтры по желанию пользователя. Было бы удобно. Непонятно почему ядро такой возможности не предоставляет. Все равно внутри под капотом в ядре что select что QueryRun должны превратиться в один объект после обработки. |
|
|
За это сообщение автора поблагодарили: Ruff (2). |
22.01.2016, 10:53 | #3 |
Дмитрий Ерин
|
Эта мысль была с самого начала Хотел сделать следующим шагом после текстового, даже название было придумано: NQB_* (от слова Nativ). Но когда прикидывал реализацию, останавливала мысль о том, что DataSource-ы в Query могут быть вложены в произвольном порядке (то есть образуют дерево), а предложенная в SQB цепочка вызовов - она ж плоская, или даже одномерная Наверно, это тоже как-нибудь решаемо, но пока руки не дошли всерьез подумать... Спасибо за проявленный интерес)
__________________
|
|
22.01.2016, 11:07 | #4 |
Участник
|
Да я тоже про эту проблему подумал. Пришлось бы наверно как-то ссылаться на датасорс к которому делать привязку, например по имени, передавай его в параметрах.
|
|
22.01.2016, 11:22 | #5 |
Участник
|
прикольная лекция, красивая постановка задачи.
+ kotlin - 10 минутный интеллектуальный оргазм. c 54 минуты лекции идет генерация HTML в коде ))))) Попробуйте. Практически все движки сайтов содержат подобные вещи. в том числе vbulletin, xenforo, ipboard, википедия и прочие CMS. просто посмотрите как там делается. я напишу общий вывод. не геморройтесь с текстовым видом, создайте нормальные объекты с подобъектами, похожие на аксаптовские семейства классов Query* и Dict*. с объектами работать из кода на порядки проще! и только в конце генерите строку. опять же: проверить идею просто - берете любой cms и разбираетесь как делаются запросы к mySQL в этом CMS. DML в целом (и обертка вокруг текстовых запросов, в частности) - своеобразный ослиный мостик нашей профессии )))) эта задача решается почти всеми и по-разному. Последний раз редактировалось mazzy; 22.01.2016 в 12:24. |
|
22.01.2016, 11:31 | #6 |
Участник
|
и еще. если останетесь в текущей концепции...
генерировать запрос каждый раз - неудобно. как правило, нужны некие темплейты: = это выборка по клиентам/поставщикам = это выборка по платежам = это выборка по СФ = это выборка по складским проводкам = это выборка по складским остаткам и проводками а в конкретном месте кода темплейты заполняются конкретными условиями, группировками и прочее. важно такие темплейты сохранять где-то и удобно работать с ними. в Аксапте - это Query, запомненный в AOT, и который превращается в объект одной строкой кода X++: Query q = new Query(querystr(MyAOTquery)); в частности, Query в аксапте не имеет механизма контроля обязательных условий и полей, непротиворечивых условий, отвратительно работает со ссылками таблиц на себя (привет сопоставлению проводок по клиенту/поставщику через Query) Последний раз редактировалось mazzy; 22.01.2016 в 12:03. |
|
|
За это сообщение автора поблагодарили: Ruff (2). |
04.02.2016, 14:15 | #7 |
Участник
|
Цитата:
Сообщение от mazzy
и Query в Аксапте - это далеко не лучший образец для подражания.
в частности, Query в аксапте не имеет механизма контроля обязательных условий и полей, непротиворечивых условий, отвратительно работает со ссылками таблиц на себя (привет сопоставлению проводок по клиенту/поставщику через Query) - Во-первых, чтобы посмотреть все, что надо, необходимо раскрыть все узлы и пройтись по узлам и посмотреть свойства - Во вторых, нельзя скрыть неиспользованные узлы, а они занимают место на экране |
|
|
За это сообщение автора поблагодарили: Logger (3). |
22.01.2016, 11:48 | #8 |
Участник
|
и еще одно... уж, извините.
знающим java будет понятно. мне всегда режет глаз сложение строк. в старых java vm это приводит к дикой сборке мусора. См. холивары на тему StringBuilder vs конкатенация строк. в Аксапте до версии 2012 включительно используется очень старый джавовский движок. вообще говоря, если уж работаете со строками хоть каким способом, то лучше избегать массового сложения строк, а использовать strfmt. еще один повод все перевести в объекты, а строку генерировать только в самом конце ) |
|
|
За это сообщение автора поблагодарили: Ace of Database (2). |
27.01.2016, 09:30 | #9 |
Дмитрий Ерин
|
Нууу... это ж котлин, там действительно всё красиво
Но имеем то, что имеем (Х++)... Про конкатенацию почему-то ожидал вопроса) Мне тоже режет. Хотя какое-нибудь "((%1.%4==%2.%5)||(%2.%6!=%3.%5))" режет не меньше), но холиварить не стану - я тоже против длинных сложений. В моем случае перевесил критерий переносимости кода (Java -> X++ -> etc.). Речь ведь о прототипе. Цитата:
Цитата:
Но тут я вижу немного другое назначение - улучшить читабельность кода, когда все-таки (так сложились звезды) запрос конструируется программно, и потом может подвергаться доработкам. При этом не важно, как он формируется и хранится внутри - текстом или объектами (в примере ниже - на выходе получаем QueryRun). Вот, для затравки, первые наброски: X++: queryRun = SimpleQueryBuilder::newQuery(). // здесь параметром вполне может выступить querystr(MyAOTquery) select (tablenum(CustTable)). groupBy (fieldnum(CustTable, AccountNum)). groupBy (fieldnum(CustTable, PartyId)). join (tablenum(CustTransOpen)). relations (true). sumField (fieldnum(CustTransOpen, AmountCur)). parent(). join (tablenum(CustGroup)). relations (true). mode (JoinMode::ExistsJoin). range (fieldnum(CustGroup, PaymIdType), paymIdType). queryRun(); while (queryRun.next()) { ... } Удобно ли? Или те же буквы, только в профиль?) |
|
|
За это сообщение автора поблагодарили: mazzy (2). |
27.01.2016, 12:06 | #10 |
Участник
|
хотел удержаться... но не удержался...
обещал не использовать термин "программистский подход"... Про программистский подход, программистское мышление и стереотипы но ту тему тоже создал некий пользователь Ruff ))))) что хочу сказать. улучшить читабельность кода - хорошая задача. для учебных целей. а в промышленном внедрении нафиг не нужная. проект улучшить читабельность кода - своеобразный мастурбатор для программистов. да, удовольствие доставляет. результат - крайне сомнительный. 1. писать запросы в коде, со всеми полям, join'ами, условиями на поля... само по себе неблагодарное дело. программист тратит свое время не на "читабельность", а на то, чтобы вспомнить обязательные таблицы в join, обязательные связи между таблицами, обязательные поля группировок, непустые поля... пример? до 2009 включительно номенклатура состоит из 8 таблиц. 3 из которых обязательны, а одна из них должна джойнится три раза с разными условиями. навскидку запрос напишите? а в 2012 и дальше ввели общие для всех компаний продукты и вместо номенклатуры "используемые продукты" по отдельным компаниям. там порядка 20 таблиц. навскидку запрос напишите? "читабельность кода" - очень малая часть трудозатрат. нужны пресеты для работы с логическими сущностями. 2. все становится намного хуже, если идет работа с внешними системами. в 1С например, таблицы и поля имеют числовые идентификаторы вместо названий. а в SAP часто используются немецкие названия полей или еще хуже - английская транскрипция немецких терминов. читабельность кода? ха-ха-ха-ха!!!! нужны удобные алиасы для полей и таблиц (особенно внешних). нужны пресеты для сущностей вместо обращения к отдельным таблицам например, сущность "номенклатура" - состоит из... с сущностью "номенклатура" можно работать так то... 3. во внешних системах и внешних программах часто используются дефиниции таблиц и связей в json- или xml-файлах. (см. например, java-проект Hibernate, http://devcolibri.com/1394 ). использование таких дефиниций на порядки более упросит жизнь программиста, нежели пресловутая "читаемость кода" 4. реально упросить жизнь программисту можно одним способом - снизить сложность используемых программистом сущностей. другими словами, что нужно: = чтение уже готовых дефиниций таблиц и связей в объект = работа с пресетами сущностей, вместо работы с отдельными нормализованными(!) таблицами (как целиком, так и с частью запроса. см. например, InventSum::initQueryFrom) = пресет должен подсказывать программисту какие компоненты пресета являются обязательными для заполнения по бизнес-логике (поля, условия, группировки, агрегатные функции) = пресет должен подсказывать программисту когда он нарушает целостность данных из-за отсутствующего компонента запроса (группировка, условие, агрегатная функция) = пресет должен подсказывать программисту когда он может нарушить целостность данных из-за возможного дублирования уникальных полей далее нужен код типа: X++: Query q1 = new QueryBuilder::constructFromPreset(mySuperPreset1); Query q2 = new QueryBuilder::addFromPreset(q1, mySuperPreset2); тем не менее, как я уже говорил подобные DML - очень хорошая учебная задача. своеобразный "ослиный мостик". все такое делают. но на практике программисту проще написать str s = strfmt("select %1 from %2 %3 %4 where %5", fields, tableWithAlias, orders, groups, whereclause); чем разбираться с очередным билдером, который занимается только "читабельностью кода" ))) Последний раз редактировалось mazzy; 27.01.2016 в 12:17. |
|
04.02.2016, 13:55 | #11 |
Участник
|
Цитата:
Если это умозрительная конструкция, хотелось бы в этой фразе заменить "на практике" на "в теории". Последний раз редактировалось belugin; 04.02.2016 в 14:07. |
|
04.02.2016, 13:59 | #12 |
Участник
|
Цитата:
Цитата:
Сообщение от документация
...
A data entity is an abstraction from the physical implementation of database tables. For example, in normalized tables, a lot of the data for each customer might be stored in a customer table, and then the rest might be spread across a small set of related tables. In this case, the data entity for the customer concept appears as one de-normalized view, in which each row contains all the data from the customer table and its related tables. A data entity encapsulates a business concept into a format that makes development and integration easier. The abstracted nature of a data entity can simplify application development and customization. Later, the abstraction also insulates application code from the inevitable churn of the physical tables from version to version of Microsoft Dynamics AX. To summarize: Data entity provides conceptual abstraction and encapsulation (de-normalized view) of underlying table schemas to represent key data concepts and functionalities. ... Последний раз редактировалось belugin; 04.02.2016 в 14:06. Причина: Ссылка на более концептуальный материал |
|
|
За это сообщение автора поблагодарили: Ruff (2). |
27.01.2016, 12:25 | #13 |
Участник
|
Цитата:
Сообщение от Ruff
Вот, для затравки, первые наброски:
X++: queryRun = SimpleQueryBuilder::newQuery(). // здесь параметром вполне может выступить querystr(MyAOTquery) select (tablenum(CustTable)). groupBy (fieldnum(CustTable, AccountNum)). groupBy (fieldnum(CustTable, PartyId)). join (tablenum(CustTransOpen)). relations (true). sumField (fieldnum(CustTransOpen, AmountCur)). parent(). join (tablenum(CustGroup)). relations (true). mode (JoinMode::ExistsJoin). range (fieldnum(CustGroup, PaymIdType), paymIdType). queryRun(); while (queryRun.next()) { ... } Удобно ли? Или те же буквы, только в профиль?) X++: mode (JoinMode::ExistsJoin). и не поставлен на открытые проводки, которые вполне штатно могут отсутствовать. почему выбираются клиенты только с открытыми проводками? это логика такая? или это логическая ошибка? почему в запросе читаются только открытые проводки без самих проводок? открытые проводки сами по себе используются крайне редко, поскольку это логически "подчиненная таблица". отсутствие проводок в запросе - это логическая ошибка или так задумано? что вы дальше будете делать с этой несопоставленной суммой по всему клиенту? прекрасная ошибка - суммирование AmountCur без группировки по валютам. запрос вернет сумму рублей с долларами, еврами, гривнами, тугриками и остальными валютами. правило: если выполняется агрегирование по AmounCur, то группировка по валютам является обязательной. именно такая информация и должна присутствовать в пресете. подумайте о таких логических взаимосвязях только один раз - при создании пресета, а потом пусть программа думает и предупреждает. тогда и будет "удобно". )))) упростите именно логическую часть, упростите бизнес-логику для программиста. программисты даже купят у вас такое решение ) а упрощение синтаксиса... ну, да... оно конечно тоже приятно. ))) ========================== добавил, подумав над запросом: PaymIdType есть в группе, а есть в самом клиенте. кроме того, он есть в paymMode, который присутствует в проводке. мне кажется, что фильтрация по полю PaymIdType в группе - всегда логически ошибочна. мне кажется, что нет случаев, когда фильтрация по полю PaymIdType в группе была бы логически оправдана. да, я понимаю, что запрос написан "для примера". но получился отличный пример реальных трудозатрат - время программиста тратится на связи между таблицами и на логику, а не на синтаксис. упростите логику. дайте возможность написать логику один раз, а потом массово использовать. тогда проект имеет и промышленную ценность. а упрощение синтаксиса... ну, да... оно конечно тоже приятно. ))) ================= трудозатраты: время последней правки 13:00 - время создания готового поста с первоначальным текстом 12:25 ~= 0:35 минут потрачено на обдумывание логики запроса сам пост написан минуты за 2 ))) и это очень типичное соотношение трудозатрат между логикой и синтаксисом. Последний раз редактировалось mazzy; 27.01.2016 в 13:02. Причина: добавил про PaymIdType и время |
|
27.01.2016, 10:02 | #14 |
Участник
|
Удобно.
Только я бы сразу добавил методы Join OuterJoin ExistsJoin Бантик, но приятно. |
|
|
За это сообщение автора поблагодарили: mazzy (2). |
27.01.2016, 10:50 | #15 |
Участник
|
|
|
|
За это сообщение автора поблагодарили: mazzy (2), Ruff (5), Logger (2). |
27.01.2016, 11:39 | #16 |
Участник
|
|
|
27.01.2016, 11:50 | #17 |
Участник
|
|
|
|
За это сообщение автора поблагодарили: mazzy (2). |
27.01.2016, 13:36 | #18 |
Участник
|
Преимуществ не вижу, одни недостатки - например риск SQL injection-a.
Подход с использованием System.Data.SqlClient на мой взгляд намого лучше. Если код правильно отформатировать - и читать проще и скопировать для отладки в SQL Server Management Studio можно. |
|
|
За это сообщение автора поблагодарили: Ruff (2). |
28.01.2016, 02:57 | #19 |
Дмитрий Ерин
|
Тема получилась интересной, узнал много нового, всем спасибо Жаль, что оказалась баяном (прошу прощения у Максима Белугина за случайный "плагиат")... По этой причине "нативный" билдер дописывать, думаю, смысла нет, а с "внешним", наверно еще поковыряюсь на досуге.
|
|
Теги |
download, t-sql, готовый пример, запросы, пример |
|
|