mySQL连接表重复问题

时间:2011-11-24 08:59:51

标签: mysql sql

我有2个表(pcgroup和客户端pc)。假设今天的日期是24/11,我需要在过去2天内只使用mySQL查找哪台PC在线以及哪台PC不在线。

INSERT INTO pcgroup(id, groupName) 
  VALUES(1, 'defaultGroup');
INSERT INTO clientpc(id, pcGroupId, clientPcName, lastOnlineTime) 
  VALUES(1, 1, 'pc1', '2011-11-24');
INSERT INTO clientpc(id, pcGroupId, clientPcName, lastOnlineTime) 
  VALUES(2, 1, 'pc2', '2011-11-24');
INSERT INTO clientpc(id, pcGroupId, clientPcName, lastOnlineTime) 
  VALUES(3, 1, 'pc3', '2011-11-20');
INSERT INTO clientpc(id, pcGroupId, clientPcName, lastOnlineTime) 
  VALUES(4, 1, 'pc4', '2011-11-20');
INSERT INTO clientpc(id, pcGroupId, clientPcName, lastOnlineTime) 
  VALUES(5, 1, 'pc5', '2011-11-20');

这是我现在的查询

SELECT DISTINCT
  pcgroup.id AS pcGroupID, pcgroup.groupName,
  online.onlinePC, offline.offlinePC
FROM
  (
    SELECT   
      pcgroup.Id, pcGroup.groupName
    FROM
      pcgroup
    WHERE
      (pcgroup.Id = 1)
  ) pcgroup
LEFT  JOIN
  (
    SELECT
      clientpc.Id, clientpc.pcGroupId, clientpc.clientPcName AS onlinePC
    FROM
      clientpc
    WHERE
      DateDiff(CURDATE(),clientpc.lastOnlineTime) <= 2
    AND
      DateDiff(CURDATE(),clientpc.lastOnlineTime) IS NOT NULL
  ) online  
 ON 
  pcgroup.Id = online.pcGroupId
 LEFT JOIN
   (
     SELECT
       clientpc.Id, clientpc.pcGroupId, clientpc.clientPcName AS offlinePC
     FROM
       clientpc
     WHERE
       (DateDiff(CURDATE(),clientpc.lastOnlineTime) > 2
     OR
        DateDiff(CURDATE(),clientpc.lastOnlineTime) IS NULL)
   ) offline 
  ON pcgroup.Id = offline.pcGroupId

这是我得到的结果

*pcGroupID    groupName        onlinePC       offlinePC*
  1           defaultGroup      pc1             pc3
  1           defaultGroup      pc1             pc4
  1           defaultGroup      pc1             pc5
  1           defaultGroup      pc2             pc3
  1           defaultGroup      pc2             pc4
  1           defaultGroup      pc2             pc5

然而,我需要的是这样的东西

*pcGroupID    groupName        onlinePC       offlinePC*
  1           defaultGroup      pc1             pc3
  1           defaultGroup      pc2             pc4
  1           defaultGroup                      pc5

所以我的问题是,这是可以实现的吗?如果是的话,如何。已经处理了这个查询2天了。如果你们能帮助我,我真的很感激。

4 个答案:

答案 0 :(得分:2)

我启动了一个MySQL实例并模拟了Postgres解决方案中使用的rowid。创作脚本:

CREATE TABLE pcgroup(id int, groupName varchar(64));
CREATE TABLE clientpc(id int, pcGroupId int, clientPcName varchar(64), lastOnlineTime date);

INSERT INTO pcgroup(id, groupName) VALUES(1, 'defaultGroup');
INSERT INTO clientpc(id, pcGroupId, clientPcName, lastOnlineTime) VALUES(1, 1, 'pc1', CURRENT_DATE);
INSERT INTO clientpc(id, pcGroupId, clientPcName, lastOnlineTime) VALUES(2, 1, 'pc2', CURRENT_DATE);
INSERT INTO clientpc(id, pcGroupId, clientPcName, lastOnlineTime) VALUES(3, 1, 'pc3', CURRENT_DATE-4);
INSERT INTO clientpc(id, pcGroupId, clientPcName, lastOnlineTime) VALUES(4, 1, 'pc4', CURRENT_DATE-4);
INSERT INTO clientpc(id, pcGroupId, clientPcName, lastOnlineTime) VALUES(5, 1, 'pc5', CURRENT_DATE-4);

