如何比较两个逗号分隔的字段并获取MySQL中的计数

时间:2014-05-19 14:54:22

标签: mysql

大家好我有一个MySQL表,其中包含逗号分隔值

字段
id    res
=============================
1     hh_2,hh_5,hh_6
------------------------------
2     hh_3,hh_5,hh_4
------------------------------
3     hh_6,hh_8,hh_7
------------------------------
4     hh_2,hh_7,hh_4
------------------------------

请参阅上面的示例,实际上我需要将每行'res'与其他行的'res'值进行比较,如果它们与其他行匹配则需要显示计数。请帮我算一下。

例如, 在第一行'hh_2'也存在于第四行,所以我们需要计数为2,同样我们需要比较所有行中的所有行

我已经运行了它为我工作的功能。但桌子这么大。它拥有数百万条记录,因此我的表现需要时间。用50000记录检查一条记录需要25秒。假设我的输入是60行,需要一个小时。请帮我如何优化。

   CREATE FUNCTION `combine_two_field`(s1 CHAR(96), s3 TEXT) RETURNS int(11)
    BEGIN
      DECLARE ndx INT DEFAULT 0;
      DECLARE icount INT DEFAULT 0;  
      DECLARE head1 char(10);
      DECLARE head2 char(10); 
      DECLARE head3 char(10);    
      WHILE ndx <= LENGTH(s1) DO
            SET head1 = SUBSTRING_INDEX(s3, ',', 1);    
            SET s3 = SUBSTRING(s3, LENGTH(head1) + 1 + @iSeparLen); 

            SET head2 = SUBSTRING_INDEX(s1, ',', 1);    
            SET s1 = SUBSTRING(s1, LENGTH(head2) + 1 + @iSeparLen);

            IF (head1 = head2) THEN 
                SET icount = icount + 1;
            END IF;         
       SET ndx = ndx + 1;   
      END WHILE;
      RETURN icount; 
    END 

And the table size is too big and i want to reduce fetching time also ...

更新查询:

DROP PROCEDURE IF EXISTS `pcompare7` $$
CREATE DEFINER=`root`@`localhost` PROCEDURE `pcompare7`(IN in_analysis_id INT(11))
BEGIN

