【问题标题】:SQL query to match a list of values with a list of fields in any order without repetitionSQL查询以任何顺序将值列表与字段列表匹配,而不重复
【发布时间】:2014-03-05 20:01:13
【问题描述】:

我最近不得不编写一个查询来过滤一些如下所示的特定数据:

假设我有 3 个不同的值,我想在我的数据库中我的一个表的 3 个不同字段中搜索它们,它们必须以所有可能的顺序搜索而不重复。

这是一个示例(为了便于理解,我将使用命名查询表示法来显示必须放置值的位置):

val1 = "a", val2 = "b", val3 = "c"

这是我生成的查询:

SELECT * FROM table WHERE
(fieldA = :val1 AND fieldB = :val2 AND fieldC = :val3) OR
(fieldA = :val1 AND fieldB = :val3 AND fieldC = :val2) OR
(fieldA = :val2 AND fieldB = :val1 AND fieldC = :val3) OR
(fieldA = :val2 AND fieldB = :val3 AND fieldC = :val1) OR
(fieldA = :val3 AND fieldB = :val1 AND fieldC = :val2) OR
(fieldA = :val3 AND fieldB = :val2 AND fieldC = :val1)

我要做的是生成一个模拟排列而不重复的查询。有没有更好的方法来进行这种类型的查询?

这对于 3x3 是可以的,但如果我需要对 9x9 等更大的东西做同样的事情,那么生成查询将是一团糟。

我正在使用 MariaDB,但我可以接受可以在 PostgreSQL 上运行的答案。 (我想了解是否有一种无需“蛮力”即可编写此类查询的智能方法)

【问题讨论】:

  • 您的列的日期类型是什么?因为,对于整数,很快就有一个更快的解决方案......
  • 都是'字符串'

标签: mysql sql postgresql permutation mariadb


【解决方案1】:

没有更好的方法很多,但你可以使用in

SELECT *
FROM table
WHERE :val1 in (fieldA, fieldB, fieldC) and
      :val2 in (fieldA, fieldB, fieldC) and
      :val3 in (fieldA, fieldB, fieldC)

至少它更短。而且,这是标准 SQL,因此它应该适用于任何数据库。

【讨论】:

  • 当一次只允许 1 个 val 满足 1 个字段时,这将允许 fieldA 和 fieldB 满足 :val1。
  • @TMcKeown 。 . .我只是将它改回我最初考虑的方式(检查变量中的值)。我以为切换顺序只是表面上的,但正如你所指出的,显然不是。
  • 我觉得我的方法也行...提示提示...没有漂亮的方法
  • 非常感谢...您的替代方案更简洁。
【解决方案2】:

...我可以接受可以在 PostgreSQL 上运行的答案。 (我想要 了解是否有一种无需“蛮力”即可编写此类查询的智能方法)

Postgres 中有一种“智能方式”,使用排序数组。

整数

对于integer 值,请使用附加模块intarraysort_asc()

SELECT * FROM tbl
WHERE  sort_asc(ARRAY[id1, id2, id3]) = '{1,2,3}' -- compare sorted arrays

适用于任何个元素。

其他类型

正如评论中所阐明的,我们正在处理字符串
创建适用于任何可排序的类型sort_asc()的变体:

CREATE OR REPLACE FUNCTION sort_asc(anyarray)
  RETURNS anyarray LANGUAGE sql IMMUTABLE AS
'SELECT array_agg(x ORDER BY x COLLATE "C") FROM unnest($1) AS x';

不如intarray 的兄弟姐妹快,但足够快。

  • 将其设为 IMMUTABLE 以允许在索引中使用它。
  • 使用COLLATE "C" 忽略当前语言环境的排序规则:更快、不可变。
  • 要使函数适用于任何可以排序的类型,请使用polymorphic参数。

查询是一样的:

SELECT * FROM tbl
WHERE  sort_asc(ARRAY[val1, val2, val3]) = '{bar,baz,foo}';

或者,如果您不确定“C”语言环境中的排序顺序...

SELECT * FROM tbl
WHERE  sort_asc(ARRAY[val1, val2, val3]) = sort_asc('{bar,baz,foo}'::text[]);

索引

为了获得最佳读取性能,请创建functional index(以写入性能为代价):

