将双向关系表划分为不同的组

时间:2017-01-11 22:34:29

标签: c# sql sql-server linq set-theory

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

我决定将标签组分解为组中每对标签之间的双向关系。因此,如果一个组有标签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是 monstrous 。我不会在这里粘贴它,但是LINQPad显示它在我的测试数据库上生成大约12,000行单独的SELECT语句(我们在RelatedTags中有1562个标记和67个记录)。

这些解决方案有效,但它们非常幼稚且效率低下。不过,我不知道还有什么地方可以做到这一点。有什么想法吗?

2 个答案:

答案 0 :(得分:1)

如果您的每个代码都有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 - 表,而不是引入新的表/视图。

希望这有帮助。

答案 1 :(得分:0)

我真的不明白这种关系。你没解释得很好。但我得到了相同的结果。不确定我是否做对了。

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

    }
}