简体   繁体   English

使用PHP查询生成器处理复杂的WHERE子句

[英]Handling Complex WHERE clauses with a PHP Query Builder

There are several ActiveRecord styled query builder libraries out there. 那里有几个ActiveRecord样式的查询构建器库。 Some are stand alone and some come built into frameworks . 有些是独立的 ,有些是内置于框架中的 However, they really have trouble with WHERE and HAVING clauses when it comes to complex SQL. 但是,当涉及到复杂的SQL时,它们确实遇到了WHERE和HAVING子句的问题。 Setting other databases aside - I am trying to come up with a MySQL and PostgreSQL compatible WHERE() method that could fix these current method downfalls. 将其他数据库放在一边 - 我试图想出一个MySQL和PostgreSQL兼容的WHERE()方法,可以解决这些当前方法的垮台。

What follows is a long list of ideas and examples showing the best I could come up with so far. 接下来是一长串的想法和例子,展示了迄今为止我能想到的最好的想法和例子。 However, I can't seem to solve all of the use cases and I feel my partial solution is sloppy. 但是,我似乎无法解决所有用例 ,我觉得我的部分解决方案很草率。 Anyone that can answer with something that solves all of these problems will not only answer this question - but a will be responsible for fixing a problem that has hunted PHP implementations for several years now. 任何可以用解决所有这些问题的方法回答的人不仅会回答这个问题 - 而且还会负责解决几年来一直困扰着PHP实施的问题。

Common Operators 共同运营商

    =   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

Example Where clauses 示例where子句

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)

There are many common ActiveRecord formats that the where() clause uses in the different current libraries. where()子句在不同的当前库中使用了许多常见的ActiveRecord格式。

$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));

Here is the final format that I have so far. 这是我到目前为止的最终格式。 It should handle grouping (...) AND (...) as well as prepared statement bound params ("?" & ":name"). 它应该处理分组(...) 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)

:UPDATE: :更新时间:

Over the course of my question I came to realize that WHERE and HAVING conditions only get more complex the deeper you go. 在我的问题过程中,我逐渐意识到,在你走得更深的时候,WHERE和HAVING条件变得越来越复杂。 Trying to abstract even 80% of the features would result in a massive library just for WHERE and HAVING. 试图抽象甚至80%的功能将导致一个庞大的库只为WHERE和HAVING。 As Bill points out, that just isn't reasonable for a scripting language like PHP. 比尔指出,对于像PHP这样的脚本语言来说,这是不合理的。

