Разделение таблицы двусторонних отношений на отдельные группы

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

Я решил разбить группы тегов на двусторонние отношения между каждой парой тегов в группе. Так что если у группы есть теги 1 и 2, есть запись, которая выглядит следующим образом:

ID     TagID    RelatedTagID
1      1        2
2      2        1

По сути, группа представлена ​​как декартово произведение каждого тега в ней. Расширьте это до 3 тегов:

ID    Name
1     MM
2     Managed Maintenance
3     MSP

Наши отношения выглядят так:

ID    TagID    RelatedTagID
1     1        2
2     2        1
3     1        3
4     3        1
5     2        3
6     3        2

У меня есть пара методов, чтобы сгруппировать их, но они менее чем звездные. Во-первых, я написал представление, которое перечисляет каждый тег вместе со списком тегов в его группе:

SELECT
    TagKey AS ID,
    STUFF
        ((SELECT ',' + cast(RelatedTagKey AS nvarchar)
          FROM RelatedTags rt
          WHERE rt.TagKey = t.TagKey
          FOR XML PATH('')), 1, 1, '') AS RelatedTagKeys
FROM (
    SELECT DISTINCT TagKey
    FROM RelatedTags
) t

Проблема в том, что каждая группа появляется в результатах столько раз, сколько в ней тегов, и я не смог придумать, как обойти этот запрос в одном запросе. Так что это возвращает меня:

ID    RelatedTagKeys
1     2,3
2     1,3
3     1,2

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

Второе решение, которое я придумал, это запрос LINQ. Ключ, используемый для группировки тегов, - это список самой группы. Это, вероятно, намного хуже, чем я думал.

from t in Tags.ToList()
where t.RelatedTags.Any()
group t by 
    string.Join(",", (new List<int> { t.ID })
        .Concat(t.RelatedTags.Select(i => i.Tag.ID))
        .OrderBy(i => i))
into g
select g.ToList()

Я действительно ненавижу группировку по результатам вызова string.Join, но когда я попытался просто сгруппировать по списку ключей, он не сгруппировался должным образом, поместив каждый тег в группу отдельно. Кроме того, SQL, который он генерирует, является чудовищным. Я не собираюсь вставлять его здесь, но LINQPad показывает, что он генерирует около 12 000 строк отдельных операторов SELECT в моей тестовой базе данных (у нас есть 1562 тега и 67 записей в RelatedTags).

Эти решения работают, но они довольно наивны и неэффективны. Я не знаю, куда еще пойти с этим, хотя. Есть идеи?

2 ответа

Решение

Я полагаю, что работать с вашими данными станет легче, если у вас есть groupId для каждого из ваших тегов, так что связанные теги имеют одинаковое значение groupId, Чтобы объяснить, что я имею в виду, я добавил второй набор связанных тегов в ваш набор данных:

INSERT INTO tags ([ID], [Name]) VALUES
    (1, 'MM'),
    (2, 'Managed Maintenance'),
    (3, 'MSP'),
    (4, 'UM'),
    (5, 'Unmanaged Maintenance');

а также

INSERT INTO relatedTags ([ID], [TagID], [RelatedTagID]) VALUES
    (1, 1, 2),
    (2, 2, 1),
    (3, 1, 3),
    (4, 3, 1),
    (5, 2, 3),
    (6, 3, 2),
    (7, 4, 5),
    (8, 5, 4);

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

tagId | groupId
------|-------- 
1     | 1
2     | 1
3     | 1
4     | 4
5     | 4

Данные состоят из двух групп связанных тегов, т.е. {1,2,3} а также {4,5}, Поэтому в приведенной выше таблице отмечены теги, принадлежащие к той же группе, с groupIdт.е. 1 за {1,2,3}, а также 4 за {4,5},

Чтобы получить такое представление / таблицу, вы можете использовать следующий запрос:

with rt as
( (select r2.tagId, r2.relatedTagId
   from relatedTags r1 join relatedTags r2 on r1.tagId = r2.relatedTagId)
 union 
  (select r3.tagId, r3.tagId as relatedTagId from relatedTags r3)
)
select rt.tagId, min(rt.relatedTagId) as groupId from rt
group by tagId

Конечно, вместо того, чтобы вводить новую таблицу / представление, вы также можете расширить свой основной tags-стол groupId приписывать.

Надеюсь это поможет.

Я действительно не понимаю отношения. Вы не очень хорошо объяснили. Но я как-то получил те же результаты. Не уверен, что сделал все правильно.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;


namespace ConsoleApplication41
{
    class Program
    {
        static void Main(string[] args)
        {
            Data.data = new List<Data>() {
                new Data() { ID = 1, TagID = 1, RelatedTagID = 2},
                new Data() { ID = 2, TagID = 2, RelatedTagID = 1},
                new Data() { ID = 3, TagID = 1, RelatedTagID = 3},
                new Data() { ID = 4, TagID = 3, RelatedTagID = 1},
                new Data() { ID = 5, TagID = 2, RelatedTagID = 3},
                new Data() { ID = 6, TagID = 3, RelatedTagID = 2}
            };

            var results = Data.data.GroupBy(x => x.RelatedTagID)
                .OrderBy(x => x.Key)
                .Select(x => new {
                    ID = x.Key,
                    RelatedTagKeys = x.Select(y => y.TagID).ToList()
                }).ToList();

            foreach (var result in results)
            {
                Console.WriteLine("ID = '{0}', RelatedTagKeys = '{1}'", result.ID, string.Join(",",result.RelatedTagKeys.Select(x => x.ToString())));
            }
            Console.ReadLine();

        }
    }
    public class Data
    {
        public static List<Data> data { get; set; }
        public int ID { get; set; }
        public int TagID { get; set; }
        public int RelatedTagID { get; set; }

    }
}
Другие вопросы по тегам