【问题标题】:Sorting with many to many relationship多对多关系排序
【发布时间】:2015-07-28 01:00:02
【问题描述】:

我有 3 张桌子 person、person_speaks_language 和 language。

  • 人有 80 条记录
  • 语言有 2 条记录

我有以下记录

  • 前 10 人会说一种语言
  • 前 70 人(包括第一组)会说 2 种语言
  • 最后 10 人不会说任何语言

按照我想按语言对人员进行排序的示例,我该如何正确地做到这一点。

我正在尝试使用下面的 SQL 但似乎很奇怪

SELECT "person".*
FROM "person"
  LEFT JOIN "person_speaks_language" ON "person"."id" = "person_speaks_language"."person_id"
  LEFT JOIN "language" ON "person_speaks_language"."language_id" = "language"."id"
ORDER BY "language"."name"
  ASC

数据集

71,Catherine,Porter,male,NULL
72,Isabelle,Sharp,male,NULL
73,Scott,Chandler,male,NULL
74,Jean,Graham,male,NULL
75,Marc,Kennedy,male,NULL
76,Marion,Weaver,male,NULL
77,Melvin,Fitzgerald,male,NULL
78,Catherine,Guerrero,male,NULL
79,Linnie,Strickland,male,NULL
80,Ann,Henderson,male,NULL
11,Daniel,Boyd,female,English
12,Ora,Beck,female,English
13,Hulda,Lloyd,female,English
14,Jessie,McBride,female,English
15,Marguerite,Andrews,female,English
16,Maurice,Hamilton,female,English
17,Cecilia,Rhodes,female,English
18,Owen,Powers,female,English
19,Ivan,Butler,female,English
20,Rose,Bishop,female,English
21,Franklin,Mann,female,English
22,Martha,Hogan,female,English
23,Francis,Oliver,female,English
24,Catherine,Carlson,female,English
25,Rose,Sanchez,female,English
26,Danny,Bryant,female,English
27,Jim,Christensen,female,English
28,Eric,Banks,female,English
29,Tony,Dennis,female,English
30,Roy,Hoffman,female,English
31,Edgar,Hunter,female,English
32,Matilda,Gordon,female,English
33,Randall,Cruz,female,English
34,Allen,Brewer,female,English
35,Iva,Pittman,female,English
36,Garrett,Holland,female,English
37,Johnny,Russell,female,English
38,Nina,Richards,female,English
39,Mary,Ballard,female,English
40,Adrian,Sparks,female,English
41,Evelyn,Santos,female,English
42,Bess,Jackson,female,English
43,Nicholas,Love,female,English
44,Fred,Perkins,female,English
45,Cynthia,Dunn,female,English
46,Alan,Lamb,female,English
47,Ricardo,Sims,female,English
48,Rosie,Rogers,female,English
49,Susan,Sutton,female,English
50,Mary,Boone,female,English
51,Francis,Marshall,male,English
52,Carl,Olson,male,English
53,Mario,Becker,male,English
54,May,Hunt,male,English
55,Sophie,Neal,male,English
56,Frederick,Houston,male,English
57,Edwin,Allison,male,English
58,Florence,Wheeler,male,English
59,Julia,Rogers,male,English
60,Janie,Morgan,male,English
61,Louis,Hubbard,male,English
62,Lida,Wolfe,male,English
63,Alfred,Summers,male,English
64,Lina,Shaw,male,English
65,Landon,Carroll,male,English
66,Lilly,Harper,male,English
67,Lela,Gordon,male,English
68,Nina,Perry,male,English
69,Dean,Perez,male,English
70,Bertie,Hill,male,English
1,Nelle,Gill,female,Spanish
2,Lula,Wright,female,Spanish
3,Anthony,Jensen,female,Spanish
4,Rodney,Alvarez,female,Spanish
5,Scott,Holmes,female,Spanish
6,Daisy,Aguilar,female,Spanish
7,Elijah,Olson,female,Spanish
8,Alma,Henderson,female,Spanish
9,Willie,Barrett,female,Spanish
10,Ada,Huff,female,Spanish
11,Daniel,Boyd,female,Spanish
12,Ora,Beck,female,Spanish
13,Hulda,Lloyd,female,Spanish
14,Jessie,McBride,female,Spanish
15,Marguerite,Andrews,female,Spanish
16,Maurice,Hamilton,female,Spanish
17,Cecilia,Rhodes,female,Spanish
18,Owen,Powers,female,Spanish
19,Ivan,Butler,female,Spanish
20,Rose,Bishop,female,Spanish
21,Franklin,Mann,female,Spanish
22,Martha,Hogan,female,Spanish
23,Francis,Oliver,female,Spanish
24,Catherine,Carlson,female,Spanish
25,Rose,Sanchez,female,Spanish
26,Danny,Bryant,female,Spanish
27,Jim,Christensen,female,Spanish
28,Eric,Banks,female,Spanish
29,Tony,Dennis,female,Spanish
30,Roy,Hoffman,female,Spanish
31,Edgar,Hunter,female,Spanish
32,Matilda,Gordon,female,Spanish
33,Randall,Cruz,female,Spanish
34,Allen,Brewer,female,Spanish
35,Iva,Pittman,female,Spanish
36,Garrett,Holland,female,Spanish
37,Johnny,Russell,female,Spanish
38,Nina,Richards,female,Spanish
39,Mary,Ballard,female,Spanish
40,Adrian,Sparks,female,Spanish
41,Evelyn,Santos,female,Spanish
42,Bess,Jackson,female,Spanish
43,Nicholas,Love,female,Spanish
44,Fred,Perkins,female,Spanish
45,Cynthia,Dunn,female,Spanish
46,Alan,Lamb,female,Spanish
47,Ricardo,Sims,female,Spanish
48,Rosie,Rogers,female,Spanish
49,Susan,Sutton,female,Spanish
50,Mary,Boone,female,Spanish
51,Francis,Marshall,male,Spanish
52,Carl,Olson,male,Spanish
53,Mario,Becker,male,Spanish
54,May,Hunt,male,Spanish
55,Sophie,Neal,male,Spanish
56,Frederick,Houston,male,Spanish
57,Edwin,Allison,male,Spanish
58,Florence,Wheeler,male,Spanish
59,Julia,Rogers,male,Spanish
60,Janie,Morgan,male,Spanish
61,Louis,Hubbard,male,Spanish
62,Lida,Wolfe,male,Spanish
63,Alfred,Summers,male,Spanish
64,Lina,Shaw,male,Spanish
65,Landon,Carroll,male,Spanish
66,Lilly,Harper,male,Spanish
67,Lela,Gordon,male,Spanish
68,Nina,Perry,male,Spanish
69,Dean,Perez,male,Spanish
70,Bertie,Hill,male,Spanish

