Функція ранжування в MySQL


155

Мені потрібно дізнатися ранг клієнтів. Тут я додаю відповідний ANSI стандартний запит SQL для моєї вимоги. Будь ласка, допоможіть мені конвертувати його в MySQL.

SELECT RANK() OVER (PARTITION BY Gender ORDER BY Age) AS [Partition by Gender], 
  FirstName, 
  Age,
  Gender 
FROM Person

Чи є якась функція, щоб дізнатися ранг в MySQL?

Відповіді:


266

Один із варіантів - використовувати змінну рейтингу, таку як:

SELECT    first_name,
          age,
          gender,
          @curRank := @curRank + 1 AS rank
FROM      person p, (SELECT @curRank := 0) r
ORDER BY  age;

(SELECT @curRank := 0)Частина дозволяє ініціалізацію змінної , не вимагаючи окрему SETкоманду.

Тестовий випадок:

CREATE TABLE person (id int, first_name varchar(20), age int, gender char(1));

INSERT INTO person VALUES (1, 'Bob', 25, 'M');
INSERT INTO person VALUES (2, 'Jane', 20, 'F');
INSERT INTO person VALUES (3, 'Jack', 30, 'M');
INSERT INTO person VALUES (4, 'Bill', 32, 'M');
INSERT INTO person VALUES (5, 'Nick', 22, 'M');
INSERT INTO person VALUES (6, 'Kathy', 18, 'F');
INSERT INTO person VALUES (7, 'Steve', 36, 'M');
INSERT INTO person VALUES (8, 'Anne', 25, 'F');

Результат:

+------------+------+--------+------+
| first_name | age  | gender | rank |
+------------+------+--------+------+
| Kathy      |   18 | F      |    1 |
| Jane       |   20 | F      |    2 |
| Nick       |   22 | M      |    3 |
| Bob        |   25 | M      |    4 |
| Anne       |   25 | F      |    5 |
| Jack       |   30 | M      |    6 |
| Bill       |   32 | M      |    7 |
| Steve      |   36 | M      |    8 |
+------------+------+--------+------+
8 rows in set (0.02 sec)

52
+1 за хитру вбудовану ініціалізацію, це прекрасний трюк.
Чарльз

28
Хіба він не попросив розділу? Моє розуміння розділів полягає в тому, що набір результатів матиме окремі рейтинги для чоловіків та жінок.
Джессі Діллон

2
@Jesse: Якщо це так, я нещодавно відповів на подібне запитання: stackoverflow.com/questions/3162389/multiple-ranks-in-one-table
Даніель Вассалло

6
Що робити, якщо я хочу дати Анну і Бобу 4 місце?
Фахім Паркар

