Обработка сложных предложений WHERE с помощью PHP Query Builder

Там есть несколько библиотек построителей запросов ActiveRecord. Некоторые из них являются самостоятельными, а некоторые из них встроены в рамки . Однако у них действительно есть проблемы с предложениями WHERE и HAVING, когда дело доходит до сложного SQL. Отключение других баз данных – я пытаюсь придумать метод WHERE (), совместимый с MySQL и PostgreSQL, который мог бы устранить эти текущие потоки методов.

Ниже следует длинный список идей и примеров, показывающих лучшее, что я мог бы придумать до сих пор. Однако я не могу решить все варианты использования, и я чувствую, что мое частное решение неряшливо. Любой, кто может ответить тем, что решает все эти проблемы, не только ответит на этот вопрос, но и будет отвечать за исправление проблемы, которая уже несколько лет преследует реализацию PHP.

Общие операторы

= Equal <> Not Equal > Greater Than < Less Than >= Greater Than Or Equal <= Less Than Or Equal BETWEEN between values on right NOT logical NOT AND logical AND OR logical OR 

Пример Где предложения

 SELECT ... FROM table... WHERE column = 5 WHERE column > 5 WHERE column IS NULL WHERE column IN (1, 2, 3) WHERE column NOT IN (1, 2, 3) WHERE column IN (SELECT column FROM t2) WHERE column IN (SELECT c3 FROM t2 WHERE c2 = table.column + 10) WHERE column BETWEEN 32 AND 34 WHERE column BETWEEN (SELECT c3 FROM t2 WHERE c2 = table.column + 10) AND 100 WHERE EXISTS (SELECT column FROM t2 WHERE c2 > table.column) 

Существует множество распространенных форматов ActiveRecord, которые предложение where () использует в разных текущих библиотеках.

 $this->db->where(array('session_id' => '?', 'username' => '?')); $this->db->fetch(array($id, $username)); // vs with is_int($key) $this->db->where(array('session_id', 'username')); $this->db->fetch(array($id, $username)); // vs with is_string($where) $this->db->where('session_id', '?'); $this->db->where('username'); $this->db->fetch(array($id, $username)); // vs with is_array($value) $this->db->where('session_id', '?'); $this->db->where('username', array('Sam', 'Bob')); $this->db->fetch(array($id)); 

Вот окончательный формат, который у меня есть до сих пор. Он должен обрабатывать группировку (...) AND (...) а также подготовленные параметры привязки оператора («?» & «: Name»).

 function where($column, $op = '=', $value = '?', $group = FALSE){} // Single line $this->db->where('column > 5'); $this->db->where('column IS NULL'); // Column + condition $this->db->where('column', '='); // WHERE column = ? (prepared statement) $this->db->where('column', '<>'); // WHERE column <> ? (prepared statement) // Column + condition + values $this->db->where('column', '=', 5); // // WHERE column = 5 $this->db->where('column', 'IN', '(SELECT column FROM t2)'); // WHERE column IN (SELECT column FROM t2) $this->db->where('column', 'IN', array(1,2,3)); // WHERE column IN (1, 2, 3) $this->db->where('column', 'NOT IN', array(1,2,3)); // WHERE column NOT IN (1, 2, 3) // column + condition + values + group $this->db->where( array( array('column', '<', 20), array('column', '>', 10) ), NULL, NULL, $group = TRUE ); // WHERE (column < 20 AND column > 10) 

:ОБНОВИТЬ:

В ходе моего вопроса я понял, что условия ГДЕ и ИХ становятся более сложными, чем глубже вы идете. Попытка абстрагировать даже 80% функций приведет к массивной библиотеке только для WHERE и HAVING. Как отмечает Билл, это просто не разумно для языка сценариев, такого как PHP.

Решение состоит в том, чтобы вручную обработать часть WHERE вашего запроса. Пока вы используете " вокруг своих столбцов», вы можете использовать тот же запрос WHERE в Postgre, SQLite и MySQL, поскольку они используют почти тот же синтаксис SQL. (Для MySQL вы должны str_replace() их с тиком »).

