【问题标题】:MySQL Select multiple columns group by sorted columns valuesMySQL按排序的列值选择多个列分组
【发布时间】:2018-10-09 14:59:07
【问题描述】:

我有这个表列结构:

id - n1 - n2 - n3

这里有一些虚拟数据:

id - n1 - n2 - n3
1 - 3 - 2 - 1
2 - 6 - 5 - 7
3 - 2 - 3 - 1
4 - 1 - 6 - 5
5 - 5 - 6 - 7
6 - 3 - 5 - 6

这个想法是按顺序选择和计数每个唯一的不同组 n1、n2 和 n3。

所以,例如,我们可以得到这样的结果:

total - n1s - n2s - n3s
2 - 1 - 2 - 3
2 - 5 - 6 - 7
1 - 1 - 5 - 6
1 - 3 - 5 - 6

你能帮我设置状态来实现吗?

我正在尝试在没有多重选择和 PHP 数组排序的情况下尝试...

谢谢。

【问题讨论】:

  • 您的样本数据和预期的输出不匹配。没有对应于 1,2,3 序列的行,但您的预期输出有它
  • 虽然问题比较模糊,但这类问题可能是设计不佳的症状
  • @MadhurBhaiya 有,id 1 和 id 3 都有 1、2、3。这就是我的意思。我希望能够根据每个数字列中的排序值进行计数......明白了吗?谢谢。
  • @Strawberry 我没有按顺序保存所有数字的列。这就是你所说的糟糕的设计吗?这就是为什么我希望能够按顺序计算与所有数字匹配的所有行......你能帮忙吗?谢谢。
  • @user4561667 明白了这一点。你真的应该做草莓的suggestion 来规范化表格。但我还是想试一试。似乎您有不止 3 列的 n。预计有多少列。我们确定在它们中(连续),值是唯一的还是预期重复的?

标签: mysql sorting field


【解决方案1】:

考虑以下 - 标准化数据集...

DROP TABLE IF EXISTS my_table;

CREATE TABLE my_table
(id INT NOT NULL
,n INT NOT NULL
,val INT NOT NULL
,PRIMARY KEY(id,n)
);

INSERT INTO my_table VALUES
(1, 1, 3),
(1, 2, 2),
(1, 3, 1),
(2, 1, 6),
(2, 2, 5),
(2, 3, 7),
(3, 1, 2),
(3, 2, 3),
(3, 3, 1),
(4, 1, 1),
(4, 2, 6),
(4, 3, 5),
(5, 1, 5),
(5, 2, 6),
(5, 3, 7),
(6, 1, 3),
(6, 2, 5),
(6, 3, 6);

这是一个快速(编写)且肮脏的解决方案。提供更快/更优雅的解决方案...

SELECT vals
     , COUNT(*) total
  FROM 
     ( SELECT id
            , GROUP_CONCAT(val ORDER BY val) vals 
         FROM my_table 
        GROUP 
           BY id
     ) x 
 GROUP 
    BY vals;
+-------+-------+
| vals  | total |
+-------+-------+
| 1,2,3 |     2 |
| 1,5,6 |     1 |
| 3,5,6 |     1 |
| 5,6,7 |     2 |
+-------+-------+

【讨论】:

  • 我认为您没有理解我的问题...您的表结构与我的不匹配...我想在排序时计算与列中相同数字匹配的行。谢谢。
  • 我认为你不明白我的回答。您应该修改您的(非标准化)结构以匹配我的(标准化)结构
  • 对不起,我不明白。你能指出我正确的位置来了解规范化和非规范化结构吗?我想要的只是收集行并在相同的数字出现在多列中时进行计数,关于列。
  • 您是否建议使用关系表?使用外键?只创建一个排序列并在那里按顺序包含所有数字然后按该排序列选择会更容易......或者我错过了什么?这就是我要避免的,所以我想使用我拥有的列进行选择,但在按顺序排列时按唯一不同的列计数。谢谢。
  • 我建议的是我所描述的结构。如果相同的值,代表本质上相同的事物,可以在不同的列中任意出现,那么这是设计不佳的高度症状。同样,任何时候你发现自己有上面列举的列,比如 2),警钟应该开始响起。
【解决方案2】:

我们只需要表达式来“排序”列 n1、n2 和 n3 中的值。如果我们有,那么我们可以做一个简单的GROUP BYCOUNT

SELECT COUNT(1) AS total
     , IF(t.n1<=t.n2,IF(t.n1<=t.n3,t.n1,t.n3),IF(t.n2<=t.n3,t.n2,t.n3)) AS n1s
     , IF(t.n1<=t.n2,IF(t.n2<=t.n3,t.n2,IF(t.n1<=t.n3,t.n3,t.n1)),IF(t.n1<=t.n3,t.n1,IF(t.n2<=t.n3,t.n3,t.n2 ))) AS n2s
     , IF(t.n1<=t.n2,IF(t.n2<=t.n3,t.n3,t.n2),IF(t.n1<=t.n3,t.n3,t.n1)) AS n3s
  FROM this_table_column_structure t
 GROUP BY n1s,n2s,n3s
 ORDER BY total DESC, n1s, n2s, n3s

