Конструктор SQL-запросов

Crys

Двинутый новичок
Шальная мысля - а может как с i18n сделать ?
Наклепать файлов в духе:
Я использую ?1w, ?2t - То есть вопр. знак - показывает, что это надо заменять, цифра - порядок замены, символ - предварительная обработка (надо слэшить, надо ли из массива строку замутить). Без цифры - грабли могут быть, если порядок следования того, что надо заменять - отличается в разных языках.

В принципе, на 30 различных запросов - мне пока что хватает семи "шаблонов" (четыре из них базовые - SELECT, INSERT, DELETE, UPDATE). Но использую экспериментально в сравнительно небольшом проекте, дабы мало ли что переделать по-другому.
 

whirlwind

TDD infected, paranoid
Может надо рассматривать QueryObject непосредственно над классами модели иначе я смысла не вижу. У меня пока все по простому сделано, это лишь зачатки QO, но даже это значительно сокращает объем кода. При чем ничуть не ограничивает сложность запроса или же его специфику - произвольные "текстовые" куски могут быть добавлены в любое место запроса.

Вот пример как это выглядит

PHP:
    function stageSelect(){
        $reshipping = MetaData::createObject("Operation.Reshipping");
        $registry   = MetaData::createObject("Operation.Operation_Registry");
        $country    = MetaData::createObject("Reference.Country");
        $status     = MetaData::createObject("Reference.Delivery_Status");
        $state      = MetaData::createObject("Reference.State");
        $sm         = MetaData::createObject("Reference.Shipping_Method");
        // cache status ID for optimizing query
        if ( !$status->selectBy("code","wait") )
            throw new Exception("No status code found: wait");
        
        $sql = new ORM2SQL_Query;
        $what = array(
            $sql->absf($reshipping,null,"reshipping_id"),
            $sql->absf($registry,"base_interseq_id","id"),
            $sql->absf($registry,"base_timepos","order_timepos"),
            $sql->absf($reshipping,"timepos","timepos"),
            $sql->absf($sm,"name","shipping_method"),
            $sql->absf($reshipping,"fname","fname"),
            $sql->absf($reshipping,"lname","lname"),
            $sql->absf($reshipping,"address","address"),
            $sql->absf($reshipping,"city","city"),
            $sql->absf($reshipping,"zip","zip"),
            $sql->absf($reshipping,"phone","phone"),
            $sql->absf($reshipping,"comment","comment"),
            $sql->absf($country,"name","country"),
            $sql->absf($state,"short_name","state"),
        );
        $from = array(
            $sql->leftjoin($reshipping,$registry),
            $sm->getTableName(),
            $country->getTableName(),
            $state->getTableName(),
        );
        $cond = array(
            sprintf("%s IS NOT NULL",$sql->absf($registry,"base_entry")),
            sprintf("%s=%d",$sql->absf($reshipping,"status"),$status->getId()),
            $sql->eqf($reshipping,"shipping_method",$sm),
            $sql->eqf($reshipping,"country",$country),
            $sql->eqf($reshipping,"state",$state),
        );
        $sort = $sql->absf($reshipping,"timepos");
        $qtxt = $sql->select($what,$from,$cond,null,null,$sort);
        $rs = $reshipping->getConnection()->executeQuery($qtxt);
        $this->getBaseController()->setRecordSet($rs);
        return true;
    }

В дальнейшем я планирую инкапсулировать все элементы запроса в одном классе. Кажется это будет еще удобнее.
 

fisher

накатила суть
offtop:
тут как-то пролетал отличный пост про работу, авторства кажется voxus'a, в котором была чудестная фраза (в перечислении качеств, которыми должен обладать соискатель): если вы и писали собственную CMS, то это было давно. Так вот, искренне считаю, что то же самое можно сказать и про любое ORM. Товарищи с не в меру объектными мозгами, кончайте страдать ерундой и смущать остальных. ORM сгодится только для фабрики очень ограниченных по функциональности проектов, электронных сми к примеру. Минусов у ORM гораздо брольше чем плюсов: в первую очередь поддержка в тех проектах, где стали использовать ORM а потом поняли что ой, и стали смешивать подходы. А такое произойдет почти в любом нормальном проекте.
 

Gas