更新

预期结果是:每个人必须使用语言顺序只出现一次

为了进一步解释这个案例,我将采用一个新的小型数据集,仅使用人员 ID 和语言名称

1,English
2,English
3,English
4,English
19,English
1,Spanish
2,Spanish
3,Spanish
4,Spanish
5,Spanish
14,Spanish
15,Spanish
16,Spanish
19,Spanish
21,Spanish
25,Spanish

我使用相同的顺序,但如果我使用限制,例如 LIMIT 8,结果将是

1,English
2,English
3,English
4,English
19,English
1,Spanish
2,Spanish
3,Spanish

而预期的结果是

1,English
2,English
3,English
4,English
19,English
5,Spanish
14,Spanish
15,Spanish

我想要做什么

我要做的是对可能与 Y 具有多对多关系的 X 列表进行排序、分页和过滤,在这种情况下,X 是一个人,Y 是语言。我需要以一般方式进行。如果我想按一些 Y 属性对列表进行排序,我会遇到麻烦。

列表会这样显示:

firstname, lastname, gender  , languages
Daniel   , Boyd    , female  , English Spanish
Ora      , Beck    , female  , English
Anthony  , Jensen  , female  , Spanish
....

我只需要以正确的顺序返回一个带有 ID 的数组

这是我需要结果只出现一次的主要原因是因为 ORM(我正在使用)尝试对每个结果进行水合,并且如果我使用偏移量和限制对结果进行分页。结果可能不是预期的。我在做多对多关系的假设

我不能使用string_agg或group_concat,因为我不知道真实数据,我不知道是整数还是字符串

【问题讨论】:

  • 您的查询有什么问题?请提供样本 daa 和期望的结果?
  • 奇怪的是什么?
  • 怎么奇怪?!
  • 如果有人说两种语言,他们不应该出现两次吗?那将是我所期望的结果。如果您想按语言对人们进行分类,并且有些人会说多种语言,那不是您想要的吗?
  • @JeffRosenberg 你说的是问题中的数据集正确吗?

标签: sql sqlite postgresql sorting


【解决方案1】:

如果您希望每个人只出现一次,那么您需要按该人进行聚合。然后,如果您想要语言列表,则需要以某种方式将它们组合起来,然后想到连接。

