SQL: рекурсивный цикл - PullRequest
       6

SQL: рекурсивный цикл

0 голосов
/ 05 февраля 2020

Таблица выглядит следующим образом:

EXCHANGE

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

Person1, Person2, Person3, Person7, Person8
Person5, Person6, Person9

Пока у меня есть следующий запрос, но, похоже, результаты не пересекаются в таблице строк и выводятся в виде 1 столбца.

DECLARE @r VARCHAR(MAX), @n INT, @i INT 
SELECT @i = 1,
       @r = 'SELECT BOX, ' + CHAR(13), 
       @n = (SELECT TOP 1 COUNT( USERS ) 
                   FROM EXCHANGE 
                  GROUP BY BOX 
                  ORDER BY COUNT( USERS ) DESC ) ;      
WHILE @i <= @n BEGIN 
           SET @r = @r + 
           CASE WHEN @i = 1  
                THEN 'MAX( CASE Seq WHEN ' + CAST( @i AS VARCHAR ) + ' 
                                 THEN USERS 
                                            ELSE SPACE(0) END ) + ' + CHAR(13) 
           WHEN @i = @n 
             THEN 'MAX( CASE Seq WHEN ' + CAST( @i AS VARCHAR ) + ' 
                                 THEN '', '' + USERS 
                                 ELSE SPACE(0) END ) ' + CHAR(13) 
             ELSE 'MAX( CASE Seq WHEN ' + CAST( @i AS VARCHAR ) + ' 
                                 THEN '', '' + USERS 
                                 ELSE SPACE(0) END ) + ' + CHAR(13)  
           END ;
           SET @i = @i + 1 ;
END 
SET @r = @r + ' 
    FROM ( SELECT BOX, USERS, 
                  ROW_NUMBER() OVER ( PARTITION BY BOX ORDER BY USERS )
             FROM EXCHANGE p ) D ( BOX, USERS, Seq ) 
           GROUP BY BOX;' 
EXEC( @r ) ;

Ответы [ 2 ]

2 голосов
/ 05 февраля 2020

Этот тип хождения по графику - боль в SQL Сервер - у вас есть циклы. Проблема в том, чтобы избежать циклов. Поскольку SQL Сервер не имеет очень хороших типов данных, вам необходимо хранить посещенные узлы в виде строк.

Вы можете делать все это в рекурсивном CTE. Идея состоит в том, чтобы следовать всем путям от узла без повторения какого-либо узла. Держите минимум посещенных узлов. Вуаля! Это указывает путь:

with cte as (
      select box, users,
             convert(varchar(max), concat(',', box, ',', users, ',')) as path,
             (case when box < users then box else users end) as min_node
      from exchange
      union all
      select cte.box, e.users,
             concat(cte.path, e.users, ','),
             (case when min_node < e.users then min_node else e.users end)
      from cte join
           exchange e
           on e.box = cte.users
      where path not like '%,' + e.users + ',%'
     )
select cte.box, min(cte.users), min(cte.path), min(cte.min_node) as grouping
from cte
group by cte.box;

Здесь - это дБ <> скрипка.

Это предполагает, что края симметричны c, поэтому если a, b), у вас также есть (b, a).

Если это не так, легко добавить CTE, который делает это так:

select box, users
from exchange
union   -- on purpose to remove duplicates
select users, box
from exchange;
0 голосов
/ 20 февраля 2020

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RecusriveGroup
{
    public class FinalResult
    {
        public string GroupName { get; set; }
        public string BoxName { get; set; }
        public string UserName { get; set; }
    }
    class Program
    {
        static void Main(string[] args)
        {
            using(var con = new SqlConnection("Data Source=SQLServer;Initial Catalog=TESTDB;Integrated Security=SSPI"))
            {
                con.Open();
                var cmd = new SqlCommand("select distinct Box from Exchange");
                cmd.Connection = con;
                var adapter = new SqlDataAdapter(cmd);
                DataSet dsResult = new DataSet();
                adapter.Fill(dsResult);
                var finalResult = new List<FinalResult>();
                var groupId = 0;
                foreach (DataRow row in dsResult.Tables[0].Rows)
                {
                    if(finalResult.Any(f => f.BoxName.Equals(row["Box"])))
                    {
                        continue;
                    }
                    groupId++;
                    RecursiveCall("Group" + groupId, row["Box"].ToString(), "", con, finalResult);
                }
                foreach(var result in finalResult)
                {
                    var cmd1 = new SqlCommand("INSERT INTO FinalResult(Box, [User], [Group]) VALUES(@Box, @User, @Group)", con);
                    cmd1.Parameters.AddWithValue("@Box", result.BoxName);
                    cmd1.Parameters.AddWithValue("@User", result.UserName);
                    cmd1.Parameters.AddWithValue("@Group", result.GroupName);
                    cmd1.ExecuteNonQuery();
                }
            }
            Console.ReadLine();
        }
        private static void RecursiveCall(string groupName, string boxName, string userName, SqlConnection sqlConnection, List<FinalResult> finalResult)
        {
            DataSet dsResult = new DataSet();
            if (!string.IsNullOrEmpty(boxName) && !string.IsNullOrEmpty(userName))
            {
                var cmd = new SqlCommand("select Box, Users from Exchange WHERE Box = @BoxName OR Users = @UserName");
                cmd.Parameters.AddWithValue("@BoxName", boxName);
                cmd.Parameters.AddWithValue("@UserName", userName);
                cmd.Connection = sqlConnection;
                var adapter = new SqlDataAdapter(cmd);
                adapter.Fill(dsResult);
            }
            else if(!string.IsNullOrEmpty(boxName))
            {
                var cmd = new SqlCommand("select Box, Users from Exchange WHERE Box = @BoxName");
                cmd.Parameters.AddWithValue("@BoxName", boxName);
                cmd.Connection = sqlConnection;
                var adapter = new SqlDataAdapter(cmd);
                adapter.Fill(dsResult);
            } 
            else
            {
                var cmd = new SqlCommand("select Box, Users from Exchange WHERE Users = @UserName");
                cmd.Parameters.AddWithValue("@UserName", userName);
                cmd.Connection = sqlConnection;
                var adapter = new SqlDataAdapter(cmd);
                adapter.Fill(dsResult);
            }
            foreach (DataRow row in dsResult.Tables[0].Rows)
            {
                if (finalResult.Any(f => f.BoxName.Equals(row["Box"].ToString()) && f.UserName.Equals(row["Users"].ToString())))
                {
                    continue;
                }
                finalResult.Add(new FinalResult() { GroupName = groupName, BoxName = row["Box"].ToString(), UserName = row["Users"].ToString() });
                RecursiveCall(groupName, row["Box"].ToString(), row["Users"].ToString(), sqlConnection, finalResult);
            }
        }
    }
}
...