---
# System prepended metadata

title: SQL от Тигоря

---

# SQL от Тигоря
## Select

[Тут картиночки](https://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_10002.htm)

Простейший селект (все остальное не обязательное):

```sql
SELECT * FROM TABLE_NAME
```

селект средней сложности:

```sql=
WITH 
QUERY_1 AS (
    SELECT ...
), ...
QUERY_R AS (
    SELECT ...
)
SELECT DISTINCT  
COL_NAME_1, ... , COL_NAME_N

FROM TABLE_NAME_1, ..., TABLE_NAME_M

WHERE CONDITION

GROUP BY COL_NAME_I, ..., COL_NAME_K

HAVING CONDITION

ORDER BY COL_NAME_U, ..., COL_NAME_V DESC

LIMIT L

OFFSET O
;

```

Как читается:

FROM **откуда** берем данные -> WHERE **как** отбираем -> WITH **что используем** для отбора данных -> SELECT **что выбираем** -> GROUP BY **как группируем** для отбора -> HAVING **какие значения после применения агрегатов берем** -> ORDER BY **как сортируем** -> LIMIT сколько берем -> OFFSET начиная с какой строки

А теперь по порядку:

- **WITH** - это что-то типа view или переменной. `WITH ИМЯ AS (Запрос)`. Результаты запроса не выполняются сразу, он будет вызван только в тот момент, когда мы обращаемся к этому имени, поэтому он может быть оптимизирован транслятором SQL (много сложных слов - читай как: результат запроса на самом деле не формируется и нигде не хранится, поэтому эта штука быстрая). Если нужно несколько WITH, пишем так: 
```sql=
WITH QUERY_1 AS (
    SELECT ...
), ...
QUERY_R AS (
    SELECT ...
)
...
SELECT
```

- **DISTINCT** - позволяет выбрать уникальные значения (считаются по всем столбцам). Например:
```sql
SELECT * FROM TABLE_NAME 
```


| Column 1 | Column 2 | Column 3 |
| -------- | -------- | -------- |
| 1     | A     | B     |
| 1     | A     | B     |
| 2     | A     | B     |
| 3     | C     | D     |

```sql
SELECT DISTINCT * FROM TABLE_NAME 
```


| Column 1 | Column 2 | Column 3 |
| -------- | -------- | -------- |
| 1     | A     | B     |
| 2     | A     | B     |
| 3     | C     | D     |

- **COL_NAME_1, ... , COL_NAME_N** - то, что мы хотим получить в качестве столбцов в итоговой таблице. Это может быть:
  - Имя столбца одной из таблиц, которые указаны в FROM, также можно указать с псевдонимом, который будет использоваться далее. Например: 
  ```sql
  SELECT some_shit AS "Pretty thing" FROM TABLE_NAME
  ```
  - Агрегатная функция
  ```sql
  SELECT COUNT(*) FROM STUDENTS
  -- посчитаем количество всех студентов
  ```
  - Агрегатная функция от столбца (это разные вещи)
  ```sql
  SELECT MAX(AGE) FROM STUDENTS
  -- посчитаем возраст самого старшего из всех студентов
  ```
  - оконная функция (про это будет позже)
  - Обычная функция от столбца или выражение, например:
  ```sql 
  SELECT TO_DATE(DATE_FIELD), CASH*1000 FROM TABLE_NAME
  ```
  - Строка (ВАЖНО: одинарные кавычки) или число - получится столбец в котором ничего не будет кроме этой строки
  ```sql
  SELECT NAME, 'its a string', 1 FROM TABLE_NAME
  ```
  | Column 1 | Column 2 | Column 3 |
  | -------- | -------- | -------- |
  | Tigor     | its a string     | 1     |
  | Not tigor     | its a string     | 1     |
  - CASE WHEN THEN ELSE END - это набор условий. Если что-то, то одно, иначе другое. Например:
  ```sql
  SELECT 
      CASE WHEN DAY < 6 
          THEN 'Workday', 
          ELSE 'Weekend' 
      END 
  FROM DAYS_TABLE
  ```
  ```sql
  SELECT 
      CASE 
      WHEN DAY = 1 THEN 'Monday', 
      ...
      WHEN DAY = 7 THEN 'Sunday' 
      END 
  FROM DAYS_TABLE
  ```
- **FROM TABLE_NAME_1, ..., TABLE_NAME_M** - откуда хотим брать данные. Это может быть:
  - Имя таблицы, также можно указать с псевдонимом:
  ```sql
  SELECT * FROM LONG_TABLE_NAME TAB
  ```
  - JOIN (будет потом)
  - Имя сохраненного запроса из WITH
  - На самом деле почти все что угодно тк в оракле все есть таблица

- **WHERE CONDITION** - условия для отбора строк из таблиц которые указаны во FROM. Внутри может быть какое-нибудь дикое условие, в котором могут тоже быть селекты и прочее. Есть специальные операторы, которые можно использовать в WHERE:
  - AND: дальше таблица, как рассчитать EXP_1 AND EXP_2
  
  | AND | TRUE | FALSE |NULL |
  | -------- | -------- | -------- |-------- |
  | **TRUE**     | TRUE     | FALSE     |NULL     |
  | **FALSE**     | FALSE     | FALSE     |FALSE     |
  |**NULL**     | NULL     | FALSE     |NULL     |

  - OR: дальше таблица, как рассчитать EXP_1 OR EXP_2
  
  | OR | TRUE | FALSE |NULL |
  | -------- | -------- | -------- |-------- |
  | **TRUE**     | TRUE     | TRUE     |NULL     |
  | **FALSE**     | TRUE     | FALSE     |NULL     |
  |**NULL**     | TRUE     | NULL     |NULL     |

  - IN: принадлежность множеству
  ```sql
  SELECT * FROM TABLE_NAME
  WHERE SMTH IN ('A', 'B', 'C') 
  -- это TRUE когда SMTH = 'A' OR SMTH = 'B' OR SMTH = 'C'
  ```
  ```sql
  SELECT * FROM TABLE_NAME
  WHERE SMTH NOT IN ('A', 'B', 'C') 
  -- это TRUE когда SMTH != 'A' AND SMTH != 'B' AND SMTH != 'C'
  ```
  ```sql
  SELECT * FROM TABLE_NAME
  WHERE SMTH IN (SELECT ...) 
  -- это TRUE когда SMTH есть в таблице, 
  -- которая возвращается из второго селекта
  -- Таблица должна возвращать один столбец
  ```
  - BETWEEN A AND B - проверяет принадлежность диапазону
  ```sql
  SELECT NAME, TRUE AS IS_LEGAL FROM LITTLE_GIRLS
  WHERE AGE BETWEEN 18 and 25 
  -- это TRUE когда AGE >= 18 AND AGE <= 25
  ```
  - IS (NOT) NULL проверка NULL/не NULL
  ```sql
  SELECT NAME AS WORKER FROM STUDENT
  WHERE WORK_PLACE IS NOT NULL 
  ```
  
  - LIKE - помогает определить похожесть. Редко кто использует, дичь та еще
  ```sql
  SELECT NAME FROM NAMES
  WHERE NAME LIKE 'Alex%'
  ```
  | Column 1 | 
  | -------- | 
  | Alex     |
  | Alexey     |
  | Alexandr     |
  | Alexandra     |
        
- **GROUP BY COL_NAME_I, ..., COL_NAME_K** - позволяет сгруппировать строки из запроса. Зачем? Чтобы что-то посчитать (а точнее агрегатную функцию) для каждой группы. Без агрегата не работает. Подробнее позже.
```sql
SELECT CLASS, COUNT(*) FROM STUDENTS
GROUP BY CLASS
```
| Column 1 | Column 2 |
| -------- | -------- |
| М80-201М-19     | 10     |
| М80-202М-19     | 3     |
| М80-203М-19     | 54     |
| М80-204М-19     | 5     |

- **HAVING** - позволяет отобрать строки после применения агрегата. Зачем? Ну например, чтобы отсечь группы где студентов меньше 10
```sql
SELECT CLASS, COUNT(*) FROM STUDENTS
GROUP BY CLASS
HAVING COUNT(*) < 10
```
| Column 1 | Column 2 |
| -------- | -------- |
| М80-201М-19     | 10     |
| М80-203М-19     | 54     |

- **ORDER BY COL_NAME_U, ..., COL_NAME_V DESC** - сортировка. По умолчанию по возрастанию (ASC), если добавить в конец DESC, будет по убыванию. А еще можно писать номер столбцов вместо имени столбца. А можно для каждого указывать по возрастанию или нет - в конце пример

```sql
SELECT NAME FROM TABLE_NAME
ORDER BY NAME
```
| Column 1 | 
  | -------- | 
  | Igor     |
  | Tigor     |
  | Zigor     |
  
  ```sql
SELECT NAME FROM TABLE_NAME
ORDER BY NAME DESC
```
| Column 1 | 
  | -------- | 
  | Zigor     |
  | Tigor     |
  | Igor     |
```sql
SELECT NAME, AGE, MONEY FROM TABLE_NAME
ORDER BY 2 ASC, 3 DECS
```
 | Column 1 | Column 2 | Column 3 |
  | -------- | -------- | -------- |
  | Tigor     | 0     | 10     |
  | Another tigor     | 0     | 5     |
  | Not tigor     | 10     | 1     |
 
- **LIMIT L  .. OFFSET O** - позволяет указать лимит выдачи и начиная с какого-то места. Работает в самом конце после сортировки. Например для примера выше:
```sql
SELECT NAME, AGE, MONEY FROM TABLE_NAME
ORDER BY 2 ASC, 3 DECS
LIMIT 1 -- выбираем одну строку
```
 | Column 1 | Column 2 | Column 3 |
  | -------- | -------- | -------- |
  | Tigor     | 0     | 10     |
```sql
SELECT NAME, AGE, MONEY FROM TABLE_NAME
ORDER BY 2 ASC, 3 DECS
OFFSET 1 -- пропускаем первую строку
```
 | Column 1 | Column 2 | Column 3 |
  | -------- | -------- | -------- |
  | Another tigor     | 0     | 5     |
  | Not tigor     | 10     | 1     |
  
```sql
SELECT NAME, AGE, MONEY FROM TABLE_NAME
ORDER BY 2 ASC, 3 DECS
LIMIT 1 OFFSET 1 -- выбираем одну строку, пропустив первую
```
 | Column 1 | Column 2 | Column 3 |
  | -------- | -------- | -------- |
  | Another tigor     | 0     | 5     |
  
  
## Запросы с группировкой

Часто требуется посчитать что-то используя группировку. Для этого можно использовать агрегатные функции + group by + having
```sql=
SELECT COL_NAME_1, ... , AGGREGATE_FUN_1(COL_NAME_N),...

FROM TABLE_NAME_1, ..., TABLE_NAME_M

WHERE CONDITION

GROUP BY COL_NAME_I, ..., COL_NAME_K

HAVING CONDITION
```

Как это работает: выбираем строки из таблиц из `FROM` -> отбираем их по условию из `WHERE` -> группируем строки по полям, которые указаны в `GROUP BY` (важно: не получится использовать группировку по разным полям в одном запросе - дальше будет пример) -> считаем для них агрегатные функции в `SELECT` -> отбираем нужные значения с помощью `HAVING`
Ограничения:
пусть есть таблица вида:
| Department | Class | Student | Year |
| -------- | -------- | -------- |-------- |
|8 факультет | М80-204М-19 |  Золотарев Игорь | 2019
|..|..|..|..|
- нельзя сделать группировку по разным полям в одном запросе (например хотим посчитать одновременно количество студентов на каждом факультете и узнать год, когда был принят первый студент в группе - пример херовый, надеюсь, поймешь)
  ```sql
  -- это не правильно, мы получим количество студентов 
  -- в каждой группе факультета и год принятия первого 
  -- студента в каждой группе на факультете
  SELECT DEPARTMENT, CLASS, COUNT(STUDENT), MIN(YEAR)
  FROM STUDENTS
  GROUP BY DEPARTMENT, CLASS
  ```
  как надо:
  ```sql
  SELECT DEPARTMENT, CLASS, COUNT, MIN 
  FROM 
      (SELECT DEPARTMENT, CLASS, COUNT(STUDENT) AS COUNT,
      FROM STUDENTS
      GROUP BY DEPARTMENT) T1
      INNER JOIN
      (SELECT DEPARTMENT, CLASS, MIN(YEAR) 
      FROM STUDENTS
      GROUP BY CLASS) T2
      ON T1.DEPARTMENT = T2.DEPARTMENT AND T1.CLASS = T2.CLASS
  ```
- в `SELECT` обязательно должны быть все поля, которые указаны в `GROUP BY` (иначе не понятно, какое значение агрегатной функции принадлежит какой группе)
  ```sql
  -- это не будет работать
  SELECT COUNT(STUDENT), MIN(YEAR)
  FROM STUDENTS
  GROUP BY DEPARTMENT, CLASS
  ```
- в рамках одного запроса можно группировать только по полям, которые есть в таблицах (то есть рассчетные поля в `SELECT` не могут участвовать в группировке, например, по `CASE WHEN` не получится группировать, нужно писать второй запрос)
  ```sql
  SELECT COUNT(STUDENT), 
  CASE YEAR > 2020 
      THEN 'CURRENT_YEAR' 
      ELSE 'PAST_TIMES'
  END AS WHICH_YEAR
  FROM STUDENTS
  GROUP BY DEPARTMENT, CLASS, WHICH_YEAR -- это не будет работать
  ```
- в HAVING нельзя указать псевдонимы столбцов
  ```sql
  SELECT DEPARTMENT, CLASS, COUNT(STUDENT) AS CNT
  FROM STUDENTS
  GROUP BY DEPARTMENT, CLASS 
  HAVING CNT > 10 -- это не будет работать
  ```
  так правильно:
  ```sql
  SELECT DEPARTMENT, CLASS, COUNT(STUDENT) AS CNT
  FROM STUDENTS
  GROUP BY DEPARTMENT, CLASS 
  HAVING COUNT(STUDENT) > 10
  ```


Какие есть агрегатные функции:
- `MIN(COL)`
- `MAX(COL)`
- `AVG(COL)` - среднее
- `SUM(COL)`
- `MEDIAN(COL)`
- `COUNT(COL)` - вернет количество уникальных значений для данного столбца в указанной группе
- `COUNT(*)` - вернет количество записей в группе (в т.ч. не уникальные)
- `VARIANCE(COL)` - дисперсия

и [другие](https://docs.oracle.com/database/121/SQLRF/functions003.htm#SQLRF20035)

**Все агрегатные функции, кроме `COUNT` работают со значениями, которые приводятся к числовым - числа, логические значения и даты (т.к. дата представляется в оракле как набор чисел)** 