CREATE INDEX tbl_arr_idx ON tbl (sort_asc(ARRAY[val1, val2, val3]));

SQL Fiddle demonstrating all.

【讨论】:

  • 如果有帮助的话,我正在研究立方体模块。但错过了intarray 模块中的排序功能。非常好。
【解决方案3】:

我的回答假设有一个我们可以挑选出来的Key 列。输出应该是满足所有 3 个值的所有键以及正在使用的每个字段和值:

这个“应该”为您提供符合条件的密钥列表

SELECT F.KEY
FROM (
SELECT DISTINCT L.Key, L.POS
FROM (
  SELECT Key, 'A' AS POS, FieldA AS FIELD FROM table AS A
  UNION ALL
  SELECT Key, 'B' AS POS, FieldB AS FIELD FROM table AS A
  UNION ALL
  SELECT Key, 'C' AS POS, FieldC AS FIELD FROM table AS A ) AS L
WHERE L.FIELD IN(:VAL1, :VAL2, :VAL3)
) AS F
GROUP BY F.KEY
HAVING COUNT(*) = 3

【讨论】:

  • 这是一个别名列,但是您表示一个别名或计算列
  • 括号引用 ([identifier]) 是 SQL Server 的东西,不是吗?
  • @muistooshort:是 SQL Server 但不是(标准)SQL(绝对不是 Postgres 或 MySQL)。
  • 感谢分享这一点,而对于我投票支持的特定约束看起来更干净,我可以看到其他情况,这种实现方式可能是更好的选择
【解决方案4】:

虽然 Gordon 的答案肯定更短,而且几乎肯定也更快,但我正在考虑如何在组合数量增加时最小化代码更改。

而且我可以为 Postgres 想出一个更短但更“易于更改”的东西:

with recursive params (val) as (
   values (1),(2),(3) -- these are the input values
), all_combinations as (
   select array[val] as elements
   from params
   union all
   select ac.elements||p.val
   from params p
     join all_combinations ac 
       on array_length(ac.elements,1) < (select count(*) from params)
)
select *
from the_table
where array[id1,id2,id3] = any (select elements from all_combinations);

它有什么作用?

首先我们创建一个包含我们正在寻找的值的 CTE,然后递归 CTE 构建一个列表,其中包含这些值的所有可能排列。该列表将包含太多元素,因为它还将包含具有 1 个或两个元素的数组。

最终选择将应比较的列放入数组中,并将其与 CTE 生成的排列进行比较。

这是一个 SQLFiddle 示例:http://sqlfiddle.com/#!15/43066/1

当值(和列)的数量增加时,您只需将新值添加到values 行构造函数,并将附加列添加到where 条件中的列数组中。

【讨论】:

  • 我正在阅读 PostgreSQL 中的递归,并想知道如何使用它来完成这项工作。感谢分享。
  • @GabrielMazetto 在这里真正有帮助的不仅是 CTE,还有 Postgres 中数组的效率。如果这是经常使用的东西,我可能会将排列生成(两个 CTE)放入一个函数中。然后你可以简单地写:select * from the_table where array[id1,id2,id3] = any (select elements from generate_permutation(1,2,3)
  • 如果你对数组进行排序,你可以减少到一个单一的相等性检查。
【解决方案5】:

使用一种天真的方法,我会为这项工作使用in 子句,并且由于不应该有任何重复,所以当字段重复时排除。

您还可以进行一些优化。

首先您可以排除最后一个字段,因为:

A <> B, A <> C
A <> B, B <> C, 

也意味着:

C <> B,  C <> A

此外,以下查询不需要先前查询的字段,因为:

A <> B == B <> A

查询可以写成:

SELECT * FROM table
WHERE :val1 in (fieldA, fieldB, fieldC) and
  :val2 in (fieldA, fieldB, fieldC) and
  :val3 in (fieldA, fieldB, fieldC) and
  fieldA not in (fieldB, fieldC) and
  fieldB <> fieldC

这是一种幼稚的方法,可能还有其他使用 MySQL API 的方法,但这个可以完成工作。

【讨论】:

    猜你喜欢
    • 2011-02-07
    • 1970-01-01
    • 1970-01-01
    • 2017-11-22
    • 1970-01-01
    • 2018-03-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多