【问题标题】:Creating customer search using LEFT JOIN. Results excluded if no match found in second table使用 LEFT JOIN 创建客户搜索。如果在第二个表中找不到匹配项,则排除结果
【发布时间】:2019-06-21 01:47:27
【问题描述】:

我正在尝试创建一种方法来搜索客户数据库,其中一些数据(例如电话号码和电子邮件)具有一对多的关系。我有一个包含客户 ID(唯一、自动编号)、名字和姓氏的表。我有第二个表,其中包含一个电话号码,以及与该电话号码关联的客户 ID。我有第三个表,其中包含一个电子邮件地址,以及与该电子邮件地址关联的客户 ID。

这是PHP内置的查询:

$query = "SELECT *
            FROM customers
            LEFT OUTER JOIN phone_numbers ON customers.customer_id = phone_numbers.associated_customer
            LEFT OUTER JOIN email_addresses ON customers.customer_id = email_addresses.associated_customer
            WHERE first_name LIKE '%" . $_GET["fname"] . "%'
            AND last_name LIKE '%" . $_GET["lname"] . "%'
            AND phone_number LIKE '%" . $_GET["phone"] . "%'
            AND email_address LIKE '%" . $_GET["email"] . "%'";

这样,我搜索的客户只有在他们有电话号码和电子邮件地址的情况下才会出现。如果客户有多个电话号码或电子邮件,我也会从第一个表中得到重复的结果,所以如果客户有 2 个电话号码和 2 个电子邮件,我会得到 4 个结果。

我是否正确地构建表格以实现这种一对多关系?

编辑:用于澄清的示例数据。

表 1:

╔════════════════════════════════════════╗
║                customers               ║
╠═════════════╦════════════╦═════════════╣
║ customer_id ║ first_name ║ last_name   ║
╠═════════════╬════════════╬═════════════╣
║      1      ║ John       ║ Doe         ║
║      2      ║ John       ║ Wick        ║
║      3      ║ John       ║ Cena        ║
║      4      ║ John       ║ Krasinski   ║
║      5      ║ Jane       ║ Doe         ║
║      6      ║ Freddie    ║ Mercury     ║
╚═════════════╩════════════╩═════════════╝

表 2:

╔══════════════════════════════════════════════════╗
║                   phone numbers                  ║
╠════════════════╦═══════════════╦═════════════════╣
║  phone_number  ║ associated_id ║ primary_contact ║
╠════════════════╬═══════════════╬═════════════════╣
║   5555555555   ║       2       ║        0        ║
║   6692216251   ║       2       ║        1        ║
║   2025550174   ║       3       ║        1        ║
╚════════════════╩═══════════════╩═════════════════╝

表 3:

╔═══════════════════════════════════════════════════╗
║                   email_addresses                 ║
╠═════════════════╦═══════════════╦═════════════════╣
║  email_address  ║ associated_id ║ primary_contact ║
╠═════════════════╬═══════════════╬═════════════════╣
║ jdoe@aol.com    ║       1       ║        1        ║
║ jwick@email.com ║       2       ║        1        ║
║ jwick@aol.com   ║       2       ║        0        ║
╚═════════════════╩═══════════════╩═════════════════╝

搜索查询:

first name: "John"
last name: ""
phone number: ""
email address: ""

预期的结果是它返回名字字段中“John”的所有匹配项,并且只包括主要的联系方式:

╔═════════════╦════════════╦═════════════╦══════════════╦═════════════════╗
║ customer_id ║ first_name ║  last_name  ║ phone_number ║  email_address  ║
╠═════════════╬════════════╬═════════════╬══════════════╬═════════════════╣
║      1      ║ John       ║ Doe         ║              ║ jdoe@aol.com    ║
║      2      ║ John       ║ Wick        ║  6692216251  ║ jwick@email.com ║
║      3      ║ John       ║ Cena        ║  2025550174  ║                 ║
║      4      ║ John       ║ Krasinski   ║              ║                 ║
╚═════════════╩════════════╩═════════════╩══════════════╩═════════════════╝

实际结果是排除了所有没有关联电话号码和电子邮件地址的结果,并且每个电话号码和每个电子邮件地址都包含一个重复项:

╔═════════════╦════════════╦═════════════╦══════════════╦═════════════════╗
║ customer_id ║ first_name ║  last_name  ║ phone_number ║  email_address  ║
╠═════════════╬════════════╬═════════════╬══════════════╬═════════════════╣
║      2      ║ John       ║ Wick        ║  5555555555  ║ jwick@email.com ║
║      2      ║ John       ║ Wick        ║  6692216251  ║ jwick@email.com ║
║      2      ║ John       ║ Wick        ║  5555555555  ║ jwick@aol.com   ║
║      2      ║ John       ║ Wick        ║  6692216251  ║ jwick@aol.com   ║
╚═════════════╩════════════╩═════════════╩══════════════╩═════════════════╝

但是,假设我的搜索查询如下:

first name: "John"
last name: ""
phone number: "5555555555"
email address: ""

预期的结果是:

╔═════════════╦════════════╦═════════════╦══════════════╦═════════════════╗
║ customer_id ║ first_name ║  last_name  ║ phone_number ║  email_address  ║
╠═════════════╬════════════╬═════════════╬══════════════╬═════════════════╣
║      2      ║ John       ║ Wick        ║  5555555555  ║ jwick@email.com ║
╚═════════════╩════════════╩═════════════╩══════════════╩═════════════════╝

我知道 SQL 注入攻击。我会采取措施防止它们发生,但这将是一个内部系统,所以我并不担心。

【问题讨论】:

  • 您的预期输出是什么,请提供示例数据
  • 您正在编写危险的查询。使用 PDO 时,请使用准备好的语句并参数化查询。如果字段是可选的,您应该动态构建查询。
  • 可能你的列名发生冲突尝试列名而不是* 输出它的显示。并尝试在 where 子句中使用表对象。
  • 请阅读有关 SQL 注入攻击的信息,并了解为什么将用户提供的值直接插入到 SQL 查询中而不进行清理是非常危险的。正如@user3783243 所说,你真的需要参数化这个查询。 w3schools.com/sql/sql_injection.asp
  • 您使用的 DBMS 是什么?

标签: php sql search pdo left-join


【解决方案1】:

您能否尝试以下查询,该查询针对每个表(电话和电子邮件)考虑两种情况 1. 有一个非空的搜索条件,因此您将使用它来过滤表格 2. 搜索条件为空,则只查找primary_contact。客户端没有联系人的情况使用外连接处理,但您需要允许 primary_contact 为空。

不处理客户只有次要联系人的情况,但如果您有联系电话(或电子邮件),则必须是主要联系人。

查询要长得多,但它的逻辑是相同的两倍(一个用于电子邮件,一个用于电话)

SELECT *
FROM customers c
LEFT OUTER JOIN phone_numbers p ON c.customer_id = p.associated_customer
LEFT OUTER JOIN email_addresses e ON c.customer_id = e.associated_customer
WHERE c.first_name LIKE '%" . $_GET["fname"] . "%'
AND c.last_name LIKE '%" . $_GET["lname"] . "%'
AND (( '%" . $_GET["phone"] . "%' <> '' and
        p.phone_number LIKE '%" . $_GET["phone"] . "%' )
     or
     ( '%" . $_GET["phone"] . "%' = '' and
        (p.primary_contact = 1 or p.primary_contact is null) ))
AND (( '%" . $_GET["email"] . "%' <> '' and
        e.email_address LIKE '%" . $_GET["email"] . "%' )
     or
     ( '%" . $_GET["email"] . "%' = '' and
        (e.primary_contact = 1 or e.primary_contact is null) ))

【讨论】:

    【解决方案2】:

    您的数据库设计很好。

    您的条件:

    • 如果 $_GET["phone"] &lt;&gt; '' 则仅匹配电话行,否则仅匹配主要联系人。
    • 如果 $_GET["email"] &lt;&gt; '' 则仅匹配电子邮件行,否则仅匹配主要联系人。

    这会导致以下查询:

    SELECT *
    FROM customers c
    LEFT JOIN phone_numbers p ON p.associated_customer = c.customer_id
    LEFT JOIN email_addresses e ON e.associated_customer = c.customer_id
    WHERE c.first_name LIKE :fname
      AND c.last_name LIKE :lname
      AND
      (
        (:phone <> '' AND p.phone_number LIKE '%' || :phone || '%')
        OR
        (:phone = '' AND (p.primary_contact = 1 OR p.primary_contact IS NULL))
      )
      AND
      (
        (:email <> '' AND e.email_address LIKE '%' || :email || '%')
        OR
        (:email = '' AND (e.primary_contact = 1 OR e.primary_contact IS NULL))
      )
    ORDER BY c.first_name, c.last_name, p.phone_numbers, e.email_addresses;
    

    (为了便于阅读,我已将您的 GET 命令替换为绑定变量。)

    无论如何,对于弱搜索模式,比如包含某个数字的电话号码和包含某个字母的电子邮件地址,您仍然可以获得笛卡尔积:

    例如:John Smith,电话:123456、234567,电子邮件:one@company.com、two@company.com。搜索:John Smith,电话包含 2,电子邮件包含 o。结果:

    名称 |姓名 |电话 |电子邮件 ------+--------+--------+---------------- 约翰 |史密斯 | 123456 | one@company.com 约翰 |史密斯 | 123456 | two@company.com 约翰 |史密斯 | 234567 | one@company.com 约翰 |史密斯 | 234567 | two@company.com

    您可能想以某种方式考虑这一点,例如使用字符串聚合,从而得到如下结果:

    名称 |姓名 |电话 |电子邮件 ------+--------+----------------+------------------ --------------- 约翰 |史密斯 | 123456, 234567 | one@company.com, two@company.com

    【讨论】:

    • 抱歉,很久才回复。我最近真的很忙。我使用$stmt-&gt;bindParam(':fname', $_GET["fname"], PDO::PARAM_STR); 绑定了参数。我从这个查询返回零结果。我在搜索中输入的内容并不重要。
    • 我无法解释这一点。删除条件并将它们一对一添加。当您将WHERE 子句减少为WHERE c.first_name LIKE :fname 时,您会得到结果吗?添加AND c.last_name LIKE :lname后还能得到结果吗?
    猜你喜欢
    • 2016-08-15
    • 2013-03-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多