8
Це не реалізує приклад із запитання, оскільки він пропускає partition by genderчастину аналітичної функції (яка "
нумерує

53

Ось загальне рішення, яке присвоює рядки щільний ранг над розділом. Він використовує змінні користувача:

CREATE TABLE person (
    id INT NOT NULL PRIMARY KEY,
    firstname VARCHAR(10),
    gender VARCHAR(1),
    age INT
);

INSERT INTO person (id, firstname, gender, age) VALUES
(1,  'Adams',  'M', 33),
(2,  'Matt',   'M', 31),
(3,  'Grace',  'F', 25),
(4,  'Harry',  'M', 20),
(5,  'Scott',  'M', 30),
(6,  'Sarah',  'F', 30),
(7,  'Tony',   'M', 30),
(8,  'Lucy',   'F', 27),
(9,  'Zoe',    'F', 30),
(10, 'Megan',  'F', 26),
(11, 'Emily',  'F', 20),
(12, 'Peter',  'M', 20),
(13, 'John',   'M', 21),
(14, 'Kate',   'F', 35),
(15, 'James',  'M', 32),
(16, 'Cole',   'M', 25),
(17, 'Dennis', 'M', 27),
(18, 'Smith',  'M', 35),
(19, 'Zack',   'M', 35),
(20, 'Jill',   'F', 25);

SELECT person.*, @rank := CASE
    WHEN @partval = gender AND @rankval = age THEN @rank
    WHEN @partval = gender AND (@rankval := age) IS NOT NULL THEN @rank + 1
    WHEN (@partval := gender) IS NOT NULL AND (@rankval := age) IS NOT NULL THEN 1
END AS rnk
FROM person, (SELECT @rank := NULL, @partval := NULL, @rankval := NULL) AS x
ORDER BY gender, age;

Зауважте, що призначення змінних розміщені всередині CASEвиразу. Це (теоретично) піклується про порядок оцінювання. TheIS NOT NULLДодаються для обробки типу даних перетворення і короткі запитання замикання.

PS: Його можна легко перетворити на номер рядка на розділі, видаливши всі умови, які перевіряють краватку.

| id | firstname | gender | age | rank |
|----|-----------|--------|-----|------|
| 11 | Emily     | F      | 20  | 1    |
| 20 | Jill      | F      | 25  | 2    |
| 3  | Grace     | F      | 25  | 2    |
| 10 | Megan     | F      | 26  | 3    |
| 8  | Lucy      | F      | 27  | 4    |
| 6  | Sarah     | F      | 30  | 5    |
| 9  | Zoe       | F      | 30  | 5    |
| 14 | Kate      | F      | 35  | 6    |
| 4  | Harry     | M      | 20  | 1    |
| 12 | Peter     | M      | 20  | 1    |
| 13 | John      | M      | 21  | 2    |
| 16 | Cole      | M      | 25  | 3    |
| 17 | Dennis    | M      | 27  | 4    |
| 7  | Tony      | M      | 30  | 5    |
| 5  | Scott     | M      | 30  | 5    |
| 2  | Matt      | M      | 31  | 6    |
| 15 | James     | M      | 32  | 7    |
| 1  | Adams     | M      | 33  | 8    |
| 18 | Smith     | M      | 35  | 9    |
| 19 | Zack      | M      | 35  | 9    |

Демонстрація на db <> скрипці


2
Це рішення, або рішення Мукеша, має бути правильним рішенням. Хоча технічно я вважаю, що рішення обох хлопців представляють щільний рейтинг, а не регулярний ранг. Ось гарне пояснення відмінностей: sqlservercurry.com/2009/04 / ... .
modulitos

Чи можете ви також повідомити нам, яким саме має бути .php код? Я намагався дотримуватися, але вище код не працює. Як ввести у формат .php?
творець

Це рішення не дуже загальне; він не працюватиме, якщо rank_column має значення 0. sqlfiddle.com/#!2/9c5dd/1
Майк

1
@mike Додайте розділ ELSE до заяви CASE:ELSE @rank_count := @rank_count + 1
Принц Одам

1
@abhash ORDER BY gender, age DESC?
Салман

52

Незважаючи на те, що найбільш відповідне місце займає відповідь, він не розділяє, Ви можете зробити самовстановлення, щоб також розділити всю річ:

SELECT    a.first_name,
      a.age,
      a.gender,
        count(b.age)+1 as rank
FROM  person a left join person b on a.age>b.age and a.gender=b.gender 
group by  a.first_name,
      a.age,
      a.gender

Використовуйте кейс

CREATE TABLE person (id int, first_name varchar(20), age int, gender char(1));

INSERT INTO person VALUES (1, 'Bob', 25, 'M');
INSERT INTO person VALUES (2, 'Jane', 20, 'F');
INSERT INTO person VALUES (3, 'Jack', 30, 'M');
INSERT INTO person VALUES (4, 'Bill', 32, 'M');
INSERT INTO person VALUES (5, 'Nick', 22, 'M');
INSERT INTO person VALUES (6, 'Kathy', 18, 'F');
INSERT INTO person VALUES (7, 'Steve', 36, 'M');
INSERT INTO person VALUES (8, 'Anne', 25, 'F');

Відповідь :

Bill    32  M   4
Bob     25  M   2
Jack    30  M   3
Nick    22  M   1
Steve   36  M   5
Anne    25  F   3
Jane    20  F   2
Kathy   18  F   1

це чудова відповідь саме тому, що мені потрібно зробити рейтинг розділів. Дякую вам сер!
Кім стек

IMO він має таку ж складність, як і підвідбір у відповіді @Sam Kidman: O (n ^ 2). Але не знаю, чи можна зробити це краще в MySQL.
xmedeko

Перевірте onlamp.com/pub/a/mysql/2007/03/29/… для чудового підручника по тих же рядках
ferics2

Самостійно вступайте, щоб отримати звання! Це чудово. Нарешті, рішення без змінних та без віконних функцій MySQL 8 . :)
Тімо

