创建复杂的IF,OR,NOT& AND SQL查询(MySQL)

时间:2013-06-14 08:16:34

标签: php mysql sql codeigniter

(道歉,如果这是重复的 - 我已经尝试过搜索,但我可能不知道我正在努力实现的目标 - 请随时纠正我!)

背景

所以我有一个基于PHP的应用程序(Codeigniter,但我在这部分使用普通的SQL语言),它有一个MySQL数据库,有2个表 - '联系人'和'命令'。

为简单起见,我们假设:

  • '接触'有3个cols:Id,FirstName,LastName
  • '为了'有4个cols:Id,ContactId,ItemBought,ItemValidDate
  • '行中的行示例'表:22,11,成人会员,2012/13
  • Id是两个表的主键,ContactId是' contact'的外键。而ItemBought和ItemValidDate都是简单的varchar(我们存储'季节'而不是日期 - 我知道,它不理想,但它是客户想要的)

在某些时候,我知道,我将不得不为3个表扩展这个并使用OrderItem表,以允许订单包含多个项目,因此我想找到一个解决方案可以建立的。但目前,我甚至不了解基本知识,所以我将它保存到2桌

问题

我想创建一个搜索页面,允许用户根据许多不同的标准查找记录子集。

请参阅screenshot of search page

此表单提交了一系列标准:

[order_type_operator] => Array
    (
        [0] => equal
        [1] => equalor
        [2] => notequal
    )

[order_type] => Array
    (
        [0] => Adult Membership
        [1] => Adult Membership
        [2] => Adult Membership
    )

[order_expire] => Array
    (
        [0] => 2005/06
        [1] => 2006/07
        [2] => 2010/11
    )

[submit] => Start Search

然后循环遍历此数组,测试是否已提交值,并构建我的SQL查询。

所以,我希望我已经正确地解释了它,以便明确用户可以使用此表单来搜索符合许多不同条件的记录 - 理论上,无限数量的条件 - 结束最新的符合此条件的联系人列表

我尝试过什么

示例1 - 简单WHERE

  • " 查找具有'成人会员资格'的订单记录的联系人记录。在' 2009/10'"
  • 即。 SELECT * FROM contact JOIN order ON contact.Id = order.ContactId WHERE (order.ItemBought = 'Adult Membership' AND order.ItemValidDate = '2009/10')

这很好用。

示例2 - 在哪里或在哪里

  • "查找具有“成人会员资格”的订单记录的联系人记录。在' 2009/10' 中有成人会员资格的订单记录'在' 2010/10'
  • 即。 SELECT * FROM contact JOIN order ON contact.Id = order.ContactId WHERE (order.ItemBought = 'Adult Membership' AND order.ItemValidDate = '2009/10') OR (order.ItemBought = 'Adult Membership' AND order.ItemValidDate = '2010/11')

只要用户要求的每个条件都是OR查询,这都可以正常工作。我假设我可以使用括号和OR来构建此查询,因为我喜欢这么大?例如。在2005/06年度,或2006/07年度,或2007/08年度,或2008/09年度等中找到成人会员资格,就像上面的SQL一样,有更多括号加入' OR'?

示例3 - 地点和地点 - 我被困住了!

  • "查找具有'成人会员资格'的订单记录的联系人记录在' 2009/10'或者2010/11并且拥有成人会员资格的订单记录'在' 2012/13'

目前,我一直在尝试UNION,但如果有更多查询要遵循此规定(例如2008年或2009年和2010年的成人会员资格),这意味着要做多个SELECT。 (也许这就是答案?)

e.g。 `SELECT * FROM contact     JOIN订单ON contact.Id = order.ContactId WHERE(order.ItemBought =' Adult Membership' AND order.ItemValidDate =' 2009/10')OR(order.ItemBought =' ;成人会员资格' AND order.ItemValidDate =' 2010/11')

UNION

SELECT * FROM contact
JOIN order ON contact.Id = order.ContactId WHERE (order.ItemBought = 'Adult Membership' AND order.ItemValidDate = '2012/13)`

示例4 - 但是没有记录......吹响我的想法

  • "查找具有'成人会员资格'的订单记录的联系人记录在' 2009/10'并拥有“成人会员资格”的订单记录。在' 2010/10'但是没有订购赞助'在2007/08

我想知道运行这些查询,将结果存储在PHP数组中然后执行IN (*array of ids already selected*),但这似乎我没有正确使用SQL。

聪明的人 - 我做错了什么?

非常感谢你提前帮助。

PS。不要求你为我编写代码!

PPS。如果您知道任何好的教程,那么我很乐意跟随它们!

PPPS。如果这是重复,那么请接受我的道歉!

3 个答案:

答案 0 :(得分:0)

我认为你必须退后一步,先尝试在纸上形象化你的问题。示例1和2非常简单,但让我们看一下示例3

对于所有标准为“AND”或“OR”的情况 - 事情非常简单。做一个很长的时间,只是先说谎。 但是,当你开始混合它们时,你必须回答一个严肃的问题:

如何分割条件

