在两个子查询之间查找不同的值

时间:2015-02-27 15:08:36

标签: mysql sql subquery left-join inner-join

我有一个数据库表,其中包含有关客户可用补丁的数据。服务器。有两列,identifier是每个补丁的标识符,date是该数据的插入日期。

我想比较过去一周的标识符与当前周的标识符。如果标识符存在于上周的日期并且不再存在,则表示该修补程序已应用。如果它存在于当前周,而不是过去一周,那就意味着它是一个新的补丁。我想破译哪些补丁是新的,哪些已安装。

为此,我构建了两个查询,如下所示:

SELECT `identifier`AS 'id1', `date` AS 'data1' FROM `patches` WHERE `date`="2015-02-02";

SELECT `identifier` AS 'id2', `date` AS 'data2' FROM `patches` WHERE `date`="2015-02-09"

我需要编写某种联接来检索已安装或已经安装标识符的数据,以及每个数据的计数。上述两个查询都有效,但我需要比较它们的结果。

我使用的列如下所示:

|  date  |     identifier     |
+--------+--------------------+
| 2/2/15 | 192.168.0.25-0001  |
| 2/2/15 | 192.168.0.77-1457  |
| 2/2/15 | 192.168.0.123-1329 |
| 2/2/15 | 192.168.0.84-2214  |
| 2/9/15 | 192.168.0.25-0001  |
| 2/9/15 | 192.168.0.77-1457  |
| 2/9/15 | 192.168.0.44-5311  |
| 2/9/15 | 192.168.0.78-1384  |

目前,我必须在Excel中进行查找并将其与CSV进行比较。如果标识符之前存在且不再存在,则excel会在单元格中添加#N / D,因此我会计算出#N / D'#N / D'单元格可以获得应用的补丁数量。如何在SQL中获取此信息?

更新:所以,我测试了shawnt00的答案和McAdam331的答案,他们两个都有效。但现在我有另一个与此问题相关的问题:

在我的工作中,我们每周都会制作这些补丁报告。因此,每周都有新的补丁和应用补丁。 我正在构建一个网页,用户(我们的员工)可以登录,选择一个客户,初始日期和最终日期,并通过ajax加载请求的数据。 一切运行良好,网页完成,登录系统也是如此。 问题是: 如果用户仅选择2个日期(初始和最终),我如何获得"中间"中的数据信息。这两个日期? 例如,让我们说用户登录并选择日期" 2015-02-02"和" 2015-02-23"。 假设数据库包含两个日期以及它们之间的日期的数据,这些数据是" 2015-02-09"和" 2015-02-16",我怎样才能使用这些查询,你们让我们进行同样的比较,但每周一次? 在上面的示例中,我需要在" 2015-02-02"之间获取新的和应用的补丁的数量。和" 2015-02-09",然后" 2015-02-09"和" 2015-02-16",然后" 2015-02-16"和" 2015-02-23"最后" 2015-03-02",而不是" 2015-02-02"和" 2015-03-02"。 我尝试使用php创建某种循环来遍历mysql中的日期和foreach日期我运行查询并总结我得到的数字,以便在每种情况下显示最终计数,但它没有& #39;工作。 有人能帮助我吗?

4 个答案:

答案 0 :(得分:2)

对日期进行硬编码是不理想的,并且使这种动态变得非常容易(比如相对于当前日期)。我想这会回答你对你感兴趣的几个星期的问题。

SELECT
    identifier,
    case
        when count(`date`) = 2 then 'Not applied'
        when   max(`date`) = '2015-02-09' /* and count(`date`) = 1 */ then 'New patch'
        when   min(`date`) = '2015-02-02' /* and count(`date`) = 1 */ then 'Applied'
    end as `status`
FROM patches
WHERE `date` IN ('2015-02-02', '2015-02-09')
GROUP BY identifier

总结也很简单:

SELECT `status`, count(*) FROM (
    SELECT
        identifier,
        case
            when count(`date`) = 2 then 'Not applied'
            when   max(`date`) = '2015-02-09' then 'New patch'
            when   min(`date`) = '2015-02-02' then 'Applied'
        end as `status`
    FROM patches
    WHERE `date` IN ('2015-02-02', '2015-02-09')
    GROUP BY identifier
) as T
GROUP BY `status`

如果你没有将这个日期分摊超过52周,这可能会有效。我担心用户的日期选择与周一日期所代表的周选择相匹配。

SELECT
    identifier,
    case
        when week(min(`date`)) = week(:end) then 'New patch'
        when week(max(`date`)) = week(:end) then 'Not applied'
        when week(max(`date`)) < week(:end) then 'Applied'
    end as `status`

   /*   -- This might better work for all dates
        when min(`date`)) = date_sub(:end, mod(dayofweek(:end) + 5, 7) day)
            then 'New patch'
        when max(`date`)) = date_sub(:end, mod(dayofweek(:end) + 5, 7) day)
            then 'Not applied'
        when max(`date`)) < date_sub(:end, mod(dayofweek(:end) + 5, 7) day)
            then 'Applied'
   */
