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

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

В принципе, на 30 различных запросов - мне пока что хватает семи "шаблонов" (четыре из них базовые - SELECT, INSERT, DELETE, UPDATE). Но использую экспериментально в сравнительно небольшом проекте, дабы мало ли что переделать по-другому.
 
Может надо рассматривать 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;
    }


В дальнейшем я планирую инкапсулировать все элементы запроса в одном классе. Кажется это будет еще удобнее.
 
offtop:
тут как-то пролетал отличный пост про работу, авторства кажется voxus'a, в котором была чудестная фраза (в перечислении качеств, которыми должен обладать соискатель): если вы и писали собственную CMS, то это было давно. Так вот, искренне считаю, что то же самое можно сказать и про любое ORM. Товарищи с не в меру объектными мозгами, кончайте страдать ерундой и смущать остальных. ORM сгодится только для фабрики очень ограниченных по функциональности проектов, электронных сми к примеру. Минусов у ORM гораздо брольше чем плюсов: в первую очередь поддержка в тех проектах, где стали использовать ORM а потом поняли что ой, и стали смешивать подходы. А такое произойдет почти в любом нормальном проекте.
 
но даже это значительно сокращает объем кода
гм, запрос из 6 джойнов по объёму таки будет меньше. Сам для простейших операций использую нечто вроде table data gateway, но запросы посложнее пишу как есть, а то получается код ради кода. Хотя каждый решает сам как ему заморачиваться.
 
fisher
где стали использовать ORM а потом поняли что ой, и стали смешивать подходы. А такое произойдет почти в любом нормальном проекте
Смешивать подходы какие с какими? Даже если так, одно из основных преимуществ - ORM позволяет запускать проекты максимально быстро. Как правило заказчик согласен запуститься, что бы начать получать бабло, а потом несколько лет развиваться в сторону улучшения, естествено если продукт приносит деньги. Это справедливо, если ПП - это не гостевая книга. Что касается минусов, то не нужно останавливаться на... достигнутом. Что мешает разработать, например, абстрактные агрегаторы, которые будут решать из самого легкого - лефтджойн, из более сложных - сворачивать сложные группировки, когда это требуется для оптимизации. Все это вполне решаемо. За-то плюсы ORM: бизнес-логика находится именно там, где она должна быть. ORM это не просто удобный объектный интерфейс к БД, это способ держать код большого проекта "в узде", не надо забывать об этом. К тому же "устойчивая" во всех смыслах модель, это половина контроллера. Кстати, именно в контроллерах - там где объекты вынуждены мутировать в некий абстрактный набор атрибутов, именно там в первую очередь проекты вылазят из рамок ORM. И именно там нужен SQL-конструктор или абстрактный агрегатор. Как правило, с бизнес-логикой, реализованной в непосредственно в классах ORM, таких сложностей не возникает - они используют цельные объекты.

-~{}~ 24.07.06 10:41:

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

PHP:
$sql->leftjoin($reshipping,$registry),

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

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

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

а когда программист проводит аудит системы, он должен видеть SQL целиком, а не решить обратную задачу или копаться в трейс-логах. он так быстрее понимает что к чему. он так быстрее видит ошибки проектирования. так проще, проще черт подери, проще. так что когда мне надо _быстро_ реализовать какую-то штуку я просто пишу SQL. без всякой объектной ерунды. и всячески пропагандирую этот образ жизни как наиболее правильный с точки зрения быстрой и эффективной разработки сложных проектов. просто потому что я на эти грабли наступал, ошибался, и как только я вижу как этим ужасным вирусом заражается молодняк - да, обязательно прихожу и начинаю флудить.
 
fisher
+1
подпишусь под каждым словом
опыт аналогичный
эмоций примерно столько же
=)))
 
тут мысль возникла - возможно, применение QueryObject имеет смысл в системах, где пользователь самостоятельно имеет возможность строить отчеты. т.е. есть некое большое приложение, у него есть стандартные отчеты (выборки). если какому-нибудь большому директору хочется построить какой-нибудь не стандартный отчет, то у него есть два пути - пнуть программера (если это нормальный директор, то из-за единственного раза он так не будет делать), либо получить в свое распоряжение инструмент, который визуально ему даст возможность строить выборки, а на выходе этого инструмента будет QueryObject. почему не plain sql? потому что корректность синтаксиса проверить проще. собственно эта идея - развитие "ORM позволяет запускать проекты максимально быстро".
 
Long
возможно...
но на практике ни разу не встречал достаточно гибкой системы отчетов, которую нельзя бы было реализовать парой десятков параметризованных скл-запросов (шаблонов).

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

-~{}~ 25.07.06 01:46:

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

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

Тем не менее, в последнее время мне нравятся решения из ZendFramework, а там QueryObject используется. Впрочем, я в своей практике не использую его явно - только опосредованно (Zend_Db_Table и Zend_Db_Table_Row используют его для построения своих запросов на выборку)
 
fisher, +1 решпект
В своё время тоже пытался написать ORM, но хорошо подумал и отказался.
 
>простые запросы мы не рассматриваем, вообще.
А почему, собственно? Почему необходимо писать код работы с простым запросом, если его вообще можно не писать, т.к. 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 - это набор инструментов для решения типовых задач. Нельзя не следовать протоколу, но инструмент можно использовать или не использовать. Я всегда стремлюсь сделать инструмент, т.е. разрабатывая какой-либо продукт я не в коей мере не ограничиваю к доступ программиста к ресурсам (по кр. мере стараюсь). По этому со своей колокольни и не могу понять - как дополнительный инструмент может усложнить жизнь программисту? Ведь мера использования инструмента определяется исключительно программистом.
 
whirlwind
речь как раз и шла о "смешанном подходе"...
в большой команде с учетом сказаного тобой это как раз и произойдет, что не есть гут в рамках одного проекта

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

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

В последнее время стал замечать, например в тех же контроллерах, сопли из кусков запроса, что бы не писать дважды схожие куски (например основной и для пагинатора, для админки и с доп фильтром по юзеру для мемберки, и тп). Еще не так давно подумывал как бы оформить Nested Sets в виде классов и понял что очень сложно подобрать абстракции, а проще было бы если бы класс для работы с деревьями возвращал базовый SQL, который можно потом дополнить чем угодно. Тут речь скорее о формализации запроса, в целях доступа к его составным элементам. Кто нибудь такое использует?
 
На использование в сложных системах, разрабатываемых целиком "внутри", смотрю отрицательно, аргументы повторять по сотому разу не буду. Но куча однотипных моделей вида
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 далеко не в пользу первого ;)
 
Есть такое:

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. Не покрывает все кейзы(какие именно можно прочесть по ссылкам выше).
 
Не, тихо, стоп, не в ту сторону :) Я не про 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. т.е. объект нужен не потому что он чтото там генерировать умеет, а для того, что бы передавать его куданить :)
 
whirlwind
а ты мог бы описать ситуацию, когда написание всей этой ООП-лапши проще или удобней, чем написать другую функцию с полным SQL-запросом целиком?

Я вот не представляю, как мне дебажить ошибку в таком запросе.
Запрос у меня "сверстался" объектом, который получен из какого-то другого места, а тут я просто изменил пару параметров.
А база мне пишет, что у меня "нарушение внешнего ключа".
И вперед по 3м скриптам минимум (описание класса, инициация объекта и собсно вызов с ошибкой)
 
кстати, неплохой пример, только я бы переделал
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();
 
Сверху