Наступает момент, когда абстракция болит больше, чем помогает, ГДЕ условия – одно из таких мест.

Я немного поработал в библиотеке Zend_Db , которая включает в себя класс PHP для построения SQL-запросов . Я решил плутать, пытаясь обработать любой мыслимый синтаксис SQL в WHERE и HAVING по нескольким причинам:

  • PHP – это язык сценариев, который анализирует и компилирует код для каждого запроса (если вы не используете кеш байтового кода). Таким образом, среда PHP чувствительна к громоздким библиотекам кода – более того, чем Java или C # или Python или что у вас есть. Поэтому высокий приоритет заключается в том, чтобы библиотеки были как можно более тонкими.

    Все библиотеки Zend_Db над Zend_Db я работал, составляли около 2000 строк кода PHP. Напротив, Java Hibernate имеет порядок 118K строк кода. Но это не так много, поскольку библиотека Java предварительно скомпилирована и не должна загружаться по каждому запросу.

  • Выражения SQL следуют генерирующей грамматике, которая является более компактной, и ее легче читать и поддерживать, чем любая из построенной на PHP конструкции. Изучение грамматики выражений SQL намного проще, чем изучение API, который может имитировать его. Вы в конечном итоге поддерживаете «упрощенную грамматику». Или вы начинаете так, и окажетесь под принуждением вашего сообщества пользователей в функции Creep до тех пор, пока ваш API не станет слишком сложным.

  • Чтобы отлаживать приложение, использующее такой API, вам неизбежно понадобится доступ к окончательному выражению SQL, поэтому речь идет о самой проницательной абстракции, которую вы можете иметь.

  • Единственным преимуществом использования PHP-интерфейса для выражений SQL было бы то, что он помогает выполнить завершение кода в интеллектуальных редакторах и IDE. Но когда многие операторы и операнды используют строковые константы, такие как '>=' , вы испортите любой интеллект завершения кода.


Обновление: я просто прочитал хорошую статью в блоге « Прощание с ОРМ ». Автор, Альдо Кортези, предлагает использовать язык выражения SQL в SQLAlchemy Python. Синтаксический сахар и перегрузка операторов, стандартная в Python (но не поддерживаемая в PHP), делают это очень эффективным поисковым решением.

Вы также можете посмотреть на DBIx :: Class Perl, но в итоге он становится довольно уродливым.

Это часть моего класса ActiveRecord, я не обрабатываю подзапросы (я даже не беспокоюсь):

 public function Having($data, $operator = 'LIKE', $merge = 'AND') { if (array_key_exists('query', $this->sql) === true) { foreach ($data as $key => $value) { $this->sql['having'][] = ((empty($this->sql['having']) === true) ? 'HAVING' : $merge) . ' ' . $this->Tick($key) . ' ' . $operator . ' ' . $this->Quote($value); } } return $this; } public function Where($data, $operator = 'LIKE', $merge = 'AND') { if (array_key_exists('query', $this->sql) === true) { foreach ($data as $key => $value) { $this->sql['where'][] = ((empty($this->sql['where']) === true) ? 'WHERE' : $merge) . ' ' . $this->Tick($key) . ' ' . $operator . ' ' . $this->Quote($value); } } return $this; } 

Еще одна вещь, которую вы можете рассмотреть, – это методы customHaving () и customWhere ().

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

Изучив это, я обнаружил, что проблема с Zend-Db и другими такими двигателями заключается в том, что они стараются быть всеми для всех людей. Чтобы обратиться к самой большой аудитории, они должны предложить самые общие функциональные возможности, которые, как я вижу, становятся их собственными уничтожениями (и как это объясняет Билл Карвин).

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

