将 table 双向关系分成不同的组

Dividing up a table of two-way relationships into distinct groups

我正在开发一个应用程序,用户可以在其中标记 "components" 作为工作流程的一部分。在许多情况下,他们最终会得到几个互为同义词的标签。他们希望将这些组合在一起,以便在将一个标签添加到组件时,也可以添加该组中的其余标签。

我决定将标签组分解为组中每对标签之间的双向关系。因此,如果一个组具有标签 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 语句(我们在 RelatedTags 中有 1562 个标签和 67 条记录)。

这些解决方案有效,但它们非常幼稚且效率低下。不过,我不知道还能去哪里。有什么想法吗?

我真的不明白其中的关系。你解释的不是很好。但是我以某种方式得到了相同的结果。不确定我是否做对了。

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; }

    }
}

如果每个标签都有一个 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);

然后,一个 table 持有以下信息应该会使很多其他事情变得更容易(我首先解释 table 的内容,然后如何使用查询获取它):

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

数据包含两组相关标签,即{1,2,3}{4,5}。因此,上面的 table 标记属于同一组的标签具有相同的 groupId,即 1 代表 {1,2,3}4 代表 {4,5}

要实现这样的 view/table,您可以使用以下查询:

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

当然,除了引入新的 table / 视图,您还可以通过 groupId 属性扩展您的主要 tags-table。

希望这对您有所帮助。