会回来

total   n1s   n2s   n3s
-----  ----  ----  ----
    2     1     2     3
    2     5     6     7
    1     1     5     6
    1     3     5     6

【讨论】:

  • 嗨,你能解释一下IF吗?我需要为我表中的每个数字列做长 IF 吗?谢谢。
  • 使用三列 n1、n2 和 n3,我们可以使用条件测试对值进行“排序”。如果n1 &lt; n2 and n1 &lt; n3,那么 n1 是最低的。如果n2 &lt; n1 and n2 &lt; n3,那么 n2 是最低的。三列共有八种可能的组合。 SQL 中的表达式只是示例,一种对值进行排序的可能方法,返回最小值为 col n1s,第二低为 n2s,最高为 n3s
  • 这里记录了 MySQL IF() 函数 dev.mysql.com/doc/refman/5.7/en/… 一种更符合 ANSI 标准的可移植方法是使用 CASE 表达式代替 MySQL IF() 函数。
  • 这个答案是基于有 三个 列要排序,如问题所示,有四个、五个或更多列,然后显然使用表达式进行比较来排序变得笨拙。在这种情况下,我会考虑在表中存储额外的列来标识值的有序“序列”,以使提取更简单。
【解决方案3】:

作为第一种方法(如果时间允许),您应该真正考虑normalizing 您的餐桌,正如@Strawberry 的answer 中所建议的那样

但是,使用User Defined Functions 的第二种方法允许任意数量的列(尽管由于字符串操作和冒泡排序而效率低下)是可能的。

我们基本上需要创建一个函数,它可以对逗号分隔的字符串中的值进行排序。我找到了一个可以进行排序的工作函数。从here复制代码:

-- sort comma separated substrings with unoptimized bubble sort
DROP FUNCTION IF EXISTS sortString;
DELIMITER |
CREATE FUNCTION sortString(inString TEXT) RETURNS TEXT
BEGIN
  DECLARE delim CHAR(1) DEFAULT ','; -- delimiter 
  DECLARE strings INT DEFAULT 0;     -- number of substrings
  DECLARE forward INT DEFAULT 1;     -- index for traverse forward thru substrings
  DECLARE backward INT;   -- index for traverse backward thru substrings, position in calc. substrings
  DECLARE remain TEXT;               -- work area for calc. no of substrings
-- swap areas TEXT for string compare, INT for numeric compare
  DECLARE swap1 TEXT;                 -- left substring to swap
  DECLARE swap2 TEXT;                 -- right substring to swap
  SET remain = inString;
  SET backward = LOCATE(delim, remain);
  WHILE backward != 0 DO
    SET strings = strings + 1;
    SET backward = LOCATE(delim, remain);
    SET remain = SUBSTRING(remain, backward+1);
  END WHILE;
  IF strings < 2 THEN RETURN inString; END IF;
  REPEAT
    SET backward = strings;
    REPEAT
      SET swap1 = SUBSTRING_INDEX(SUBSTRING_INDEX(inString,delim,backward-1),delim,-1);
      SET swap2 = SUBSTRING_INDEX(SUBSTRING_INDEX(inString,delim,backward),delim,-1);
      IF  swap1 > swap2 THEN
        SET inString = TRIM(BOTH delim FROM CONCAT_WS(delim
        ,SUBSTRING_INDEX(inString,delim,backward-2)
        ,swap2,swap1
        ,SUBSTRING_INDEX(inString,delim,(backward-strings))));
      END IF;
      SET backward = backward - 1;
    UNTIL backward < 2 END REPEAT;
    SET forward = forward +1;
  UNTIL forward + 1 > strings
  END REPEAT;
RETURN inString;
END |
DELIMITER ;

您需要在您的 MySQL 服务器上运行此代码,以便此函数在查询中可用,就像本机内置 MySQL 函数一样。现在,查询部分变得简单。您需要做的就是Concat_ws() 所有使用逗号的数字列。然后,在连接的字符串上应用 sortString() 函数。最后,在Group By 子句中使用“ordered”字符串,得到想要的结果。

试试:

SELECT sortString(CONCAT_WS(',', n1, n2, n3)) AS n_sequence -- add more columns here
       COUNT(id) AS total 
FROM your_table 
GROUP BY n_sequence 
ORDER BY total DESC 

现在我建议您可以使用您的应用程序代码将逗号分隔的n_sequence 更改回表格列显示。

【讨论】:

    猜你喜欢
    • 2014-07-24
    • 2011-07-01
    • 2018-04-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-01
    • 2019-02-18
    • 1970-01-01
    相关资源
    最近更新 更多