может по одной?
но даже это значительно сокращает объем кода
гм, запрос из 6 джойнов по объёму таки будет меньше. Сам для простейших операций использую нечто вроде table data gateway, но запросы посложнее пишу как есть, а то получается код ради кода. Хотя каждый решает сам как ему заморачиваться.
 

whirlwind

TDD infected, paranoid
fisher
где стали использовать ORM а потом поняли что ой, и стали смешивать подходы. А такое произойдет почти в любом нормальном проекте
Смешивать подходы какие с какими? Даже если так, одно из основных преимуществ - ORM позволяет запускать проекты максимально быстро. Как правило заказчик согласен запуститься, что бы начать получать бабло, а потом несколько лет развиваться в сторону улучшения, естествено если продукт приносит деньги. Это справедливо, если ПП - это не гостевая книга. Что касается минусов, то не нужно останавливаться на... достигнутом. Что мешает разработать, например, абстрактные агрегаторы, которые будут решать из самого легкого - лефтджойн, из более сложных - сворачивать сложные группировки, когда это требуется для оптимизации. Все это вполне решаемо. За-то плюсы ORM: бизнес-логика находится именно там, где она должна быть. ORM это не просто удобный объектный интерфейс к БД, это способ держать код большого проекта "в узде", не надо забывать об этом. К тому же "устойчивая" во всех смыслах модель, это половина контроллера. Кстати, именно в контроллерах - там где объекты вынуждены мутировать в некий абстрактный набор атрибутов, именно там в первую очередь проекты вылазят из рамок ORM. И именно там нужен SQL-конструктор или абстрактный агрегатор. Как правило, с бизнес-логикой, реализованной в непосредственно в классах ORM, таких сложностей не возникает - они используют цельные объекты.

-~{}~ 24.07.06 10:41:

Gas
гм, запрос из 6 джойнов по объёму таки будет меньше
где ж меньше? меньше этого?

PHP:
$sql->leftjoin($reshipping,$registry),
хочу это увидеть.
 

fisher

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

всё решаемо - вопрос в том, насколько эффективно.
простые запросы мы не рассматриваем, вообще.
а когда они сложные - у вас никогда не будет под рукой инструмента, который всё бы предусмотрел - все тонкости, диалекты и тд. такое всегда проще написать (быстрее работает, легче поддерживать, не надо знать никакой дополнительный апи) на plain sql.

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

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

StUV

Rotaredom
fisher
+1
подпишусь под каждым словом
опыт аналогичный
эмоций примерно столько же
=)))
 

Long

Новичок
тут мысль возникла - возможно, применение QueryObject имеет смысл в системах, где пользователь самостоятельно имеет возможность строить отчеты. т.е. есть некое большое приложение, у него есть стандартные отчеты (выборки). если какому-нибудь большому директору хочется построить какой-нибудь не стандартный отчет, то у него есть два пути - пнуть программера (если это нормальный директор, то из-за единственного раза он так не будет делать), либо получить в свое распоряжение инструмент, который визуально ему даст возможность строить выборки, а на выходе этого инструмента будет QueryObject. почему не plain sql? потому что корректность синтаксиса проверить проще. собственно эта идея - развитие "ORM позволяет запускать проекты максимально быстро".
 

StUV

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

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

-~{}~ 25.07.06 01:46:

зы: молчу про 500++ строчные запросы ;)
 

crocodile2u

http://vbolshov.org.ru
Я тоже увлекался идеей ORM. Сейчас же использую только лишь самые простые решения (шлюзы таблиц, записей, активная запись - в ее простых формах).

whirlwind - код, который ты привел, лично мне бы жизнь не упростил.

Тем не менее, в последнее время мне нравятся решения из ZendFramework, а там QueryObject используется. Впрочем, я в своей практике не использую его явно - только опосредованно (Zend_Db_Table и Zend_Db_Table_Row используют его для построения своих запросов на выборку)
 

maxru

МИФИст
fisher, +1 решпект
В своё время тоже пытался написать ORM, но хорошо подумал и отказался.
 

whirlwind

TDD infected, paranoid
>простые запросы мы не рассматриваем, вообще.
А почему, собственно? Почему необходимо писать код работы с простым запросом, если его вообще можно не писать, т.к. ORM прекрасно с этим справляется? Хотите пример? Пожалуста

