【问题标题】:Complex SQL query with multiple tables and relations具有多个表和关系的复杂 SQL 查询
【发布时间】:2015-10-04 10:50:56
【问题描述】:

在此查询中,我必须列出为完全相同的球队效力的一对球员及其 playerID 和 playerName。如果一名球员为 3 支球队效力,则另一个球员必须为完全相同的 3 支球队效力。不多,不多。如果两名球员目前没有为任何球队效力,他们也应该被包括在内。查询应返回 (playerID1, playername1, playerID2, playerName2) 且不重复,例如如果玩家 1 信息在玩家 2 之前,则不应有另一个元组包含玩家 2 信息在玩家 1 之前。

例如,如果球员 A 为洋基队和红袜队效力,而球员 b 为洋基队、红袜队和道奇队效力,我就不应该得到他们。他们都必须为洋基队和红袜队效力,而没有其他人。现在,如果球员为同一支球队效力,这个查询可以找到答案。

Tables:
player(playerID: integer, playerName: string)
team(teamID: integer, teamName: string, sport: string)
plays(playerID: integer, teamID: integer)

Example data:
PLAYER    
playerID    playerName
1           Rondo
2           Allen
3           Pierce
4           Garnett
5           Perkins

TEAM      
teamID     teamName       sport
1          Celtics        Basketball
2          Lakers         Basketball
3          Patriots       Football
4          Red Sox        Baseball
5          Bulls          Basketball

PLAYS
playerID    TeamID
1           1
1           2
1           3
2           1
2           3
3           1
3           3

所以我应该得到这个答案-

 2, Allen, 3, Pierce 
 4, Garnett, 5, Perkins

.

2, Allen, 3 Pierce 是个不错的选择,因为他们都只为凯尔特人和爱国者队效力 4,加内特,5,帕金斯是一个答案,因为这两名球员都没有为应该输出的球队效力。

现在我的查询是

SELECT p1.PLAYERID, 
       f1.PLAYERNAME, 
       p2.PLAYERID, 
       f2.PLAYERNAME 
FROM   PLAYER f1, 
       PLAYER f2, 
       PLAYS p1 
       FULL OUTER JOIN PLAYS p2 
                    ON p1.PLAYERID < p2.PLAYERID 
                       AND p1.TEAMID = p2.TEAMID 
GROUP  BY p1.PLAYERID, 
          f1.PLAYERID, 
          p2.PLAYERID, 
          f2.PLAYERID 
HAVING Count(p1.PLAYERID) = Count(*) 
       AND Count(p2.PLAYERID) = Count(*) 
       AND p1.PLAYERID = f1.PLAYERID 
       AND p2.PLAYERID = f2.PLAYERID; 

我不是 100% 确定,但我认为这可以找到为同一支球队效力的球员,但我想找出为上述所有相同球队效力的球员

在此之后我被困在如何处理它上。有关如何解决此问题的任何提示。谢谢你的时间。

【问题讨论】:

  • 我了解到您希望找到 2 名球员在完全相同的球队中比赛。你能澄清一下“不重复”是什么意思吗?
  • 您使用的是什么 DBMS?
  • 一名球员可以为多支球队效力。我想要为同一支球队效力的球员。假设球员 A 为公牛队、魔术队、凯尔特人队效力,球员 B 为公牛队、魔术队、凯尔特人队、热火队效力。他们不算数,因为热火队不是 A 为之效力的团队成员。我想找到两个为完全相同的球队效力的不同球员,仅此而已。此外,如果两名球员目前不为任何球队效力,这是可能的。它们应该在输出中。另一件事,假设 A 队和 B 队比赛。输出应该是 A、B 或 B、A,而不是两者。如果还有任何问题,请告诉我。
  • 我正在使用 SQL 在 postgres 上执行此操作。
  • 您的联接完全错误。您不应该将 having 子句中的隐式连接与 from 子句中的显式 JOINs 混合使用(如果有的话:隐式连接应该在 WHERE 子句中完成

标签: sql postgresql


【解决方案1】:

我相信这个查询会做你想做的事:

SELECT array_agg(players), player_teams
FROM (
  SELECT DISTINCT t1.t1player AS players, t1.player_teams
  FROM (
    SELECT
      p.playerid AS t1id,
      concat(p.playerid,':', p.playername, ' ') AS t1player,
      array_agg(pl.teamid ORDER BY pl.teamid) AS player_teams
    FROM player p
    LEFT JOIN plays pl ON p.playerid = pl.playerid
    GROUP BY p.playerid, p.playername
  ) t1
INNER JOIN (
  SELECT
    p.playerid AS t2id,
    array_agg(pl.teamid ORDER BY pl.teamid) AS player_teams
  FROM player p
  LEFT JOIN plays pl ON p.playerid = pl.playerid
  GROUP BY p.playerid, p.playername
) t2 ON t1.player_teams=t2.player_teams AND t1.t1id <> t2.t2id
) innerQuery
GROUP BY player_teams


Result:
PLAYERS               PLAYER_TEAMS
2:Allen,3:Pierce      1,3
4:Garnett,5:Perkins

它在plays 中的每个玩家的 teamid 上使用 array_agg 来匹配具有完全相同团队配置的玩家。例如,我在团队中添加了一个列,但只要不从 group by 子句中删除,就可以将其删除而不会影响结果。

SQL Fiddle example.用 Postgesql 9.2.4 测试

编辑:修复了重复行的错误。

【讨论】:

  • 有没有办法在不使用 array_agg 和 concat 的情况下做到这一点,因为我不熟悉这两个?
  • 然后熟悉thesetwo
  • @user2632133 也许可以,但为什么不使用postgresql提供的工具呢?如果您不熟悉这些功能,请阅读手册 :)
  • 因为我希望它在 postgre 上也能像在纸上一样在外面工作。我不想依赖 postgre。
  • @user2632133 我不确定如何在标准 ANSI SQL 中执行此操作,我猜某种游标可能会起作用。不过,这适用于 postgres,这就是问题被标记为的原因。
【解决方案2】:

似乎 OP 可能不再感兴趣了,但万一其他人觉得它有用, 这是在纯 SQL 中有效的查询(至少对我而言;))

SELECT M.p1, pr1.playername, M.p2, pr2.playername FROM player pr1 
INNER JOIN player pr2 INNER JOIN
(
   SELECT plays1.player p1, plays2.player p2, plays1.team t1 FROM plays plays1 
   INNER JOIN plays plays2 
   ON (plays1.player < plays2.player AND plays1.team = plays2.team)
   GROUP BY plays1.player, plays2.player HAVING COUNT(*) = 
((SELECT COUNT(*) FROM plays plays3 WHERE plays3.player = plays1.player) + 
(SELECT COUNT(*) FROM plays plays4 WHERE plays4.player = plays2.player)) /2
) M ON pr1.playerID = M.p1 AND pr2.playerID = M.p2 
UNION ALL
SELECT M.pid, M.pname, N.pid2, N.pname2 FROM
(
(SELECT p.playerID pid, p.playerName pname, pl.team FROM player p
 LEFT JOIN plays pl ON p.playerId = pl.player WHERE pl.team IS NULL) M
 INNER JOIN
 (SELECT p.playerID pid2, p.playerName pname2, pl.team FROM player p
  LEFT JOIN plays pl ON p.playerId = pl.player WHERE pl.team IS NULL) N 
 ON (pid < pid2)
)

【讨论】:

    【解决方案3】:

    没什么大不了的,这里有解决办法

    with gigo as(select a.playerid as playerid,count(b.teamname) as nteams from player a 
    full outer join plays c on a.playerid=c.playerid full outer join team b 
    on b.teamid=c.teamid group by a.playerid)
    select array_agg(a.*),g.nteams from player a inner join gigo g on a.playerid=g.playerid 
    group by g.nteams having count(a.*)>1 order by g.nteams desc
    

    【讨论】:

      【解决方案4】:

      这个解决方案对我有用:

      SELECT TMP1. PLAYERID,TMP2.PLAYERID FROM
      (
          SELECT a.playerid , a.teamid,b.team_sum 
          FROM plays  A
          INNER JOIN 
          (
              SELECT PLAYERID,SUM(teamid) AS team_sum
              FROM plays
              GROUP BY 1
           ) B
      
          ON a.playerid=b.playerid
       ) TMP1
      
      INNER JOIN
      
      (
          SELECT a.playerid , a.teamid,b.team_sum
          FROM plays  A
      
          INNER JOIN 
          (
              SELECT PLAYERID,SUM(teamid) AS team_sum
              FROM plays
              GROUP BY 1
          ) B
      
      ON a.playerid=b.playerid
      
      )TMP2
      ON TMP1.PLAYERID < TMP2.PLAYERID
      AND TMP1.TEAMID=TMP2.TEAMID
      AND TMP1.TEAM_SUM=TMP2.TEAM_SUM
      GROUP BY 1,2
      
      UNION ALL
      SELECT n1,n2 FROM  
      (
          SELECT TMP3.PLAYERID AS n1,TMP4.PLAYERID AS n2 FROM 
          PLAYER  TMP3
          INNER JOIN PLAYER TMP4
          ON TMP3.PLAYERID<TMP4.PLAYERID
          WHERE TMP3.PLAYERID  NOT IN (SELECT  PLAYERID FROM plays  )
          AND tmp4.playerid NOT IN (SELECT playerid FROM plays)
      ) TMP5
      

      【讨论】:

        【解决方案5】:

        想到了两种可能的解决方案:

        1. 光标 - 循环遍历每个玩家并将他与其他所有玩家进行比较,直到得出结论。
        2. 递归查询 - 相同的想法虽然稍微复杂一些,但绝对是更好的方法。可能也有更好的性能。

        您能否提供一些示例数据以便我创建示例?

        【讨论】:

        • 要么使用 SQL fiddle (sqlfiddle.com),要么将每个表的几行数据添加到原始帖子中。
        【解决方案6】:

        您想要的基本数据类型似乎是集合,而不是数组。因此,一种选择可能是使用 PL/Python 代码类似于下面的代码(请参阅此答案的底部以获取可能适用于此目的的函数)。当然,这绝不是一种“纯 SQL”方法。

        但坚持使用 PostgreSQL(尽管不是标准 SQL),您可能还希望将 DISTINCT 与 array_agg 一起使用。请注意,以下仅给出符合条件的第一对(原则上可能还有更多)。

        WITH teams AS (
          SELECT playerID, array_agg(DISTINCT teamID ORDER BY teamID) AS teams
          FROM plays
          GROUP BY playerID),
        teams_w_nulls AS (
          SELECT a.playerID, b.teams
          FROM player AS a
          LEFT JOIN teams AS b
          ON a.playerID=b.playerID),
        player_sets AS (
          SELECT teams, array_agg(DISTINCT playerID ORDER BY playerID) AS players
          FROM teams_w_nulls
          GROUP BY teams
          -- exclude players who are only share a team list with themselves.
          HAVING array_length(array_agg(DISTINCT playerID ORDER BY playerID),1)>1)
        SELECT a.teams, b.playerID, b.playerName, c.playerID, c.playerName
        FROM player_sets AS a
        INNER JOIN player AS b
        ON a.players[1]=b.playerID
        INNER JOIN player AS c
        ON a.players[2]=c.playerID;
        

        上面的查询给出以下输出:

         teams | playerid | playername | playerid | playername 
        -------+----------+------------+----------+------------
         {1,3} |        2 | Allen      |        3 | Pierce
               |        4 | Garnett    |        5 | Perkins
        (2 rows)
        

        PL/Python 函数示例:

        CREATE OR REPLACE FUNCTION set(the_list integer[])
          RETURNS integer[] AS
        $BODY$
            return list(set(the_list))
        $BODY$
          LANGUAGE plpython2u;
        
        CREATE OR REPLACE FUNCTION pairs(a_set integer[])
          RETURNS SETOF integer[] AS
        $BODY$
            def pairs(x):
                for i in range(len(x)):
                    for j in x[i+1:]:
                        yield [x[i], j]
            return list(pairs(a_set))
        $BODY$
          LANGUAGE plpython2u;
        
        SELECT set(ARRAY[1, 1, 2, 3, 4, 5, 6, 6]);
        

        上面使用这些函数的代码版本(输出类似,但是当给定的一组团队有多个配对时,这种方法会选择所有配对):

        WITH teams AS (
          SELECT playerID, set(array_agg(teamID)) AS teams
          FROM plays
          GROUP BY playerID),
        teams_w_nulls AS (
          SELECT a.playerID, b.teams
          FROM player AS a
          LEFT JOIN teams AS b
          ON a.playerID=b.playerID),
        player_pairs AS (
          SELECT teams, pairs(set(array_agg(playerID))) AS pairs
          FROM teams_w_nulls
          GROUP BY teams)
          -- no need to exclude players who are only share a team 
          -- list with themselves.
        SELECT teams, pairs[1] AS player_1, pairs[2] AS player_2
        FROM player_pairs;
        

        【讨论】:

          【解决方案7】:

          我们使用每个球员的球队数和 ascii(team_name)+team_id 的总和进行查询,将其称为 team_value。我们做一个自连接,与自身相同的查询,其中 counts 和 team_values 匹配但 id 不等于 id,这为我们提供了我们想要获取的 ID

          select * from player where player_id in 
          (
           select set2.player_id orig
           from
           (select count(*) count,b.player_id , nvl(sum(a.team_id+ascii(team_name)),0) team_value
             from plays a, player b , team c
             where a.player_id(+)=b.player_id
              and a.team_id = c.team_id(+)
             group by b.player_id) set1,
          (select count(*) count,b.player_id , nvl(sum(a.team_id+ascii(team_name)),0) team_value
             from plays a, player b , team c
             where a.player_id(+)=b.player_id
              and a.team_id = c.team_id(+)
             group by b.player_id) set2
          where set1.count=set2.count and set1.team_value=set2.team_value
            and set1.player_id<>set2.player_id
          )
          

          【讨论】:

            【解决方案8】:

            这是一个带有 UNION 和 2-3 个简单连接的简单查询。 UNION 之前的第一个查询包含为相同数量的球队效力相同次数的球员姓名和球员ID。 UNION 之后的第二个查询包含根本没有为任何球队效力的球员姓名和球员 ID。

            只需复制粘贴此查询并尝试执行它,您将看到预期的结果。

                select playername,c.playerid from 
                (select a.cnt, a.playerid from 
                (select count(1) cnt , PLAYERID from plays group by  PLAYERID) a ,
                (select count(1) cnt , PLAYERID from plays group by  PLAYERID) b 
                where a.cnt=b.cnt
                and  a.playerid<> b.playerid ) c ,PLAYER  d
                where c.playerid=d.playerid
                UNION
                select e.playername,e.playerid 
                from player e 
                left outer join plays f on 
                e.playerid=f.playerid where nvl(teamid,0 )=0
            

            【讨论】:

              【解决方案9】:

              试试这个: 这里的测试是您问题中的 PLAYS 表。

              select group_concat(b.name),a.teams from
              (SELECT playerid, group_concat(distinct teamid ORDER BY teamid) AS teams
                FROM test
                GROUP BY playerid) a, player b
              where a.playerid=b.playerid
              group by a.teams
              union
              select group_concat(c.name order by c.playerid),null from player c where c.playerid not in (select        playerid from test);
              

              【讨论】:

                【解决方案10】:

                对于任何感兴趣的人,这个简单的查询对我有用

                SELECT UNIQUE PLR1.PID,PLR1.PNAME, PLR2.PID, PLR2.PNAME
                FROM PLAYS PLY1,PLAYS PLY2, PLAYER PLR1, PLAYER PLR2
                WHERE PLR1.PID < PLR2.PID AND PLR1.PID = PLY1.PID(+) AND PLR2.PID = PLY2.PID(+)
                AND NOT EXISTS(( SELECT PLY3.TEAMID FROM PLAYS PLY3 WHERE PLY3.PID = PLR1.PID) 
                MINUS
                ( SELECT PLY4.TEAMID FROM PLAYS PLY4 WHERE PLY4.PID = PLR2.PID));
                

                【讨论】:

                  【解决方案11】:

                  select p1.playerId, p2.playerId, count(p1.playerId)
                  from plays p1, plays p2
                  WHERE p1.playerId<p2.playerId
                  and p1.teamId = p2.teamId
                  GROUP BY p1.playerId, p2.playerId
                  having count(*) = (select count(*) from plays where playerid = p1.playerid)

                  【讨论】:

                    【解决方案12】:
                    WITH temp AS (
                      SELECT p.playerid, p.playername, listagg(t.teamname,',') WITHIN GROUP (ORDER BY t.teamname) AS teams
                      FROM player p full OUTER JOIN plays p1 ON p.playerid = p1.playerid
                        LEFT JOIN team t ON p1.teamid = t.teamid GROUP BY (p.playerid , p.playername))
                    SELECT concat(concat(t1.playerid,','), t1.playername), t1.teams 
                    FROM temp t1 WHERE nvl(t1.teams,' ') IN (
                      SELECT nvl(t2.teams,' ') FROM temp t2 
                      WHERE t1.playerid <> t2.playerid) 
                    ORDER BY t1.playerid
                    

                    【讨论】:

                      【解决方案13】:

                      这是 ANSI SQL ,没有使用任何特殊函数。

                      SELECT   TAB1.T1_playerID AS playerID1 , TAB1.playerName1  ,   
                        TAB1.T2_playerID AS playerID2, TAB1. playerName2
                       FROM
                      (select   T1.playerID AS T1_playerID ,  T3. playerName  AS  playerName1 ,
                      
                      T2.playerID  AS T2_playerID ,  T4. playerName AS playerName2  ,COUNT (T1.TeamID) AS MATCHING_TEAM_ID_CNT
                      FROM PLAYS T1
                      INNER JOIN PLAYS T2  ON(  T1.TeamID = T2.TeamID AND T1.playerID <> T2.playerID )
                      INNER JOIN player T3 ON (  T1.playerID=T3.playerID)
                      INNER JOIN player T4 ON (  T2.playerID=T4.playerID)
                       GROUP BY 1,2,3,4
                      ) TAB1
                      
                      INNER JOIN 
                      ( SELECT  T1.playerID AS playerID, COUNT(T1.TeamID) AS TOTAL_TEAM_CNT
                       FROM PLAYS  T1
                      GROUP BY T1.playerID) TAB2
                      ON(TAB1.T2_playerID=TAB2.playerID AND    
                        TAB1.MATCHING_TEAM_ID_CNT =TAB2.TOTAL_TEAM_CNT)
                      
                      INNER JOIN 
                      ( SELECT  T1.playerID AS playerID, COUNT(T1.TeamID) AS TOTAL_TEAM_CNT
                      FROM PLAYS  T1
                      GROUP BY T1.playerID 
                      ) TAB3
                      ON( TAB1. T1_playerID = TAB3.playerID  AND 
                       TAB1.MATCHING_TEAM_ID_CNT=TAB3.TOTAL_TEAM_CNT)
                      WHERE playerID1  < playerID2
                      
                          UNION ALL (
                          SELECT   T1.playerID, T1.playerName ,T2.playerID,T2.playerName
                          FROM
                          PLAYER T1 INNER JOIN PLAYER T2
                          ON (T1.playerID<T2.playerID) 
                          WHERE T1.playerID NOT IN ( SELECT playerID FROM PLAYS))
                      

                      【讨论】:

                        【解决方案14】:

                        假设您的 teamId 是唯一的,此查询将起作用。它只是通过对 teamid 求和来识别具有完全相同球队的所有球员,或者如果球员没有 id,则它将为空。然后计算团队比赛的比赛次数。我在 postgre 9.3 中使用 SQL fiddle 进行了测试。

                        SELECT 
                             b.playerID
                            ,b.playerName
                        FROM (
                        --Join the totals of teams to your player information and then count over the team matches.
                                SELECT 
                                        p.playerID
                                        ,p.playerName
                                        ,m.TeamMatches
                                        ,COUNT(*) OVER(PARTITION BY TeamMatches) as Matches
                                FROM player p
                                        LEFT JOIN (
                                        --Assuming your teamID is unique as it should be. If it is then a sum of the team ids for a player will give you each team they play for. 
                                        --If for some reason your team id is not unique then rank the table and join same as below. 
                                            SELECT 
                                                 ps.playerName
                                                ,ps.playerID
                                                ,SUM(t.teamID) as TeamMatches
                                            FROM plays p
                                                    LEFT JOIN team t ON p.teamID = p.teamID
                                                    LEFT JOIN player ps ON p.playerID = ps.playerID
                                            GROUP BY 
                                                    ps.playerName
                                                ,ps.playerID
                                        ) m ON p.playerID = m.playerID
                        ) b
                        WHERE
                        b.Matches <> 1
                        

                        【讨论】:

                          【解决方案15】:

                          这个查询应该解决它。 通过在 PLAYS 上进行自我加入。 - 比较玩家ID - 将匹配的行数与每个玩家的总计数进行比较。

                          select p1.playerId, p2.playerId, count(p1.playerId)
                          from plays p1, plays p2
                          WHERE p1.playerId<p2.playerId
                          and p1.teamId = p2.teamId
                          GROUP BY p1.playerId, p2.playerId
                          having count(*) = (select count(*) from plays where playerid = p1.playerid)

                          【讨论】:

                          • 请使用 'join' 和 'on' 而不是旧的。
                          【解决方案16】:

                          在 SQl 2008 中创建函数

                          ALTER FUNCTION [dbo].[fngetTeamIDs] ( @PayerID int ) RETURNS varchar(101) AS Begin
                          
                          declare @str varchar(1000)
                          
                          SELECT @str= coalesce(@str + ', ', '') + CAST(a.TeamID AS varchar(100)) FROM (SELECT DISTINCT TeamID from Plays where PayerId=@PayerID) a
                          
                          return @str
                          
                          END
                          

                          --选择 dbo.fngetTeamIDs(2)

                          从这里开始查询

                          drop table #temp,#A,#B,#C,#D
                          
                          (select PayerID,count(*) count 
                          into #temp
                          from Plays 
                          group by PayerID)
                          
                          
                          select *
                          into #A
                          from #temp as T
                          
                          where T.count in (
                                  select T1.count from #temp as T1
                                  group by T1.count having count(T1.count)>1 
                          )
                          
                          select A.*,P.TeamID
                          into #B
                          from #A A inner join Plays P
                          on A.PayerID=P.PayerID
                          order by A.count
                          
                          
                          select B.PayerId,B.count, 
                          (
                          select dbo.fngetTeamIDs(B.PayerId)
                          ) as TeamIDs
                          into #C
                          from #B B
                          group by B.PayerId,B.count
                          
                          
                          select TeamIDs 
                          into #D
                          from #c as C
                          group by C.TeamIDs
                          having count(C.TeamIDs)>1
                          
                          select C.PayerId,P.PlayerName,D.TeamIDs
                          from #D D inner join #C C
                          on D.TeamIDs=C.TeamIDs
                          inner join Player P
                          on C.PayerID=P.PlayerID
                          

                          【讨论】:

                            猜你喜欢
                            • 1970-01-01
                            • 2019-11-08
                            • 1970-01-01
                            • 1970-01-01
                            • 2017-06-15
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            相关资源
                            最近更新 更多