【问题标题】:SQL: Distinct and OrderBy issueSQL:Distinct 和 OrderBy 问题
【发布时间】:2014-07-04 14:15:32
【问题描述】:

我目前正在处理一个应该返回CONCENTRATOR 表中所有行的查询。但是,它必须可以按所有集中器的列以及部门名称和类型名称进行排序。

这里是集中器的专栏:

CONCENTRATOR_ID
NAME
INTERNALADDRESS
TYPE_ID
DEPARTMENT_ID

TYPE_IDDEPARTMENT_ID 分别链接到 DEPARTMENT 表和 TYPE 表,并且都有一个 NAME 列。

这里是限制:

  • 集中器可按 ID、姓名、地址、类型名称和部门名称排序
  • 不同的部门名称(如果同一个集中器有2个部门,则只返回一行)

要恢复,我需要像 SELECT * 这样的集中器列,还需要 DISTINCT department.name,但看起来很复杂......我尝试了很多请求,但找不到任何一个工作。 有人可以帮帮我吗?

我正在寻找的请求应该是这样的:

SELECT DISTINCT d.NAME as "department.name", t.NAME as "type.name", *
FROM "CONCENTRATOR" c
LEFT OUTER JOIN "CONCENTRATOR_GROUP" USING(CONCENTRATOR_ID)
LEFT OUTER JOIN "GROUP" g USING(GROUP_ID)
LEFT OUTER JOIN "TYPE" t USING(TYPE_ID)
LEFT OUTER JOIN "DEPARTMENT" d USING(DEPARTMENT_ID)
ORDER BY TRIM(UPPER(c.name)) ASC

【问题讨论】:

  • 能否给我们展示一些示例数据,以及示例输出。
  • 您能确认 MS SQL-Server 是 RDBMS 吗?您的联接看起来不像 SQL-Server 语法。
  • 抱歉我的错误,这是 Oracle 而不是 SQL Server,我编辑了标签。关于示例数据,您是在谈论此查询的结果吗?
  • OUTER 关键字是可选的,可以用LEFT JOIN 省略。
  • 您的查询有什么问题 - 好吧,它会出错,因为您使用的是 * 而不是 c.*,而 using() 而不是 on 可能会给您带来问题。也许您可以使用这些表和其中的一些数据设置一个 SQL Fiddle,这是您当前查询的最佳尝试;并在问题中添加您希望该数据的输出是什么。目前很难说出你真正需要什么。如果一个集中器有两个部门,您将如何选择显示哪个部门?

标签: sql oracle sql-order-by distinct


【解决方案1】:

这里有几点需要注意。我真的不喜欢“自然连接”,因为它们只是在我看来掩盖了有用的细节,所以我没有使用它们。我不得不假设表“GROUP”是通过 CONCENTRATOR_GROUP 连接的,以作为缺少细节的示例。

表名“GROUP”不是一个好主意,因为它是一个非常常用的保留字。我不建议使用这样的词作为表名。由于这个“GROUP”被引用(在Oracle中引用对象名称是不正常的,我的经验)。

您谈论“独特”,好像它具有某种我应该凭直觉理解的神奇品质。它没有,我也没有。假设只有 2 个部门也都是“不同的”

DeptX 部门

所以现在让我们假设有 2 个集中器,它们也是“不同的”:

ConcenA 关注B

两个部门都使用了两个集中器,因此我们生成以下查询:

select distinct 
c.name as c_name, d.name as d_name
from concentrators c 
inner join departments d on c.dept_id=d.dept_id 

The result is:

ConcenA DeptX
ConcenB DeptX
ConcenA DeptY
ConcenB DeptY

所有 4 行都是“不同的”

关键是“选择不同”是一个“行运算符”,即它考虑整行来确定该行的任何部分是否与所有其他行不同。没有“选择不同”的微妙之处或选项,它总是以相同的方式工作(在整个行上)。因此,考虑到这一点,我们现在知道“选择不同”根本不是正确的技术(并且由于不同的技术定义,您可能还会感觉到它也不是描述您的问题的好方法) .

因此,由于“选择不同”不是正确的技术,通常人们可以将这些技术作为技术:“group by”或“row_number()” 因为这些确实为我们提供了微妙之处和选择。

现在你还没有解释为什么或如何只选择一个部门(事实上,对我来说,你只选择一个部门听起来很奇怪)但下面我为你提供一种方法这使用 row_number() 并且使用的“微妙”是 ORDER BY,它按字母顺序将数字 1 赋予第一个部门名称,所有其他部门都超过 1;这发生在 每个 CONCENTRATOR_ID 上,因为 row_number() 被该字段“分区”。

    SELECT
      department_name
    , type_name
    , NAME
    , CONCENTRATOR_ID
    , INTERNALADDRESS
    , TYPE_ID
    , DEPARTMENT_ID
FROM (

            SELECT
                  d.NAME                           AS department_name
                , t.NAME                           AS type_name
                , c.CONCENTRATOR_ID
                , c.NAME
                , c.INTERNALADDRESS
                , c.TYPE_ID
                , c.DEPARTMENT_ID
                , ROW_NUMBER() OVER (PARTITION BY c.CONCENTRATOR_ID
                                     ORDER BY d.NAME, t.NAME, c.NAME) AS RN
            FROM CONCENTRATOR c
                  LEFT OUTER JOIN CONCENTRATOR_GROUP cg
                        ON c.CONCENTRATOR_ID = cg.CONCENTRATOR_ID
                  LEFT OUTER JOIN "GROUP" g
                        ON cg.GROUP_ID = g.GROUP_ID
                  LEFT OUTER JOIN TYPE t
                        ON c.TYPE_ID = t.TYPE_ID
                  LEFT OUTER JOIN DEPARTMENT d
                        ON c.DEPARTMENT_ID = c.DEPARTMENT_ID
      ) sq
WHERE RN = 1 /* HERE is where we restrict output to one department per concentrator */
ORDER BY
      NAME ASC
    , CONCENTRATOR_ID
;

我没有理由更改联接的类型,因为您可以看到它们仍保留为左外联接 - 但我怀疑所有或其中一些可能没有正当理由。如果可以,请使用更有效的 INNER JOIN。

【讨论】:

  • 感谢您的完整解释和代码,看到它我自己无法做到这一点。关于保留的作品,您是绝对正确的,但是由于我目前正在开发现有的应用程序,因此我没有机会更改表名。同样,引用的表名实际上真的很不方便,但我不得不这样做。我还按照您的建议将连接更改为 INNER JOIN。无论如何,非常感谢你,我接受了你的回答,因为这工作得很好!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-09-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-29
  • 2020-03-30
  • 1970-01-01
相关资源
最近更新 更多