24

Виправлення версії Даніеля для обчислення відсотків разом з рангом. Також двоє людей з однаковими оцінками отримають однакове звання.

set @totalStudents = 0;
select count(*) into @totalStudents from marksheets;
SELECT id, score, @curRank := IF(@prevVal=score, @curRank, @studentNumber) AS rank, 
@percentile := IF(@prevVal=score, @percentile, (@totalStudents - @studentNumber + 1)/(@totalStudents)*100),
@studentNumber := @studentNumber + 1 as studentNumber, 
@prevVal:=score
FROM marksheets, (
SELECT @curRank :=0, @prevVal:=null, @studentNumber:=1, @percentile:=100
) r
ORDER BY score DESC

Результати запиту на вибіркові дані -

+----+-------+------+---------------+---------------+-----------------+
| id | score | rank | percentile    | studentNumber | @prevVal:=score |
+----+-------+------+---------------+---------------+-----------------+
| 10 |    98 |    1 | 100.000000000 |             2 |              98 |
|  5 |    95 |    2 |  90.000000000 |             3 |              95 |
|  6 |    91 |    3 |  80.000000000 |             4 |              91 |
|  2 |    91 |    3 |  80.000000000 |             5 |              91 |
|  8 |    90 |    5 |  60.000000000 |             6 |              90 |
|  1 |    90 |    5 |  60.000000000 |             7 |              90 |
|  9 |    84 |    7 |  40.000000000 |             8 |              84 |
|  3 |    83 |    8 |  30.000000000 |             9 |              83 |
|  4 |    72 |    9 |  20.000000000 |            10 |              72 |
|  7 |    60 |   10 |  10.000000000 |            11 |              60 |
+----+-------+------+---------------+---------------+-----------------+

1
Незважаючи на те, що це не дуже оптимально в роботі, це приголомшливо!
Gaspa79

18

Поєднання відповіді Даніеля та Салмана. Однак звання не дасть, оскільки триває послідовність зв’язків. Натомість він пропускає ранг до наступного. Тож максимум завжди досягає кількості рядків.

    SELECT    first_name,
              age,
              gender,
              IF(age=@_last_age,@curRank:=@curRank,@curRank:=@_sequence) AS rank,
              @_sequence:=@_sequence+1,@_last_age:=age
    FROM      person p, (SELECT @curRank := 1, @_sequence:=1, @_last_age:=0) r
    ORDER BY  age;

Схема та тестовий випадок:

CREATE TABLE person (id int, first_name varchar(20), age int, gender char(1));

INSERT INTO person VALUES (1, 'Bob', 25, 'M');
INSERT INTO person VALUES (2, 'Jane', 20, 'F');
INSERT INTO person VALUES (3, 'Jack', 30, 'M');
INSERT INTO person VALUES (4, 'Bill', 32, 'M');
INSERT INTO person VALUES (5, 'Nick', 22, 'M');
INSERT INTO person VALUES (6, 'Kathy', 18, 'F');
INSERT INTO person VALUES (7, 'Steve', 36, 'M');
INSERT INTO person VALUES (8, 'Anne', 25, 'F');
INSERT INTO person VALUES (9, 'Kamal', 25, 'M');
INSERT INTO person VALUES (10, 'Saman', 32, 'M');

Вихід:

+------------+------+--------+------+--------------------------+-----------------+
| first_name | age  | gender | rank | @_sequence:=@_sequence+1 | @_last_age:=age |
+------------+------+--------+------+--------------------------+-----------------+
| Kathy      |   18 | F      |    1 |                        2 |              18 |
| Jane       |   20 | F      |    2 |                        3 |              20 |
| Nick       |   22 | M      |    3 |                        4 |              22 |
| Kamal      |   25 | M      |    4 |                        5 |              25 |
| Anne       |   25 | F      |    4 |                        6 |              25 |
| Bob        |   25 | M      |    4 |                        7 |              25 |
| Jack       |   30 | M      |    7 |                        8 |              30 |
| Bill       |   32 | M      |    8 |                        9 |              32 |
| Saman      |   32 | M      |    8 |                       10 |              32 |
| Steve      |   36 | M      |   10 |                       11 |              36 |
+------------+------+--------+------+--------------------------+-----------------+

