【问题标题】:Solution for subquery in MySQL WHERE INMySQL WHERE IN 中子查询的解决方案
【发布时间】:2016-10-18 20:23:28
【问题描述】:

我正在尝试解决 WHERE 子查询或寻找不同的解决方案。 我想要实现的是基于这个查询:

SELECT c.orig_point_id, 
   (SELECT attempts 
    FROM 
       (SELECT 
            orig_carrier_id, 
            orig_point_id, 
            term_point_id, 
            term_route, 
            currency_sell, 
            is_special, 
            COUNT(*) AS attempts 
        FROM cdr 
        WHERE 1=1 
            AND start_time >= '2016-10-01 0:00:00' 
            AND start_time <= '2016-10-31 23:59:59' 
        GROUP BY orig_carrier_id, currency_sell) AS c0 
    WHERE c0.orig_carrier_id=3 
        AND c0.currency_sell="USD" 
    LIMIT 1) AS attempts,
   (SELECT SPLIT(clear_number) as array 
   FROM 
       (SELECT 
           COUNT(*) as total, 
           clear_number, 
           orig_carrier_id,
           currency_sell 
       FROM `cdr` 
       WHERE `start_time`>='2016-10-01 00:00:00' 
           AND start_time <= '2016-10-31 23:59:59' 
       GROUP BY `clear_number` 
       ORDER BY total DESC) AS c0 
   WHERE c0.orig_carrier_id=3 
       AND c0.currency_sell="USD" 
   LIMIT 1) AS splitted_number
FROM cdr AS c 
GROUP BY c.orig_carrier_id, c.currency_sell;

SPLIT 是一个函数。该部分中的查询找到一个数字(最频繁),然后函数将其拆分为 ex。 12345,1234,123,12,1。当我尝试将其用作 IN 子查询时,问题就来了。直接使用时,mysql 表示不支持功能。看起来查询太复杂了。 当我将子查询别名为解决方法时,它返回 NULL,因此解决方法不起作用,我相信它返回 NULL 的原因与它不可行的原因相同。

SELECT
    CONCAT_WS(" - ",country,region) AS route_name 
FROM numbering_plan_external 
WHERE 
    prefix IN(
        SELECT array 
        FROM 
            (SELECT SPLIT(clear_number) as array 
            FROM 
                (SELECT 
                    COUNT(*) as total, 
                    clear_number, 
                    orig_carrier_id,
                    currency_sell 
                FROM `cdr` 
                WHERE `start_time`>='2016-10-01 00:00:00' 
                    AND start_time <= '2016-10-31 23:59:59' 
                GROUP BY `clear_number` 
                ORDER BY total DESC) AS c0 
            WHERE c0.orig_carrier_id=3 
                AND c0.currency_sell="USD" 
            LIMIT 1) AS splitted_number) 
        ORDER BY prefix DESC LIMIT 1) AS top_route

我在这里做错了什么,还是有不同的方法来实现这一点。我可以只留下拆分号码,然后通过 PHP 找到路线。根据结果​​,这将需要大量查询,如果可能,我会尽量避免。

提前谢谢各位。

一些样本数据

