Как посчитать количество двунаправленно подключенных соседей A или B? - PullRequest
2 голосов
/ 03 декабря 2010

Ниже приведен запрос, который я создал для подсчета количества общих сильно связанных (подключенных в обоих направлениях) соседей двух пользователей:

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

Теперь я хотел бы подсчитать количество сильно связанных соседей пользователя1ИЛИ user2.Так, например, для пары (z1, z2) число сильносвязанных соседей равно 3 (z1 сильно связан с z2, z3, z5 и z2 игнорируется, так как это один из узлов в паре, а z2 сильно связан сz1, z5 и z7. опять же, z1 игнорируется и считается ((z3, z5) U (z5, z7)) 3).

Кто-нибудь знает, как создать запрос для подсчета количества всехузлы, которые сильно связаны с одним из узлов из пары для каждой пары (запрос должен автоматически рассчитывать количество всех соседей для каждой записи)?

Edit # 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: alt text

Итак, соседи, сильно связанные с z1 ИЛИ 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
...

Спасибо!

PS

Я отправил аналогичный вопрос Как использовать JOIN вместо UNION для подсчета соседей «A OR B»? но это не одно и то же, поэтому я надеюсь, что этот вопрос не будет считаться дубликатом.

Ответы [ 3 ]

2 голосов
/ 05 декабря 2010

Я полагаю, что приведенный ниже запрос приведет к желаемому результату.Я структурировал запрос так, чтобы сделать каждую стадию конвейера явной, что имеет дополнительный побочный эффект - дает оптимизатору запросов четкий совет о том, как минимизировать размеры промежуточных наборов строк.Смотрите комментарии в самом запросе для целей каждого этапа.

;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
1 голос
/ 04 декабря 2010

На основании 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)

Чтобы просто получить счет, вы должны использовать 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
1 голос
/ 03 декабря 2010

Я думаю, что пример запроса, который вы задаете в этом вопросе, неверен (на основе описания) - он возвращает 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
...