1
Я новачок у MySQL, але це рішення добре? У документах MySQL сказано, що "порядок оцінки виразів, що містять змінні користувача, не визначений". dev.mysql.com/doc/refman/5.7/uk/user-variables.html
narduk

13

Починаючи з MySQL 8, ви можете нарешті використовувати віконні функції також у MySQL: https://dev.mysql.com/doc/refman/8.0/en/window-functions.html

Ваш запит може бути написаний точно так само:

SELECT RANK() OVER (PARTITION BY Gender ORDER BY Age) AS `Partition by Gender`, 
  FirstName, 
  Age,
  Gender 
FROM Person

Це не неправильно, просто не працює зі старими версіями SQL. плюс це було своєрідне копіювання та минуле його запитання, тому не здається, що це відповідає.
newdark-it

4
@ brand-it Для тих, хто в MySQL 8+, ця відповідь є важливою, оскільки дає нам знати, що Rank доступний. Якби я не прокручував так далеко, я вважав би, що попередні відповіді були єдиним рішенням.
Стів Сміт

1
@SteveSmith Добре, що приємно мати цю відповідь для тих, хто використовує новішу версію MYSQL.
newdark-it

Так, мене відштовхує багато відповідей із користувацькими змінними та логічними блоками. Нова версія MySQL дозволяє зробити це набагато просто за допомогою функції RANK (), яка пропонує вбудовану групування за розділами.
Джеймс Бонд

5

@Sam, ваш погляд ідеально підходить, але я думаю, що ви неправильно зрозуміли, що говорять документи MySQL на зазначеній сторінці - або я неправильно розумію :-) - і я просто хотів додати це, щоб, якщо хтось почуває себе незручно з @ Відповідь Даніеля вони будуть більш заспокоєні або принаймні копають трохи глибше.

Ви бачите, що "@curRank := @curRank + 1 AS rank"всередині SELECTне "одне твердження", це одна "атомна" частина заяви, тому вона повинна бути безпечною.

Документ, на який ви посилаєтесь, продовжує демонструвати приклади, коли однакова визначена користувачем змінна у 2 (атомних) частинах оператора, наприклад, "SELECT @curRank, @curRank := @curRank + 1 AS rank" .

Можна стверджувати, що @curRankвикористовується у відповіді @ Даніеля двічі: (1) "@curRank := @curRank + 1 AS rank"та (2), "(SELECT @curRank := 0) r"але оскільки друге використання є частиноюFROM пункту, я впевнений, що він гарантовано оцінюється спочатку; по суті, роблячи це другим і попереднім твердженням.

Насправді, на тій самій сторінці з документами MySQL, на яку ви посилалися, ви побачите те саме рішення у коментарях - це може бути те, де його отримав @Daniel; так, я знаю, що це коментарі, але це коментарі на офіційній сторінці документів, і це має певну вагу.


Нічого цього не підтверджується документацією. Це просто (нечітка) спекуляція. Як і всі відповіді, використовуючи та записуючи одну і ту ж змінну, про яку в керівництві явно не визначено, хоча в цьому посібнику є багато непослушного тексту про те, що може працювати, як ви очікували, не кажучи про те, що він думає, що ви очікуєте чи яке використання. опис негарантованої поведінки. PS Станом на 8.0 присвоєння змінної за межами SET застаріле.
філіпсі

4

Найбільш прямим рішенням для визначення рангу заданого значення є підрахунок кількості значень перед ним. Припустимо, у нас є такі значення:

10 20 30 30 30 40
  • Усі 30значення вважаються 3-ю
  • Усі 40значення вважаються 6-м (рангом) або 4-м (щільний ранг)

Тепер повернемось до початкового питання. Ось деякі зразкові дані, які сортуються як описано в ОП (очікувані ранги додаються праворуч):