http://prolib.ru/.closed/test.tgz

Один доменный объект + 2 контроллера: для формы элемента и для списка элементов которые позволяют так же и редактировать объекты класса. php в корне - это пример как запускается контроллер. Всего 3.5кб пусть не идеального, но прозрачного кода, в котором практически нет ничего лишнего. Вам не нужны никакие запросы. Остается только разверстать шаблон.

Проект на 90% состоит из простых объектов - они различаются только составом атрибутов. Простых контроллеров в проекте около 50%. Остальные - отчеты и специфические документы, объем которых колеблется в пределах 15-50% от общей массы. Это те запчасти, которые будут очень часто добавляться (и/или изменяться) и при этом не очень хорошо работать под базовым ORM-ом. Зато в базовой реализации они будут работать без глупых ошибок, и они позволят работать пользователям. Пользователи скажут где их беспокоит производительность, и при общем объеме кода проекта под мегабайт, именно там нужно делать рефакторинг, а не там где это плохо выглядит.

>потому что программист не понимает что такое эффективная работа, и для чего он программирует тут вообще.

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

В "Рефакторинге" есть очень дельный совет - не стоит обращать внимание на производительность на этапе рефакторинга (и подробно расписано почему) - лично я полностью с этим согласен, тем паче, что это мнени подкреплено практикой. ORM позволяет создавать хороший дизайн. А бизнес системы, они знаете ли, имеют свойство очень динамично изменяться. Так вот эти изменения тесно связаны с рефакторингом. А ORM изначально помогает правильно структурировать код. Если вы посмотрите первый пример из "Рефакторинга" то вы сразу поймете что нужно делать, если бы в вашей системе было понятие "Тип цен", "Справочник цен". А оно у вас наверняка будет, и соответственно, будет выделено в отдельный объект, следовательно для него будут написаны контроллеры такие же простые (т.к. эти объекты простые) как в аттаче, следовательно такого рефакторинга вам делать не придется. тут я ударился в примеры... :)


>но при возникновении багов даже 3-5 страничный запрос намного проще править, если не составляет труда охватить "одним взглядом" сам конечный запрос + строящий его код (имхо)

А что это у вас за запросы такие? Вы уверены, что это эффективный запрос? Вы уверены что ваша БД имеет правильную структуру? Я видел продукты с 30кб портянками, крутыми многостраничными запросами, а в БД ни констрейнтов, ни ключей... И как раз такие запросы мне в первую очередь приходилось оптимизировать, т.к. они работали, при увеличении объема данных, очень медленно. Может быть дело совсем не в обозримости большого запроса?

-~{}~ 25.07.06 12:42:

fisher

Кажется я понял, в чем у нас непонятки :) CMF и CMS - это разные принципы. Ни один продукт, с которым я работаю не является закрытым. Или подробнее: CMS - Это протокол "Как надо делать". CMF - это набор инструментов для решения типовых задач. Нельзя не следовать протоколу, но инструмент можно использовать или не использовать. Я всегда стремлюсь сделать инструмент, т.е. разрабатывая какой-либо продукт я не в коей мере не ограничиваю к доступ программиста к ресурсам (по кр. мере стараюсь). По этому со своей колокольни и не могу понять - как дополнительный инструмент может усложнить жизнь программисту? Ведь мера использования инструмента определяется исключительно программистом.
 

StUV

Rotaredom
whirlwind
речь как раз и шла о "смешанном подходе"...
в большой команде с учетом сказаного тобой это как раз и произойдет, что не есть гут в рамках одного проекта

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

whirlwind

TDD infected, paranoid
А давайте поднимем старую тему. Прошло уже дофига лет, каждый наверное смотри сейчас по-другому на сабж.

Давайте не будем исходить из того, что конструктор запросов облегчает конструирование SQL, так как облегчать там по сути нечего. Текстовый запрос передается в качестве аргумента так же прекрасно как и экземпляр объекта. Но насколько удобно повторно использовать код с plain-sql?

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

fixxxer

