1

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

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

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

4

2 回答 2

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,即1for{1,2,3}4for {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属性扩展主表。

希望这可以帮助。

于 2017-01-11T23:32:51.687 回答
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; }

    }
}
于 2017-01-11T23:20:21.087 回答