FROM patches
WHERE `date` BETWEEN :start and :end
GROUP BY identifier

这是一周一周的结果......

SELECT
    p.identifier,
    p.`date`,
    sum(case when pb.`date` is null and p.`date` < max_date then 1 else 0) as new
    sum(case when pf.`date` is null and p.`date` > min_date then 1 else 0) as applied
FROM
    patches as p
    left outer join patches as pb
        on pb.identifier = p.identifier and pb.`date` = date_sub(p.`date`, 7 day)
    left outer join patches as pf
        on pf.identifier = p.identifier and pf.`date` = date_add(p.`date`, 7 day)
    cross join
    (select min(`date`) as min_date, max(`date`) as max_date from patches) as rng
WHERE p.`date` BETWEEN :start and :end
GROUP BY p.identifier, p.`date`

答案 1 :(得分:0)

有更清洁/更严格的方法,但只是将您的查询投入LEFT OUTER JOIN并仅选择上周出现的记录,但不是本周,您会得到:

SELECT
    `id1`
FROM
    (SELECT `identifier`AS 'id1', `date` AS 'data1' FROM `patches` WHERE `date`="2015-02-02") last_week
    LEFT OUTER JOIN (SELECT `identifier` AS 'id2', `date` AS 'data2' FROM `patches` WHERE `date`="2015-02-09") current_week ON
        last_week.id1 = current_week.id2
WHERE
     current_week.id2 IS NULL

更新:清除了一点以删除第一个派生表。这应该在MySQL中具有相同的性能,因为执行路径很可能完全相同。虽然,我已经看到MySQL做出了一些奇怪的决定,所以YMMV:

SELECT
    `id1`
FROM
    `patches` last_week    
    LEFT OUTER JOIN (SELECT `identifier` AS 'id2', `date` AS 'data2' FROM `patches` WHERE `date`="2015-02-09") current_week ON
        last_week.identifier = current_week.id2
WHERE
    last_week.`date` = "2015-02-02"
    current_week.id2 IS NULL

此外,既然你是从Excel / Vlookup思维模式来看这个,你就可以这样想。如果您将Vlookup放在最后几周的数据上并查找当前周的数据,然后查找#N / A记录,那么这基本上就是您从上述查询中得到的结果。如果您只想要VLOOKUP在该场景中返回值的记录,那么您可以删除第一个查询中的WHERE条件并将JOIN更改为INNER JOIN(或将where条件更改为WHERE current_week IS NOT NULL

如果您切换它并将VLOOKUP放在CURRENT_WEEK上以查找不在前一周数据中的记录,那么只需在第一个查询中翻转FROM语句中的表。 CURRENT_WEEK LEFT OUTER JOIN LAST_WEEK,并更改WHERE以查找last_Week.id1 IS NULL。

答案 2 :(得分:0)

根据您的预期结果,就是这么简单:

SELECT `identifier`, `date` FROM `patches` WHERE `date`="2015-02-02"
UNION
SELECT `identifier`, `date` FROM `patches` WHERE `date`="2015-02-09"

但问题是,为什么你不这样做:

SELECT `identifier`, `date` FROM `patches` WHERE `date`="2015-02-02" OR `date`="2015-02-09";

我认为你错过了你的要求......

根据你的意见:-)试试这个:

SELECT `identifier`, `date`, COUNT(*) as `counter`
FROM `patches` 
WHERE `date`="2015-02-02" OR `date`="2015-02-09"
GROUP BY `identifier`
HAVING (`counter`=1);

尝试更深入地解释你的目标。

答案 3 :(得分:0)

我建议使用NOT IN运算符。您可以获取02/02上发生的所有行,而不是02/09上的行:

SELECT identifier, dateCol
FROM myTable
WHERE dateCol = '2015-02-02' 
  AND identifier NOT IN(
    SELECT identifier
    FROM myTable
    WHERE dateCol = '2015-02-09');

要反过来,只需翻转日期即可。如果您想获得仅在第一周内的标识符计数,您可以在该子查询上使用COUNT()函数,并按日期分组:

SELECT dateCol, COUNT(*) AS numFixedPatches
FROM(
  SELECT identifier, dateCol
  FROM myTable
  WHERE dateCol = '2015-02-02' 
    AND identifier NOT IN(
      SELECT identifier
      FROM myTable
      WHERE dateCol = '2015-02-09')) tmp
GROUP BY dateCol;

以下是SQL Fiddle示例。

因此,相反的例子(获得新补丁)看起来像这样:

SELECT dateCol, COUNT(*) AS numNewPatches
FROM(
   SELECT identifier, dateCol
   FROM myTable
   WHERE dateCol = '2015-02-09' AND identifier NOT IN(
      SELECT identifier
      FROM myTable
      WHERE dateCol = '2015-02-02')) tmp
GROUP BY datecol;