К.О.
Партнер клуба
На использование в сложных системах, разрабатываемых целиком "внутри", смотрю отрицательно, аргументы повторять по сотому разу не буду. Но куча однотипных моделей вида
PHP:
class FooModel {
    const SQL_GET_FOO = "SELECT ..........";
    const SQL_INSERT_FOO = "INSERT .........";
    ....
    function getFooById($id) { .......
}
наводит на уместность автоматической генерации заготовок моделей по их имени и структуре базы (потом, разумеется, эти классы ручками допиливаются).

Еще частая ситуация, когда изменения каких-то полей аффектят кучу хранилищ: при изменении e-mail'а надо, помимо базы, дергнуть авторизационный memcachedb, при изменении имени - sphinx... тут вполне справляется... ммм, не знаю, как это правильно называется, пусть будет Observer + ObservedStorage :)

Но взгляд на проблему меняется, если начинаем рассматривать распределенные СУБД, такие как Google BigTable, Amazon SimpleDB, HBase, Hypertable. На первый взгляд, здесь ORM намного более уместен. Второй взгляд у меня появится, когда более плотно займусь этим вопросом, что будет в ближайшее время :).

P.S. Nested Sets мне вообще не нравится. При наличии кэширования выборок, они ничем не лучше обычного списка смежности или материализованных путей, в зависимости от задачи.

P.P.S. Отмечу положительную тенденцию в разделе "теория". Такими темпами я может и перестану сравнивать phpclub и sitepoint далеко не в пользу первого ;)
 

Sherman

Mephi
Есть такое:

http://git.shadanakar.org/?p=onPHP.git;a=tree;f=core/OSQL;h=3b34eec2cb918f4ed53cfd87e6c0ee2ba47b4500;hb=HEAD

Прохоливарено в жжешечке пару раз:

http://slonik-v-domene.livejournal.com/10818.html?thread=52546#t52546
http://raa.livejournal.com/171875.html
http://raa.livejournal.com/172033.html

Резюме:
1. Это имеет смысл рассматривать только в объектной среде.
2. Не покрывает все кейзы(какие именно можно прочесть по ссылкам выше).
 

whirlwind

TDD infected, paranoid
Не, тихо, стоп, не в ту сторону :) Я не про OQL :) Я про повторное использование (внимание - не сокращение записи и не автогенерация SQL). Например

PHP:
        $sql->select('COUNT(*)')
            ->from('vfx_call')
            ->where('created >= ?', $dateFrom)
             ->_and('created <= ?', $dateTo);
        if ( $a )
        {
             $sql->where('foo = bar');
        }
        $this->applyAnySubclassFilter($sql);

        $sth = $dbh->prepareStatement($sql->getText());
        $rs = $sth->executeQuery($sql->getData(), ResultSet::FETCHMODE_NUM);
        ...
        $pager->setTotalRecords($rs->get(1));
        ...
        $clearSelectExpr = true;
        $sql->select('*', $clearSelectExpr);
        $sql->order('created DESC');
        $sth = $dbh->prepareStatement($sql->getText());
        ...
Запросы пишутся ручками, но не плайн sql - имеется возможность изменить запрос без переписывания стопицот раз той же критерии например. Тока не надо говорить что у вас датамапперы все такие из себя универсальные, что предусматривают все возможные варианты запросов даже на один единственный чих в проекте :D

PS. т.е. объект нужен не потому что он чтото там генерировать умеет, а для того, что бы передавать его куданить :)
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
whirlwind
а ты мог бы описать ситуацию, когда написание всей этой ООП-лапши проще или удобней, чем написать другую функцию с полным SQL-запросом целиком?

Я вот не представляю, как мне дебажить ошибку в таком запросе.
Запрос у меня "сверстался" объектом, который получен из какого-то другого места, а тут я просто изменил пару параметров.
А база мне пишет, что у меня "нарушение внешнего ключа".
И вперед по 3м скриптам минимум (описание класса, инициация объекта и собсно вызов с ошибкой)
 

AmdY

Пью пиво
Команда форума
кстати, неплохой пример, только я бы переделал
PHP:
$sql->select('*') 
            ->from('vfx_call') 
            ->where('created >= ?', $dateFrom) 
            ->addWhere('created <= ?', $dateTo);
            ->limit($offset, $perpage);
$list = $sql->fetchAll();
$count = $sql->select('COUNT(*)')->fetchOne(0); 
$sql->free();
 
Сверху