drop  table  if exists `tmp_in_results`;
CREATE TEMPORARY TABLE `tmp_in_results` (
  `t_id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
  `r_id` bigint(11) NOT NULL,
  `r_res` char(11) NOT NULL,
  PRIMARY KEY (`t_id`),
  KEY r_res (r_res)

)
ENGINE = InnoDB;

SELECT splite_snp(r_snp,id,ruid) FROM results  WHERE technical_status = 1 and critical_status = 1 and autosomal_status = 1 and gender_status != "NO CALL" and analys_id = in_analysis_id;


-- SELECT * FROM tmp_in_results;
-- COmpare Functionality 
SELECT a.t_id, b.id, SUM(IF(FIND_IN_SET(a.r_res, b.r_snp), 1, 0)) FROM tmp_in_results a CROSS JOIN results b GROUP BY a.t_id, b.id;
END $$

创建TEMP表的功能:

  DROP FUNCTION IF EXISTS `splite_snp` $$
    CREATE DEFINER=`root`@`localhost` FUNCTION `splite_snp`(s1 TEXT, in_id bigint(96), ruid char(11)) RETURNS tinyint(1)
    BEGIN
      DECLARE ndx INT DEFAULT 0;
      DECLARE icount INT DEFAULT 0;
      DECLARE head1 TEXT;
      DECLARE head2 TEXT;
      DECLARE intpos1 char(10);
      DECLARE intpos2 char(10);
      DECLARE Separ char(3) DEFAULT ',';
      DECLARE iSeparLen INT;

      SET @iSeparLen = LENGTH( Separ );

      WHILE s1 != '' DO

            SET intpos1 = SUBSTRING_INDEX(s1, ',', 1);
            SET s1 = SUBSTRING(s1, LENGTH(intpos1) + 1 + @iSeparLen);

        INSERT INTO tmp_in_results(r_id,r_res) VALUES(in_id,intpos1);

      END WHILE;
      RETURN TRUE;
    END $$

新表结构

pc_input

id    in_res     in_id
=============================
1     hh_2     1000
------------------------------
2     hh_3     1000
------------------------------
3     hh_6     1001
------------------------------
4     hh_2     1001
------------------------------

res_snp

id    r_res     r_id
=============================
1     hh_2     999
------------------------------
2     hh_3     999
------------------------------
3     hh_9     999
------------------------------
4     hh_2     998
------------------------------
5     hh_6     998
------------------------------
6     hh_9     998
------------------------------

结果:

in_id    r_id     matches_count
=============================
1000     999     2 (hh_2,hh_3)
------------------------------
1000     998     1 (hh_2)
------------------------------
1001     999     1 (hh_2)
------------------------------
1001     998     2 (hh_2,hh_6)
------------------------------

我添加了单独的索引in_res,in_id和r_res以及r_id

QUERY:

SELECT b.r_id,count(*) FROM  pc_input AS a INNER JOIN results_snps AS b ON (b.r_snp = a.in_snp) group by a.in_id,b.r_id;

但是mysql服务器被冻结了。云请你建议任何其他方式或优化我的查询。

EXPLAIN TABLE:res_snp

Field   Type    Null    Key     Default     Extra
id  bigint(11)  NO  PRI     NULL    auto_increment
r_snp   varchar(50) NO  MUL     NULL    
r_id    bigint(11)  NO  MUL     NULL    

EXPLAIN TABLE:pc_input

  Field     Type    Null    Key     Default     Extra
id  bigint(11)  NO  PRI     NULL    auto_increment
in_snp  varchar(55) NO  MUL     NULL    
in_id   bigint(11)  NO  MUL     NULL

解释查询:

id  select_type     table   type    possible_keys   key     key_len     ref     rows    Extra
1   SIMPLE  a   ALL     in_snp  NULL    NULL    NULL    192     Using temporary; Using filesort
1   SIMPLE  b   ref     r_snp   r_snp   52  rutgers22042014.a.in_snp    2861    Using where0

1 个答案:

答案 0 :(得分:0)

这是可能的,但令人讨厌。正确规范化的数据库会容易得多,但有时你必须使用现有的数据库。

这样的事情应该这样做(未经测试)。这使用了几个子查询来生成0到9之间的数字,组合允许范围从0到99.然后将其与substring_index一起使用以将字符串拆分起来,并与DISTINCT一起使用以获取将以其他方式生成的重复项(我假设在任何一行上都不应该有重复 - 如果它们可以被删除但它变得更复杂),那么它只是用作子查询来进行计数

SELECT aRes, COUNT(*)
FROM
(
    SELECT DISTINCT sometable.id, SUBSTRING_INDEX(SUBSTRING_INDEX(sometable.res, ',', 1 + units.i + tens.i * 10), ',', -1) AS aRes
    FROM sometable
    CROSS JOIN (SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) units
    CROSS JOIN (SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) tens
) Sub1
GROUP BY aRes

编辑 - 现已测试: -

http://www.sqlfiddle.com/#!2/0ef59/4

编辑 - 可能的解决方案。希望能快速接受。

首先将输入行提取到临时表中: -

CREATE TEMPORARY TABLE tmp_record
(
    unique_id   INT NOT NULL AUTO_INCREMENT,
    id  INT,
    res varchar(25),
    PRIMARY KEY (unique_id),
    KEY `res` (`res`)
);

使用您的测试数据加载上面的

INSERT INTO tmp_record (unique_id, id, res)
VALUES
(1, 1,  'hh_2'),
(2, 1,  'hh_5'),
(3, 1,  'hh_6'),
(4, 2,  'hh_3'),
(5, 2,  'hh_5'),
(6, 2,  'hh_4');

然后你可以按如下方式进行连接。

SELECT a.id, b.id, SUM(IF(FIND_IN_SET(a.res, b.res), 1, 0))
FROM tmp_record a
CROSS JOIN sometable b
GROUP BY a.id, b.id

这是将每个输入行与主表上的每一行连接,并检查单个输入是否在逗号分隔列表中。如果是,那么IF返回1,否则为0.然后它总结这些值,按2个ID分组。

未经测试但希望这应该有效。我不确定性能(当你处理大量潜在记录时可能会很慢)。

请注意,临时表仅持续与数据库的连接存在的时间长度。如果你需要在几个脚本上执行此操作,那么你可能需要创建一个普通的表(并记得在完成它后删除它)