让我们说有人接受了这些标准:

  1. 和A
  2. 或B
  3. 和C
  4. 这为您提供了如此多的查询排列!例如:

    1. (A或B)和C
    2. A或(B和C)
    3. (A和C)或B
    4. 如果你再添加一个'OR',你将结束数十种组合!让你离开你必须猜测该做什么的地方。甚至不想想如果没有涉及会发生什么......

      这不是您问题的直接答案,而是更多指向可能解决方案的指针。上次我们不得不做类似的事情时,我们最终将这些条件组合成块。

      您可以在块中添加条件或添加新的搜索块。在上面的示例中将块视为括号。块中的所有内容都是“AND”或“NOT AND”,块之间可以指定“和”或“或”。这样您就可以立即知道如何构建查询。这在独立应用程序中就像一个魅力。在页面上很好地实现它可能有点棘手,但你明白了。

答案 1 :(得分:0)

正如ZorleQ所说它很快就会变得一团糟

对于第3个问题,使用子选择连接的可能解决方案如下

SELECT contact.*, order.*
FROM contact
INNER JOIN order
ON contact.Id = order.ContactId
INNER JOIN (SELECT DISTINCT ContactId 
            FROM order 
            WHERE (ItemBought = 'Adult Membership' AND ItemValidDate = '2009/10') 
            OR (ItemBought = 'Adult Membership' AND ItemValidDate = '2010/11')) Sub1
ON contact.Id = Sub1.ContactId
INNER JOIN (SELECT DISTINCT ContactId
            FROM order 
            WHERE (ItemBought = 'Adult Membership' AND ItemValidDate = '2012/13')) Sub2
ON contact.Id = Sub2.ContactId

您可以在不使用子选择的情况下执行此操作,只需使用普通连接,如下所示

SELECT contact.*, order.*
FROM contact
INNER JOIN order
ON contact.Id = order.ContactId
LEFT OUTER JOIN order Sub1
ON contact.Id = Sub1.ContactId AND Sub1.ItemBought = 'Adult Membership' AND Sub1.ItemValidDate = '2009/10'
LEFT OUTER JOIN order Sub2
ON contact.Id = Sub2.ContactId AND Sub2.ItemBought = 'Adult Membership' AND Sub2.ItemValidDate = '2010/11'
INNER JOIN order  Sub3
ON contact.Id = Sub3.ContactId AND Sub3.ItemBought = 'Adult Membership' AND Sub3.ItemValidDate = '2012/13'
WHERE Sub1.ContactId IS NOT NULL OR Sub2 IS NOT NULL

您的第4个问题可以使用LEFT OUTER JOIN来查找2007/08购买赞助商的记录,并且仅返回未找到匹配项的行(即,检查LEFT OUTER JOINed表上的ContactId是NULL)。

SELECT contact.*, order.*
FROM contact
INNER JOIN order
ON contact.Id = order.ContactId
INNER JOIN order Sub1
ON contact.Id = Sub1.ContactId AND Sub1.ItemBought = 'Adult Membership' AND Sub1.ItemValidDate = '2009/10'
INNER JOIN order Sub2
ON contact.Id = Sub2.ContactId AND Sub2.ItemBought = 'Adult Membership' AND Sub2.ItemValidDate = '2010/10'
LEFT OUTER JOIN order Sub3
ON contact.Id = Sub3.ContactId AND Sub3.ItemBought = 'Sponsorship' AND Sub3.ItemValidDate = '2007/08'
WHERE Sub3.ContactId IS NULL

答案 2 :(得分:0)

我对这样的所有问题的解决方案,其中用户可能提供或不提供多个标准如下...(此示例适用于oracle,但也应该能够在MySQL中完成)...

传入所有过滤器变量,无论它们是否为空或填充了值。在这个例子中,我会说我有3个值,用户可能会或可能不会填充SELECT作为过滤器。

SELECT
   *
FROM
    table
WHERE
    NVL2(InputVariable1, InputVariable1, Column1) = Column1
OR  NVL2(InputVariable2, InputVariable2, Column2) = Column2 
OR  NVL2(InputVariable3, InputVariable3, Column3) = Column3

NVL2 - 这是一个oracle函数。如果第一个值不为null,则返回第二个值,否则返回第三个值。如果您没有使用oracle,并且没有NVL2的等效功能,那么只需自己编写该功能即可。

因此,使用上面的示例,代码ALWAYS将所有三个InputVariables传递给select语句,即使它们是NULL。通过使用NVL2或等效函数,如果InputVariable不为null,则仅在InputVariable和Column ONLY之间进行比较;否则它位于Column和Column之间,当然总是为true,从而有效地忽略了你想要的过滤器变量(即空过滤器值匹配所有行 - 即如果用户没有指定LastName,则包括所有LastNames)。

此解决方案允许您使用许多过滤器变量而无需事先进行大量处理 - 每次都将它们全部传递到SELECT中,无论它们是否为空。

如果您有多组过滤变量(即用户通过复选框或某种类似的机制启用一组输入值),您可以在CASE语句中执行上述操作。每种情况都应该检查给定集的启用值,并返回评估整个过滤器变量集的结果(与上面完全相同)。然后,将整个CASE结构的结果与1进行比较,如...

WHERE

CASE [ expression ]

    WHEN enableSet1 
    THEN     NVL2(InputVariable1, InputVariable1, Column1) = Column1
         OR  NVL2(InputVariable2, InputVariable2, Column2) = Column2 
         OR  NVL2(InputVariable3, InputVariable3, Column3) = Column3

   WHEN condition_2 THEN result_2
   ...
   WHEN condition_n THEN result_n

END = 1

这是有效的,因为CASE结构的值是被评估的THEN块的结果。

这将允许您在单个SELECT语句的范围内完成所需或大部分所需的过滤 - 再次,无需进行大量预处理来构建SELECT。