对我来说,使用双引号建议使用 Postgres 或 Oracle。这是 Postgres 的语法:

SELECT p.id, string_agg(l.name) as languages
FROM person p LEFT JOIN 
     person_speaks_language psl
     ON p.id = psl.person_id LEFT JOIN
     language l
     ON psl.language_id = l.id
GROUP BY p.id
ORDER BY COUNT(l.name) DESC, languages;

与string_agg() 类似的功能存在于大多数数据库中。

【讨论】:

  • 我可以使用它来获得预期的结果,但我需要在 sqlite 中工作
  • 在 SQLite 中,您将使用 group_concat() 而不是 string_agg()。
【解决方案2】:

Bertie Hill 出现在两行中并没有错,每行使用一种语言,即关系模型的数据表格视图。不依赖于数据值或数据值的数量。这是完全正确的,没有混淆。

但在这里,要求很混乱,因为您确实需要三个单独的列表:

  • 会说一种语言
  • 会说两种语言[或语言文件中当前的语言数量]
  • 不会说任何语言 [on file] ) ...

但是您希望将这三个列表放在一个列表中。

连接数据值从来都不是一个好主意。它违反了基本标准,特别是 1NF。这可能很常见,但这是一个严重的错误。它可能是由所谓的“理论家”教授的,但它仍然是一个严重的错误。即使在结果集中,是的。

  1. 它会造成混乱,就像我在顶部详述的那样。

  2. 对于串联字符串,随着语言数量的变化,该串联字段的宽度将会增加,并最终超出空间,无论它出现在哪里(例如,屏幕上的字段宽度)。

这只是它不正确、不可扩展、不合标准的众多原因中的两个。

顺便说一句,在您的“数据集”(它不是您的代码生成的结果集)中,性别似乎很好地混淆了。

因此,即使它不受欢迎,答案也是唯一正确的答案是您的代码是正确的(当然可以清理它),并且您必须让用户了解 sub 的危险-标准代码或报告。

  • 您可以按person.name(而不是language.name)排序,然后编写更智能的SQL,这样(例如)person.name 不会在第二行和后续行中为说一种以上语言的人重复,等等。这只是漂亮的印刷品。

Gordon 的回应是,对于那些坚持不合标准的代码终有一天会崩溃的人来说,不回答。

回复评论

在关系模型中:

  • 行没有顺序,这被认为是物理或实现方面,我们无法控制,无论如何都会发生变化,并且我们被警告不要依赖它。如果在输出结果集中寻找顺序,那么我们必须ORDER BY, 这就是它的人生目的。

  • 数据具有含义,并且该含义在关系键中携带。表示不能在代理项(即 ID 列)中进行。

将自己限制在您提供的文件(它们不是表格)中,数据中没有这样的东西:

  • 说一种语言的前 10 人

获得会说一种语言的人很简单,相信你已经明白了:

  SELECT  person.first_name,
          person.last_name
      FROM person P,
      (SELECT person_id
          FROM person_speaks_language
          GROUP BY person_id
          HAVING COUNT(*) = 1          -- change this for 2 languages, etc
          ) AS PL
      WHERE P.person_id = PL.person_id

但是“第一”? “第一”的标准是什么?记录创建日期?

      ORDER BY date_created            -- if it exists in the data

记录 ID 首先没有给出任何信息:随着记录的添加和删除,最初可能存在的任何“顺序”都将完全丢失。

你不能从定义上没有意义的东西中提取意义或赋予意义。如果记录 ID 是相关的,即。如果您打算将其用于某种目的,那么它不是记录 ID,请根据实际名称命名该字段。

我看不到,我不明白,“数据集”和更新的“小数据集”之间的差异的相关性。 “数据集”大小无关紧要,字段标题无关紧要,结果集的含义是相关的。

问题不在于 关系模型 中的某些“限制”,问题在于 (a) 您对数据的固定视图值,以及 (b) 您缺乏了解关系模型是什么,它做了什么,了解它使整个问题消失了,我们只剩下一个简单的 SQL(如标记)“如何做”问题。例如。如果我有一个包含人员和语言的关系数据库,没有 ID 列,那么没有什么是我不能用它做的,没有我不能从数据中生成的报告。

请尝试使用一个示例来传达数据中的含义,以及您正在尝试做的事情。

预期结果是:每个人只能出现一次

它们已经只出现一次(每种语言)

使用语言顺序