При создании SQL-движка первое, что нужно попытаться сделать, – ограничить объем того, что SQL может произвести ваш движок. Вы не должны позволять ему производить select * from table например; движок должен требовать от разработчика определения каждого select , where и having явно указанным столбцом. В качестве другого примера часто бывает полезно, чтобы каждый столбец имел псевдоним (обычно не требуемый базой данных).

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

Библиотеки, которые я написал до сих пор, составляют около 600 строк кода (около 170 строк являются обработкой ошибок). Он касается объединений ISO, подзапросов (в предложениях SELECT , FROM и WHERE ), любых двухсторонних предложений сравнения, IN , EXISTS и BETWEEN (с подзапросами в предложении WHERE). Он также неявно создает привязки вместо прямого ввода значений в SQL.

Ограничения (кроме уже упомянутых): SQL написан явно для Oracle. Непроверено на любой другой платформе базы данных.

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

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

 <?php $substmt = new OraSqlStatement; $substmt->AddVarcharCol ('value','VALUE') ->AddVarcharCol ('identity','UID',false) ->AddVarcharCol ('type','info_type',false) ->AddFrom ('schemaa.user_propertues','up') ->AddWhere ('AND') ->AddComparison ('UID', '=', 'e.identity', 'column') ->AddComparison ('info_type', '=', 'MAIL_ADDRESS'); $stmt = new OraSqlStatement; $stmt->AddVarcharCol ('company_id', 'Company') ->AddVarcharCol ('emp_no', 'Emp Id') ->AddVarcharCol ('person_id', 'Pers Id') ->AddVarcharCol ('name', 'Pers Name') ->AddDateCol ('employed_date', 'Entry Date') ->AddDateCol ('leave_date', 'Leave Date') ->AddVarcharCol ('identity', 'User Id') ->AddVarcharCol ('active', 'Active') ->AddVarcharCol ($substmt, 'mail_addy') ->AddFrom ('schemab.employee_tab', 'e') ->AddFrom ('schemaa.users_vw','u','INNER JOIN','u.emp_no=e.emp_number') ->AddWhere ('AND') ->AddComparison ('User Id', '=', 'my_user_id') ->AddSubCondition ('OR') ->AddComparisonNull ('Leave Date', false) ->AddComparisonBetween ('Entry Date', '2011/01/01', '2011/01/31'); echo $stmt->WriteSql(); var_dump($stmt->GetBindArray()); ?> 

Что производит:

 SELECT company_id "Company", emp_no "Emp Id", person_id "Pers Id", name "Pers Name", employed_date "Entry Date", leave_date "Leave Date", identity "User Id", active "Active", ( SELECT value "VALUE" FROM schemaa.user_propertues up WHERE upper(identity) = upper(e.identity) AND upper(TYPE) = upper (:var0) ) "mail_addy" FROM schemab.employee_tab e INNER JOIN schemaa.users_vw u ON u.emp_no = e.emp_number WHERE upper (identity) = upper (:var1) AND ( leave_date IS NOT NULL OR employed_date BETWEEN to_date (:var2,'YYYY/MM/DD') AND to_date (:var3,'YYYY/MM/DD') ) 

Наряду с массивом bind:

 array 0 => string 'MAIL_ADDRESS' (length=12) 1 => string 'my_user_id' (length=10) 2 => string '2011/01/01' (length=10) 3 => string '2011/01/31' (length=10) 

API SQLAlchemy – лучший из тех, с которыми я работал до сих пор. Это Python-библиотека, но вы все еще можете быть вдохновлены ею. Это не только для WHERE-предложений — весь запрос SQL (будь то выбор или DML) выражается структурой данных, которая легко модифицируется.

(Я имею в виду его SQL-инструментарий, а не ORM-части. 🙂

Вы можете рассматривать SQLBuilder, написанный на PHP, и он может генерировать кросс-платформенный SQL для MySQL и PostgreSQL, устанавливая другой драйвер запроса.

Вариант использования находится здесь: https://github.com/c9s/SQLBuilder/blob/2.0/tests/SQLBuilder/Query/SelectQueryTest.php