我可以在SQL查询中使用窗口函数进行分组吗?

时间:2016-11-14 17:25:58

标签: sql oracle oracle11g

我需要员工薪水最低的员工 我是用反连接做的。

     select emp.employee_id,emp.last_name,emp.salary,emp.department_id
     from employees emp
     left join employees sml 
     on sml.department_id = emp.department_id and sml.salary < emp.salary
     where sml.employee_id is null and emp.department_id is not null

但是我被告知可以使用窗口功能使用一个选择来实现。 但是我无法通过department_id对其进行分组并同时使用它。 这是一个错误还是我是愚蠢的?

     SELECT  department_id,
     min(salary) OVER (partition by department_id)  as minsalary
     FROM employees;
     GROUP BY department_id

SQL Developer说00979. 00000 - &#34;不是GROUP BY表达式&#34;

4 个答案:

答案 0 :(得分:4)

如果您在没有group by的情况下运行第二个查询 - 您可能已经尝试过,从您发布的内容中添加分号 - 您将看到每个员工都获得一行,每个都显示他们部门的最低工资。最小值是分析min(),因为它有一个窗口子句。 PARTITION BY相当于GROUP BY,但没有对整个结果集进行聚合。

获得相同结果的最简单方法(几乎)是使用RANK()解析函数,它根据您提供的分区和顺序对值进行排名,同时允许绑定:

SELECT employee_id, last_name, salary, department_id,
  RANK() OVER (PARTITION BY department_id ORDER BY salary) AS rnk
FROM employees
ORDER BY department_id, rnk;

EMPLOYEE_ID LAST_NAME                     SALARY DEPARTMENT_ID        RNK
----------- ------------------------- ---------- ------------- ----------
        200 Whalen                          4400            10          1
        202 Fay                             6000            20          1
        201 Hartstein                      13000            20          2
        119 Colmenares                      2500            30          1
        118 Himuro                          2600            30          2
        117 Tobias                          2800            30          3
        116 Baida                           2900            30          4
        115 Khoo                            3100            30          5
        114 Raphaely                       11000            30          6
...
        102 De Haan                        17000            90          1
        101 Kochhar                        17000            90          1
        100 King                           24000            90          3
...

对于部门20和30,您可以看到排名为1的行是最低薪水。对于90部门,有两名员工排名第一,因为他们的薪水最低。

您可以将其用作内联视图,并仅选择排名为1的行:

SELECT employee_id, last_name, salary, department_id
FROM (
  SELECT employee_id, last_name, salary, department_id,
    RANK() OVER (PARTITION BY department_id ORDER BY salary) AS rnk
  FROM employees
)
WHERE rnk = 1
ORDER BY department_id;

EMPLOYEE_ID LAST_NAME                     SALARY DEPARTMENT_ID
----------- ------------------------- ---------- -------------
        200 Whalen                          4400            10
        202 Fay                             6000            20
        119 Colmenares                      2500            30
        203 Mavris                          6500            40
        132 Olson                           2100            50
        107 Lorentz                         4200            60
        204 Baer                           10000            70
        173 Kumar                           6100            80
        101 Kochhar                        17000            90
        102 De Haan                        17000            90
        113 Popp                            6900           100
        206 Gietz                           8300           110
        178 Grant                           7000              

13 rows selected. 

如果你不必担心关系,那就有一个更简单的选择,但这里不合适。

请注意,这比您的原始查询多一行。您正在加入on sml.department_id = emp.department_id。如果部门ID为空,就像员工178那样,则该连接失败,因为您无法通过相等性测试将null与null进行比较。由于此解决方案没有加入,因此不适用,并且您在结果中看到该员工。

答案 1 :(得分:2)

WITH cte AS (
    SELECT
       emp.*
       ,ROW_NUMBER() OVER (PARTITION BY emp.department_id ORDER BY emp.salary) as RowNumber
    FROM
       employees emp
)

SELECT c.*
FROM
    cte c
WHERE
    c.RowNumber = 1

您可以使用ROW_NUMBER()按部门获得一行最低薪水。如果您希望绑定情况下的所有行都将其切换为RANK()

否则你可以用MIN() OVER来做,但这会给你带来联系

WITH cte AS (
    SELECT
       emp.*
       ,MIN(emp.salary) OVER (PARTITION BY emp.department_id) as DeptMinSalary
    FROM
       employees emp
)

SELECT c.*
FROM
    cte c
WHERE
    c.salary = c.DeptMinSalary

作为派生表而不是公用表表达式:

SELECT t.*
FROM
    (SELECT
       emp.*
       ,ROW_NUMBER() OVER (PARTITION BY emp.department_id ORDER BY emp.salary) as RowNumber
    FROM
       employees emp) t
WHERE
    t.RowNumber = 1

关于这个主题的最后一个想法是因为你问“我可以在带有窗口函数的SQL查询中进行分组吗?” Alex说明PARTITION BY就像是Window Function中的子分组。但是使用带有Window函数的GROUP BY分组意味着将在正在评估的Window函数之前评估GROUP BY结果集。

答案 2 :(得分:2)

要记住的第一件事是窗口函数(如OVER()子句)对查询结果起作用。即:服务器首先执行查询,然后才应用您定义的窗口函数。

这意味着您实际上可以在同一查询中使用窗口函数和group by子句,但需要对其进行封装,如下所示:

     SELECT  department_id,
     min(min(salary)) OVER (partition by department_id)  as minsalary
     FROM employees;
     GROUP BY department_id

但是,我同意这不是使用窗口函数的好地方。 Matt的主张最好在这里(ROW_NUMBER()CTE中的subquery,然后仅在主SELECT中选择所需的行)。

答案 3 :(得分:0)

在这种情况下你不需要窗口函数,因为简单的group by也会起作用。

错误是正确的,因为窗口函数不是聚合函数。 窗口函数不能成为Group by-member。

但你可以使用&#34; distinct&#34;代替。

SELECT DISTINCT department_id,
     min(salary) OVER (partition by department_id)  as minsalary
FROM employees;

在你的特殊情况下,当然这一切都是超大的。但我认为理解是游戏的名称。