The solution is just to hand craft the WHERE portion of your query. 解决方案只是手工制作查询的WHERE部分。 As long as you use " around your columns you can use the same WHERE query in Postgre, SQLite, and MySQL since they use almost the same SQL syntax. (For MySQL you must str_replace() them with a tick`). 只要您使用"围绕您的列,您可以在Postgre,SQLite和MySQL中使用相同的WHERE查询,因为它们使用几乎相同的SQL语法。(对于MySQL,您必须使用tick`来str_replace()它们)。

There comes a point where abstraction hurts more than it helps, WHERE conditions are one such place. 有一点,抽象伤害的程度超过了它的帮助,条件就是这样一个地方。

I worked quite a bit on the Zend_Db library, which includes a PHP class for constructing SQL queries . 我在Zend_Db库上工作了Zend_Db ,它包含一个用于构造SQL查询的PHP I decided to punt on trying to handle every imaginable SQL syntax in WHERE and HAVING clauses, for several reasons: 我决定尝试在WHEREHAVING子句中处理所有可以想象的SQL语法,原因如下:

  • PHP is a scripting language that parses and compiles code on every request (unless you use a bytecode cache). PHP是一种脚本语言,可以在每个请求中解析和编译代码(除非您使用字节码缓存)。 So the PHP environment is sensitive to bulky code libraries -- more so than Java or C# or Python or what have you. 所以PHP环境对庞大的代码库很敏感 - 比Java或C#或Python更重要,或者你有什么。 It's therefore a high priority to keep libraries as lean as we can. 因此,尽可能保持图书馆的精益是我们的首要任务。

    All of the Zend_Db library I worked on was about 2,000 lines of PHP code. 我工作的所有Zend_Db库大约有2000行PHP代码。 By contrast, Java Hibernate is on the order of 118K lines of code. 相比之下,Java Hibernate大约有118K行代码。 But that's not so much of an issue since a Java library is precompiled and doesn't have to be loaded on every request. 但这并不是一个问题,因为Java库是预编译的,不需要在每个请求上加载。

  • SQL expressions follow a generative grammar that is more compact, and easier to read and maintain that any of the PHP-based construction you showed. SQL表达式遵循生成语法,这种语法比您展示的任何基于PHP的构造更紧凑,更易于阅读和维护。 Learning the SQL expression grammar is far easier than learning an API that can simulate it. 学习SQL表达式语法比学习可以模拟它的API要容易得多。 You end up supporting a "simplified grammar." 你最终支持“简化语法”。 Or else you start out that way, and find yourself coerced by your user community into Feature Creep until your API is unusably complex. 或者你开始这样做,并发现自己被用户社区强制进入Feature Creep,直到你的API变得非常复杂。

  • To debug an application that used such an API, you'd inevitably need access to the final SQL expression, so it's about the leakiest abstraction you can have. 要调试使用这种API的应用程序,您不可避免地需要访问最终的SQL表达式,因此它是关于您可以拥有的最漏洞的抽象

  • The only advantage to using a PHP-based interface for SQL expressions would be that it assists code-completion in smart editors and IDE's. 为SQL表达式使用基于PHP的接口的唯一好处是它可以帮助智能编辑器和IDE中的代码完成。 But when so many of the operators and operands use string constants like '>=' , you spoil any code-completion intelligence. 但是,当许多运算符和操作数使用字符串常量如'>=' ,就会破坏任何代码完成情报。


update: I just read a good blog article " A Farewell to ORMs ." 更新:我刚读了一篇很好的博客文章“ 告别ORM” The writer, Aldo Cortesi, suggests using the SQL Expression Language in Python's SQLAlchemy. 作者Aldo Cortesi建议在Python的SQLAlchemy中使用SQL表达式语言 Syntactic sugar and operator overloading that is standard in Python (but not supported in PHP) make this a very effective query-generating solution. Python中标准的语法糖和运算符重载(但PHP不支持)使这成为一种非常有效的查询生成解决方案。

You might also look at Perl's DBIx::Class, but it ends up being pretty ugly. 你可能也会看看Perl的DBIx :: Class,但它最终会非常难看。

I know this is an extremely old posting, but I'm going to reply to it anyway, because I'm in the process of developing my own classes to meet similar needs to what the question asks. 我知道这是一个非常古老的帖子,但无论如何我都会回复它,因为我正在开发自己的类来满足问题所要求的类似需求。

After looking into it, I've found that the problem with Zend-Db and other such engines is that they try to be all things to all people. 在研究之后,我发现Zend-Db和其他类似引擎的问题在于它们试图成为所有人的一切。 To appeal to the largest audience, they need to offer the most general functionality, which becomes their own undoing as far as I can see (and as expertly explained by Bill Karwin). 为了吸引最大的受众,他们需要提供最一般的功能,据我所知,这将成为他们自己的撤销(并由Bill Karwin专业解​​释)。

One of the most obvious over-complications that many engines do, is to confuse the generation of SQL code with its execution (making it easier to write dirty SQL). 许多引擎最明显的过度复杂化之一是将SQL代码的生成与其执行混淆(使得编写脏SQL变得更容易)。 In many applications, it's a good idea to separate both of these quite explicitly, encouraging the developer to think about injection attacks etc. 在许多应用程序中,最好将这两者明确地分开,鼓励开发人员考虑注入攻击等。

In building an SQL engine, the first thing to try to do, is to limit the scope of what SQL your engine can produce. 在构建SQL引擎时,首先要做的是限制引擎可以生成的SQL的范围。 You should not allow it to produce a select * from table for example; 例如,您不应该允许它select * from table生成select * from table ; the engine should require the developer to define each select , where and having column explicitly. 引擎应该要求开发人员明确定义每个selectwherehaving列。 As another example, it's often useful to require every column to have an alias (normally not required by the database). 另一个例子是,要求每个列都有一个别名(通常数据库不需要)通常很有用。

Notice that limiting the SQL in these ways does not limit what you can actually get out of the database. 请注意,以这些方式限制SQL并不限制您实际可以从数据库中获取的内容。 Yes, it makes the up-front coding more verbose on occasion, but it also makes it more structured, and lets you dump hundreds of lines of library-code which were only ever there in the first place to deal with complicated exceptions and provide (ahem) "flexibility". 是的,它使得前期编码有时更加冗长,但它也使它更加结构化,并且允许您转储数百行库代码,这些代码只是首先处理复杂的异常并提供(咳)“灵活性”。

The libraries I've written so far are about 600 lines of code (~170 lines of which is error-handling). 到目前为止我写的库大约有600行代码(大约170行是错误处理的)。 It deals with ISO joins, sub-statements (in the SELECT , FROM and WHERE clauses), any 2-sided comparison clause, IN , EXISTS and BETWEEN (with sub-statements in the WHERE clause). 它处理ISO连接,子语句(在SELECTFROMWHERE子句中),任何双边比较子句, INEXISTSBETWEEN (与WHERE子句中的子语句)。 It also implicitly creates bindings, instead of directly injecting values into the SQL. 它还隐式创建绑定,而不是直接将值注入SQL。

Limitations (other than those already mentioned): the SQL is written expressly for Oracle. 限制(除了已经提到的限制):SQL是专门为Oracle编写的。 Untested on any other database platform. 在任何其他数据库平台上未经测试。

I'm willing to share the code, assuming that any improvements are sent back. 我愿意分享代码,假设已经发回任何改进。

As an example of what the libraries let me produce, I hope that the following is simple enough to be intuitive, while also being complex enough to show the expandability potential: 作为图书馆让我制作的一个例子,我希望以下内容足够简单直观,同时也足够复杂以展示可扩展性:

<?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());
?>

Which produces: 哪个产生:

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') 
      )

Along with the bind array: 与绑定数组一起:

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)

This is part of my ActiveRecord class, I don't handle sub queries (I don't even bother): 这是我的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;
}

One other thing that you may consider is having a customHaving() and customWhere() methods. 您可以考虑的另一件事是使用customHaving()和customWhere()方法。

SQLAlchemy 's API is the best one I've worked with so far. SQLAlchemy的API是我迄今为止使用过的最好的API。 It's a Python-library, but you can still be inspired by it. 它是一个Python库,但你仍然可以从中受到启发。 It's not only for WHERE-clauses --- the entire SQL query (be it a select or DML) is expressed with a data structure that is easily modifiable. 它不仅适用于WHERE子句 - 整个SQL查询(无论是选择还是DML)都是用易于修改的数据结构表示的。

(I'm referring to its SQL-toolkit, not the ORM-parts. :-) (我指的是它的SQL工具包,而不是ORM部分。:-)

You may consider SQLBuilder, written in PHP and it can generate cross platform SQL for MySQL and PostgreSQL by setting different query driver. 您可以考虑使用PHP编写的SQLBuilder,它可以通过设置不同的查询驱动程序为MySQL和PostgreSQL生成跨平台SQL。

The use case is here: https://github.com/c9s/SQLBuilder/blob/2.0/tests/SQLBuilder/Query/SelectQueryTest.php 用例在这里: https//github.com/c9s/SQLBuilder/blob/2.0/tests/SQLBuilder/Query/SelectQueryTest.php

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM