---
# System prepended metadata

title: Переделать

---


<style>
    .markdown-body .alert {color: #070707!important;}    body { overflow-x: hidden; color: #312928; background: #f5f3ed url(https://i.imgur.com/B8wy02N.png) top left repeat } :root { --color-primary: #004d40; --color-primary-dark: #1c1c1e; --color-primary-darker: #111; --color-primary-light: #39796b; --color-secondary: #00838f; --color-secondary-dark: #005662; --color-secondary-light: #4fb3bf; --color-accent: #9d76ff; --border-color: #080808; --text-color-light: #8effff; --text-color-bright: #fff; --text-color-dark: #333 } #doc { display: block; width: 100%; margin: 31px auto 31px; background: #f5f3ed url(https://i.imgur.com/S2fW9vg.jpg) top left repeat; border-image-width: 100px; border: 32px solid; border-image: url(https://i.imgur.com/XzF7kg2.png) 38 round round } .tool-bar>a.fa { color: var(--color-primary-light) !important; border: none } .tool-bar>a.fa:hover { color: var(--text-color-bright) !important } .ui-infobar__user-info>ul>li>div>.flex>span { color: var(--text-color-light) !important } .markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4, .markdown-body h5, .markdown-body h6 { font-weight: 500; text-align: left; border: none; text-align: center; font-size: 80px; color: #424242; font-family: cursive } .markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4 {    padding-top: 35px;  text-align: center }} .markdown-body h1 { font-size: 3.7em; text-align: center } .markdown-body h2 { font-size: 1.5em } .markdown-body h3 { font-size: 1.4em } .markdown-body h4 { font-size: 1.3em } .markdown-body h5 { font-size: 1.2em } .markdown-body h6 { font-size: .8em } .markdown-body hr { background-color: var(--color-secondary); margin: 1.25em 0; height: 1.1px } .markdown-body blockquote, .markdown-body blockquote blockquote { border-left-color: var(--color-secondary); color: #999; padding: 0 !important; padding-right: 5em !important } .markdown-body blockquote:before { color: var(--color-secondary) } .markdown-body blockquote small, .markdown-body blockquote i.fa { color: var(--color-accent); opacity: .8 } .markdown-body table { display: table; padding: 1em; margin-left: 1em; width: calc(100% - 2em) } .markdown-body table th, .markdown-body table td, .markdown-body table tr { border: none !important } .markdown-body table tr { background-color: transparent !important; border-bottom: 1px solid rgba(255, 255, 255, .3) !important } .markdown-body .sequence-diagram rect[stroke=none] { fill: #000; opacity: .2; filter: invert(1) } .markdown-body .graphviz polygon { fill: transparent } .markdown-body .vega svg { background-color: transparent !important } .markdown-body .fretboard .svg_wrapper { background-color: transparent } .markdown-body .alert { color: #fff; position: relative; z-index: 1; padding: 0; border: none; font-weight: 500 } .markdown-body p { padding: 0 1em } .markdown-body .alert-success:before, .markdown-body .alert-info:before, .markdown-body .alert-warning:before, .markdown-body .alert-danger:before { content: ' '; position: absolute; z-index: -1; width: 100%; height: 100% } h1:after, h2:after, h3:after, h4:after, h3.md-focus:before, h4.md-focus:before, h3.md-focus:after, h4.md-focus:after { content: ''; display: block; background-repeat: no-repeat; background-position: top center; background-size: auto 80%; width: 100%; z-index: 1; margin: 0; padding: 20px 30px; border: 0 } h1:after, h2:after, h3:after, h4:after { background-image: url("https://lh3.googleusercontent.com/wJsGW_MA3V4aC2clzv0Z0OAVWwdXE11CFoA2LjG-1AavRukDFaCBQU5HqJKe9oi57buAMrsCEkd9XJeIBfGjP4h7HzOKhIjANL94EnNrD8GADHG7I-3GFLdHVq_bF1DJSYCPj9tu5CPzv4Lb4an0Ef93_CRbZ--6QyqFrNWzHz5XL-iRrzbrrA") } table { width: 550px; margin: 24px auto; border-collapse: separate; border-spacing: 4px } tr:nth-child(odd) { background-color: #f0f8ff } tr:nth-child(even) { background-color: #ffb6c1 } td { vertical-align: top; padding: 8px 16px } td:first-child { text-align: center } td:not(:first-child) { text-align: left } #doc pre { background-color: #f4f3ee !important; border-image-width: 100px !important; border: 32px solid !important; border-image: url(https://i.imgur.com/XzF7kg2.png) 38 round round !important } .ui-infobar { margin: 0; max-width: none; width: 100%; height: 80px; display: flex; justify-content: flex-end; align-items: center; box-shadow: 0 1px 10px rgb(0 0 0/20%), 0 1px 1px rgb(0 0 0/20%); box-sizing: border-box; margin-bottom: 100px; position: relative; background: #f5f3ed url(https://i.imgur.com/D3JHgvv.png) top left repeat } .ui-infobar:after { content: ''; position: absolute; z-index: -1; bottom: -300px; display: block; width: 100%; height: 300px; background: url(https://i.imgur.com/kZDzETE.png) no-repeat, url(https://i.imgur.com/visfeOY.png) repeat-x }
    #doc, .toc { z-index: 100; } .container-fluid { padding-right: 0; padding-left: 15px; margin-right: auto; margin-left: auto } #ui-toc-affix { z-index: 1000; font-size: 1.2em } .ui-comment-app { right: 100px !important; top: 100px } .ui-toc-dropdown .nav>li>a { font-size: 25px } .ui-comment-container { margin-left: 55px !important } .ribbon-container { background: 0 0 !important; position: relative; z-index: 1; top: -30px } .ribbon { background: #f6a655 url(http://portfolio.queenofwhisks.com/demo/overlay/noise.png); box-shadow: 2px 6px 12px rgba(0, 0, 0, .5); font-family: Charter, serif; font-size: 16px; line-height: 1; text-align: center; margin: 0 auto; padding: .25em 0; position: relative; width: 86%; top: 80px } .ribbon:before, .ribbon:after { border: 1.5em solid #ee9e4f; content: ''; display: block; position: absolute; bottom: -1em; z-index: -1 } .ribbon:before { left: -2em; border-right-width: 1.5em; border-left-color: transparent; box-shadow: 2px 2px 2px rgba(0, 0, 0, .5) } .ribbon:after { right: -2em; border-left-width: 1.5em; border-right-color: transparent; box-shadow: -2px 2px 2px rgba(0, 0, 0, .5) } .ribbon-content { border-top: .0625em dashed #d5700b; border-bottom: .0625em dashed #d5700b; box-shadow: inset 0px 1px 1px rgba(0, 0, 0, .08), inset 0px -1px 1px rgba(255, 255, 255, .08), 0px -1px 1px rgba(255, 255, 255, .2), 0px 1px 1px rgba(0, 0, 0, .08); display: block; padding: .6875em 0 } .ribbon-content:before, .ribbon-content:after { content: ''; display: block; position: absolute; border-style: solid; border-color: #b86614 transparent transparent; bottom: -1em } .ribbon-content:before { left: 0; border-width: 1em 0 0 1em } .ribbon-content:after { right: 0; border-width: 1em 1em 0 0 } .ribbon a { color: #c2660a; text-decoration: none; text-shadow: 1px 1px 1px rgba(255, 255, 255, .25) }
    body { counter-reset: h1 } h1 { counter-reset: h2 } h2 { counter-reset: h3 } h3 { counter-reset: h4 } h2:before { counter-increment: h2; content: counter(h2) ". " } h3:before { counter-increment: h3; content: counter(h2) "." counter(h3) ". " } h4:before { counter-increment: h4; content: counter(h2) "." counter(h3) "." counter(h4) ". " }
</style>

<style type="text/css">
#wrap {
   width:600px;
   margin:0 auto;
}
.wrap {
    display: -ms-flex;
    display: -webkit-flex;
    display: flex;
    width: 100%;
}

.left_col {
    min-width: 75px;
    flex-grow: 0;
}

.right_col {
    flex-grow: 1;
}
</style>

:::danger
# Переделать

[https://mukeshonplsql.blogspot.com/2015/10/hr-schema-queries.html](https://mukeshonplsql.blogspot.com/2015/10/hr-schema-queries.html)


## Задание

Найти коды  и фамилии сотрудников, суммарный стаж работы которых в компании не менее 4 лет.

```sql
SELECT emp.empno, emp.empname FROM emp
JOIN career c ON emp.empno = c.empno
GROUP BY emp.empno, emp.empname
HAVING sum(MONTHS_BETWEEN(NVL(c.enddate, current_date), c.startdate)) >= 48;
```

## Задание

Найти всех сотрудников (коды и фамилии), ни разу не увольнявшихся из компании.

```sql
SELECT DISTINCT emp.empno, emp.empname FROM emp
JOIN career c ON c.empno = emp.empno
WHERE (SELECT count(*) FROM career
    WHERE c.empno = emp.empno AND enddate IS NOT NULL) = 0;
```

## Задание

Найти фамилии, коды должностей, названия должностей и периоды работы (даты приема и даты увольнения) для бухгалтеров (Accountant) и исполнительных директоров (Executive Director),  работавших или работающих в компании. Для работающих дата увольнения для периода неопределена и при выводе либо отсутсвует, либо определяется как Null.

```sql
SELECT e.empname, j.jobno, j.jobname, career.startdate, career.enddate FROM career
JOIN emp e ON e.empno = career.empno
JOIN job j ON j.jobno = career.jobno
WHERE j.jobname in ('Accountant', 'Executive Director');
```

## Задание

Найти номера и названия отделов, в которых в период с 01.01.2015 по 31.12.2015 работало не более 7 сотрудников.

```sql
SELECT DISTINCT dept.deptid, dept.deptname FROM dept
JOIN career c ON c.deptid = dept.deptid
WHERE (SELECT count(*) FROM career
    WHERE (career.deptid = dept.deptid 
        AND career.startdate <= to_date('31.12.2015','dd-mm-yyyy') AND to_date('01.01.2015','dd-mm-yyyy') <= career.enddate)) <= 7;
```    

## Задание

Найти фамилии работников, никогда не работавших  ни в исследовательском  (Research) отделе, ни в отделе поддержки (Support). 

```sql
SELECT DISTINCT(emp.empname) FROM emp
JOIN career c ON c.empno = emp.empno
JOIN dept d ON d.deptid = c.deptid
WHERE (SELECT count(*) FROM career
    WHERE c.empno = emp.empno AND d.deptname IN ('Research', 'Support')) = 0;
```    

## Задание

Найти количество сотрудников, работавших в двух и более отделах. Если сотрудник работает в настоящее время, то отдел также учитывается.

```sql
SELECT count(DISTINCT(emp.empno)) FROM emp
JOIN career c ON c.empno = emp.empno
WHERE (SELECT count(DISTINCT deptid) FROM career
    WHERE career.empno = emp.empno) >= 2;
```      

## Задание

Найти коды и фамилии сотрудников, работавших только на одной должности. Если сотрудник работает в настоящее время, то должность также учитывается.

```sql
SELECT DISTINCT emp.empno, emp.empname FROM emp
JOIN career c ON c.empno = emp.empno
WHERE (SELECT count(DISTINCT jobno) FROM career
    WHERE career.empno = emp.empno) >= 2;
```   

## Задание

Выполните выборку табельных номеров сотрудников, чьи отделы расположены в Сиэтле (Seattle).

```sql
SELECT e.emp_no
FROM employee e, department d
WHERE e.dept_no = d.dept_no AND d.location = 'Seattle';

SELECT emp_no
FROM employee
WHERE dept_no = (
	SELECT dept_no
	FROM department
	WHERE location = 'Seattle');
```

## Задание

Выполните выборку должностей, занимаемых больше, чем двумя сотрудниками.

```sql
SELECT job
FROM works_on
WHERE job IS NOT NULL
GROUP BY job
HAVING COUNT(*) > 2
```



</br>
:::

</br>

# Легкие задачи

## Задание

Вывести список сотрудников, получающих заработную плату большую чем у непосредственного руководителя

```sql
SELECT  *
FROM Employee AS employees, Employee AS chieves
WHERE chieves.id = employees.chief_id
AND employees.salary > chieves.salary;
```

## Задание

Вывести список сотрудников, получающих максимальную заработную плату в своем отделе

```sql
SELECT  *
FROM Employee AS employees
WHERE employees.salary = (
SELECT  MAX(salary)
FROM Employee AS max
WHERE max.department_id = employees.department_id);
```

## Задание

Вывести список ID отделов, количество сотрудников в которых не превышает 3 человек

```sql
SELECT  department_id
FROM Employee
GROUP BY  department_id
HAVING COUNT(*) <= 3;
```

## Задание

Вывести список сотрудников, не имеющих назначенного руководителя, работающего в том-же отделе

```sql
SELECT  *
FROM Employee AS employees
LEFT JOIN Employee AS chieves
ON (employees.chief_id = chieves.Id AND employees.department_id = chieves.department_id)
WHERE chieves.id IS NULL;
```

## Задание

Вывести список служащих, фактические объемы продаж которых не попадают в диапазон от 80 до 120 процентов плана.

```sql
SELECT  NAME
       ,SALES
       ,QUOTA
FROM salesreps
WHERE SALES not BETWEEN 0.8 * QUOTA AND 1.2 * QUOTA; 
```

## Задание

Сформируйте поле, которое указывает последние 4 цифры номера телефона сотрудника. Уберите в телефоне символ «.». Определите работников с самым длинным email адресом. Сформируйте поле с именем и фамилией сотрудника


**Решение:**

1) Сформируйте поле, которое указывает последние 4 цифры номера телефона сотрудника

```sql
SELECT  substr(PHONE_NUMBER,length(PHONE_NUMBER)-3 )
FROM hr.employees;
```

2) Уберите в телефоне символ «.»


```sql
SELECT REPLACE(phone_number, '.', '') from hr.employees;
-- в случае если подстрока меняется на пустую строку, второй параметр не обязателен
SELECT REPLACE(phone_number, '.') from hr.employees; 
```
3) Определите работников с самым длинным email адресом

```sql
SELECT  *
FROM hr.employees
WHERE length(email) = (
    SELECT  MAX(length(email))
    FROM hr.employees; 
)
```

4) Сформируйте поле с именем и фамилией сотрудника

```sql
SELECT  FIRST_NAME || ' ' || LAST_NAME FROM hr.employees;
```

## Задание

Напишите отчёт, в котором отражена структура отдела, которым руководит Mourgos. 
Выведите фамилии, оклады и номер отдела сотрудников. 

```sql
SELECT last_name, salary, department_id        
FROM employees        
START WITH last_name = 'Mourgos'        
CONNECT BY PRIOR employee_id = manager_id;
```


## Задание

Создайте отчёт, который показывает иерархию менеджеров, 
которым подчиняется сотрудник Lorentz. 
Выведите сначала менеджера, перед которым непосредственно отчитывается Lorentz.

```sql
SELECT last_name        
FROM employees        
WHERE last_name != 'Lorentz'        
START WITH last_name = 'Lorentz'        
CONNECT BY PRIOR manager_id = employee_id;
```

## Задание

Создайте отчёт с отступом, в котором отражается иерархия управления, 
начиная с сотрудника по фамилии Kochhar. 
Выведите фамилии, номера менеджеров и номера отделов сотрудников. 
Назовите столбцы как показано в примере выходных результатов. 

```sql
COLUMN name FORMAT A20
SELECT  LPAD(last_name,LENGTH(last_name)+(LEVEL*2)-2,'_') name
       ,manager_id mgr
       ,department_id deptno
FROM employees START
WITH last_name = 'Kochhar'
CONNECT BY PRIOR employee_id = manager_id / COLUMN name CLEAR
```

## Задание

Создайте отчёт, отражающий иерархию управления компанией. 
Начните с сотрудника самого высокого уровня и исключите из выходных данных всех служащих с идентификатором должности IT_PROG, а также сотрудника De Haan и всех, кто перед ним отчитывается. 

```sql
SELECT  last_name
       ,employee_id
       ,manager_id
FROM employees
WHERE job_id != 'IT_PROG' START 
WITH manager_id IS NULL 
CONNECT BY PRIOR 
    employee_id = manager_id 
    AND last_name != 'De Haan';
```


## Задание

Напишите запрос для вывода фамилий, названия отдела, идентификатора местоположения отдела и  города, в котором он находится, для всех служащих, зарабатывающих комиссионные. 

```sql
SELECT  a.last_name
       ,b.department_name
       ,c.location_id
       ,c.city
FROM employees a
INNER JOIN departments b
    ON a.department_id = b.department_id
INNER JOIN locations c
    ON b.location_id = c.location_id
WHERE a.commission_pct is not null
```

## Задание

Покажите номер отдела с наивысшей средней заработной платой и наименьший оклад работающего в нём сотрудника.

```sql
SELECT  department_id
       ,minsalary
FROM
(
	SELECT  department_id
	       ,AVG(salary)sal
	       ,MIN(salary)minsalary
	FROM employees
	GROUP BY  department_id
)
WHERE sal = (
    SELECT  MAX(AVG(salary))
    FROM employees
    GROUP BY  department_id
)
```


## Задание

Выведите номера , наименования и местоположения отделов, в которых не работают торговые представители (job_id = ‘SA_REP’).

```sql
SELECT  a.department_id
       ,a.department_name
       ,a.manager_id
       ,a.location_id
FROM departments a
WHERE a.department_id not IN (
    SELECT nvl(department_id, 0)
    FROM employees
    WHERE job_id = 'sa_rep'
);
```



## Задание

Создайте запрос для вывода фамилии, номера отдела и оклада всех служащих, чей номер отдела и оклад совпадают с номером отдела и окладом любого служащего, зарабатывающего комиссионные.

```sql
SELECT  last_name
       ,department_id
       ,salary
FROM employees
WHERE (salary, department_id) IN (
    SELECT salary, department_id
    FROM employees
    WHERE commission_pct IS NOT NULL
)
```



## Задание

Выведите фамилию, название отдела и оклад всех служащих,  чей оклад и комиссионные совпадают с окладом и комиссионными любого служащего, работающего в отделе, идентификатор местоположения которого Location_ID = 1700.

```sql
SELECT
    e.last_name,
    d.department_name,
    e.salary
FROM
    employees e,
    departments d
WHERE
    e.department_id = d.department_id
    AND (salary, NVL(commission_pct, 0)) IN (
        SELECT
            salary,
            NVL(commission_pct, 0)
        FROM
            employees e,
            departments d
        WHERE
            e.department_id = d.department_id
            AND d.location_id = 1700
)
```

## Задание

Создайте запрос для вывода фамилии, даты найма и оклада всех служащих,  которые получают такой же оклад и такие же комиссионные, как Kochhar. Не выводите данные о сотруднике Kochhar. 

```sql
SELECT
    last_name,
    hire_date,
    salary
FROM
    employees
WHERE
    (salary, NVL(commission_pct, 0)) IN (
    SELECT
        salary,
        NVL(commission_pct, 0)
    FROM
        employees
    WHERE
        last_name = 'Kochhar'
)
AND last_name != 'Kochhar'
```



## Задание

Выведите фамилию, должность и оклад всех служащих, оклад которых превышает оклад каждого клерка торгового менеджера (JOB_ID = ‘SA_MAN’).
Отсортируйте результаты по убыванию окладов.

```sql
SELECT
    last_name,
    job_id,
    salary
FROM
    employees
WHERE
    salary > ALL (
        SELECT
            salary
        FROM
            employees
        WHERE
            job_id = 'SA_MAN'
    )
ORDER BY
    salary DESC
```



## Задание

Выведите номера, фамилии и отделы служащих, живущих в городах, названия которых начинаются с буквы Т.

```sql
SELECT
    employee_id,
    last_name,
    department_id
FROM
    employees
WHERE
    department_id IN (
    SELECT
        department_id
    FROM
        departments
    WHERE
        location_id IN (
            SELECT
                location_id
            FROM
                locations
            WHERE
                city LIKE 'T%'
        )
)
```



## Задание

Напишите запрос для нахождения всех сотрудников, которые зарабатывают больше среднего оклада по их отделу. 
Выведите фамилию, оклад, номер отдела и средний оклад по отделу. 
Отсортируйте результаты по средней зарплате. Используйте псевдонимы для выбираемых столбцов.

```sql
SELECT
    ename,
    salary,
    deptno,
    dept_avg
FROM
    (
        SELECT
            e.last_name ename,
            e.salary salary,
            e.department_id deptno,
            round(AVG(a.salary)) dept_avg
        FROM
            employees e,
            employees a
        WHERE
            e.department_id = a.department_id
            AND e.salary > (
                SELECT
                    AVG(salary)
                FROM
                    employees
                WHERE
                    department_id = e.department_id
            )
        GROUP BY
            e.last_name,
            e.salary,
            e.department_id
    )
ORDER BY
    dept_avg
```

## Задание

Найдите всех сотрудников, не являющихся руководителями. 
Выполните это с помощью оператора NOT EXIST.

```sql
SELECT   *
FROM     employees
ORDER BY manager_id
SELECT   a.last_name,
         a.employee_id,
         a.manager_id
FROM
         employees a
WHERE
    NOT EXISTS (
        SELECT  *
        FROM    employees b
        WHERE   b.manager_id = a.employee_id
    )
```
Может ли это же быть сделано с помощью оператора NOT IN?

```sql
SELECT
    a.last_name,
    a.employee_id,
    a.manager_id
FROM
    employees a
WHERE
    a.employee_id not IN (
        SELECT
            b.manager_id
        FROM
            employees b
        WHERE
            b.manager_id = a.employee_id
    )
```


## Задание

Выведите фамилии сотрудников, зарабатывающих меньше среднего оклада по их отделу. 

```sql
SELECT  last_name
FROM employees a
WHERE a.salary < (
    SELECT  AVG(b.salary)
    FROM employees b
    WHERE b.department_id = a.department_id
)
```

## Задание

Выведите фамилии сотрудников, у которых есть коллеги по отделу, 
которые были приняты на работу позже, но имеют более высокий оклад.

```sql
SELECT  last_name
FROM employees a
WHERE exists (
    SELECT  *
    FROM employees b
    WHERE b.department_id = a.department_id
    AND b.hire_date > a.hire_date
    AND b.salary > a.salary
) 
```


## Задание

Найти список ID отделов с максимальной суммарной зарплатой сотрудников

```sql
WITH dep_salary AS
(
	SELECT  department_id
	       ,SUM(salary) AS salary
	FROM employee
	GROUP BY  department_id
)
SELECT  department_id
FROM dep_salary
WHERE dep_salary.salary = (
    SELECT  MAX(salary)
    FROM dep_salary
);
```

## Задание

Вывести список служащих, фактические объемы продаж которых не попадают в диапазон от 80 до 120 процентов плана.

```sql
SELECT  NAME
       ,SALES
       ,QUOTA
FROM salesreps
WHERE SALES not BETWEEN 0.8 * QUOTA AND 1.2 * QUOTA; 
```

## Задание

Сформируйте поле, которое указывает последние 4 цифры номера телефона сотрудника. Уберите в телефоне символ «.». Определите работников с самым длинным email адресом. Сформируйте поле с именем и фамилией сотрудника


**Решение:**

1) Сформируйте поле, которое указывает последние 4 цифры номера телефона сотрудника

```sql
SELECT  substr(PHONE_NUMBER,length(PHONE_NUMBER)-3 )
FROM hr.employees;
```

2) Уберите в телефоне символ «.»


```sql
SELECT REPLACE(phone_number, '.', '') from hr.employees;
-- в случае если подстрока меняется на пустую строку, второй параметр не обязателен
SELECT REPLACE(phone_number, '.') from hr.employees; 
```
3) Определите работников с самым длинным email адресом

```sql
SELECT  *
FROM hr.employees
WHERE length(email) = (
    SELECT  MAX(length(email))
    FROM hr.employees; 
)
```

4) Сформируйте поле с именем и фамилией сотрудника

```sql
SELECT  FIRST_NAME || ' ' || LAST_NAME FROM hr.employees;
```

## Задание

Напишите отчёт, в котором отражена структура отдела, которым руководит Mourgos. 
Выведите фамилии, оклады и номер отдела сотрудников. 

```sql
SELECT last_name, salary, department_id        
FROM employees        
START WITH last_name = 'Mourgos'        
CONNECT BY PRIOR employee_id = manager_id;
```


## Задание

Создайте отчёт, который показывает иерархию менеджеров, 
которым подчиняется сотрудник Lorentz. 
Выведите сначала менеджера, перед которым непосредственно отчитывается Lorentz.

```sql
SELECT last_name        
FROM employees        
WHERE last_name != 'Lorentz'        
START WITH last_name = 'Lorentz'        
CONNECT BY PRIOR manager_id = employee_id;
```

## Задание

Создайте отчёт с отступом, в котором отражается иерархия управления, 
начиная с сотрудника по фамилии Kochhar. 
Выведите фамилии, номера менеджеров и номера отделов сотрудников. 
Назовите столбцы как показано в примере выходных результатов. 

```sql
COLUMN name FORMAT A20
SELECT  LPAD(last_name,LENGTH(last_name)+(LEVEL*2)-2,'_') name
       ,manager_id mgr
       ,department_id deptno
FROM employees START
WITH last_name = 'Kochhar'
CONNECT BY PRIOR employee_id = manager_id / COLUMN name CLEAR
```

## Задание

Создайте отчёт, отражающий иерархию управления компанией. 
Начните с сотрудника самого высокого уровня и исключите из выходных данных всех служащих с идентификатором должности IT_PROG, а также сотрудника De Haan и всех, кто перед ним отчитывается. 

```sql
SELECT  last_name
       ,employee_id
       ,manager_id
FROM employees
WHERE job_id != 'IT_PROG' START 
WITH manager_id IS NULL 
CONNECT BY PRIOR 
    employee_id = manager_id 
    AND last_name != 'De Haan';
```


## Задание

Напишите запрос для вывода фамилий, названия отдела, идентификатора местоположения отдела и  города, в котором он находится, для всех служащих, зарабатывающих комиссионные. 

```sql
SELECT  a.last_name
       ,b.department_name
       ,c.location_id
       ,c.city
FROM employees a
INNER JOIN departments b
    ON a.department_id = b.department_id
INNER JOIN locations c
    ON b.location_id = c.location_id
WHERE a.commission_pct is not null
```

## Задание

Покажите номер отдела с наивысшей средней заработной платой и наименьший оклад работающего в нём сотрудника.

```sql
SELECT  department_id
       ,minsalary
FROM
(
	SELECT  department_id
	       ,AVG(salary)sal
	       ,MIN(salary)minsalary
	FROM employees
	GROUP BY  department_id
)
WHERE sal = (
    SELECT  MAX(AVG(salary))
    FROM employees
    GROUP BY  department_id
)
```


## Задание

Выведите номера , наименования и местоположения отделов, в которых не работают торговые представители (job_id = ‘SA_REP’).

```sql
SELECT  a.department_id
       ,a.department_name
       ,a.manager_id
       ,a.location_id
FROM departments a
WHERE a.department_id not IN (
    SELECT nvl(department_id, 0)
    FROM employees
    WHERE job_id = 'sa_rep'
);
```



## Задание

Создайте запрос для вывода фамилии, номера отдела и оклада всех служащих, чей номер отдела и оклад совпадают с номером отдела и окладом любого служащего, зарабатывающего комиссионные.

```sql
SELECT  last_name
       ,department_id
       ,salary
FROM employees
WHERE (salary, department_id) IN (
    SELECT salary, department_id
    FROM employees
    WHERE commission_pct IS NOT NULL
)
```



## Задание

Выведите фамилию, название отдела и оклад всех служащих,  чей оклад и комиссионные совпадают с окладом и комиссионными любого служащего, работающего в отделе, идентификатор местоположения которого Location_ID = 1700.

```sql
SELECT
    e.last_name,
    d.department_name,
    e.salary
FROM
    employees e,
    departments d
WHERE
    e.department_id = d.department_id
    AND (salary, NVL(commission_pct, 0)) IN (
        SELECT
            salary,
            NVL(commission_pct, 0)
        FROM
            employees e,
            departments d
        WHERE
            e.department_id = d.department_id
            AND d.location_id = 1700
)
```

## Задание

Создайте запрос для вывода фамилии, даты найма и оклада всех служащих,  которые получают такой же оклад и такие же комиссионные, как Kochhar. Не выводите данные о сотруднике Kochhar. 

```sql
SELECT
    last_name,
    hire_date,
    salary
FROM
    employees
WHERE
    (salary, NVL(commission_pct, 0)) IN (
    SELECT
        salary,
        NVL(commission_pct, 0)
    FROM
        employees
    WHERE
        last_name = 'Kochhar'
)
AND last_name != 'Kochhar'
```



## Задание

Выведите фамилию, должность и оклад всех служащих, оклад которых превышает оклад каждого клерка торгового менеджера (JOB_ID = ‘SA_MAN’).
Отсортируйте результаты по убыванию окладов.

```sql
SELECT
    last_name,
    job_id,
    salary
FROM
    employees
WHERE
    salary > ALL (
        SELECT
            salary
        FROM
            employees
        WHERE
            job_id = 'SA_MAN'
    )
ORDER BY
    salary DESC
```



## Задание

Выведите номера, фамилии и отделы служащих, живущих в городах, названия которых начинаются с буквы Т.

```sql
SELECT
    employee_id,
    last_name,
    department_id
FROM
    employees
WHERE
    department_id IN (
    SELECT
        department_id
    FROM
        departments
    WHERE
        location_id IN (
            SELECT
                location_id
            FROM
                locations
            WHERE
                city LIKE 'T%'
        )
)
```



## Задание

Напишите запрос для нахождения всех сотрудников, которые зарабатывают больше среднего оклада по их отделу. 
Выведите фамилию, оклад, номер отдела и средний оклад по отделу. 
Отсортируйте результаты по средней зарплате. Используйте псевдонимы для выбираемых столбцов.

