【问题标题】:How to count the number of bidirectionally connected neighbors of A or B?如何计算 A 或 B 的双向连接邻居的数量?
【发布时间】:2010-12-03 11:56:29
【问题描述】:

以下是我创建的查询,用于计算两个用户的共同强连接(双向连接)邻居的数量:

DECLARE @monthly_connections_test TABLE (
  calling_party VARCHAR(50)
  , called_party VARCHAR(50))

INSERT INTO @monthly_connections_test
          SELECT 'z1', 'z2'
UNION ALL SELECT 'z1', 'z3'
UNION ALL SELECT 'z1', 'z4'
UNION ALL SELECT 'z1', 'z5'
UNION ALL SELECT 'z1', 'z6'
UNION ALL SELECT 'z2', 'z1'
UNION ALL SELECT 'z2', 'z4'
UNION ALL SELECT 'z2', 'z5'
UNION ALL SELECT 'z2', 'z7'
UNION ALL SELECT 'z3', 'z1'
UNION ALL SELECT 'z4', 'z7'
UNION ALL SELECT 'z5', 'z1'
UNION ALL SELECT 'z5', 'z2'
UNION ALL SELECT 'z7', 'z4'
UNION ALL SELECT 'z7', 'z2'

SELECT  t1.user1, t1.user2,
        0 AS calling_calling, 0 AS calling_called, 
        0 AS called_calling, 0 AS called_called, 
        COUNT(*) AS both_directions
  FROM (SELECT relevant_monthly_connections.calling_party AS user1, 
               relevant_monthly_connections_1.calling_party AS user2,
               relevant_monthly_connections.called_party AS calledUser
          FROM @monthly_connections_test relevant_monthly_connections 
            INNER JOIN @monthly_connections_test AS relevant_monthly_connections_1 
               ON    relevant_monthly_connections.called_party  = relevant_monthly_connections_1.called_party 
                 AND relevant_monthly_connections.calling_party < relevant_monthly_connections_1.calling_party
       ) t1 
     INNER JOIN @monthly_connections_test AS relevant_monthly_connections_2
       ON     relevant_monthly_connections_2.called_party  = t1.user1
          AND relevant_monthly_connections_2.calling_party = t1.calledUser
  GROUP BY t1.user1, t1.user2

现在我想计算 user1 或 user2 的强连接邻居。因此,例如对于 (z1, z2) 对,强连接邻居的数量为 3(z1 与 z2、z3、z5 强连接,并且 z2 被忽略,因为它是该对中的节点之一,而 z2 与z1、z5 和 z7。同样,z1 被忽略,count((z3, z5) U (z5, z7)) 为 3)。

有谁知道如何创建查询来计算与每对中的一个节点强连接的所有节点的数量(查询必须自动计算每条记录的所有邻居的数量)?

编辑#1:

以下查询返回所有双向连接的表:

WITH bidirectionalConnections AS
(
SELECT calling_party AS user1, called_party AS user2 FROM @monthly_connections_test WHERE calling_party < called_party
INTERSECT
SELECT called_party AS user2, calling_party AS user2 FROM @monthly_connections_test
)
SELECT user1, user2 FROM bidirectionalConnections

现在,对于每对节点,必须在表 bidirectionalConnections 中检查有多少节点与该对中的第一个或第二个节点强连接。

必须自动生成结果中的对及其邻居的数量。

编辑#2:

这是@monthly_connections_test 表描述的图片:

所以与z1 OR z2强连接的邻居是z3、z5、z7

z1,z3:z2,z5

z1、z4:z2、z3、z5、z7

...

z1、z7:z2、z3、z4、z5

...

结果表应采用以下格式:

user1, user2, total_neighbors_count
z1, z2, 3
z1, z3, 2
z1, z4, 4
...
z1, z7, 4
...

谢谢!

附言

我已经发布了类似的问题How to use JOIN instead of UNION to count the neighbors of “A OR B”?,但它不一样,所以我希望这个问题不要被视为重复。

【问题讨论】:

  • 原始问题和编辑#2 之间强连接邻居的定义发生了变化——最初,(z1,z2) 有 3 个强连接邻居,但在编辑#2 中他们有 4 个。哪个是正确的?
  • 3 是正确的。感谢您的关注!

标签: sql sql-server graph social-networking


【解决方案1】:

我相信下面展示的查询将产生所需的结果。我已经对查询进行了结构化,以使管道的每个阶段都显式化,这具有额外的副作用,即为查询优化器提供关于如何最小化中间行集大小的强烈提示。有关每个阶段的目的,请参阅查询本身中的 cmets。

;WITH
  -- identify the strongly connected parties
  -- both directions are included here for later convenience
  stronglyConnected AS (
    SELECT DISTINCT
      l.calling_party AS party1
    , l.called_party AS party2
    FROM @monthly_connections_test AS l
    INNER JOIN @monthly_connections_test AS r
      ON r.calling_party = l.called_party
      AND r.called_party = l.calling_party
  )
  -- identify all of the parties that participated in a strong connection
, uniqueParties AS (
    SELECT DISTINCT party1 AS party FROM stronglyConnected
  )
  -- make all unique pairs of such parties
, allPairs AS (
    SELECT
      u1.party AS party1
    , u2.party AS party2
    FROM uniqueParties AS u1
    CROSS JOIN uniqueParties AS u2
    WHERE u1.party < u2.party
  )
  -- find the neighbours of each pair
, pairNeighbors AS (
    SELECT DISTINCT
      p.party1
    , p.party2
    , sc.party2 AS neighbor
    FROM allPairs AS p
    INNER JOIN stronglyConnected AS sc
      ON sc.party1 IN (p.party1, p.party2)
      AND sc.party2 NOT IN (p.party1, p.party2)
  )
  -- count the neighbours of each pair
, neighbourCounts AS (
    SELECT
      party1 AS user1
    , party2 AS user2
    , COUNT(*) AS total_neighborCount
    FROM pairNeighbors
    GROUP BY
      party1
    , party2
  )
-- show the final result
SELECT * FROM neighbourCounts ORDER BY 1, 2
-- handy for testing, debugging and answering other queries:
-- SELECT * FROM stronglyConnected ORDER BY 1, 2
-- SELECT * FROM uniqueParties ORDER BY 1
-- SELECT * FROM allPairs ORDER BY 1, 2
-- SELECT * FROM pairNeighbors ORDER BY 1, 2

【讨论】:

    【解决方案2】:

    我认为您在问题中提供的示例查询是错误的(基于描述) - 它返回 z5 - z7 作为强连接对,而样本数据中根本不存在该组合。我相信这是一个正确的实现:

    SELECT calling.*
    FROM    @monthly_connections_test AS calling
    WHERE   EXISTS  (   SELECT 1
                        FROM @monthly_connections_test AS called
                        WHERE   calling.calling_party   = called.called_party
                        AND     calling.called_party    = called.calling_party
            )
    AND     calling.calling_party   < calling.called_party  
    

    我已经扩展了这个实现来提供你想要的。这不是一个特别漂亮的解决方案,应该在更大的数据集上进行测试,因为它可能无法很好地扩展。自从您的其他问题引用 SQL 2008 以来,我一直使用 SQL 2008 变量表示法。

    DECLARE @user1 varchar(50) = 'z1'
    DECLARE @user2 varchar(50) = 'z2'
    
    
    ;WITH strongCTE
    AS
    (
        SELECT  calling.calling_party AS c1,
                calling.called_party AS c2
        FROM    @monthly_connections_test AS calling
        WHERE   EXISTS  (   SELECT 1
                            FROM @monthly_connections_test AS called
                            WHERE   calling.calling_party   = called.called_party
                            AND     calling.called_party    = called.calling_party
                )
        AND     calling.calling_party   < calling.called_party  
    )
    SELECT COUNT(1) AS ConnectedNeighboursToUser1orUser2
    FROM
    (
        SELECT  c2
        FROM    strongCTE
        WHERE   c1 = @user1
        AND     c2 NOT IN (@user1,@user2)
        GROUP BY c1,c2
    
        UNION
    
        SELECT  c2
        FROM    strongCTE
        WHERE   c1 = 'z2'
        AND     c2 NOT IN (@user1,@user2)
        GROUP BY c1,c2
    ) AS x
    

    【讨论】:

    • 感谢您的回答!然而,该解决方案需要创建一个包含对及其邻居的所有组合的表。
    • @niko - 根据您提供的示例数据,您能否使用预期输出更新问题?我不清楚你想要什么。
    【解决方案3】:

    基于 Edit2,以下查询给出了已列出的结果:

    declare @party1 varchar(50)
    declare @party2 varchar(50)
    
    --Since we're only interested in strong connections, we can treat both parties as calling_party in the following queries
    
    select @party1 = 'z1', @party2 = 'z7'
    
    select
        distinct mt.called_party 
    from
        @monthly_connections_test mt
            inner join
        @monthly_connections_test mt2
            on
                mt.called_party = mt2.calling_party and
                mt.calling_party = mt2.called_party
    where
        mt.calling_party in (@party1,@party2) and
        not mt.called_party in (@party1,@party2)
    

    要获得计数,您可以适应在 select 子句中使用 COUNT(distinct mt.called_party)


    下面给出了每个连接对的所有组计数。如果我们需要避免强连接对的重复,我认为这会变得更加棘手:

    select grp.called_party,grp.calling_party,COUNT(distinct mt.called_party )
    from
        (select
              CASE WHEN calling_party < called_party THEN calling_party ELSE called_party END as calling_party,CASE WHEN calling_party < called_party THEN called_party ELSE calling_party END as called_party FROM @monthly_connections_test) grp,
        @monthly_connections_test mt
            inner join
        @monthly_connections_test mt2
            on
                mt.called_party = mt2.calling_party and
                mt.calling_party = mt2.called_party
    where
        mt.calling_party in (grp.called_party,grp.calling_party) and
        not mt.called_party in (grp.called_party,grp.calling_party)
    group by grp.called_party,grp.calling_party
    

    【讨论】:

    • 感谢您的回答,但如何再次使用此查询自动计算所有对的邻居数?
    • @niko - 抱歉,这个问题已经很长了。输入应该是所有连接的对,还是只有那些自身强连接的对?
    • 对于在原始表中表示为强或不强连接的每一对,都必须计算强连接。例如,不是强连接的 (z1, z4) 存储在原始表中,对于这对,必须计算与 z1 或 z4 相关的所有强连接。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-02-04
    • 1970-01-01
    • 2015-12-19
    • 1970-01-01
    相关资源
    最近更新 更多