INSERT INTO pcgroup(id, groupName) VALUES(2, 'group2');
INSERT INTO clientpc(id, pcGroupId, clientPcName, lastOnlineTime) VALUES(6, 2, 'pc6', CURRENT_DATE-4);
INSERT INTO clientpc(id, pcGroupId, clientPcName, lastOnlineTime) VALUES(7, 2, 'pc7', CURRENT_DATE-4);
INSERT INTO clientpc(id, pcGroupId, clientPcName, lastOnlineTime) VALUES(8, 2, 'pc8', CURRENT_DATE);
INSERT INTO clientpc(id, pcGroupId, clientPcName, lastOnlineTime) VALUES(9, 2, 'pc9', CURRENT_DATE);
INSERT INTO clientpc(id, pcGroupId, clientPcName, lastOnlineTime) VALUES(10, 2, 'pc10', CURRENT_DATE);

脚本(遵循与Postgres解决方案相同的逻辑):

-- Apply sort to union
SELECT pcGroupID, groupName, onlinePC, offlinePC
  FROM (
        SELECT online.pcGroupID, online.groupName, online.clientPcName AS onlinePC, IFNULL(offline.clientPcName, '-') AS offlinePC

          FROM (-- Apply a groupName-based row number to the list of "online" PCs
                SELECT pcGroupID, groupName, clientPcName, lastOnlineTime
                      ,if(@lastGroupID!=pcGroupID
                          ,CONCAT_WS('_', pcGroupID, @curRow := 1)
                          ,CONCAT_WS('_', pcGroupID, @curRow := @curRow + 1)) AS row_number
                          ,@lastGroupID := pcGroupID
                  FROM (-- Filter to the list of online PCs
                        SELECT g.id AS pcGroupID, g.groupName, c.clientPcName, c.lastOnlineTime
                          FROM pcgroup g
                              ,clientpc c
                          WHERE c.pcGroupId = g.id
                            AND c.lastOnlineTime >= CURRENT_DATE - 2
                          ORDER BY g.id, c.clientPcName ) x
                      ,(SELECT @curRow := 0) r ) AS online

               LEFT OUTER JOIN (

                -- Apply a groupName-based row number to the list of "offline" PCs
                SELECT pcGroupID, groupName, clientPcName, lastOnlineTime
                      ,if(@lastGroupID!=pcGroupID
                          ,CONCAT_WS('_', pcGroupID, @curRow := 1)
                          ,CONCAT_WS('_', pcGroupID, @curRow := @curRow + 1)) AS row_number
                          ,@lastGroupID := pcGroupID
                  FROM (-- Filter to the list of offline PCs
                        SELECT g.id AS pcGroupID, g.groupName, c.clientPcName, c.lastOnlineTime
                          FROM pcgroup g
                              ,clientpc c
                          WHERE c.pcGroupId = g.id
                            AND c.lastOnlineTime < CURRENT_DATE - 2
                          ORDER BY g.id, c.clientPcName ) x
                      ,(SELECT @curRow := 0) r ) AS offline

            ON (online.row_number = offline.row_number)


UNION

        SELECT offline.pcGroupID, offline.groupName, IFNULL(online.clientPcName, '~') AS onlinePC, offline.clientPcName AS offlinePC

          FROM (-- Apply a groupName-based row number to the list of "online" PCs
                SELECT pcGroupID, groupName, clientPcName, lastOnlineTime
                      ,if(@lastGroupID!=pcGroupID
                          ,CONCAT_WS('_', pcGroupID, @curRow := 1)
                          ,CONCAT_WS('_', pcGroupID, @curRow := @curRow + 1)) AS row_number
                          ,@lastGroupID := pcGroupID
                  FROM (-- Filter to the list of online PCs
                        SELECT g.id AS pcGroupID, g.groupName, c.clientPcName, c.lastOnlineTime
                          FROM pcgroup g
                              ,clientpc c
                          WHERE c.pcGroupId = g.id
                            AND c.lastOnlineTime >= CURRENT_DATE - 2
                          ORDER BY g.id, c.clientPcName ) x
                      ,(SELECT @curRow := 0) r ) AS online

               RIGHT OUTER JOIN (

                -- Apply a groupName-based row number to the list of "offline" PCs
                SELECT pcGroupID, groupName, clientPcName, lastOnlineTime
                      ,if(@lastGroupID!=pcGroupID
                          ,CONCAT_WS('_', pcGroupID, @curRow := 1)
                          ,CONCAT_WS('_', pcGroupID, @curRow := @curRow + 1)) AS row_number
                          ,@lastGroupID := pcGroupID
                  FROM (-- Filter to the list of offline PCs
                        SELECT g.id AS pcGroupID, g.groupName, c.clientPcName, c.lastOnlineTime
                          FROM pcgroup g
                              ,clientpc c
                          WHERE c.pcGroupId = g.id
                            AND c.lastOnlineTime < CURRENT_DATE - 2
                          ORDER BY g.id, c.clientPcName ) x
                      ,(SELECT @curRow := 0) r ) AS offline

            ON (online.row_number = offline.row_number)

    ) z ORDER BY pcGroupID, groupName, OnlinePC, offlinePC

