【问题标题】:PostgreSQL's ILIKE query with conditional filteringPostgreSQL 的带有条件过滤的 ILIKE 查询
【发布时间】:2020-12-31 16:33:46
【问题描述】:

我正在编写一个用户搜索方法,它应该通过电话号码、电子邮件和用户名搜索用户。问题是用户可以隐藏他们的电话号码和电子邮件。 因此,如果他们确实将其隐藏在应用程序的隐私设置中,则不应将其列在搜索结果中。

我正在使用 typeorm 来构建这个搜索查询,这就是我的尝试 如果不存在隐藏个人信息的要求:

const whereClause = `
        (
            "user".phone ILIKE :searchTerm
                OR "user".username ILIKE :searchTerm
                OR "user".profile_email ILIKE :searchTerm
        ) AND "user".id != :userId
        `;

const users = await this.userRepository.createQueryBuilder('user')
                                       .where(whereClause, filters)
                                       .leftJoinAndSelect('user.subscription', 'subscription')
                                       .leftJoinAndSelect('user.profilePrivacyPermissions', 'perms')
                                       .limit(ESearchResponseLimit.USERS)
                                       .getMany();

个人资料隐私表如下所示:

屏幕类型 1 对应于电话号码,2 对应于电子邮件。目前我们只考虑两种可能的状态:isEverybodyisNobody(我们不考虑联系人)。因此,例如,如果用户决定隐藏他的电话号码,则 isNobody 值将等于 true,其他值将设置为 false。

我需要在我正在构建的搜索查询中以某种方式考虑这些隐私设置。应该是这样的:

        const whereClause = `
        (
            ("user".phone ILIKE :searchTerm IF (perms.screenType = ${EScreenType.PHONE_NUMBER} AND perms.isEverybody))
                OR "user".username ILIKE :searchTerm
                OR ("user".profile_email ILIKE :searchTerm IF (perms.screenType = ${EScreenType.EMAIL} AND perms.isEverybody))
        ) AND "user".id != :userId
        `;

问题是没有IF这样的运营商,但我的想法是如果它没有隐藏我需要通过电话搜索,如果它也没有隐藏我需要通过电子邮件搜索。对于如何在查询中实现这一点,我什至想不出一个遥远的想法。

如果可以使用原始查询,那也很好。

我发现 PostgreSQL 中有 IF 运算符,但我不知道如何在这里使用它。

编辑:

如果我在隐藏电子邮件时通过电话搜索(这意味着对于电子邮件屏幕perms.isEverybody = false),用户对象的 profilePrivacyPermissions 字段如下所示:

"profilePrivacyPermissions": [
      {
        "screenType": 1,
        "isEverybody": true,
        "isContacts": false,
        "isNobody": false
      }
    ]

但它应该如下所示:

    "profilePrivacyPermissions": [
      {
        "screenType": 1,
        "isEverybody": true,
        "isContacts": false,
        "isNobody": false
      },
      {
        "screenType": 2,
        "isEverybody": false,
        "isContacts": false,
        "isNobody": true
      },
      {
        "screenType": 4,
        "isEverybody": true,
        "isContacts": false,
        "isNobody": false
      },
      {
        "screenType": 5,
        "isEverybody": true,
        "isContacts": false,
        "isNobody": false
      },
      {
        "screenType": 3,
        "isEverybody": true,
        "isContacts": false,
        "isNobody": false
      }
    ]

我知道这是因为查询的 where 子句中的 perms 条件。

【问题讨论】:

  • 使用布尔假人。优化器会理解:例如 WHERE ( :ignore_xclause OR field ILIKE :xfield) ... 为 :gnore_xclause 使用 True 这将被优化。
  • @wildplasser mmm 不确定我是否理解正确。你能写一个更详细的例子吗?
  • 我添加了一个答案来说明。

标签: node.js postgresql typeorm


【解决方案1】:

IF 不是 Postgres 中 SQL 的一部分(也不是标准 SQL)。它是PL/pgSQL 添加的程序元素的一部分。