```sql
SELECT
    ename,
    salary,
    deptno,
    dept_avg
FROM
    (
        SELECT
            e.last_name ename,
            e.salary salary,
            e.department_id deptno,
            round(AVG(a.salary)) dept_avg
        FROM
            employees e,
            employees a
        WHERE
            e.department_id = a.department_id
            AND e.salary > (
                SELECT
                    AVG(salary)
                FROM
                    employees
                WHERE
                    department_id = e.department_id
            )
        GROUP BY
            e.last_name,
            e.salary,
            e.department_id
    )
ORDER BY
    dept_avg
```

## Задание

Найдите всех сотрудников, не являющихся руководителями. 
Выполните это с помощью оператора NOT EXIST.

```sql
SELECT   *
FROM     employees
ORDER BY manager_id
SELECT   a.last_name,
         a.employee_id,
         a.manager_id
FROM
         employees a
WHERE
    NOT EXISTS (
        SELECT  *
        FROM    employees b
        WHERE   b.manager_id = a.employee_id
    )
```
Может ли это же быть сделано с помощью оператора NOT IN?

```sql
SELECT
    a.last_name,
    a.employee_id,
    a.manager_id
FROM
    employees a
WHERE
    a.employee_id not IN (
        SELECT
            b.manager_id
        FROM
            employees b
        WHERE
            b.manager_id = a.employee_id
    )
```


## Задание

Выведите фамилии сотрудников, зарабатывающих меньше среднего оклада по их отделу. 

```sql
SELECT  last_name
FROM employees a
WHERE a.salary < (
    SELECT  AVG(b.salary)
    FROM employees b
    WHERE b.department_id = a.department_id
)
```

## Задание

Выведите фамилии сотрудников, у которых есть коллеги по отделу, 
которые были приняты на работу позже, но имеют более высокий оклад.

```sql
SELECT  last_name
FROM employees a
WHERE exists (
    SELECT  *
    FROM employees b
    WHERE b.department_id = a.department_id
    AND b.hire_date > a.hire_date
    AND b.salary > a.salary
) 
```


## Задание

Выведите номера, фамилии и наименования отделов всех сотрудников. 
Используйте скалярный подзапрос в команде SELECT для вывода наименований отделов.

```sql
SELECT  a.employee_id
       ,a.last_name
       ,(
SELECT  b.department_name
FROM departments b
WHERE a.department_id = b.department_id) department
FROM employees a
ORDER BY department
```

 
# Нетривиальные задачи


## Поиск по шаблону
Из служащих отделов 10 и 20 требуется выбрать только тех, в имени которых встречается буква «A» или чье название должности заканчивается на «ER»

**Решение:**


<div class="wrap">
<div class="left_col">
       
```sql
SELECT
    first_name,
    job_id
FROM
     hr.employees
WHERE
    department_id IN (30, 50)
    AND (
        first_name like '%A%'
        or job_id like '%ER'
    )
```
    
</div>
 <div class="right_col">
     
Результат:

<img src="https://i.imgur.com/MmXR5Sk.png">

</div>
</div>


##  Сотрудники, работающие в компании дольше руководителя подразделения

Используются таблицы стандартной демо-схемы HR.

Одной командой SELECT вывести всех сотрудников, которые приняты на работу 
раньше руководителя подразделения компании, к которому они приписаны в данный 
момент.

В результате вывести следующие данные:
* Название подразделения компании
* Имя сотрудника
* Фамилия сотрудника
* На сколько суток раньше руководителя данный сотрудник был принят на работу


**Предлагаемые решения:**

* Решение через соединение таблиц

```sql
SELECT d.department_name, e1.first_name, e1.last_name, TO_NUMBER(e2.hire_date - e1.hire_date) days
FROM employees e1, departments d, employees e2
WHERE e1.department_id = d.department_id
  AND d.manager_id = e2.employee_id
  AND TO_NUMBER(e2.hire_date - e1.hire_date) > 0
```




##  Чаще всего встречающиеся имена


Используются таблицы стандартной демо-схемы HR.

Одной командой SELECT вывести наиболее часто встречающтеся имена сотрудников.

Если несколько имен встречаются чаще всего, то вывести все эти имена.


**Предлагаемые решения:**
* Решение с использованием виртуальных таблиц и аналитических функций

```sql
WITH t AS (
  SELECT DISTINCT e.first_name, COUNT(1) OVER (PARTITION BY e.first_name) cnt
  FROM employees e
), t2 AS (
  SELECT first_name, cnt, MAX(cnt) OVER () max_cnt
  FROM t 
)
SELECT first_name 
FROM t2 
WHERE cnt = max_cnt
```

##  Выбор сотрудников, менявших должность без записи в историю

Одной командой SELECT выбрать сотрудников, для которых
* в таблице истории занятия должностей зафиксированы переходы на другую должность 
* в таблице истории занятия должностей отсутствуют сведения об их первой должности, которую они занимали при приеме на работу в компанию. 

В результат вывести: 
* идентификатор сотрудника, 
* фамилию сотрудника, 
* дата, когда сотрудник был принят на работу, 
* идентификатор должности, которую занимает сотрудник, 
* идентификатор подразделения, к которому в данный момент приписан сотрудник. 

Результат отсортировать по возрастанию по полям: 
* дата, когда сотрудник был принят на работу, 
* фамилия сотрудника.


**Предлагаемые решения:**
* Решение с использованием групповых сравнений и вложенных подзапросов



```sql
SELECT e.employee_id, e.last_name, e.hire_date, e.job_id, e.department_id  
FROM hr.employees e
WHERE e.employee_id = ANY(SELECT j.employee_id FROM hr.job_history j)
AND (e.employee_id, e.hire_date) <> ALL(SELECT j.employee_id, j.start_date FROM hr.job_history j)
ORDER BY e.last_name ASC, e.hire_date ASC
```

* Решение с использованием виртуальной таблицы и соединением с историей занимаемых должностей

```sql
WITH emp AS (
  SELECT e.employee_id, e.hire_date, e.last_name, e.job_id, e.department_id 
  FROM employees e, job_history j
  WHERE j.employee_id = e.employee_id
)
SELECT distinct emp.employee_id, emp.last_name, emp.hire_date, emp.job_id, emp.department_id 
FROM emp, job_history j
WHERE emp.employee_id = j.employee_id (+)
AND emp.hire_date = j.start_date (+)
AND j.employee_id IS NULL
ORDER BY emp.last_name ASC, emp.hire_date ASC
```

* Решение с использованием EXISTS, функций аггрегирования и вложенного подзапроса

```sql
SELECT
    e.employee_id,
    e.last_name,
    e.hire_date,
    e.job_id,
    e.department_id
FROM
    employees e
WHERE
    EXISTS (
        SELECT
            1
        FROM
            job_history jj
        WHERE
            jj.employee_id = e.employee_id
        GROUP BY
            jj.employee_id
        HAVING
            MIN(start_date) <> e.hire_date
    )
ORDER BY
    e.last_name ASC,
    e.hire_date ASC
```

* Решение с использованием одностороннего соединения таблиц и аггрегирующих функций

```sql
SELECT
    jh.employee_id,
    e.last_name,
    e.hire_date,
    e.job_id,
    e.department_id
FROM
    job_history jh
    LEFT JOIN employees e ON jh.employee_id = e.employee_id
GROUP BY
    (
        jh.employee_id,
        e.last_name,
        e.hire_date,
        e.job_id,
        e.department_id
    )
HAVING
    MIN(start_date) <> hire_date
ORDER BY
    e.last_name ASC,
    e.hire_date ASC
```

## Оконная функция

Вывести должности и подсчитать количество сотрудников на каждой должности в каждом отделе. Столбик назвать «EmpCount».

<center>
Результат:

![](https://i.imgur.com/BdQjsb8.png)
</center>

```sql
SELECT
    DISTINCT department_id,
    job_id,
    COUNT(employee_id) OVER(PARTITION BY department_id, job_id) AS "EmpCount"
FROM
    HR.employees
ORDER BY 1;
```

## Сбор информации из последующих строк

Требуется найти каждого служащего, который зарабатывает меньше, чем служащий, принятый на работу сразу после него. Исходя из следующего результирующего множества:

**Предлагаемые решения:**

1. С помощью подзапросов определите для каждого служащего:
  - Дату приема на работу следующего служащего, имеющего более высокую  заработную плату.
  - Дату приема на работу следующего служащего.<br> 
  Если эти две даты совпадают, мы нашли то, что искали:
       
```sql
SELECT  last_name
       ,salary
       ,hire_date
FROM
(
  SELECT
      a.last_name,
      a.salary,
      a.hire_date,
      (
          SELECT MIN(hire_date)
          FROM   hr.employees b
          WHERE  b.hire_date > a.hire_date
                 AND b.salary > a.salary
      ) AS next_salary_grtr,
      (
          SELECT MIN(hire_date)
          FROM   hr.employees b
          WHERE  b.hire_date > a.hire_date
      ) AS next_hire
  FROM hr.employees a
) x
WHERE next_salary_grtr = next_hire
```
    
 <center>
     
Результат:
    
![](https://i.imgur.com/14Iyz9s.png)
     
</center>

Для каждого служащего скалярные подзапросы возвращают значение HIREDATE служащего, принятого на работу сразу после него, и HIREDATE первого служащего из тех, кто был принят на работу позже, но зарабатывает больше рассматриваемого служащего. Рассмотрим исходные данные:

```sql
SELECT
    a.last_name,
    a.salary,
    a.hire_date,
    (
        SELECT MIN(hire_date)
        FROM   hr.employees b
        WHERE  b.hire_date > a.hire_date
               AND b.salary > a.salary
    ) AS next_salary_grtr,
    (
        SELECT MIN(hire_date)
        FROM   hr.employees b
        WHERE  b.hire_date > a.hire_date
    ) AS next_hire
FROM hr.employees a
```

    
 <center>
     
Результат:

![](https://i.imgur.com/efLuBLm.jpg)
    
</center>

Не обязательно, чтобы те, кто был принят на работу позже, был принят сразу после рассматриваемого служащего. Следующий (и последний) шаг –выбрать только те строки, в которых значение NEXT_SAL_GRTR (наименьшее значение HIREDATE среди служащих, зарабатывающих больше рассматриваемого служащего) равно NEXT_HIRE (ближайшее последующее значение HIREDATE относительно HIREDATE текущего служащего).

2. С помощью оконной функции LEAD OVER можно получить заработную плату служащего, принятого на работу следующим после рассматриваемого служащего. Затем остается просто сравнить их заработные платы:

```sql
SELECT
    last_name,
    salary,
    hire_date
FROM(
  SELECT
      last_name,
      salary,
      hire_date,
      lead(salary) over(
          order by
              hire_date
      ) next_salary
  FROM
      hr.employees
)
WHERE
    salary < next_salary
```
Оконная функция  LEAD OVER идеально подходит для решения задач такого типа. Она принимает аргумент, определяющий, на сколько строк вперед она должна «заглянуть» (по умолчанию аргумент равен 1). Возможность «перепрыгивать» через несколько строк важна в случае присутствия дублирующихся значений в столбце, по которому осуществляется упорядочивание. Следующий пример показывает, как просто с помощью LEAD OVER получить заработную плату служащего, принятого на работу «следующим»:


**Предлагаемые решения:**

<div class="wrap">
<div class="left_col">
       
```sql
SELECT
    last_name,
    salary,
    hire_date,
    lead(salary) over(
        order by hire_date
    ) next_salary
FROM
  hr.employees
```
    
</div>
 <div class="right_col">
     
Результат:
![](https://i.imgur.com/kiA14Yw.png)

     
</div>
</div>

Заключительный шаг – выбрать только те строки, в которых значение SAL меньше NEXT_SAL. Поскольку по умолчанию областью действия LEAD OVER является одна строка, в случае присутствия дубликатов в таблице EMP, в частности, если несколько служащих были приняты на работу в один день, сравниваться будут их SAL. Это может не соответствовать требуемому поведению. Если стоит задача сравнивать SAL каждого служащего с SAL служащего, который был принят на работу следующим, но не в один день с рассматриваемым служащим, в качестве альтернативы можно использовать следующее решение:

```sql
SELECT
    last_name,
    salary,
    hire_date
FROM(
    SELECT
        last_name,
        salary,
        hire_date,
        lead(salary, cnt - rn + 1) over(
            order by hire_date
        ) next_salary
    FROM (
        SELECT
            last_name,
            salary,
            hire_date,
            COUNT(*) over(partition by hire_date) cnt,
            row_number() over(
                partition by hire_date
                ORDER BY employee_id
            ) rn
        FROM hr.employees
      )
) WHERE salary < next_salary
```

<center>
Результат:

![](https://i.imgur.com/lNzwiLB.png)

</center>

Основная идея данного решения – найти, на сколько строк текущая строка отстоит от строки, с которой должна сравниваться. Например, если имеется пять дубликатов, первый из пяти должен сравниваться со строкой, отстоящей от него на пять строк. Значение CNT для каждого служащего с дублирующимся значением HIREDATE представляет общее количество дубликатов данного HIREDATE. Значение RN представляет ранг служащего 10!го отдела (DEPTNO 10). Ранги подразделяются по HIREDATE, поэтому только для служащих с дублирующимися HIREDATE это значение может быть больше 1. Сортируются
ранги по EMPNO (это выбрано произвольно). Теперь, когда известно общее количество дублирующихся значений и для каждого их них определен ранг, чтобы найти расстояние до следующего значения HIREDATE, необходимо просто из общего числа дубликатов вычесть ранг текущего и прибавить единицу (CNT – RN + 1).


## Ход конем

Требуется получить множество, содержащее имя каждого служащего, отдел, в котором он работает, его заработную плату, дату его приема на работу и заработную плату сотрудника, принятого на работу последним в отделе. Должно быть получено следующее результирующее множество:

 
<center>

![](https://i.imgur.com/uD4Xkdo.png)
    
</center>

**Предлагаемые решения:**

1) Чтобы найти для каждого отдела значение SAL служащего, принятого на работу последним, используем в подзапросе выражение CASE. Для всех остальных заработных плат возвращаем нуль. С помощью оконной функции MAX OVER во внешнем запросе получаем значения SAL, не равные нулю, для отдела каждого служащего:

```sql
SELECT  department_id
       ,last_name
       ,salary
       ,hire_date
       ,MAX(latest_salary)over(partition by department_id) latest_salary
FROM
(
	SELECT  department_id
	       ,last_name
	       ,salary
	       ,hire_date
	       ,CASE
                WHEN hire_date = MAX(hire_date)over(partition by department_id)
             THEN salary  ELSE 0
            END latest_salary
	FROM hr.employees
) x
ORDER BY 1, 4 DESC
```

Первый шаг – использовать оконную функцию MAX OVER в выражении CASE, чтобы найти в каждом отделе служащего, который был принят на работу самым последним или позже всех. Если значение HIREDATE служащего совпадает со значением, возвращенным MAX OVER, с помощью выражения CASE возвращаем значение SAL этого служащего; в противном случае возвращаем 0. Результаты показаны ниже:

```sql
SELECT  department_id
        ,last_name
        ,salary
        ,hire_date
        ,CASE WHEN hire_date = MAX(hire_date)over(partition by department_id) THEN salary  ELSE 0 END latest_salary
FROM hr.employees
```
<center>

![](https://i.imgur.com/qPOCCLT.png)
 
</center>

Поскольку LATEST_SAL будет содержать 0 или SAL служащих, принятых на работу последними, приведенный выше запрос можно поместить во вложенный запрос и опять применить MAX OVER, но на этот раз, чтобы получить наибольшее отличное от нуля значение LATEST_SAL для каждого DEPTNO.


2) Используем скалярный подзапрос с двумя уровнями вложенности.  Сначала находим для всех отделов HIREDATE служащего, принятого на работу последним. Затем используем агрегатную функцию MAX (на случай дублирования значений), чтобы выбрать значение SAL каждого такого служащего:

```sql
SELECT
    e.department_id,
    e.last_name,
    e.salary,
    e.hire_date,
(   SELECT
            MAX(d.salary)
        FROM
            hr.employees d
        WHERE
            d.department_id = e.department_id
            AND d.hire_date = (
                SELECT
                    MAX(f.hire_date)
                FROM
                    hr.employees f
                WHERE
                    f.department_id = e.department_id
            )
    ) AS latest_salary
FROM
    hr.employees e
ORDER BY 1, 4 desc
```
Первый шаг – использовать скалярный подзапрос, чтобы найти для каждого отдела HIREDATE служащего, принятого на работу самым последним:

```sql
SELECT
    e.department_id,
    e.last_name,
    e.salary,
    e.hire_date,
(   SELECT
        MAX(f.hire_date)
    FROM
        hr.employees f
    WHERE
        f.department_id = e.department_id
) AS last_hire
FROM
    hr.employees e
ORDER BY 1, 4 desc
```

<center>

![](https://i.imgur.com/PQtJPZC.png)
 
</center>

Следующий шаг – найти в каждом отделе SAL служащих, дата приема на работу которых совпадает с датой LAST_HIRE. С помощью агрегатной функции MAX выбираем наибольшее значение SAL (если в один день было нанято несколько служащих).

## Вычисление простых подсумм

В данном рецепте под «простой подсуммой» подразумевается резуль!
тирующее множество, содержащее значения, полученные в результа!
те агрегации одного столбца, и общую сумму таблицы. В качестве при!
мера возьмем результирующее множество, содержащее суммы зара!
ботных плат таблицы EMP по должностям (JOB), а также сумму всех
заработных плат таблицы EMP. Суммы зарплат по JOB – это подсум!
мы, а сумма всех заработных плат таблицы EMP – это общая сумма.
Такое результирующее множество выглядело бы так

| JOB_TITLE                | SALARYARY |
|--------------------------|-----------|
| Accountant               | 39600     |
| Accounting  Manager      | 12008     |
| Administration Assistant | 4400      |
| Purchasing Manager       | 11000     |
| ...                      | ...       |
| Sales  Manager           | 61000     |
| TOTAL                    | 691416    |

**Предлагаемые решения:**

1) Используя агрегатную функцию SUM, просуммируйте заработные платы; с помощью расширения ROLLUP оператора GROUP BY организуйте результаты в подсуммы (по JOB) и найдите общую сумму (для всей таблицы):
   
```sql
SELECT
    case
        grouping(job_title)
        WHEN 0 THEN job_title else 'TOTAL'
    end job_title,
    SUM(salary) salary_sum
FROM hr.employees
JOIN hr.jobs USING (job_id)
GROUP BY rollup(job_title) 
```

2) Используя агрегатную функцию SUM, просуммируйте заработные платы по DEPTNO. Затем посредством оператора UNION ALL объедините этот запрос с запросом, вычисляющим сумму всех заработных плат таблицы:

```sql
SELECT
    job_title,
    SUM(salary) AS salary
FROM
    hr.employees
    JOIN hr.jobs USING (job_id)
GROUP BY
    job_title
UNION ALL
SELECT
    'TOTAL', SUM(salary)
FROM
    hr.employees
    JOIN hr.jobs USING (job_id)
```


##  Изощренно отсортированный список сотрудников по подразделениям компании


Используются телицы стандартной демо-схемы HR.

Одной командой select вывести отсортированный список сотрудников компании.

В результат вывести 8 (восемь) столбцов:
1) идентификатор сотрудника
2) Фамилию сотрудника
3) имя сотрудника
4) название подразделения компании, к которому приписан сотрудник
5) Дату найма сотрудника
6) е-mail сотрудника
7) оклад, установленный сотруднику
8) номер телефона

Cведения о сотрудникам, для которых неизвестно к какому подразделению компании они приписаны, выводить не нужно.
Результат отсортировать:

1) по названию подразделения компании (по возрастанию)
2) внутри каждого подразделения сотрудников вывести в порядке:
    1)  с окладом менее 4000, отсортированных по дате найма (.по возрастанию),
    2)  с окладом не менее 4000, но менее 8000, отсортированных по e-mail (по убыванию)
    3)  с окладом не менее 8000, но менее 12000, отсортированных по номеру телефона (по убыванию, NULL-значения в конце)
    4)  с окладом не менее 12000 или с неизвестным окладом, отсортированных по величине оклада (по возрастанию, NULL-значения в конце)

