2021-09-14 12:36:35 +00:00
---
toc_priority: 21
2021-09-19 13:14:42 +00:00
toc_title: Меню
2021-09-14 12:36:35 +00:00
---
2021-09-19 13:14:49 +00:00
# Н а б о р данных публичной библиотеки Нью-Йорка "Что в меню?" {#menus-dataset}
2021-09-14 12:36:35 +00:00
2021-09-14 18:03:05 +00:00
Н а б о р данных создан Нью-Йоркской публичной библиотекой. Он содержит исторические данные о меню отелей, ресторанов и кафе с блюдами, а также их ценами.
2021-09-14 12:36:35 +00:00
2021-09-14 18:03:05 +00:00
Источник: http://menus.nypl.org/data
Эти данные находятся в открытом доступе.
2021-09-14 12:36:35 +00:00
2021-09-19 13:19:05 +00:00
Данные взяты из архива библиотеки, и они могут быть неполными и сложными для статистического анализа. Тем не менее, это тоже очень интересно.
2021-09-14 18:03:05 +00:00
В наборе всего 1,3 миллиона записей о блюдах в меню — очень небольшой объем данных для ClickHouse, но это все равно хороший пример.
2021-09-16 14:02:36 +00:00
## Загрузите набор данных {#download-dataset}
2021-09-14 18:03:05 +00:00
Выполните команду:
```bash
wget https://s3.amazonaws.com/menusdata.nypl.org/gzips/2021_08_01_07_01_17_data.tgz
```
2021-09-19 13:19:11 +00:00
При необходимости замените ссылку на актуальную ссылку с http://menus.nypl.org/data.
2021-09-19 13:19:17 +00:00
Размер архива составляет около 35 МБ.
2021-09-14 18:03:05 +00:00
2021-09-16 14:02:36 +00:00
## Распакуйте набор данных {#unpack-dataset}
2021-09-14 18:03:05 +00:00
```bash
tar xvf 2021_08_01_07_01_17_data.tgz
```
Размер распакованных данных составляет около 150 МБ.
2021-09-16 15:01:14 +00:00
Данные нормализованы и состоят из четырех таблиц:
- `Menu` — информация о меню: название ресторана, дата, когда было просмотрено меню, и т.д.
- `Dish` — информация о блюдах: название блюда вместе с некоторыми характеристиками.
- `MenuPage` — информация о страницах в меню, потому что каждая страница принадлежит какому-либо меню.
- `MenuItem` — один из пунктов меню. Блюдо вместе с е г о ценой на какой-либо странице меню: ссылки на блюдо и страницу меню.
2021-09-14 18:03:05 +00:00
2021-09-16 14:02:36 +00:00
## Создайте таблицы {#create-tables}
2021-09-14 18:03:05 +00:00
2021-09-16 15:01:14 +00:00
Для хранения цен используется тип данных [Decimal ](../../sql-reference/data-types/decimal.md ).
2021-09-14 18:03:05 +00:00
```sql
CREATE TABLE dish
(
id UInt32,
name String,
description String,
menus_appeared UInt32,
times_appeared Int32,
first_appeared UInt16,
last_appeared UInt16,
lowest_price Decimal64(3),
highest_price Decimal64(3)
) ENGINE = MergeTree ORDER BY id;
CREATE TABLE menu
(
id UInt32,
name String,
sponsor String,
event String,
venue String,
place String,
physical_description String,
occasion String,
notes String,
call_number String,
keywords String,
language String,
date String,
location String,
location_type String,
currency String,
currency_symbol String,
status String,
page_count UInt16,
dish_count UInt16
) ENGINE = MergeTree ORDER BY id;
CREATE TABLE menu_page
(
id UInt32,
menu_id UInt32,
page_number UInt16,
image_id String,
full_height UInt16,
full_width UInt16,
uuid UUID
) ENGINE = MergeTree ORDER BY id;
CREATE TABLE menu_item
(
id UInt32,
menu_page_id UInt32,
price Decimal64(3),
high_price Decimal64(3),
dish_id UInt32,
created_at DateTime,
updated_at DateTime,
xpos Float64,
ypos Float64
) ENGINE = MergeTree ORDER BY id;
```
2021-09-16 15:01:14 +00:00
## Импортируйте данные {#import-data}
2021-09-14 18:03:05 +00:00
2021-09-19 13:19:23 +00:00
Импортируйте данные в ClickHouse, выполните команды:
2021-09-14 18:03:05 +00:00
```bash
clickhouse-client --format_csv_allow_single_quotes 0 --input_format_null_as_default 0 --query "INSERT INTO dish FORMAT CSVWithNames" < Dish.csv
clickhouse-client --format_csv_allow_single_quotes 0 --input_format_null_as_default 0 --query "INSERT INTO menu FORMAT CSVWithNames" < Menu.csv
clickhouse-client --format_csv_allow_single_quotes 0 --input_format_null_as_default 0 --query "INSERT INTO menu_page FORMAT CSVWithNames" < MenuPage.csv
clickhouse-client --format_csv_allow_single_quotes 0 --input_format_null_as_default 0 --date_time_input_format best_effort --query "INSERT INTO menu_item FORMAT CSVWithNames" < MenuItem.csv
```
2021-09-19 13:19:30 +00:00
Поскольку данные представлены в формате CSV с заголовком, используется формат [CSVWithNames ](../../interfaces/formats.md#csvwithnames ).
2021-09-14 18:03:05 +00:00
2021-09-16 15:01:14 +00:00
Отключите `format_csv_allow_single_quotes` , так как для данных используются только двойные кавычки, а одинарные кавычки могут находиться внутри значений и не должны сбивать с толку CSV-парсер.
2021-09-16 15:03:35 +00:00
Отключите [input_format_null_as_default ](../../operations/settings/settings.md#settings-input-format-null-as-default ), поскольку в данных нет значений [NULL ](../../sql-reference/syntax.md#null-literal ).
2021-09-16 15:01:14 +00:00
В противном случае ClickHouse попытается проанализировать последовательности `\N` и может перепутать с `\` в данных.
Настройка [date_time_input_format best_effort ](../../operations/settings/settings.md#settings-date_time_input_format ) позволяет анализировать поля [DateTime ](../../sql-reference/data-types/datetime.md ) в самых разных форматах. К примеру, будет распознан ISO-8601 без секунд: '2000-01-01 01:02'. Без этой настройки допускается только фиксированный формат даты и времени.
2021-09-14 18:03:05 +00:00
2021-09-16 15:01:14 +00:00
## Денормализуйте данные {#denormalize-data}
2021-09-14 18:03:05 +00:00
2021-09-16 15:01:14 +00:00
Данные представлены в нескольких таблицах в [нормализованном виде ](https://ru.wikipedia.org/wiki/%D0%9D%D0%BE%D1%80%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F_%D1%84%D0%BE%D1%80%D0%BC%D0%B0 ).
2021-09-14 18:03:05 +00:00
2021-09-16 15:01:14 +00:00
Это означает, что вам нужно использовать условие объединения [JOIN ](../../sql-reference/statements/select/join.md#select-join ), если вы хотите получить, например, названия блюд из пунктов меню.
2021-09-14 18:03:05 +00:00
2021-09-19 13:25:56 +00:00
Для типовых аналитических задач гораздо эффективнее работать с предварительно объединенными данными, чтобы не использовать `JOIN` каждый раз. Такие данные называются денормализованными.
2021-09-14 18:03:05 +00:00
2021-09-16 15:01:14 +00:00
Создайте таблицу `menu_item_denorm` , которая будет содержать все данные, объединенные вместе:
2021-09-14 18:03:05 +00:00
```sql
CREATE TABLE menu_item_denorm
ENGINE = MergeTree ORDER BY (dish_name, created_at)
AS SELECT
price,
high_price,
created_at,
updated_at,
xpos,
ypos,
dish.id AS dish_id,
dish.name AS dish_name,
dish.description AS dish_description,
dish.menus_appeared AS dish_menus_appeared,
dish.times_appeared AS dish_times_appeared,
dish.first_appeared AS dish_first_appeared,
dish.last_appeared AS dish_last_appeared,
dish.lowest_price AS dish_lowest_price,
dish.highest_price AS dish_highest_price,
menu.id AS menu_id,
menu.name AS menu_name,
menu.sponsor AS menu_sponsor,
menu.event AS menu_event,
menu.venue AS menu_venue,
menu.place AS menu_place,
menu.physical_description AS menu_physical_description,
menu.occasion AS menu_occasion,
menu.notes AS menu_notes,
menu.call_number AS menu_call_number,
menu.keywords AS menu_keywords,
menu.language AS menu_language,
menu.date AS menu_date,
menu.location AS menu_location,
menu.location_type AS menu_location_type,
menu.currency AS menu_currency,
menu.currency_symbol AS menu_currency_symbol,
menu.status AS menu_status,
menu.page_count AS menu_page_count,
menu.dish_count AS menu_dish_count
FROM menu_item
JOIN dish ON menu_item.dish_id = dish.id
JOIN menu_page ON menu_item.menu_page_id = menu_page.id
JOIN menu ON menu_page.menu_id = menu.id;
```
2021-09-16 15:01:14 +00:00
## Проверьте загруженные данные {#validate-data}
2021-09-14 18:03:05 +00:00
Запрос:
```sql
SELECT count() FROM menu_item_denorm;
```
Результат:
```text
┌─count()─┐
│ 1329175 │
└─────────┘
```
2021-09-16 14:02:36 +00:00
## Примеры запросов {#run-queries}
2021-09-14 18:03:05 +00:00
2021-09-16 14:02:36 +00:00
### Усредненные исторические цены на блюда {#query-averaged-historical-prices}
2021-09-14 18:03:05 +00:00
Запрос:
```sql
SELECT
round(toUInt32OrZero(extract(menu_date, '^\\d{4}')), -1) AS d,
count(),
round(avg(price), 2),
bar(avg(price), 0, 100, 100)
FROM menu_item_denorm
WHERE (menu_currency = 'Dollars') AND (d > 0) AND (d < 2022 )
GROUP BY d
ORDER BY d ASC;
```
Результат:
```text
┌────d─┬─count()─┬─round(avg(price), 2)─┬─bar(avg(price), 0, 100, 100)─┐
│ 1850 │ 618 │ 1.5 │ █▍ │
│ 1860 │ 1634 │ 1.29 │ █▎ │
│ 1870 │ 2215 │ 1.36 │ █▎ │
│ 1880 │ 3909 │ 1.01 │ █ │
│ 1890 │ 8837 │ 1.4 │ █▍ │
│ 1900 │ 176292 │ 0.68 │ ▋ │
│ 1910 │ 212196 │ 0.88 │ ▊ │
│ 1920 │ 179590 │ 0.74 │ ▋ │
│ 1930 │ 73707 │ 0.6 │ ▌ │
│ 1940 │ 58795 │ 0.57 │ ▌ │
│ 1950 │ 41407 │ 0.95 │ ▊ │
│ 1960 │ 51179 │ 1.32 │ █▎ │
│ 1970 │ 12914 │ 1.86 │ █▋ │
│ 1980 │ 7268 │ 4.35 │ ████▎ │
│ 1990 │ 11055 │ 6.03 │ ██████ │
│ 2000 │ 2467 │ 11.85 │ ███████████▋ │
│ 2010 │ 597 │ 25.66 │ █████████████████████████▋ │
└──────┴─────────┴──────────────────────┴──────────────────────────────┘
```
Просто не принимайте это всерьез.
2021-09-16 14:02:36 +00:00
### Цены на бургеры {#query-burger-prices}
2021-09-14 18:03:05 +00:00
Запрос:
```sql
SELECT
round(toUInt32OrZero(extract(menu_date, '^\\d{4}')), -1) AS d,
count(),
round(avg(price), 2),
bar(avg(price), 0, 50, 100)
FROM menu_item_denorm
WHERE (menu_currency = 'Dollars') AND (d > 0) AND (d < 2022 ) AND ( dish_name ILIKE ' % burger % ' )
GROUP BY d
ORDER BY d ASC;
```
Результат:
```text
┌────d─┬─count()─┬─round(avg(price), 2)─┬─bar(avg(price), 0, 50, 100)───────────┐
│ 1880 │ 2 │ 0.42 │ ▋ │
│ 1890 │ 7 │ 0.85 │ █▋ │
│ 1900 │ 399 │ 0.49 │ ▊ │
│ 1910 │ 589 │ 0.68 │ █▎ │
│ 1920 │ 280 │ 0.56 │ █ │
│ 1930 │ 74 │ 0.42 │ ▋ │
│ 1940 │ 119 │ 0.59 │ █▏ │
│ 1950 │ 134 │ 1.09 │ ██▏ │
│ 1960 │ 272 │ 0.92 │ █▋ │
│ 1970 │ 108 │ 1.18 │ ██▎ │
│ 1980 │ 88 │ 2.82 │ █████▋ │
│ 1990 │ 184 │ 3.68 │ ███████▎ │
│ 2000 │ 21 │ 7.14 │ ██████████████▎ │
│ 2010 │ 6 │ 18.42 │ ████████████████████████████████████▋ │
└──────┴─────────┴──────────────────────┴───────────────────────────────────────┘
```
2021-09-16 14:02:36 +00:00
### Водка {#query-vodka}
2021-09-14 18:03:05 +00:00
Запрос:
```sql
SELECT
round(toUInt32OrZero(extract(menu_date, '^\\d{4}')), -1) AS d,
count(),
round(avg(price), 2),
bar(avg(price), 0, 50, 100)
FROM menu_item_denorm
WHERE (menu_currency IN ('Dollars', '')) AND (d > 0) AND (d < 2022 ) AND ( dish_name ILIKE ' % vodka % ' )
GROUP BY d
ORDER BY d ASC;
```
Результат:
```text
┌────d─┬─count()─┬─round(avg(price), 2)─┬─bar(avg(price), 0, 50, 100)─┐
│ 1910 │ 2 │ 0 │ │
│ 1920 │ 1 │ 0.3 │ ▌ │
│ 1940 │ 21 │ 0.42 │ ▋ │
│ 1950 │ 14 │ 0.59 │ █▏ │
│ 1960 │ 113 │ 2.17 │ ████▎ │
│ 1970 │ 37 │ 0.68 │ █▎ │
│ 1980 │ 19 │ 2.55 │ █████ │
│ 1990 │ 86 │ 3.6 │ ███████▏ │
│ 2000 │ 2 │ 3.98 │ ███████▊ │
└──────┴─────────┴──────────────────────┴─────────────────────────────┘
```
2021-09-16 15:01:14 +00:00
Чтобы получить водку, мы должны написать `ILIKE '%vodka%'` , и это хорошая идея.
2021-09-14 18:03:05 +00:00
2021-09-16 14:02:36 +00:00
### Икра {#query-caviar}
2021-09-14 18:03:05 +00:00
2021-09-16 15:01:14 +00:00
Посмотрите цены на икру. Получите название любого блюда с икрой.
2021-09-14 18:03:05 +00:00
Запрос:
```sql
SELECT
round(toUInt32OrZero(extract(menu_date, '^\\d{4}')), -1) AS d,
count(),
round(avg(price), 2),
bar(avg(price), 0, 50, 100),
any(dish_name)
FROM menu_item_denorm
WHERE (menu_currency IN ('Dollars', '')) AND (d > 0) AND (d < 2022 ) AND ( dish_name ILIKE ' % caviar % ' )
GROUP BY d
ORDER BY d ASC;
```
Результат:
```text
┌────d─┬─count()─┬─round(avg(price), 2)─┬─bar(avg(price), 0, 50, 100)──────┬─any(dish_name)──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ 1090 │ 1 │ 0 │ │ Caviar │
│ 1880 │ 3 │ 0 │ │ Caviar │
│ 1890 │ 39 │ 0.59 │ █▏ │ Butter and caviar │
│ 1900 │ 1014 │ 0.34 │ ▋ │ Anchovy Caviar on Toast │
│ 1910 │ 1588 │ 1.35 │ ██▋ │ 1/1 Brötchen Caviar │
│ 1920 │ 927 │ 1.37 │ ██▋ │ ASTRAKAN CAVIAR │
│ 1930 │ 289 │ 1.91 │ ███▋ │ Astrachan caviar │
│ 1940 │ 201 │ 0.83 │ █▋ │ (SPECIAL) Domestic Caviar Sandwich │
│ 1950 │ 81 │ 2.27 │ ████▌ │ Beluga Caviar │
│ 1960 │ 126 │ 2.21 │ ████▍ │ Beluga Caviar │
│ 1970 │ 105 │ 0.95 │ █▊ │ BELUGA MALOSSOL CAVIAR AMERICAN DRESSING │
│ 1980 │ 12 │ 7.22 │ ██████████████▍ │ Authentic Iranian Beluga Caviar the world's finest black caviar presented in ice garni and a sampling of chilled 100° Russian vodka │
│ 1990 │ 74 │ 14.42 │ ████████████████████████████▋ │ Avocado Salad, Fresh cut avocado with caviare │
│ 2000 │ 3 │ 7.82 │ ███████████████▋ │ Aufgeschlagenes Kartoffelsueppchen mit Forellencaviar │
│ 2010 │ 6 │ 15.58 │ ███████████████████████████████▏ │ "OYSTERS AND PEARLS" "Sabayon" of Pearl Tapioca with Island Creek Oysters and Russian Sevruga Caviar │
└──────┴─────────┴──────────────────────┴──────────────────────────────────┴─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
```
По крайней мере, есть икра с водкой. Очень мило.
2021-09-19 13:25:14 +00:00
## Online Playground {#playground}
2021-09-14 18:03:05 +00:00
2021-09-16 15:01:14 +00:00
Этот набор данных доступен в интерактивном р е с у р с е [Online Playground ](https://gh-api.clickhouse.tech/play?user=play#U0VMRUNUCiAgICByb3VuZCh0b1VJbnQzMk9yWmVybyhleHRyYWN0KG1lbnVfZGF0ZSwgJ15cXGR7NH0nKSksIC0xKSBBUyBkLAogICAgY291bnQoKSwKICAgIHJvdW5kKGF2ZyhwcmljZSksIDIpLAogICAgYmFyKGF2ZyhwcmljZSksIDAsIDUwLCAxMDApLAogICAgYW55KGRpc2hfbmFtZSkKRlJPTSBtZW51X2l0ZW1fZGVub3JtCldIRVJFIChtZW51X2N1cnJlbmN5IElOICgnRG9sbGFycycsICcnKSkgQU5EIChkID4gMCkgQU5EIChkIDwgMjAyMikgQU5EIChkaXNoX25hbWUgSUxJS0UgJyVjYXZpYXIlJykKR1JPVVAgQlkgZApPUkRFUiBCWSBkIEFTQw== ).