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

StUV

Rotaredom
whirlwind
а я и не про орм - т.к. помню с чего пост начинался ;)

++
все приведенные выше примеры (последних дней) - тоже как бы... не в тему
начинается все с "малого" - и выглядит красиво (как в этих примерах), но объектная модель позволяет размазать такую генерацию по иерархии наследования, по агрегациям в лесе классов модели и т.п.... - в крупных долгоживущих системах это часто встречается по принципу "у этой подсистемы нужный паблик api есть - почему бы не воспользоваться" и понеслась... - так вот, в такой каше в отладке все становится "ну оччччень непросто" - т.е. восстановив общий конечный запрос - найти ошибку или узкое место несложно, но разобраться в вариантной логике и/или принадлежности конкретного куска запроса конкретному "скл-билдер"-коду - ну просто ппц
 

atv

Новичок
но объектная модель позволяет размазать такую генерацию по иерархии наследования, по агрегациям в лесе классов модели и т.п
ну так
кривой код можно "закуячить" где угодно и на чем угодно
:)
А вот минусы построения SQL запроса как строки, которые я описал, ровными руками не исключишь, только поменяв сам подход построения запроса.

Ещё один недостаток вспомнил:
5. строку SQL запроса необходимо собирать в строгой последовательности.

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

Другими словами, часть условий, по которым вы каждый раз строите строчку SQL запроса (а именно, условия для сохранения синтаксиса SQL), находиться в SQL билдере, и остаётся только добавить условия самого веб приложения. Такое вот себе повторное использование кода. О чём тут спорить непонятно.
 

fixxxer

К.О.
Партнер клуба
Автор оригинала: atv
PHP:
$queryCondition = '';
    	if ($tagWord!="") {
	    	$listId = ClientTag::GetContentByTag($tagWord);
	    	if (count($listId)>0) {
	    		$queryCondition = " AND A.aId in (".implode(',', $listId).")";
	    	}
    	}

    	if(isset($params['genre']) && trim($params['genre']) != '') {
    	   $genres = htmlspecialchars(trim($params['genre']),ENT_QUOTES);
    	   $genres = explode(';',$genres);

    	   if(count($genres) > 0) {
    	      $queryCondition .= ' AND (';
    	      foreach($genres as $genre) {
    	         if(trim($genre) != '')
    	           $queryCondition .=  "AG.GenreId = (SELECT gId FROM Genre WHERE gName = '" . $genre . "' LIMIT 1) OR ";
    	      }
    	      $queryCondition = substr($queryCondition,0,-3);
    	      $queryCondition .= ') ';
    	    }
    	}

		$query = "SELECT COUNT(*) as cnt
			        FROM Artist A
			        LEFT JOIN ArtistGenre AG ON A.aId = AG.ArtistId
				   INNER JOIN Client C ON C.cId = A.ClientId AND C.cEnabled = 1 AND C.cActivated = 1
			       WHERE A.aEnabled=1 and A.aIsExpert=0
				     ".( $queryCondition != '' ? $queryCondition : '' );
PHP:
class ... {

    const SQL_GET_ARTISTS_BY_FILTER = "
        SELECT
            {{ IF GET_COUNT }}
               COUNT(*) AS cnt
            {{ END }}
            {{ UNLESS GET_COUNT }}
               A.*
            {{ END }}
            FROM Artist A
            LEFT JOIN ArtistGenre AG on A.aId = AG.ArtistId
            INNER JOIN Client C ON C.cId = A.ClientId AND C.cEnabled = 1 AND C.cActivated = 1 
            WHERE A.aEnabled=1 and A.aIsExpert=0
            {{ IF artist_ids }}
                 AND A.aId IN {{ in(artist_ids) }} 
            {{ END }}
            {{ IF GENRES }}
                 AND (
                    {{ BEGIN GENRES }}
                        AG.GenreId = (SELECT gId FROM Genre WHERE gName = {{ s(name) }} LIMIT 1) {{ UNLESS _last }} OR {{ END }}
                    {{ END }}
                 )
            {{ END }}
            {{ UNLESS GET_COUNT }}
                 LIMIT {{ if(limit, limit, 10) }}
                 OFFSET {{ if(offset, offset, 0) }}
            {{ END }}
    ";

    public function getArtistsCountByFilter(array $filter = array()) {
         $parameters = array('GET_COUNT' => true);
         if (!empty($filter['genres'])) {
             // FIXME:по хорошему, в фильтр все должно приходить уже в в виде
             // обычных списков - ну да ладно
             $parameters['GENRES'] = array();
             foreach (explode(';', $filter['genres']) as $genre_name) {
                 $parameters['GENRES'][] = array('name' => trim($genre_name));
             }
         }
         if (!empty($filter['tagWord'])) {
             // FIXME:этому здесь не место, должно делаться снаружи
             $artist_ids = ClientTag::GetContentByTag($filter['tagWord']);
             $parameters['artist_ids'] = $artist_ids;
         }
         return $this->dbConn->getCell(self::SQL_GET_ARTISTS_BY_FILTER, $parameters);
    }

}
 

atv