3) по идентификатору сотрудника (по возрастанию)


**Предлагаемые решения:**

* Решение с использованием оператора CASE

```sql
SELECT employee_id, last_name, first_name, department_name, hire_date, email, salary, phone_number
FROM t1
ORDER BY department_name, 
CASE
  WHEN salary < 4000 THEN sort1
  WHEN salary >= 4000 AND salary < 8000 THEN sort2
  WHEN salary >= 8000 AND salary < 12000 THEN sort3
  WHEN salary >= 12000 THEN sort4          
END
```

##  Выбор сотрудников с минимальным окладом


Выбрать сотрудников с минимальным окладом по подразделению,
* сотрудников не приписанных к подразделению выводить не нужно,
* нельзя использовать:
   * подзапросы, в том числе во фразе FROM 
   * конструкции CONNECT  BY, START WITH, GROUP BY, WITH
   * аналитические функции


**Предлагаемые решения:**
* Решение через одностороннее соединение таблиц

```sql
SELECT e1.department_id, e1.last_name,e1.salary 
FROM employees e1, employees e2
WHERE e1.salary > e2.salary (+)
  AND e1.department_id = e2.department_id (+) 
  AND e2.employee_id IS NULL 
  AND e1.department_id IS NOT NULL
ORDER BY e1.department_id, e1.last_name, e1.salary, e1.employee_id
```

## Вычисление моды

Требуется найти моду (мода - это наиболее часто встречающийся элемент рассматриваемого множества данных) столбца значений. Например, поставлена задача найти моду заработных плат 60го отдела (DEPTNO 60). Для следующего набора заработных плат:



<div class="wrap">
<div class="left_col">
       
```sql
SELECT salary
FROM   hr.employees
WHERE  department_id = 60;
```
    
</div>
 <div class="right_col">
     
