使用LEFT JOIN创建客户搜索。如果第二张表中找不到匹配项,则排除结果

时间:2019-01-28 04:26:03

标签: php sql search pdo left-join

我正在尝试创建一种搜索客户数据库的方法,该数据库中的一些数据(例如电话号码和电子邮件)具有一对多关系。我有一个具有客户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注入攻击。我将采取预防措施,但是这将是一个内部系统,因此我对此并不担心。

2 个答案:

答案 0 :(得分:1)

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

不会处理客户仅具有第二联系人的情况,但是如果您必须使用第一手的联系电话(或电子邮件)则没有意义。

查询的时间更长,但是逻辑相同(查询电子邮件的一个逻辑电话的两倍)

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

答案 1 :(得分:0)

您的数据库设计很好。

您的条件:

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

这将导致以下查询:

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。结果:

fname | lname | phone  | email
------+-------+--------+----------------
John  | Smith | 123456 | one@company.com
John  | Smith | 123456 | two@company.com
John  | Smith | 234567 | one@company.com
John  | Smith | 234567 | two@company.com

您可能希望以某种方式考虑此问题,例如使用字符串聚合,以获取以下结果:

fname | lname | phones         | emails
------+-------+----------------+---------------------------------
John  | Smith | 123456, 234567 | one@company.com, two@company.com