Новичок
fixxxer, замечательно, ты написал SQL билдер. Не такой как у других, но билдер. В чём-то интересный, а именно в самом подходе. О практичности судить не берусь, так как не пробовал.
 

fixxxer

К.О.
Партнер клуба
Кстати, в такой записи сразу много нагляднее виден писец в области подзапросов - о чем я и говорил.

Это не билдер, это Blitz templates ;)
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
спасибо за идею, шаблонизатор для составления SQL - довольно интересно
 

Lightning

Трудоголик
Хороший топик.
Насколько я понял, основной аргумент в пользу отказа от всяких Query Object-ов, SQL билдеров, ORM-ов, это то, что сгенерированный запрос трудно отлаживать и оптимизировать, т.к. сложно найти места в коде, которые строят этот запрос. Так?
Т.е. (чисто теоретически) если создать некий механизм/инструмент, который позволит сразу же находить код породивший искомый запрос, то и не будет проблемы использования сгенерированных/собраных_по_частям запросов? Правильно?
 

AmdY

Пью пиво
Команда форума
никаких проблем при отладке ORM у меня не возникало. Проблемы в "тяжести" библиотек и в том, что у 99% ORM поделок функционал ограничивает возможности по сравнению с SQL
 

Lightning

Трудоголик
Проблемы в "тяжести" библиотек и в том, что у 99% ORM поделок функционал ограничивает возможности по сравнению с SQL
Ну это, извини меня, детский сад, а не проблемы. Можно писать свой легкий Query-билдер и развивать его функционал по мере необходимости.
 

findnext

Новичок
Можно писать свой легкий Query-билдер и развивать его функционал по мере необходимости.
да можно то можно, вот только смысл есть ли в этом? на мой взгляд все эти велосипеды - туфта полная.
 

Lightning

Трудоголик
да можно то можно, вот только смысл есть ли в этом?
есть, т.к.
Проблемы в "тяжести" библиотек и в том, что у 99% ORM поделок функционал ограничивает возможности по сравнению с SQL
на мой взгляд все эти велосипеды - туфта полная.
А что не туфта на твой взгляд?
 

pilot911

Новичок
с орм код выглядит красиво, но с тз скорости разработки и отладки непрактично
 

zerkms

TDD infected
Команда форума
pilot911
с орм-то скорость отладки и разработки ниже? не смеши.

PHP:
$object = $mapper->create();
$object->setName('pilot911');
$mapper->save($object);
$foobar = $mapper->searchByField('name', 'pilot911');
при этом генератор запросов избавляет от опечаток тех же самых, и как уже отлаженный код - сокращает время на отладку, не связанную с бизнес-логикой.
 

AmdY

Пью пиво
Команда форума
pilot911
чем не удобно? скорость разработки возрастает немерено, а с отладкой какие проблемы в сравнении с обычным подходом?
 

pilot911

Новичок
почему не обратиться к модели

model_shop::getResGoodsByLinkedCategory($fields, $where='', $limit='', $order_by='')

в которой будет тот самый запрос с пропиской LEFT JOIN и тп

а непрактично потому, что при изменении запроса придется править значительно больше, чем простой запрос
 

zerkms

TDD infected
Команда форума
pilot911
а запрос там сам собой появится?
а код, которым ты разбираешь связанные по 1:n, n:m записи-сущности тоже сам появится? и он ни разу не дублируется?

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

pilot911

Новичок
Автор оригинала: zerkms
pilot911
а запрос там сам собой появится?
а код, которым ты разбираешь связанные по 1:n, n:m записи-сущности тоже сам появится? и он ни разу не дублируется?


типичная точка зрения человека, знакомство которого с орм ограничивается чтением документаций. ну и ещё - типичная для этой ситуации "надуманная задача": а вот одним прекрасным утром у нас возьмут и изменятся все запросы приложения.
был опыт работы, красиво ... но не более

я не настаиваю :) лично меня этот синтаксис путает и кажется слишком нагроможденным, он не избавляет от знания имен полей, например
 

AmdY

Пью пиво
Команда форума
PHP:
$goods = Doctrine::getTable('Goods')
->createQuery('g')
->leftJoin('g.Category c')
->where('c.alias = ?', array($category))
->fetchArray();
теперь добавим кустомизированые свойства товара
PHP:
$goods = Doctrine::getTable('Goods')
->createQuery('g')
->leftJoin('g.Category c')
->leftJoin('g.Properties') // упс и я даже не помню какая там связь между таблицами
->where('c.alias = ?', array($category))
->fetchArray();
А сейчас выберу модель, назначу ей категории и сохраню. при этом обновится данные последнего изменения товара.
PHP:
$goods =  Doctrine::getTable('Goods')->findOne($id);
$goods->link('Category', array(1,2,4));
$goods->save();
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
AmdY
если закешировать в memcached результаты запроса "select id, name from сategory", чтобы поиск без джойна бегал,
не будет ли проблем с тем, что в других местах, где мы получили этот готовый объект с запросом, все еще "думаем", что там джоин, и к нему дописываем условия?
В сложной системе такой код кажется немного хрупким.

А вот для визиток / админок, где мы заранее знаем, что оптимизация нам не грозит - может быть, вполне.
 
Сверху