嗯,language 文件中没有顺序。我们可以根据数据在结果集中给它一些顺序,无论顺序对您有意义。例如。 language.name. 当然,每种语言都有很多人说,那么您希望language.name 中的顺序是什么? last_name, first_name. 怎么样? 记录 ID 对用户没有意义,所以我不会在结果集中显示它们。 NULL 也是无意义的,并且是模棱两可的,所以我将这里的含义明确。这几乎就是你所拥有的,整理一下:

    SELECT  [language] = CASE name
                WHEN NULL THEN "[None]"
                ELSE name
                END, 
            last_name, 
            first_name
        FROM person P
            LEFT JOIN person_speaks_language PL
                ON P.id = PL.person_id
            LEFT JOIN language L
                ON PL.language_id = L.id
        ORDER BY name, 
                 last_name, 
                 first_name

但是你有:

而预期的结果是

与您的文字描述相矛盾的示例数据:

预期结果是:每个人必须使用语言顺序只出现一次

所以现在,如果我忽略文本,并检查您想要的示例数据

  • (这是一件可怕的事情,因为我加入了你的错误活动,即专注于数据值,而不是理解其含义),

您似乎希望此人只出现一次,句号,无论他们说多少种语言。您的示例数据毫无意义,因此不能要求我复制它。看看这是否有什么意义。

    SELECT  last_name,
            first_name,
            [language] = (                   -- correlated subquery
        SELECT TOP 1                         -- get the "first" language
            CASE name                        -- make meaning of null explicit
                WHEN NULL THEN "[None]"
                ELSE name
                END
            FROM person_speaks_language PL
                JOIN language L
                    ON PL.language_id = L.id
            WHERE P.id = PL.person_id        -- the subject person
            ORDER BY name                    -- id would be meaningless
            )
        FROM person P                        -- vector for person, once
        ORDER BY last_name,
                 first_name

现在,如果您只想要会说某种语言的人(存档):

    SELECT  last_name,
            first_name,
            [language] = (                     -- correlated subquery
        SELECT TOP 1                           -- get the "first" language
                name
            FROM person_speaks_language PL
                JOIN language L
                    ON PL.language_id = L.id
            WHERE P.id = PL.person_id          -- the subject person
            ORDER BY name                      -- id would be meaningless
            )
        FROM person P,
            (
            SELECT  DISTINCT person_id         -- just one occ, thanks
                FROM person_speaks_language PL -- vector for speakers
                ) AS PL_1
        WHERE P.id = PL_1.person_id            -- join them to person fields

在这两种解决方案中,都看不到外连接。 LEFT 或 RIGHT 会让你感到困惑。不要试图“获取一切”,以便您可以“查看”数据值,然后对结果集进行破坏、破解和砍伐,以便从中获得您想要的东西。不,忘记数据值并只从记录归档系统中获取您想要的内容。

对更新的响应

我试图用一个数据集来解释这个案例,我想我让事情变得比实际更难

是的,你做到了。然后查看更新...

  1. 简短的回答是,摆脱 ORM。它没有任何价值:

    • 您可以从直接填充对象的查询中访问 RDB。在胀气的野兽出现之前,我们几十年来一直这样做。特别是如果您了解并实施Open Architecture Standards。

    • 此外,正如所证明的那样,它会产生大量问题。在这里,您正在尝试解决 ORM 的疯狂限制。

    • 分页是一个直截了当的问题,如果您有规范化的数据和关系键。

  2. 长答案是……请阅读this Answer。我相信你会明白你设计应用程序组件的方法,你的窗口设计,将会改变。您的所有查询都将被简化,您只获得特定窗口或对象所需的内容。

    这个问题很可能完全消失(除了可能的分页,你可能需要一个方法)。

那么请仔细考虑那些架构问题,并提出具体的问题。

【讨论】:

  • @rkmax。完毕。您关注的数据值会让您很好地混淆,并且在 ID 和物理方面固定,这是应避免的。我对您要做什么仍然知之甚少,但无论如何我已经在答案中添加了一个部分,希望它能增加一些清晰度并推动讨论。
  • 感谢您完整而全面的解释。我用我的意图更新了这个问题。我试图用一个数据集来解释这个案例,我认为我让事情变得比实际更难
  • @rkmax。是的,你做到了。如果你能待命,我可以在星期四回复。
  • @rkmax。星期四到了。请查看并发表评论。
猜你喜欢
  • 1970-01-01
  • 2016-04-07
  • 2011-08-23
  • 1970-01-01
  • 2011-06-22
  • 1970-01-01
  • 1970-01-01
  • 2014-03-08
  • 1970-01-01
相关资源
最近更新 更多