【问题标题】:SQL - How to check if item is in a list in a recordSQL - 如何检查项目是否在记录的列表中
【发布时间】:2011-06-15 19:28:27
【问题描述】:

我有一个名为 MyRoles 的列,其中包含存储在名为 UserRoles 的字段中的项目(整数)列表。我想编写一个查询来检查特定项目是否在列表中。该列表将如下所示:“1,2,3”

我不能使用 WHERE MyRoles

查询应该是什么样的?

这和我想的差不多:

SELECT *
FROM MyTable
WHERE MyRoles CONTAINS ('1')

没有答案很容易实现并且会让我在一条丑陋的道路上走得更远,这一事实清楚地表明规范化数据库始终是最好的选择。

【问题讨论】:

  • 您没有使用normalized database的任何特殊原因?
  • 这个数据库没有完全标准化的原因有很多。不过,我有兴趣找到这个问题的答案。我现在没有尝试更改数据库。不过谢谢!
  • 上下文有帮助 - 如果您在问题中说过您知道这不是标准化的,那么问题就不会出现......
  • 我只是假设没有傻瓜会创建一个没有完全标准化的数据库。但是,我做到了。当然,我感兴趣的是我可以找到多少可能的解决方法来解决一个相当典型的问题。谢谢,奥德。
  • 我在这里看到了很多关于非规范化字段和数据库的问题,主要是因为缺乏知识。

标签: sql postgresql-8.4 denormalized


【解决方案1】:

你可以使用LIKE:

SELECT *
FROM MyTable
WHERE MyRoles LIKE ('%1%')

这可能会执行得很糟糕(因为索引对于这样的搜索将毫无用处)。当然,即使查询中不存在1,也会匹配10。您可以扩展该方法:

SELECT *
FROM MyTable
WHERE MyRoles = '1'
  OR MyRoles LIKE '1,%'
  OR MyRoles LIKE '%,1,%'

更好的解决方案是规范化您的数据库并且不包含多值字段。使用多对多表,每行包含单个角色 ID 和项目 ID。这更容易查询。

某些数据库会为此类查询提供更好的工具,但这些将是扩展而不是标准 SQL - 您没有提到特定的 RDBMS。

【讨论】:

  • 如果角色超过9个就会失败。
  • 当然,只有在少于 10 个角色时才有效。如果还有更多,此查询还将匹配角色 10、11、21、310 等。这就是规范化数据库效果最好的原因。
  • John,规范化的数据库效果最好。但这不是一个选择。因此,我的问题。
【解决方案2】:

如果你使用 LIKE,请注意:

如果 MyRoles 是 2,11,那么它将匹配 LIKE('%1%'),尽管您不希望它匹配。

一个痛苦的解决方法是使用

SELECT *
FROM MyTable
WHERE MyRoles LIKE ('%,1,%')

但是您需要在每个 MyRoles 条目中放置前导和尾随逗号。

这些丑陋的事实是每个人都告诉您更改数据库设计并创建“角色”表的原因。

【讨论】:

  • 糟糕——看起来之前的答案现在已经被编辑为这样说......对不起!我是新来的,只是想帮忙。 :)
  • 克里斯 - 请继续尝试提供帮助。对一个问题有多个答案并没有错。
  • 谢谢,克里斯。由于您引用的确切原因,我完全意识到使用 LIKE 的陷阱。您还有其他解决方法吗?
  • 您可以使用 WHERE ','+MyRoles+',' LIKE ('%,1,%') 来避免更新数据库中的 MyRoles 字段
【解决方案3】:

将其转换为数组:

SELECT *
FROM MyTable
WHERE ('{' || MyRoles || '}')::int[] && array[1]

更好的是,您可以在上述混乱中使用索引。在构建数组时,将文本完全转换为数组类型会被拒绝,但您可以解决它:

create function role_scan(text) returns int[] as $$
  select ('{' || $1 || '}')::int[];
$$ language sql immutable strict;

create index on MyTable using gin (role_scan(MyRoles));

-- actually use the index
SELECT *
FROM MyTable
WHERE role_scan(MyRoles) && array[1]

在添加索引时有一个警告,您应该注意这一点。 stats 收集器不查看(最多 9.1)到实际的数组值。重叠算子的选择性(1/200,即非常有选择性)针对所有意图和目的进行了硬编码。因此,如果您正在查询非常常见的值,您可能会在不合适的地方进行索引扫描。一种解决方法是,当您知道有大量角色适用时,直接调用底层重叠方法(提供 1/3 的选择性并且没有潜在的索引扫描):

SELECT *
FROM MyTable
WHERE arrayoverlap(role_scan(MyRoles), array[1])

【讨论】:

    【解决方案4】:
    SELECT *
    FROM MyTable
    WHERE FIND_IN_SET(1, MyRoles)
    

    编辑: 它适用于 mysql 数据库服务器。

    编辑:

    find_in_set function 用于 postgres:

    create function find_in_set(n int, s text) returns bigint as
    $$
    select z.row_number
    from
    (
        select row_number() over(), y.x
        from (select unnest(('{' || $2 || '}')::int[]) as x) as y
    ) as z
    where z.x = $1
    $$ language sql;
    

    【讨论】:

    • FIND_IN_SET 看起来像一个特定的扩展。这是什么数据库?
    • 这是mysql特有的功能。但他没有提到他的数据库服务器是什么。
    • Rahim,这正是我想要的答案类型,除了我需要它在 Postgres 中工作。谢谢!
    • @Rahim:您的解决方案会自动进行 seq 扫描。
    【解决方案5】:

    也许正则表达式可以在这里提供帮助:

    SELECT *
    FROM MyTable
    WHERE MyRoles ~ ('^(.*,)*' || 1 || '(,.*)*$')
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-11-25
      • 1970-01-01
      • 1970-01-01
      • 2018-02-25
      • 2019-07-26
      • 1970-01-01
      • 2011-09-02
      • 2017-12-21
      相关资源
      最近更新 更多