结果:

1   defaultGroup  pc1    pc3
1   defaultGroup  pc2    pc4
1   defaultGroup  ~      pc5
2   group2        pc10   pc6
2   group2        pc8    pc7
2   group2        pc9    -

- Postgresql -

我在Postgres尝试过。查询看起来更加可怕,然后确实如此。有许多功能可以缩短它:子查询因子分解(即使用WITH),pseduo行号生成器,完全外连接)。我不确定mysql是否有这个,所以我没有使用这些功能。

我认为重点是你要求两个不相关的不同列表:onlinePCs和offlinePCs。你只想把两个列表并排放在一起。为此,您可以引入行计数伪列来创建两个列表之间的关系。步骤1生成在线PC列表并计算每个组的数量(生成一个行标识符_。然后根据此行标识符将其连接到脱机PC列表。如果有多个脱机PC在线电脑,离线电脑不会出现在这个列表中。这就是为什么我们在第4步再次完成整个事情,但这次是由离线电脑驱动,以解决离线电脑比在线更多的情况PC.UNION将摆脱重复。

我还使用CURRENT_DATE并将2硬编码为离线和在线之间的天数。你需要玩这个。

创作脚本:

CREATE TABLE pcgroup(id bigint, groupName varchar);
CREATE TABLE clientpc(id bigint, pcGroupId bigint, clientPcName varchar, lastOnlineTime date);

INSERT INTO pcgroup(id, groupName) VALUES(1, 'defaultGroup');
INSERT INTO clientpc(id, pcGroupId, clientPcName, lastOnlineTime) VALUES(1, 1, 'pc1', CURRENT_DATE);
INSERT INTO clientpc(id, pcGroupId, clientPcName, lastOnlineTime) VALUES(2, 1, 'pc2', CURRENT_DATE);
INSERT INTO clientpc(id, pcGroupId, clientPcName, lastOnlineTime) VALUES(3, 1, 'pc3', CURRENT_DATE-4);
INSERT INTO clientpc(id, pcGroupId, clientPcName, lastOnlineTime) VALUES(4, 1, 'pc4', CURRENT_DATE-4);
INSERT INTO clientpc(id, pcGroupId, clientPcName, lastOnlineTime) VALUES(5, 1, 'pc5', CURRENT_DATE-4);

INSERT INTO pcgroup(id, groupName) VALUES(2, 'group2');
INSERT INTO clientpc(id, pcGroupId, clientPcName, lastOnlineTime) VALUES(6, 2, 'pc6', CURRENT_DATE-4);
INSERT INTO clientpc(id, pcGroupId, clientPcName, lastOnlineTime) VALUES(7, 2, 'pc7', CURRENT_DATE-4);
INSERT INTO clientpc(id, pcGroupId, clientPcName, lastOnlineTime) VALUES(8, 2, 'pc8', CURRENT_DATE-4);
INSERT INTO clientpc(id, pcGroupId, clientPcName, lastOnlineTime) VALUES(9, 2, 'pc9', CURRENT_DATE);
INSERT INTO clientpc(id, pcGroupId, clientPcName, lastOnlineTime) VALUES(10, 2, 'pc10', CURRENT_DATE);

查询:

SELECT online.pcGroupID, online.groupName, online.clientPcName AS onlinePC, offline.clientPcName AS offlinePC

    -- 1: Get the list of online PCs, and give them a group based pseudo rownumber
  FROM (SELECT g.id AS pcGroupID, g.groupName, c.clientPcName
              ,g.id || '_' || row_number() OVER(PARTITION BY g.id ORDER BY g.id, c.clientPcName) AS rownum
          FROM pcgroup g
              ,clientpc c
          WHERE c.pcGroupId = g.id
            AND lastOnlineTime > CURRENT_DATE - 2) AS online

    -- 2: Get the list of offline PCs, and give them a group based pseudo rownumber
       LEFT OUTER JOIN (SELECT g.id AS pcGroupID, g.groupName, c.clientPcName
                              ,g.id || '_' || row_number() OVER(PARTITION BY g.id ORDER BY g.id, c.clientPcName) AS rownum
                         FROM pcgroup g
                             ,clientpc c
                         WHERE c.pcGroupId = g.id
                           AND lastOnlineTime <= CURRENT_DATE - 2) AS offline

       -- 3: Join the list together: this will only include rows for the number of "online" pcs that exist
       ON (online.rownum = offline.rownum)

-- 4: Repeat 1-3, but this time base it on offline pcs and it will only include rows for the number of "offline" pcs that exist
--    The UNION will dump the duplicates

UNION

SELECT offline.pcGroupID, offline.groupName, online.clientPcName AS onlinePC, offline.clientPcName AS offlinePC
  FROM (SELECT g.id AS pcGroupID, g.groupName, c.clientPcName
              ,g.id || '_' || row_number() OVER(PARTITION BY g.id ORDER BY g.id, c.clientPcName) AS rownum
          FROM pcgroup g
              ,clientpc c
          WHERE c.pcGroupId = g.id
            AND lastOnlineTime > CURRENT_DATE - 2) AS online

       RIGHT OUTER JOIN (SELECT g.id AS pcGroupID, g.groupName, c.clientPcName
                              ,g.id || '_' || row_number() OVER(PARTITION BY g.id ORDER BY g.id, c.clientPcName) AS rownum
                         FROM pcgroup g
                             ,clientpc c
                         WHERE c.pcGroupId = g.id
                           AND lastOnlineTime <= CURRENT_DATE - 2) AS offline
       ON (online.rownum = offline.rownum)

结果:

 pcgroupid |  groupname   | onlinepc | offlinepc
-----------+--------------+----------+-----------
         1 | defaultGroup | pc1      | pc3
         1 | defaultGroup | pc2      | pc4
         1 | defaultGroup |          | pc5
         2 | group2       | pc10     | pc6
         2 | group2       | pc8      | pc7
         2 | group2       | pc9      |
(6 rows)

答案 1 :(得分:0)

在mysql中使用group by,如下所示

GROUP BY offlinePC;

您可以获得以下结果。

*pcGroupID    groupName        onlinePC       offlinePC*
  1           defaultGroup      pc1             pc3
  1           defaultGroup      pc2             pc4
  1           defaultGroup      pc2             pc5

答案 2 :(得分:0)

为什么不使用2个简单的查询并在您的应用程序中进行演示?:

 --- Online ---
 SELECT
     c.pcGroupID, p.groupName, c.clientPcName, 'Online' AS pcStatus
 FROM
     pcgroup AS g
   JOIN
     clientpc AS c
       ON g.Id = c.pcGroupId
 WHERE
     g.Id = @pcGroupIdToBeChecked
   AND
     c.lastOnlineTime >= CURDATE() - INTERVAL 2 DAY

 --- Offline ---
 SELECT
     c.pcGroupID, p.groupName, c.clientPcName, 'Offline' AS pcStatus
 FROM
     pcgroup AS g
   JOIN
     clientpc AS c
       ON g.Id = c.pcGroupId
 WHERE
     g.Id = @pcGroupIdToBeChecked
   AND
     ( c.lastOnlineTime < CURDATE() - INTERVAL 2 DAY
      OR
       c.lastOnlineTime IS NULL
     )

答案 3 :(得分:0)

不要在SQL中执行表示逻辑。如果您需要以某种方式格式化数据,请使用您的客户端语言进行格式化。 SQL用于检索和操作数据,而不是格式化!

BTW,因为你实际上并不想以任何方式过滤行,只想分类它们,你可以简单地SELECT * FROM clientpc,然后决定如何根据当前日期与lastOnlineTime之间的差异向用户显示每一行。

(您也可以轻松地与clientpc加入pcgroup,但即便如此,因为您打算从pcgroup获取大部分(如果不是全部)行,因此,在UI的上下文中,客户端“加入”和/或“组”可能更合适。)