CREATE TABLE IF NOT EXISTS `numbering_plan_external` (
`id` int(11) NOT NULL,
`country` varchar(255) NOT NULL,
`region` varchar(255) DEFAULT NULL,
`prefix` varchar(50) NOT NULL,
`is_mobile` tinyint(1) NOT NULL DEFAULT '0',
`last_updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`min_sale_price_currency` char(3) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL,
`min_sale_price_amount` decimal(10,4) DEFAULT NULL
 ) ENGINE=InnoDB AUTO_INCREMENT=14004 DEFAULT CHARSET=latin1;

INSERT INTO `numbering_plan_external` 
(`id`, `country`, `region`,`prefix`, `is_mobile`, `last_updated`, `min_sale_price_currency`, `min_sale_price_amount`) 
VALUES
(13047, 'Tunisia', '', '216', 0, '2016-02-17 12:30:44', NULL, NULL),
(13048, 'Tunisia', 'Mobile (ORANGE)', '2165', 1, '2016-02-17 12:30:44', NULL, NULL),
(13049, 'Tunisia', 'Mobile (ORASCOM)', '2162', 1, '2016-02-17 12:30:44', NULL, NULL),
(13050, 'Tunisia', 'Mobile (TUNTEL)', '21640', 1, '2016-02-17 12:30:44', NULL, NULL),
(13051, 'Tunisia', 'Mobile (TUNTEL)', '21641', 1, '2016-02-17 12:30:44', NULL, NULL),
(13052, 'Tunisia', 'Mobile (TUNTEL)', '2169', 1, '2016-02-17 12:30:44', NULL, NULL);

CREATE TABLE IF NOT EXISTS `cdr` (
`id` int(11) NOT NULL,
`orig_carrier_id` int(11) NOT NULL,
`orig_point_id` int(11) NOT NULL,
`term_carrier_id` int(11) NOT NULL,
`term_point_id` int(11) NOT NULL,
`clear_number` varchar(100) COLLATE latin1_general_ci NOT NULL,
`is_special` tinyint(1) NOT NULL DEFAULT '0',
`start_time` datetime NOT NULL,
`currency_sell` char(3) COLLATE latin1_general_ci NOT NULL
) ENGINE=InnoDB AUTO_INCREMENT=16385 DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci;

INSERT INTO `cdr` 
(`id`, `orig_carrier_id`, `orig_point_id`, `term_carrier_id`, `term_point_id`, `clear_number`, `is_special`, `start_time`, `currency_sell`) VALUES
(1, 3, 5, 0, 0, '21658502507', 0, '2016-10-17 00:02:04', 'USD'),
(2, 3, 5, 0, 0, '21658502507', 0, '2016-10-17 00:02:04', 'USD'),
(3, 3, 5, 0, 0, '21658502507', 0, '2016-10-17 00:03:56', 'USD'),
(4, 3, 5, 0, 0, '21658502507', 0, '2016-10-17 00:09:28', 'USD'),
(5, 3, 5, 0, 0, '21658502507', 0, '2016-10-17 00:16:35', 'USD');

【问题讨论】:

  • 如果您提供示例数据(作为我们可以重复使用的插入或文本)以及预期结果,我们可以提出更好的解决方案。 (样本不必很大!)
  • 添加了一些示例数据。
  • numbering_plan_external的相关性是什么?我在查询中看到的只是cdr。您还可以解释您期望从该数据查询的结果是什么,这将是有益的。例如现在(根据该数据)我认为没有理由使用拆分功能。
  • @Used_By_Already numbering plan external table 的相关性可以在第二个子查询中找到。它以 `SELECT CONCAT_WS(" - ",country,region) AS route_name ... 21658502507,SPLIT 将生成一个相同号码的列表,最后一位截止日期为 21658502507、2165850250、216585025 .. 2. 它用于从外部计划中查找最长前缀匹配,在我们的例子中是:`'Tunisia'、'Mobile (ORANGE) ', '2165' ` 很抱歉一开始没有解释清楚。
  • @Used_By_Already 在我的帖子中,我说过我为整个子查询设置了别名,我的意思是我替换了以 ` (SELECT SPLIT(clear_number) ... AS splitted_number ` 开头的整个块接下来的部分以 `SELECT CONCAT_WS(" - ",country,region) AS route_name ... ` 开头

标签: mysql where-in


【解决方案1】:

IN 将值视为一个整体。无论您的 SPLIT() 在做什么,即使它返回一个“csv”,整个列表都被视为一个单一值,例如它将被解析/执行为等效

WHERE foo IN ('12345,1234,...')
WHERE foo='12345,1234,...'

而不是这些

WHERE foo IN ('12345', '1234', '123', ...)
WHERE foo='12345' OR foo='1234' OR ...

您可以尝试改用 MySQL 的 find_in_set(),它基本上可以满足您的需求。

【讨论】:

  • 谢谢。实际上是一个查询列表为 SELECT CONCAT_WS(" - ",country,region) AS route_name FROM numbering_plan_external WHERE prefix IN (21658502507,2165850250,216585025,21658502,2165850,216585,21658,2165,216,21,2) ORDER BY 前缀 DESC LIMIT 1 给出的结果很好
  • 您从整体上考虑价值是非常正确的。 find_in_set 在这种情况下有所帮助。现在我的整个子查询工作正常。
  • 请注意 find_in_set 效率很低。它基本上是一个子字符串搜索/匹配,不能使用索引。如果它成为一个问题,您将不得不重新设计为适当的规范化设计,而不是使用“csv”数据。
  • 是的,我已经注意到了,你是对的。而且它有点慢。我会尝试做一个现成的值在`IN`中使用。
【解决方案2】:

在此查询中使用您的示例数据:

SELECT
      cdr.orig_point_id
    , count(cdr.*) attempts 
    , group_concat(distinct npe.region) regions
FROM cdr
INNER JOIN numbering_plan_external npe
        ON cdr.clear_number like concat(npe.prefix,'%') COLLATE latin1_general_ci
        AND npe.region <> ''
WHERE cdr.orig_carrier_id=3 
  AND cdr.currency_sell='USD' 
  AND cdr.start_time >= '2016-10-01' 
  AND cdr.start_time <  '2016-11-01' 
GROUP BY
      cdr.orig_point_id
;      

结果:

| orig_point_id | attempts |   regions      |
|---------------|----------|----------------|
|             5 |        5 | Mobile (ORANGE)|

这些表之间的连接涉及将前缀与 clear_number 的起始字符进行比较。但是,您有排序规则冲突,因此您需要指定正在使用的排序规则。使用 LIKE 不是最有效的连接条件样式,它可能会导致性能问题,因为它不使用索引。但是,它确实证明了逻辑连接确实存在,并且您不需要该拆分功能(顺便说一下,这也不适合连接)。


我已将我之前的问题的其余部分留作参考:

查询

SELECT
      orig_point_id
    , count(*) attempts 
FROM cdr
WHERE orig_carrier_id=3 
  AND currency_sell='USD' 
  AND start_time >= '2016-10-01' 
  AND start_time <  '2016-11-01' 
GROUP BY
      orig_point_id

Results

| orig_point_id | attempts |
|---------------|----------|
|             5 |        5 |

为原始查询提取

SELECT c.orig_point_id, 
   (SELECT attempts 
    FROM 
       (SELECT 
            orig_carrier_id, 
            orig_point_id, 
            term_point_id, 
            /* term_route, */
            currency_sell, 
            is_special, 
            COUNT(*) AS attempts 
        FROM cdr 
        WHERE 1=1 
            AND start_time >= '2016-10-01 0:00:00' 
            AND start_time <= '2016-10-31 23:59:59' 
        GROUP BY orig_carrier_id, currency_sell) AS c0 
    WHERE c0.orig_carrier_id=3 
        AND c0.currency_sell="USD" 
    LIMIT 1) AS attempts
FROM cdr AS c 
GROUP BY c.orig_carrier_id, c.currency_sell

Results

| orig_point_id | attempts |
|---------------|----------|
|             5 |        5 |

希望您可以看到,您的查询不需要像现在那样复杂。我怀疑如果我们对“预期结果”有更多了解,我们也许可以在没有拆分功能的情况下做到这一点。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-09-17
    • 1970-01-01
    • 1970-01-01
    • 2013-03-22
    • 2020-10-13
    • 1970-01-01
    • 1970-01-01
    • 2021-06-02
    相关资源
    最近更新 更多