+------+-----------+------+--------+    +------+------------+
| id   | firstname | age  | gender |    | rank | dense_rank |
+------+-----------+------+--------+    +------+------------+
|   11 | Emily     |   20 | F      |    |    1 |          1 |
|    3 | Grace     |   25 | F      |    |    2 |          2 |
|   20 | Jill      |   25 | F      |    |    2 |          2 |
|   10 | Megan     |   26 | F      |    |    4 |          3 |
|    8 | Lucy      |   27 | F      |    |    5 |          4 |
|    6 | Sarah     |   30 | F      |    |    6 |          5 |
|    9 | Zoe       |   30 | F      |    |    6 |          5 |
|   14 | Kate      |   35 | F      |    |    8 |          6 |
|    4 | Harry     |   20 | M      |    |    1 |          1 |
|   12 | Peter     |   20 | M      |    |    1 |          1 |
|   13 | John      |   21 | M      |    |    3 |          2 |
|   16 | Cole      |   25 | M      |    |    4 |          3 |
|   17 | Dennis    |   27 | M      |    |    5 |          4 |
|    5 | Scott     |   30 | M      |    |    6 |          5 |
|    7 | Tony      |   30 | M      |    |    6 |          5 |
|    2 | Matt      |   31 | M      |    |    8 |          6 |
|   15 | James     |   32 | M      |    |    9 |          7 |
|    1 | Adams     |   33 | M      |    |   10 |          8 |
|   18 | Smith     |   35 | M      |    |   11 |          9 |
|   19 | Zack      |   35 | M      |    |   11 |          9 |
+------+-----------+------+--------+    +------+------------+

Щоб розрахувати RANK() OVER (PARTITION BY Gender ORDER BY Age)для Сари , ви можете використовувати цей запит:

SELECT COUNT(id) + 1 AS rank, COUNT(DISTINCT age) + 1 AS dense_rank
FROM testdata
WHERE gender = (SELECT gender FROM testdata WHERE id = 6)
AND age < (SELECT age FROM testdata WHERE id = 6)

+------+------------+
| rank | dense_rank |
+------+------------+
|    6 |          5 |
+------+------------+

Для обчислення RANK() OVER (PARTITION BY Gender ORDER BY Age)для всіх рядків ви можете використовувати цей запит:

SELECT testdata.id, COUNT(lesser.id) + 1 AS rank, COUNT(DISTINCT lesser.age) + 1 AS dense_rank
FROM testdata
LEFT JOIN testdata AS lesser ON lesser.age < testdata.age AND lesser.gender = testdata.gender
GROUP BY testdata.id

