Функция ранга в MySQL


155

Мне нужно выяснить звание клиентов. Здесь я добавляю соответствующий стандартный запрос SQL ANSI для моего требования. Пожалуйста, помогите мне преобразовать его в 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часть аналитической функции (которая «нумерует» значение ранга для пола, а не для общего результата)
a_horse_with_no_name

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выражения. Это (в теории) заботится о порядке оценки вопроса. IS 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    |

Демо на дб <> скрипка


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

это прекрасный ответ именно потому, что мне нужно сделать ранжирование разделов. Спасибо, сэр!
Ким Стеки

ИМО, он имеет ту же сложность, что и подвыбор в ответе @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 docs говорится, что «порядок вычисления выражений с участием пользовательских переменных не определен». dev.mysql.com/doc/refman/5.7/ru/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.
Ньюдарк-это

Да, меня обескураживает множество ответов с пользовательскими переменными и логическими блоками. Новые версии MySQL позволяют сделать это намного проще с помощью функции RANK (), которая предлагает встроенную группировку по разделам.
Джеймс Бонд

5

@Sam, ваша точка зрения превосходна в концепции, но я думаю, что вы неправильно поняли, что документы MySQL говорят на указанной странице - или я неправильно понял :-) - и я просто хотел добавить это, чтобы, если кто-то чувствует себя некомфортно с @ Ответ Даниэля, они будут более уверены или, по крайней мере, будут копать глубже.

Вы видите, что "@curRank := @curRank + 1 AS rank"внутри SELECTэто не «одно утверждение», это одна «атомарная» часть утверждения, поэтому оно должно быть безопасным.

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

Кто-то может возразить, что @curRankв ответе @ Daniel дважды используется: (1) "@curRank := @curRank + 1 AS rank"и (2) the, "(SELECT @curRank := 0) r"но поскольку второе использование является частью FROMпредложения, я уверен, что оно гарантированно будет оценено первым; по сути, сделав это вторым и предшествующим заявлением.

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


Ничто из этого не подтверждается документацией. Это просто (нечеткое) предположение. Как и все ответы, использующие и пишущие одну и ту же переменную, которая, как говорится в руководстве, явно не определена, хотя в руководстве действительно содержится много бесполезного текста о том, что может работать, как вы ожидаете, не говоря о том, что, по вашему мнению, вы ожидаете или как использовать описание негарантированного поведения PS Начиная с 8.0 назначение переменных вне SET устарело.
Филипп

4

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

10 20 30 30 30 40
  • Все 30 значения считаются третьими
  • Все 40значения считаются 6-м (ранг) или 4-м (плотный ранг)

Теперь вернемся к первоначальному вопросу. Вот некоторые примеры данных, которые отсортированы, как описано в OP (ожидаемые ранги добавляются справа):

+------+-----------+------+--------+    +------+------------+
| 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)

Это ранжирование соответствует функции оракула 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

Ранжирование разделов в третьем варианте в этом фрагменте кода будет возвращать непрерывные ранжирующие числа. это приведет к структуре данных, похожей на 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.