普通 SQL 中,只需使用带有 AND 的布尔逻辑:

    const whereClause = `
    (   perms.isEverybody AND 
       (perms.screenType = ${EScreenType.PHONE_NUMBER} AND "user".phone ILIKE :searchTerm
     OR perms.screenType = ${EScreenType.EMAIL}        AND "user".profile_email ILIKE :searchTerm)
     OR "user".username ILIKE :searchTerm
    ) AND "user".id != :userId
    `;

或者使用SQLCASE,可能更容易理解(逻辑上等价):

    const whereClause = `
    (CASE WHEN perms.isEverybody THEN
        CASE perms.screenType
           WHEN ${EScreenType.PHONE_NUMBER} THEN "user".phone ILIKE :searchTerm
           WHEN ${EScreenType.EMAIL}        THEN "user".profile_email ILIKE :searchTerm
        END
     END
     OR "user".username ILIKE :searchTerm
    ) AND "user".id != :userId
    `;

细微的差别:只执行WHEN 子句计算为true 的第一个分支。甚至没有输入相同CASE 语句的其他分支。 (因此,如果那里潜伏着非法的东西,也不例外。)

我省略了ELSE 子句,它在SQL 中默认为NULL - 相当于WHERE 子句中的false,其中只有true 符合条件。

注意,我使用CASE 的两种不同语法变体(“简单”和“搜索”。)请参阅:

另请注意,有一个程序 CASE in PL/pgSQL,其用途相似,但与 SQL CASE 严格不同。

【讨论】:

  • mmm 我查看了查询,但我不明白它应该如何工作,例如,还应该有第二个处理电子邮件的条件:perms.screenType = ${EScreenType.EMAIL} AND perms.isEverybody。据我所知,在这里使用AND 只是选择那些指定条件解析为真的用户,但我需要这些条件来检查是否需要执行ILIKE 查询。如果条件解析为真,那么结果应该是包含其所有字段的整个用户对象的列表。好像这个查询不这样做。
  • 或者我什么都不懂。
  • then the result should be a list of whole user objects containing all its fieldsWHERE 子句对返回哪些列没有影响。它决定该行是否包含在结果中。 SELECT 列表决定返回行的什么
  • @Albert:我将EScreenType.EMAIL 误读为EScreenType.PHONE_NUMBER 并错误地将两者折叠在一起。它现在应该可以工作了。
  • 哦,是的,我明白了,谢谢你的详细回答,昨天我意识到逻辑但那里似乎仍然存在问题,我在编辑中描述了它。请你看一下好吗?
【解决方案2】:
WHERE 1=1
AND ( True OR nm.strain = :_strain ) -- Boolean logic to short-circuit optional conditions
AND ( False OR nm.strain ILIKE :_strain_like )
...

参数化查询:


WHERE 1=1
AND ( " . $ignore_strain . " OR nm.strain = :_strain ) -- Boolean logic to short-circuit optional conditions
AND ( " . $ignore_strain_like . " OR nm.strain ILIKE :_strain_like )
...

(这是PHP,但技术相同)

【讨论】:

  • 不错的方法,但似乎有点问题。不知道我是否可以简短地解释一下,我会尝试。我需要这个搜索同时通过电子邮件和电话查找用户(所以它不能总是 AND,第二行应该以 OR 开头),也就是说如果电子邮件是 g123@mail.com 并且电话是 123,并且 searchTerm是 123,无论他们的电话或电子邮件是否隐藏,用户都应该出现在搜索结果中。现在是这种情况:-->
  • const whereClause = 1=1 AND (true OR "user".phone ILIKE :searchTerm) OR (false OR "user".profile_email ILIKE :searchTerm) AND "user".id != :userId ; 不会触发通过电子邮件进行的搜索,因为左侧部分(在 OR 之前)将始终为真。
  • 有解决办法吗?我现在想出一些合理的东西似乎有点太慢了……
  • 在您的特定情况下,您可以交换 AND 和 OR:OR ( :want_phone_number AND phone_number LIKE :the_number)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-06-06
  • 1970-01-01
  • 2012-12-04
  • 1970-01-01
  • 2020-10-20
  • 1970-01-01
  • 2013-12-20
相关资源
最近更新 更多