І ось результат (об'єднані значення додаються праворуч):

+------+------+------------+    +-----------+-----+--------+
| id   | rank | dense_rank |    | firstname | age | gender |
+------+------+------------+    +-----------+-----+--------+
|   11 |    1 |          1 |    | Emily     |  20 | F      |
|    3 |    2 |          2 |    | Grace     |  25 | F      |
|   20 |    2 |          2 |    | Jill      |  25 | F      |
|   10 |    4 |          3 |    | Megan     |  26 | F      |
|    8 |    5 |          4 |    | Lucy      |  27 | F      |
|    6 |    6 |          5 |    | Sarah     |  30 | F      |
|    9 |    6 |          5 |    | Zoe       |  30 | F      |
|   14 |    8 |          6 |    | Kate      |  35 | F      |
|    4 |    1 |          1 |    | Harry     |  20 | M      |
|   12 |    1 |          1 |    | Peter     |  20 | M      |
|   13 |    3 |          2 |    | John      |  21 | M      |
|   16 |    4 |          3 |    | Cole      |  25 | M      |
|   17 |    5 |          4 |    | Dennis    |  27 | M      |
|    5 |    6 |          5 |    | Scott     |  30 | M      |
|    7 |    6 |          5 |    | Tony      |  30 | M      |
|    2 |    8 |          6 |    | Matt      |  31 | M      |
|   15 |    9 |          7 |    | James     |  32 | M      |
|    1 |   10 |          8 |    | Adams     |  33 | M      |
|   18 |   11 |          9 |    | Smith     |  35 | M      |
|   19 |   11 |          9 |    | Zack      |  35 | M      |
+------+------+------------+    +-----------+-----+--------+

3

Якщо ви хочете оцінити лише одну людину, ви можете зробити наступне:

SELECT COUNT(Age) + 1
 FROM PERSON
WHERE(Age < age_to_rank)

Цей рейтинг відповідає функції Oracle RANK (Якщо у вас є люди з тим самим віком, вони отримують однаковий ранг, а рейтинг після цього не є послідовним).

Це трохи швидше, ніж використання одного з перерахованих вище рішень у підзапиті та вибору з нього, щоб отримати рейтинг однієї людини.

Це можна використовувати для ранжування всіх, але це повільніше, ніж наведені вище рішення.

SELECT
  Age AS age_var,
(
  SELECT COUNT(Age) + 1
  FROM Person
  WHERE (Age < age_var)
 ) AS rank
 FROM Person

Це може стати набагато повільніше, ніж вище рішення, коли кількість рядків у Personтаблиці зростатиме. Це O (n ^ 2) проти O (n) повільніше.
xmedeko

2

Щоб уникнути " проте " у відповіді Ерандака у поєднанні відповідей Даніеля та Салмана, можна скористатись одним із наступних "способів обходу розділів".

SELECT customerID, myDate

  -- partition ranking works only with CTE / from MySQL 8.0 on
  , RANK() OVER (PARTITION BY customerID ORDER BY dateFrom) AS rank, 

  -- Erandac's method in combination of Daniel's and Salman's
  -- count all items in sequence, maximum reaches row count.
  , IF(customerID=@_lastRank, @_curRank:=@_curRank, @_curRank:=@_sequence+1) AS sequenceRank
  , @_sequence:=@_sequence+1 as sequenceOverAll

  -- Dense partition ranking, works also with MySQL 5.7
  -- remember to set offset values in from clause
  , IF(customerID=@_lastRank, @_nxtRank:=@_nxtRank, @_nxtRank:=@_nxtRank+1 ) AS partitionRank
  , IF(customerID=@_lastRank, @_overPart:=@_overPart+1, @_overPart:=1 ) AS partitionSequence

  , @_lastRank:=customerID
FROM myCustomers, 
  (SELECT @_curRank:=0, @_sequence:=0, @_lastRank:=0, @_nxtRank:=0, @_overPart:=0 ) r
ORDER BY customerID, myDate

Ранжування розділів у 3-му варіанті у цьому фрагменті коду поверне безперервні номери рейтингу. це призведе до структури даних, аналогічної rank() over partition byрезультату. Як приклад дивіться нижче. Зокрема, partitionSequence завжди буде починатися з 1 для кожного нового partitionRank , використовуючи цей метод:

customerID    myDate   sequenceRank (Erandac)
                          |    sequenceOverAll
                          |     |   partitionRank
                          |     |     | partitionSequence
                          |     |     |    | lastRank
... lines ommitted for clarity
40    09.11.2016 11:19    1     44    1   44    40
40    09.12.2016 12:08    1     45    1   45    40
40    09.12.2016 12:08    1     46    1   46    40
40    09.12.2016 12:11    1     47    1   47    40
40    09.12.2016 12:12    1     48    1   48    40
40    13.10.2017 16:31    1     49    1   49    40
40    15.10.2017 11:00    1     50    1   50    40
76    01.07.2015 00:24    51    51    2    1    76
77    04.08.2014 13:35    52    52    3    1    77
79    15.04.2015 20:25    53    53    4    1    79
79    24.04.2018 11:44    53    54    4    2    79
79    08.10.2018 17:37    53    55    4    3    79
117   09.07.2014 18:21    56    56    5    1   117
119   26.06.2014 13:55    57    57    6    1   119
119   02.03.2015 10:23    57    58    6    2   119
119   12.10.2015 10:16    57    59    6    3   119
119   08.04.2016 09:32    57    60    6    4   119
119   05.10.2016 12:41    57    61    6    5   119
119   05.10.2016 12:42    57    62    6    6   119
...

0
select id,first_name,gender,age,
rank() over(partition by gender order by age) rank_g
from person

CREATE TABLE person (id int, first_name varchar(20), age int, gender char(1));

INSERT INTO person VALUES (1, 'Bob', 25, 'M');
INSERT INTO person VALUES (2, 'Jane', 20, 'F');
INSERT INTO person VALUES (3, 'Jack', 30, 'M');
INSERT INTO person VALUES (4, 'Bill', 32, 'M');
INSERT INTO person VALUES (5, 'Nick', 22, 'M');
INSERT INTO person VALUES (6, 'Kathy', 18, 'F');
INSERT INTO person VALUES (7, 'Steve', 36, 'M');
INSERT INTO person VALUES (8, 'Anne', 25, 'F');
INSERT INTO person VALUES (9,'AKSH',32,'M');
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.