Результат:
![](https://i.imgur.com/mF1ncul.jpg)

     
</div>
</div>

**Предлагаемые решения:**

<div class="wrap">
<div class="left_col">
       
```sql
SELECT  MAX(salary) keep(
                    dense_rank first 
        ORDER BY cnt desc) salary
FROM
(
	SELECT  salary
	       ,COUNT(*) cnt
	FROM hr.employees
	WHERE department_id = 20
	GROUP BY  salary
)
```
    
</div>
 <div class="right_col">
     
Результат:
![](https://i.imgur.com/KH6fuEN.png)

     
</div>
</div>


Вложенное представление возвращает каждое значение столбца SAL
и число раз, сколько это значение встречается в столбце, как показано
ниже:

<div class="wrap">
<div class="left_col">
       
```sql
SELECT  salary
       ,COUNT(*) cnt
FROM hr.employees
WHERE department_id = 60
GROUP BY  salary
```
    
</div>
 <div class="right_col">
     
Результат:
     ![](https://i.imgur.com/5d82R0x.png)

</div>
</div>


Следующий шаг – использовать расширение KEEP агрегатной функции MAX для поиска моды. Если проанализировать приведенный ниже оператор KEEP, можно заметить три подоператора, DENSE_RANK, FIRST и ORDER BY CNT DESC:
keep(dense_rank first order by cnt desc)
 
С их помощью очень удобно искать моду. Оператор KEEP определяет, какое значение SAL будет возвращено функцией MAX, по значению CNT, возвращенному вложенным представлением. Выражение выполняется справа налево. Сначала значения для CNT выстраиваются поубыванию, затем выбирается первое из них и возвращается в порядке,
установленном функцией DENSE_RANK.



## Вычисление моды

Требуется найти медиану столбца числовых значений (медиана (median) – это значение среднего члена множества упорядоченных элементов).  Например, поставлена задача найти медиану заработных плат служащих 60го отдела (DEPTNO 60). Для следующего набора заработных плат:



<div class="wrap">
<div class="left_col">
       
```sql
SELECT salary
FROM   hr.employees
WHERE  department_id = 60;
```
    
</div>
 <div class="right_col">
     
Результат:
![](https://i.imgur.com/mF1ncul.jpg)

     
</div>
</div>


**Предлагаемые решения:**

<div class="wrap">
<div class="left_col">
       
```sql

SELECT
    median(salary)
FROM hr.employees
WHERE
    department_id = 60;
------------ OR ------------
SELECT
    percentile_cont(0.5) within group(
        order by salary
    )
FROM  hr.employees
WHERE department_id = 60;

```
    
</div>
 <div class="right_col">
     
Результат:
![](https://i.imgur.com/YrHlhhn.png)

     
</div>
</div>

Очевидно, что функция MEDIAN вычисляет медиану, тогда как с функцией PERCENTILE_CONT все не так явно, хотя она делает то же самое.
Передаваемый в PERCENTILE_CONT аргумент, 0,5 – это процентиль.
Конструкция WITHIN GROUP (ORDER BY SAL) определяет множество сортированных строк, с которым будет работать PERCENTILE_CONT (помним, что медиана – это середина множества упорядоченных значений). Возвращаемое значение соответствует заданной процентилив сортированном множестве строк (в данном случае, это 0,5; т. е. середина множества, поскольку граничными значениями являются 0 и 1).

## Вычисление среднего без учета наибольшего и наименьшего значений

Требуется вычислить среднее, но без учета наибольшего и наименьшего значений, чтобы уменьшить асимметрию распределения. Например, необходимо найти среднюю заработную плату служащих, исключая из расчета наибольшую и наименьшую из них.

**Предлагаемые решения:**

1. Исключаем наибольшее и наименьшее значения с помощью подзапросов:
    
<div class="wrap">
<div class="left_col">

```sql
SELECT TRUNC(AVG(salary),3) AS avg
FROM hr.employees
WHERE
    salary not IN (
        (
            SELECT MIN(salary)
            FROM hr.employees
        ),
        (
            SELECT MAX(salary)
            FROM hr.employees
        )
    );

```
    
</div>
 <div class="right_col">
     
Результат:
![](https://i.imgur.com/VMXuOeN.png)
     
</div>
</div>

Решение 2: С помощью вложенного запроса с оконными функциями MAX OVER и MIN OVER формируем результирующее множество, из которого можно без труда исключить наибольшее и наименьшее значения:

```sql
SELECT TRUNC(AVG(salary),3) AS avg
FROM
    (
        SELECT salary,
            MIN(salary) over() min_salary,
            MAX(salary) over() max_salary
        FROM hr.employees
    ) x
WHERE
    salary not IN (min_salary, max_salary);
```


Вложенный запрос Х возвращает все заработные платы, а также наибольшую и наименьшую из них:

<div class="wrap">
<div class="left_col">
       
```sql
 SELECT
    salary,
    MIN(salary) over() min_salary,
    MAX(salary) over() max_salary
FROM hr.employees
```
    
</div>
 <div class="right_col">
     
Результат:
![](https://i.imgur.com/Ihwcejm.jpg)

</div>
</div>

Обратиться к наибольшей и наименьшей заработной плате можно в любой строке, поэтому доступ к ним не составляет труда. Внешний запрос фильтрует строки, возвращенные вложенным запросом Х, так, чтобы все заработные платы, соответствующие MIN_SAL или MAX_SAL, были исключены из вычисления среднего.



## Вычисление доли от целого в процентном выражении

Требуется определить, какую долю от целого в процентном выражении для определенного столбца составляет та или иная группа значений.
Например, стоит задача вычислить, какой процент от всех заработных плат составляют заработные платы служащих 50го отдела (процентный вклад зарплат DEPTNO 50 в общую сумму заработных плат).

**Предлагаемые решения:**

1.<div class="wrap">
<div class="left_col">
       
```sql
SELECT
    TRUNC(
    (
     SUM(case WHEN  department_id = 50 THEN salary end)
     / SUM(salary)
    ) * 100
    ,2) || '%' AS pct
FROM
    hr.employees
```
    
</div>
 <div class="right_col">
     
Результат:
![](https://i.imgur.com/tHDsk3n.png)

     
</div>
</div>

2.<div class="wrap">
```sql
SELECT
    distinct TRUNC((d50 / total) * 100, 2) || '%' AS pct
FROM
    (
        SELECT
            department_id,
            SUM(salary) over() total,
            SUM(salary) over(partition by department_id) d50
        FROM hr.employees
    ) x
WHERE
    department_id = 50
```
</div>

В качестве альтернативы традиционному подходу второе решение для вычисления процента от целого использует оконные функции. Важно помнить, что оконные функции обрабатываются после предиката WHERE, по-этому значение столбца TOTAL представляет сумму всех зарплат только 50го отдела. Но для решения задачи TOTAL должен представлять сумму всех заработных плат. Вот почему фильтр по DEPTNO должен располагаться вне вложенного запроса Х.


## Определить, является ли год високосным


Требуется определить, является ли текущий год високосным.


**Предлагаемые решения:**

<div class="wrap">
<div class="left_col">
       
```sql
select 
  to_char(sysdate, 'yyyy') as Year,
  CASE WHEN
    to_char(
      last_day(add_months(trunc(sysdate,'y'),1)),
    'DD') = 29 then 'Yes'  
  ELSE 'No' 
  END AS IsLeap
from dual
```
    
</div>
 <div class="right_col">
     
Результат:
![](https://i.imgur.com/ifkIWnY.png)


Проверяется последний день февраля;
если он является 29м днем,
    то текущий год – високосный.
</div>
</div>


## Выбор всех дат года, выпадающих на определенный день недели

Требуется найти все даты года, соответствующие заданному дню недели.
Например, стоит задача сформировать список пятниц текущего года.

**Предлагаемые решения:**

<div class="wrap">
<div class="left_col">

```sql
WITH x AS (
    SELECT
        trunc(sysdate, 'y') + level - 1 AS dy
    FROM
        dual
    connect by level <= add_months (trunc(sysdate, 'y'), 12) - trunc(sysdate, 'y')
)
SELECT *
FROM x
WHERE to_char(dy, 'dy') = 'fri'
```

< 
</div>
<div class="right_col">
     
Результат:

![](https://i.imgur.com/SCviLfa.png)

</div>
</div>

Основой решения являются возвращение всех дней текущего года и выбор из них только тех, которые соответствуют интересующему дню недели. Рекурсивным оператором CONNECT BY возвращаем все дни текущего года. Затем с помощью функции TO_CHAR выбираем только пятницы.

:::danger
## Разворачивание ранжированного результирующего множества

Требуется ранжировать значения таблицы, затем развернуть результирующее множество и получить три столбца. Идея в том, чтобы показать в разных столбцах три наибольших значения, три следующих значения и все остальные. Например, необходимо ранжировать служащих в таблице EMP по значениям SAL и затем развернуть результаты в три столбца. Должно быть получено следующее результирующее множество:

    TOP_3            NEXT_3          REST
      
    KING (5000)     BLAKE (2850)    TURNER (1500)
    FORD (3000)     CLARK (2450)    MILLER (1300)
    SCOTT (3000)    ALLEN (1600)    MARTIN (1250)
    JONES (2975)    WARD (1250)
    ADAMS (1100)
    JAMES (950)
    SMITH (800)
<br>
:::

## Сравнение строк по определенной части даты

Требуется найти служащих, которые были приняты на работу в один месяц и день недели. Например, если один служащий был нанят в понедельник 10 марта 1988 года, а другой служащий – в понедельник 2 марта 2001, они должны быть включены в результирующее множество, поскольку день недели и месяц их приема на работу совпадают.В таблице EMP только трое служащих отвечают этому требованию.
Должно быть получено следующее результирующее множество:

<center>

![](https://i.imgur.com/3kSOufs.png)

</center>

**Предлагаемые решения:**

Поскольку требуется сравнивать значение HIREDATE одного служащего со значениями HIREDATE других служащих, понадобится провести рефлексивное объединение таблицы EMP. Тем самым мы получим все возможные сочетания HIREDATE. После этого для каждой строки мы просто будем извлекать день недели и месяц из значений HIREDATE и сравнивать их у разных строк.


```sql
SELECT
    a.first_name 
    || ' was hired on the same month and weekday as ' 
    || b.first_name AS msg
FROM
    hr.employees a,
    hr.employees b
WHERE
    to_char(a.hire_date, 'DMON') = to_char(b.hire_date, 'DMON')
    AND a.employee_id < b.employee_id
ORDER BY
    a.first_name
```

Благодаря рефлексивному объединению таблицы EMP мы можем сравнивать значение HIREDATE служащего SCOTT со значениями HIREDATE всех остальных служащих. Для EMPNO задан фильтр, который обеспечивает, что значение HIREDATE служащего SCOTT не будет включено в столбец OTHER_HDS:





<div class="wrap">
<div class="left_col">

```sql
--- ...
WHERE
    to_char(a.hire_date, 'DMON') = to_char(b.hire_date, 'DMON')
    AND a.employee_id != b.employee_id
ORDER BY
    a.first_name
```
    
</div>
<div class="right_col">
     
Результат:

![](https://i.imgur.com/yMxjuGd.png)


</div>
</div>

Причина вывода лишних строк кроется в фильтре по EMPNO. Использование оператора «не равно» не обеспечивает отсеивание обратных равенств. Например, в третьей строке со поставляются BRITNEY и CHRISTOPHER, а в пятой строке – CHRISTOPHER и BRITNEY.
Строки результирующего множества, с технической точки зрения, возвращены правильно, но они дублируют друг друга. Избавиться от дублирования позволит оператор «<»


:::danger
## Преобразование целых чисел в их двоичное

Требуется преобразовать целое число в его двоичное представление. Например, необходимо получить все заработные платы таблицы EMP в двоичном виде.

**Предлагаемые решения:**

```sql
SELECT
    last_name,
    salary,
(
    SELECT
        bin
    FROM
        dual model dimension by (0 attr) measures (
            salary num,
            cast(null AS varchar2(30)) bin,
            '0123456789ABCDEF' hex
        ) rules iterate (10000) until (num [0] <= 0) (
            bin [0] = substr(hex [cv()], mod(num [cv()], 2) + 1, 1) || bin [cv()],
            num [0] = trunc(num [cv()] / 2)
        )
    ) salary_binary
FROM
    hr.employees
```

<center>
Результат:
    
![](https://i.imgur.com/zMSe4xm.jpg)
</center>

В этом решении используется оператор MODEL. MODEL обладает возможностью выполнять итерации и обеспечивать доступ к значениям строк как к элементам массива. Чтобы упростить объяснение, я собираюсь показать небольшие вариа!
ции подзапроса, содержащего оператор MODEL. Приведенный далее
код является кодом подзапроса из решения за исключением того, что
здесь жестко запрограммировано возвратить двоичное представление
значения 2:

    select bin
    from dual
    model
    dimension by ( 0 attr )
    measures ( 2 num,
    cast(null as varchar2(30)) bin,
    '0123456789ABCDEF' hex
    )
    rules iterate (10000) until (num[0] <= 0) (
    bin[0] = substr (hex[cv()],mod(num[cv()],2)+1,1)||bin[cv()],
    num[0] = trunc(num[cv()]/2)
    )
    BIN
    
    10
    
Следующий запрос выводит значения, возвращенные в результате од ной итерации конструкции RULES из запроса выше:

    select 2 start_val,
    '0123456789ABCDEF' hex,
    substr('0123456789ABCDEF',mod(2,2)+1,1) ||
    cast(null as varchar2(30)) bin,
    trunc(2/2) num
    from dual
    START_VAL HEX BIN NUM
       
    2 0123456789ABCDEF 0 1
    
Поле START_VAL содержит число, для которого должно быть получе!
но двоичное представление, в данном случае это 2. Значение столбца
BIN – результат операции SUBSTR над строкой ‘0123456789ABCDEF’
(в оригинальном решении HEX). Значение NUM – это признак выхода
из цикла.
Как видно из предыдущего результирующего множества, после пер!
вой итерации цикла BIN равен 0, и NUM равен 1. Поскольку значение
NUM не меньше или не равно 0, выполняется следующая итерация,
результаты которой представляет приведенное ниже выражение SQL:

    select num start_val,
    substr('0123456789ABCDEF',mod(1,2)+1,1) || bin bin,
    
    trunc(1/2) num
    from (
    select 2 start_val,
    '0123456789ABCDEF' hex,
    substr('0123456789ABCDEF',mod(2,2)+1,1) ||
    cast(null as varchar2(30)) bin,
    trunc(2/2) num
    from dual
    )
    START_VAL BIN NUM
      
    1 10 0
    
После следущей итерации операция SUBSTR над HEX возвращает 1, к которой присоединяется предыдущее значение BIN, 0. Проверочное
значение, NUM, теперь равно 0; таким образом, это последняя итера!
ция, а возвращенное значение «10» – двоичное представление числа 2.
Разобравшись с тем, что происходит, можно убрать итерацию из опе!
ратора MODEL и строка за строкой проследить, как применяются пра!
вила для получения окончательного результирующего множества, что
показано ниже:

    select 2 orig_val, num, bin
    from dual
    model
    dimension by ( 0 attr )
    measures ( 2 num,
    cast(null as varchar2(30)) bin,
    '0123456789ABCDEF' hex
    )
    rules (
    bin[0] = substr (hex[cv()],mod(num[cv()],2)+1,1)||bin[cv()],
    num[0] = trunc(num[cv()]/2),
    bin[1] = substr (hex[0],mod(num[0],2)+1,1)||bin[0],
    num[1] = trunc(num[0]/2)
    )
    ORIG_VAL NUM BIN
      
    2 1 0
    2 0 10

<br> 
:::

## Создание календаря

Требуется создать календарь на текущий месяц. Он должен быть отформатирован, как обычный календарь: семь столбцов в ширинуи (как правило) пять строк вниз.

**Предлагаемые решения:**

```sql
WITH x AS
(
	SELECT  *
	FROM
	(
		SELECT  to_char(trunc(sysdate,'mm')+level-1,'iw') wk
		       ,to_char(trunc(sysdate,'mm')+level-1,'dd') dm
		       ,to_number(to_char(trunc(sysdate,'mm')+level-1,'d')) dw
		       ,to_char(trunc(sysdate,'mm')+level-1,'mm') curr_mth
		       ,to_char(sysdate,'mm') mth
		FROM dual connect by level <= 31
	)
	WHERE curr_mth = mth 
)
SELECT  MAX(case dw WHEN 2 THEN dm end) Mo
       ,MAX(case dw WHEN 3 THEN dm end) Tu
       ,MAX(case dw WHEN 4 THEN dm end) We
       ,MAX(case dw WHEN 5 THEN dm end) Th
       ,MAX(case dw WHEN 6 THEN dm end) Fr
       ,MAX(case dw WHEN 7 THEN dm end) Sa
       ,MAX(case dw WHEN 1 THEN dm end) Su
FROM x
GROUP BY  wk
ORDER BY wk
```

<center>
Результат:

![](https://i.imgur.com/eaO5Ewq.jpg)

</center>


Чтобы возвратить все дни текущего месяца, используйте рекурсивный оператор CONNECT BY. Затем разбейте месяц на недели по выбранному дню с помощью выражений CASE и функций MAX:

начинаем с использования рекурсивного оператора CONNECT BY, с помощью которого получаем столько строк, сколько дней в заданном месяце. Для каждого дня месяца понадобится дополнительно получить следующие данные: порядковый номер дня месяца (под псевдонимом DM), день недели (под псевдонимом DW), текущий месяц (под псевдонимом MTH) и ISOномер недели для каждого дня месяца (под псевдонимом WK). Результаты, возвращаемые представлением Х оператора WITH для первого дня текущего месяца, показаны ниже: 



<div class="wrap">
<div class="left_col">

```sql
SELECT  trunc(sysdate,'mm') dy
       ,to_char(trunc(sysdate,'mm'),'dd') dm
       ,to_char(sysdate,'mm') mth
       ,to_number(to_char(trunc(sysdate,'mm'),'d')) dw
       ,to_char(trunc(sysdate,'mm'),'iw') wk
FROM dual
```

</div>
<div class="right_col">
     
Результат:

![](https://i.imgur.com/M5VEP1j.png)


</div>
</div>

Далее пошагово увеличиваем значение DM (перебираем дни месяца) до тех пор, пока не будут получены все дни текущего месяца. Для каждого дня месяца также возвращаем соответствующий ему день недели и ISOномер недели, в которую попадает данный день. Результаты частично показаны ниже (полная дата для каждого дня приведена для ясности):

<div class="wrap">
<div class="left_col">

```sql
WITH x AS
(
	SELECT  *
	FROM
	(
		SELECT  to_char(trunc(sysdate,'mm')+level-1,'iw') wk
		       ,to_char(trunc(sysdate,'mm')+level-1,'dd') dm
		       ,to_number(to_char(trunc(sysdate,'mm')+level-1,'d')) dw
		       ,to_char(trunc(sysdate,'mm')+level-1,'mm') curr_mth
		       ,to_char(sysdate,'mm') mth
		FROM dual connect by level <= 31
	)
	WHERE curr_mth = mth 
)
SELECT *
FROM x
```
</div>
<div class="right_col">
     
Результат:

![](https://i.imgur.com/jY6UuBg.png)

</div>
</div>

Здесь мы получаем все дни текущего месяца, каждый из которых выведен в отдельной строке. В каждой строке представлены следующие данные: двузначный номер дня месяца, двузначный номер месяца, однозначный номер дня недели (1–7 для Вс.–Сб.) и двузначный ISOномер недели. Имея в своем распоряжении всю эту информацию, с помощью выражения CASE можно определить, на какой день недели выпадает каждое из значений столбца DM (каждый день месяца). Результаты частично показаны ниже:


<div class="wrap">
<div class="left_col">

```sql
WITH x AS
(
	SELECT  *
	FROM
	(
    SELECT  to_char(trunc(sysdate,'mm')+level-1,'iw') wk
           ,to_char(trunc(sysdate,'mm')+level-1,'dd') dm
           ,to_number(to_char(trunc(sysdate,'mm')+level-1,'d')) dw
           ,to_char(trunc(sysdate,'mm')+level-1,'mm') curr_mth
           ,to_char(sysdate,'mm') mth
    FROM dual connect by level <= 31
	)
	WHERE curr_mth = mth 
)
SELECT case dw when 2 then dm end as Mo,
       case dw when 3 then dm end as Tu,
       case dw when 4 then dm end as We,
       case dw when 5 then dm end as Th,
       case dw when 6 then dm end as Fr,
       case dw when 7 then dm end as Sa,
       case dw when 1 then dm end as Su
FROM x

```
</div>
<div class="right_col">

Результат:

![](https://i.imgur.com/mLjfwYe.png)


</div>
</div>

Как видно из частично представленного результата, каждый день каждой недели возвращается в отдельной строке, но номер дня располагается в одном из семи столбцов в соответствии с днем недели. Теперь остается только собрать все дни одной недели в одну строку. Чтобы возвратить все дни недели в одной строке, используем агрегатную функцию MAX и группировку по значениям столбца WK (ISOномеру недели).


##  Сведения о сотрудниках по регионам и трем цифрам


Используются таблицы стандартной демо-схемы HR.
Одной командой SELECT собрать информацию по сотрудникам, отделам, местам расположения, странам и регионам в единую таблицу. Сгруппировать сведения:
* По названию региона,
* По первым трем цифрам номера телефона сотрудника.

Вывести следующую информацию по полученным группам (семь столбцов):
* Название региона,
* Первые три цифры телефонного номера сотрудников,
* Количество сотрудников в данной группе,
* Средний оклад сотрудников группы (округлить до двух знаков после запятой),
* Количество мест расположения, где работают сотрудники данной группы,
* Количество подразделений, к которым приписаны сотрудники данной группы,
* Список названий городов, в которых работают сотрудники данной группы.
  
Названия городов в списке должны быть:
* Упорядочены по возрастанию
* Разделены символами ', ' ("запятая" и "пробел")
* Перед первым названием города не должно быть символов-разделителей
* После последнего названия города символов-разделителей быть не должно.


Результат должен быть отсортирован:
* По названию региона (по возрастанию),
* По первым трем цифрам номера телефона сотрудника (по возрастанию).


**Предлагаемые решения:**
* 1
 
```sql
WITH t1 AS (
SELECT r.region_name r_name, SUBSTR(e.phone_number, 1,3) ph_num,
e.employee_id, e.salary, l.location_id, d.department_id, l.city,
ROW_NUMBER() OVER (PARTITION BY r.region_name, SUBSTR(e.phone_number, 1,3) ORDER BY r.region_name) in_gr_num,
DENSE_RANK() OVER (ORDER BY r.region_name, SUBSTR(e.phone_number, 1,3)) gr_num,
DENSE_RANK() OVER (PARTITION BY r.region_name, SUBSTR(e.phone_number, 1,3) ORDER BY l.city) city_num 
FROM regions r, countries c, locations l, departments d, employees e
WHERE r.region_id = c.region_id
AND c.country_id = l.country_id
AND l.location_id = d.location_id
AND d.department_id = e.department_id)
--
SELECT DISTINCT r_name, ph_num,
(SELECT MAX(in_gr_num) FROM t1 WHERE gr_num = t.gr_num) c1,
(SELECT round(AVG(salary), 2) FROM t1 WHERE gr_num = t.gr_num) c2,
(SELECT count(DISTINCT location_id) FROM t1 WHERE gr_num = t.gr_num) c3, 
(SELECT count(DISTINCT department_id) FROM t1 WHERE gr_num = t.gr_num) c4,
LISTAGG(city, ', ') WITHIN GROUP (ORDER BY city) OVER (PARTITION BY gr_num) c5
FROM t1 t
```

* 2

```sql
SELECT r_name, ph_num, ce,ss cl,cd, LISTAGG(city,', ') WITHIN GROUP (ORDER BY city) OVER (PARTITION BY r_name,ph_num) c5 FROM (
  
  
  SELECT r.region_name r_name, SUBSTR(e.phone_number, 1,3) ph_num,
  COUNT(e.employee_id) ce, SUM(e.salary) ss, COUNT(l.location_id) cl, COUNT(d.department_id) cd, 
  LISTAGG(l.city,', ') WITHIN GROUP (ORDER BY l.city) OVER (PARTITION BY r.region_name, SUBSTR(e.phone_number, 1,3))
  FROM regions r, countries c, locations l, departments d, employees e
  WHERE r.region_id = c.region_id
  AND c.country_id = l.country_id
  AND l.location_id = d.location_id
  AND d.department_id = e.department_id
  GROUP BY (r.region_name, SUBSTR(e.phone_number, 1,3)), l.city
)
```

* 3

```sql
WITH t2 AS (
  SELECT DISTINCT r.region_name r_name, SUBSTR(e.phone_number, 1,3) ph_num,
  LISTAGG(l.city, ', ') WITHIN GROUP (ORDER BY l.city) OVER (PARTITION BY r.region_name, SUBSTR(e.phone_number, 1,3)) city
  FROM regions r, countries c, locations l, departments d, employees e
  WHERE r.region_id = c.region_id
  AND c.country_id = l.country_id
  AND l.location_id = d.location_id
  AND d.department_id = e.department_id
  GROUP BY r.region_name, SUBSTR(e.phone_number, 1,3), l.city),
t1 AS ( SELECT r.region_name r_name, SUBSTR(e.phone_number, 1,3) ph_num,
  COUNT(e.employee_id) ce, SUM(e.salary) ss, COUNT(l.location_id) cl, COUNT(d.department_id) cd 
  FROM regions r, countries c, locations l, departments d, employees e
  WHERE r.region_id = c.region_id
  AND c.country_id = l.country_id
  AND l.location_id = d.location_id
  AND d.department_id = e.department_id
  GROUP BY r.region_name, SUBSTR(e.phone_number, 1,3))  
--
SELECT tt1.r_name, tt1.ph_num, tt1.ce, tt1.ss, tt1.cl, tt1.cd, tt2.city
FROM t1 tt1, t2 tt2
WHERE tt1.r_name = tt2.r_name AND tt1.ph_num = tt2.ph_num
```


* 4

```sql
WITH t1 AS (
  SELECT r.region_name r_name, SUBSTR(e.phone_number, 1,3) ph_num,
  e.employee_id, e.salary, l.location_id, d.department_id, l.city,
  ROW_NUMBER() OVER (PARTITION BY r.region_name, SUBSTR(e.phone_number, 1,3) ORDER BY r.region_name) in_gr_num,
  DENSE_RANK() OVER (ORDER BY r.region_name, SUBSTR(e.phone_number, 1,3)) gr_num
  FROM regions r, countries c, locations l, departments d, employees e
  WHERE r.region_id = c.region_id
  AND c.country_id = l.country_id
  AND l.location_id = d.location_id
  AND d.department_id = e.department_id),  
t3 AS (SELECT DISTINCT gr_num, city FROM t1),
t4 AS (SELECT DISTINCT gr_num, city, ROW_NUMBER() OVER(PARTITION BY gr_num ORDER BY city) in_gr_num  FROM t3), 
t2 AS (
  SELECT DISTINCT r_name, ph_num,
  (SELECT MAX(in_gr_num) FROM t1 WHERE gr_num = t.gr_num) c1,
  (SELECT round(AVG(salary) ,2) FROM t1 WHERE gr_num = t.gr_num) c2,
  (SELECT count(DISTINCT location_id) FROM t1 WHERE gr_num = t.gr_num) c3, 
  (SELECT count(DISTINCT department_id) FROM t1 WHERE gr_num = t.gr_num) c4
  ,(SELECT SUBSTR(SYS_CONNECT_BY_PATH(city,', '), 3)
        FROM t4 WHERE CONNECT_BY_ISLEAF = 1 AND gr_num = t.gr_num AND ROWNUM = 1
        CONNECT BY  in_gr_num = PRIOR in_gr_num+1
        AND gr_num = PRIOR gr_num
        START WITH in_gr_num = 1) c5
  FROM t1 t)
--
SELECT * FROM t2
ORDER BY r_name, ph_num
```

