Модуль I · Тема 2 · Дані опитування · урок 1 з 2 · 120 хвилин
Уяви: ти зібрав із класу відповіді про те, хто скільки спить. Тридцять рядків у таблиці. І тепер хочеш дізнатися, хто недосипає.
Якщо колонку з годинами таблиця вважає текстом, вона не порахує ні суми, ні середнього. Виглядатиме все однаково — а працювати не буде.
Сьогодні ти розберешся, чому так, і навчишся готувати таблицю заздалегідь. Заразом побачиш, як ті самі дані виглядають усередині файлу, коли переходять з однієї програми в іншу.
Наприкінці уроку в тебе буде паспорт майбутнього опитування. Це два
файли: fields.json з описом полів і README.md
з поясненням. На наступному уроці з них виросте HTML-форма, яку заповнить
увесь клас.
fields.json — опис полів анкети для машини.Слова CSV, BOM і fields.json поки нічого не означають — розберемо їх по черзі.
| Етап | Хв | Що робимо |
|---|---|---|
| Вступ і задача теми | 10 | Що ми хочемо дізнатися про клас — три дослідницькі питання |
| Запис, поле, ключ | 10 | Та сама таблиця очима людини і очима бази |
| Типи і дати | 20 | П'ять типів, пастка 01.02.2026, ISO 8601 |
| Нормалізація | 10 | Три написання одного міста — три різні значення |
| Перерва | 10 | — |
| CSV зсередини | 15 | Роздільник, лапки, екранування, кодування, BOM |
| Парсер наживо | 10 | Лагодимо поламані рядки просто на сторінці |
| Формули | 15 | SUM, COUNTIF, IF, пошук, $ |
| Практика: свої поля | 15 | Таблиця в Grist, fields.json, README.md |
| Здача і ДЗ | 5 | Автоперевірка, показ таблиці вчителю |
| Разом | 120 | з перервою всередині |
~/www/form/fields.json і
~/www/form/README.md. Жодних .xlsx, жодних експортів
на мегабайти. Сама таблиця лишається в Grist, а зібрані відповіді з'являться
на наступному уроці — і ляжуть у приватну теку ~/data/,
а не в публічну ~/www/.Таблиця — це не картинка з лініями. Це база даних, у якої просто зручний вигляд
Три слова, якими далі говоритиме весь модуль:
| Слово | Що це в таблиці | Приклад |
|---|---|---|
| Запис (рядок) | одна річ, про яку ми зібрали дані | одна заповнена анкета |
| Поле (колонка) | одна характеристика, однакова для всіх записів | години_сну |
| Значення (клітинка) | перетин: це поле в цьому записі | 7 |
| Ключ | поле, яке відрізняє запис від усіх інших | id, номер анкети |
| Схема | перелік полів і їхніх типів — «паспорт» таблиці | твій fields.json |
Головне правило: усі значення в колонці — одного типу. Уяви колонку «години сну». Серед чисел трапилося слово «вісім» — і колонка вже не числова. Середнє по ній не порахується. Одна клітинка ламає всю колонку.
Зверху й знизу — одні й ті самі дані. Різниця в питанні. Людина питає «як воно виглядає». База питає «якого воно типу». Людина спокійно читає «1 лютого» і «вісім», а база ставить хрестик. За таким полем не відсортуєш і середнє не порахуєш.
id або код учасника, а не ім'я. Заразом це захищає приватність:
у публічному файлі стоїть 09-14, а не «Ковальчук Іван».У колонці «години сну» 29 чисел і одна клітинка зі словом «вісім». Ти просиш порахувати середнє — і воно виходить не таким, як очікував. Що сталося?
Однокласник зробив ключем таблиці колонку «Прізвище». Через тиждень у класі з'явився другий Мельник. Що зламається першим?
Тип важливіший за вигляд: вигляд можна перемкнути, тип — це те, що програма реально вміє з даними робити
| Тип | Що вміє | Приклад | Чого не можна |
|---|---|---|---|
| Текст | зберігати будь-що, шукати підрядок, групувати | Шевченко Т. | рахувати середнє |
| Число | сума, середнє, мінімум, максимум, сортування за величиною | 7, -3.5 | зберігати телефон — зникне перший нуль |
| Дата | сортування в часі, різниця в днях, групування за місяцем | 2026-02-01 | писати «1 лютого» словами |
| Логічний | лише два значення: так / ні | true, false | третій варіант «іноді» |
| Список (вибір) | значення тільки з наперед відомого набору | пішки, автобус, метро | вписати щось своє |
Тип — це обіцянка, яку таблиця дає формулам. Колонка числова —
отже, AVERAGE може розраховувати на числа. Порушив обіцянку хоч
в одній клітинці — ламається вся колонка, а не клітинка.
Вигляд — це маска поверх типу. Одне й те саме число 0.75
показують як 0,75, 75% або 75,0%.
Усередині лежить те саме.
Тому «поміняти формат» і «поміняти тип» — різні дії. Формат міняє зовнішність. Тип міняє зміст.
01.02.2026Це найдорожча помилка в роботі з даними. І вона не про програми, а про людей. Половина світу читає це як 1 лютого. Друга половина — як 2 січня. Сам запис не підказує, у якому порядку його склали.
Рятує міжнародний стандарт ISO 8601: дату пишуть як
РРРР-ММ-ДД. Найбільша одиниця стоїть першою, і кожна має
фіксовану довжину. Тому звичайне сортування за абеткою дає правильний
порядок у часі.
І ніяких «а в нас інакше». Запис 2026-02-01 у будь-якій
країні означає одне й те саме.
fields.json — тільки 2026-02-01. А на екрані форма
чи звіт можуть намалювати «1 лютого 2026», якщо так зрозуміліше людині.
Формат показу — справа інтерфейсу, формат зберігання — справа даних.SEPT1 він
перетворював на дату, MARCH1 — теж. Виправити це не змогли, тому
міжнародний комітет HGNC пішов іншим шляхом: перейменував 27 генів, щоб
таблиці припинили їх псувати. Тепер це SEPTIN1 і
MARCHF1.Клас з іншої школи прислав таблицю, де в колонці дат стоїть
04.03.2026 і 03.04.2026. Як точно дізнатися,
який це день і який місяць?
Ти вносиш у таблицю номер телефону 0501234567, а в клітинці
після Enter лишається 501234567. Що зробити?
Комп'ютер порівнює не сенс, а послідовність символів
Для тебе це одне місто. Для таблиці — три різні рядки символів: коди
літер у них не збігаються. Додай сюди зайвий пробіл у кінці, латинське
Kyiv і «м. Київ». У колонці на 30 записів раптом стане
шість «різних» міст.
Зверни увагу на середню плашку. COUNTIF в Excel і Google Sheets
не розрізняє великі й малі літери. Тому «київ» і «КИЇВ» він порахує
разом. А от зайвий пробіл і латинське Kyiv для нього — вже інші
значення.
Зведена таблиця й групування в Grist порівнюють точно. Там вилізуть усі шість варіантів. Виходить, одна колонка дає різні числа в різних програмах. Саме тому чистять дані, а не результати.
Не давай людині набирати текст там, де можна дати вибір. У полі-списку
(select, radio) варіанти задані наперед. Написати «київ» з малої
там просто нема як. Тому вільний текст лишають для того, що справді
неможливо передбачити.
"type": "select" з масивом options. Вільний текст —
максимум одне-два поля на всю анкету.У зведеній таблиці замість очікуваних 5 гуртків вийшло 11 рядків: «Футбол», «футбол», «Футбол » і так далі. Що ти зміниш в анкеті, щоб наступного разу цього не сталося взагалі?
Формат, у якому дані переходять із будь-якої програми в будь-яку — і в якому найлегше все зламати
CSV — це звичайний текстовий файл, який можна відкрити Блокнотом. Один рядок файлу — один запис таблиці. Кома — межа між полями. Перший рядок зазвичай заголовок.
Назва так і читається: comma-separated values, значення, розділені комами. Файл важить копійки, і його вміє читати кожна мова програмування.
Правила описані в RFC 4180 (2005). Їх усього чотири, але за кожним стоїть чиясь зіпсована таблиця:
| Правило | Що воно рятує |
|---|---|
| Поле можна взяти в подвійні лапки | усе всередині лапок — одне поле, хай там хоч десять ком |
| Поле з комою, лапками або переносом рядка треба взяти в лапки | "Ковальчук, Іван" лишається одним полем |
| Лапки всередині поля подвоюють | "" означає одну літеральну лапку у значенні |
| В усіх рядках однакова кількість полів | таблиця лишається прямокутною |
Найважливіше в цій схемі — подвоєні лапки. Поле взяте в лапки,
а всередині стоїть "". Це означає одну лапку у значенні.
Іншого способу екранування в CSV немає — зворотних слешів, як у JSON,
тут не буває.
Так, це дозволено. Але тільки якщо поле взяте в лапки. Тоді один запис займає в файлі кілька рядків. І розбір «читаємо файл порядково» на ньому розвалюється.
Саме тому CSV читають посимвольно. Програма тримає один прапорець: «я зараз усередині лапок чи ні».
Тут 4 фізичні рядки файлу, але лише 3 записи плюс заголовок: третій запис займає два рядки, бо в його полі є перенос.
Однокласник надіслав таблицю, у якій прізвище «Ковальчук, Іван» з'їхало у два стовпці, а всі дані праворуч зсунулися на одну колонку. Що сталося?
Тобі треба записати в CSV коментар він сказав "ок".
Як виглядатиме це поле у файлі?
Файл із самих байтів не знає, якою мовою його читати
Ось у чому річ: у CSV немає заголовка з назвою кодування. HTML має
<meta charset="utf-8">, XML має декларацію. CSV не має
нічого. Тож програма, яка відкриває файл, мусить здогадатися. Excel
на Windows здогадується за мовою системи, а не за вмістом.
Українська літера в UTF-8 займає два байти. Windows-1251 — однобайтове кодування. Читаючи ним, програма робить із кожного байта окремий символ. Тому з чотирьох літер виходить вісім значків:
| Що записали | Байти в UTF-8 | Що показав Excel |
|---|---|---|
| Київ | D0 9A D0 B8 D1 97 D0 B2 | РљРёС—РІ |
| Львів | D0 9B D1 8C D0 B2 D1 96 D0 B2 | Львів |
На самому початку файлу можуть стояти три службові байти
EF BB BF. Це позначка BOM, byte order mark. Для UTF-8
вона технічно не потрібна, і стандарт Unicode її не радить.
Але саме за нею Excel на Windows упізнає UTF-8. З нею він перестає малювати «кракозябри». Тому в реальному житті ця позначка живе й далі.
id раптом перестає збігатися з
id. Тому питай себе не «як правильно», а «хто читатиме цей файл».
Для Excel — з BOM. Для сервера, скрипта, чекера — без BOM.Українською, німецькою чи французькою дробову частину відділяють комою:
7,5. Кома вже зайнята. Тому в цих мовах Excel розділяє поля CSV
крапкою з комою. Файл, збережений в українському Excel, часто виглядає
так:
Формально це вже не «comma-separated». Але так роблять усі, і програми це вміють читати — треба лише вказати роздільник явно. Гірше інше. Віддай такий файл програмі, яка чекає кому, — і вона прочитає весь рядок як одну колонку.
Мовних звичок у програми ціла купа. Чим відділяти дробову частину, чим розділяти колонки, у якому порядку писати число дати, як звуться місяці. Усе це разом називають локаллю — набором налаштувань під конкретну країну й мову. Тому «той самий Excel» на двох комп'ютерах поводиться по-різному.
| Симптом | Причина | Що робити |
|---|---|---|
| Замість тексту «РљРёС—РІ» | UTF-8 прочитали як Windows-1251 | імпортувати, вказавши UTF-8; або зберегти з BOM |
| Уся таблиця в одному стовпці | роздільник ;, а чекали , | вказати роздільник у діалозі імпорту |
0501234567 стало 501234567 | колонку визначено як числову | у діалозі імпорту дати колонці тип «текст» |
1,23E+11 замість довгого номера | велике число показано в науковому форматі | те саме: колонка має бути текстовою |
SEPT1 стало 1 вер | автовизначення типу «дата» | тип «текст» при імпорті |
.xls, у якому аркуш вміщає
65 536 рядків. Коли аркуш заповнився, нові рядки просто мовчки не
додавалися. Помилки не було — було переповнення, про яке ніхто не дізнався.
Формат даних — це не дрібниця.Ти вивантажив із Grist файл responses.csv, відкрив його
подвійним кліком в Excel — і замість прізвищ бачиш «РљРёС—РІ».
Файл зіпсований?
Твій скрипт-чекер на сервері читає CSV і скаржиться, що не знаходить
колонку id, хоча в першому рядку файлу вона є. Що найімовірніше
сталося?
Встав будь-який CSV — сторінка розбере його так само, як це зробить сервер, і покаже, де він ламається
Парсер працює посимвольно за правилами RFC 4180. Він тримає той самий прапорець: «я всередині лапок». Тип колонки він визначає за її вмістом. А клітинки, які цьому типу суперечать, підсвічує.
Домовленість. Колонка, чия назва в заголовку закінчується на
*, вважається обов'язковою. Порожня клітинка в ній —
помилка.
Типи рахуються тільки за рядками з правильною кількістю колонок. Зламаний рядок не має права зіпсувати статистику.
Спробуй полагодити «приклад із помилками» просто в полі:
візьми Ковальчук, Іван у лапки, перепиши дату в ISO, заповни
порожній id* — і дивись, як зникають червоні плашки.
Парсер каже: «запис 5 — 7 полів замість 6». Ти дивишся на рядок і бачиш там прізвище з комою. Скільки записів у файлі зіпсовано?
П'ять функцій, яких вистачить на весь модуль
Формула — це не «підрахувати». Формула — це питання до таблиці. Питання поставлене так, щоб таблиця могла відповісти сама. І щоб відповідь оновилася, щойно з'явиться новий рядок.
| Питання | Формула | Що робить |
|---|---|---|
| Скільки всього годин? | =SUM(C2:C31) | додає числа в діапазоні, текст ігнорує |
| Скільки в середньому? | =AVERAGE(C2:C31) | сума ділиться на кількість непорожніх числових клітинок |
| Скільки їздять метро? | =COUNTIF(E2:E31;"метро") | рахує клітинки, що збігаються з умовою |
| Хто недосипає? | =IF(C2<7;"мало";"норма") | перевіряє умову і повертає одне з двох значень |
| Як розшифрувати код? | =XLOOKUP(A2;Довідник!A:A;Довідник!B:B;"немає") | шукає ключ в одному стовпці, повертає значення з іншого |
Тепер розберемо кожну на своїй таблиці. Уяви, що анкету заповнили
30 однокласників. Їхні відповіді лежать у рядках з 2-го по 31-й. У колонці
C — години сну, у колонці E — як людина дістається
до школи.
=SUM(C2:C31) складає всі числа з колонки C.
Саме число «разом» тобі навряд чи знадобиться. Зате воно одразу показує,
чи всю колонку таблиця бачить як числа. Хтось написав «сім» словами —
і сума мовчки стане меншою.
=AVERAGE(C2:C31) ділить суму на кількість заповнених клітинок.
Ключове слово — заповнених. Троє не відповіли, порожні клітинки в лік
не йдуть, і середнє рахується по 27 людях, а не по 30.
А от нуль — це відповідь. Якщо хтось написав 0, він
потрапить у розрахунок і потягне середнє вниз.
=COUNTIF(E2:E31;"метро") проходить колонку E
й рахує, скільки разів там стоїть слово «метро». Це і є твоя перша
діаграма: та сама формула зі словом «автобус», потім із «пішки».
Ось де мститься неохайність із попереднього кроку. «Метро» з великої літери ця формула теж порахує, а от «метро » із пробілом у кінці — уже ні.
=IF(C2<7;"мало";"норма") дивиться на години одного учня
й пише поруч одне з двох слів. Формулу ставлять у сусідню колонку
й протягують на всі 30 рядків.
Виходить нова колонка, яку далі можна порахувати тим самим
COUNTIF — і дізнатися, скільки в класі тих, кому «мало».
У відповідях зручно тримати короткі коди: 9A, 9B.
Але в звіті мають стояти нормальні назви. Тоді поруч заводять другий
аркуш-довідник: у першій колонці код, у другій — розшифровка.
=XLOOKUP(A2;Довідник!A:A;Довідник!B:B;"немає") бере код
із клітинки A2, шукає його в першій колонці довідника й повертає
те, що стоїть навпроти в другій. Останній аргумент — слово, яке з'явиться,
якщо такого коду в довіднику немає.
COUNTIF(E2:E31,"метро"), в українській —
COUNTIF(E2:E31;"метро"). Обидва записи правильні; неправильно
лише сліпо копіювати з інтернету. Скопіював чужий запис — і в клітинці
з'явиться #NAME?. Це відповідь програми «я не впізнала того,
що ти написав»: або ім'я функції з друкарською помилкою, або зайвий знак
у списку аргументів.VLOOKUP проти XLOOKUPОбидві функції роблять одне: «маю ключ — дай мені рядок — візьми з нього потрібне значення». Різниця в зручності й у кількості способів помилитися.
VLOOKUP | XLOOKUP | |
|---|---|---|
| Як задають результат | номером колонки: 2, 3 | прямо діапазоном результату |
| Вставили колонку в довідник | номер «з'їхав», формула тихо бреше | нічого не ламається |
| Шукати ліворуч від ключа | не вміє | вміє |
| Якщо не знайдено | #N/A — «такого ключа немає»; щоб замість помилки стояв твій текст, формулу обгортають у IFERROR | четвертий аргумент: що повернути |
| Де доступна | скрізь, зокрема в старих файлах | Excel 365 / 2021+, Google Sheets, LibreOffice 24.8+ |
Висновок простий: якщо XLOOKUP є — бери його. Якщо працюєш
із чужим старим файлом, читати VLOOKUP усе одно доведеться вміти.
$: що фіксуємо, а що ніКоли формулу тягнуть униз, посилання в ній їдуть разом із рядком. Це зручно. Але буває клітинка, на яку мають дивитися всі рядки — підсумок або норма. Ось тоді й потрібен знак долара.
| Запис | Що зафіксовано | Після протягування з рядка 2 у рядок 7 |
|---|---|---|
B2 | нічого | B7 |
$B2 | колонка | $B7 |
B$2 | рядок | B$2 |
$B$2 | і колонка, і рядок | $B$2 |
C2 має їхати за рядком — це «мої години». А $C$33 —
клітинка з підсумком, вона одна для всіх. Прибереш $ — і в сьомому
рядку формула стане C7/C38. Клітинка C38 порожня,
а порожнеча для формули дорівнює нулю.
Ділити на нуль не можна, тому в клітинці з'явиться #DIV/0!.
Це скорочення від англійського division by zero — «ділення на нуль».
Grist — не Excel. Формули там пишуть мовою Python. І на колонки
посилаються не літерами, а іменами: $sleep_hours.
Знак $ тут означає не «зафіксувати», а «поле цього запису».
Протягувати теж нічого не треба: формула колонки одразу діє на всі рядки.
Звичні SUM, AVERAGE, IF у Grist теж
є — просто в пітонівському записі. А от підсумок по всій колонці формулою
внизу там не пишуть.
Замість цього Grist робить окрему таблицю з підсумками. У ній один рядок на кожну групу: скільки людей їздять метро, скільки автобусом, скільки ходять пішки. І середні години сну для кожної групи окремо.
Від звичайної таблиці вона відрізняється тим, що її ніхто не заповнює руками. З'явився новий рядок відповіді — підсумки перерахувалися самі. Англійською таку таблицю звуть summary table, українською — зведеною таблицею.
Формулу =B2*$D$1 протягнули з рядка 2 у рядок 7.
Якою вона стала?
Ти написав =AVERAGE(C2:C31), але заповнено лише 18 анкет,
решта рядків порожні. Середнє вийшло «занадто хороше». У чому річ?
Сьогоднішній fields.json — це креслення, за яким
наступного уроку зросте справжня форма
Тобто помилка в типі поля, зроблена сьогодні, дійде аж до діаграми на уроці 6. Зробиш «години сну» текстовими — Chart.js не намалює по них нічого. Лишиш «місто» вільним текстом — на кругову діаграму піде одинадцять секторів замість п'яти.
fields.json| Ключ | Навіщо |
|---|---|
name | ім'я колонки в CSV і атрибут name у формі. Латиниця, нижній регістр, _ замість пробілів |
type | text, number, date, boolean, select |
required | чи стане поле required у формі й обов'язковим у перевірці |
min, max | межі для чисел і дат: відсіюють «120 годин сну» |
options | варіанти для select — той самий словник, що рятує від «Київ / київ» |
hint | підказка під полем: половина помилок зникає, коли людині пояснили, чого від неї хочуть |
Години сну в заголовку CSV живе рівно доти, доки файл
не пройде через програму з іншим кодуванням або через URL. Пробіл у імені
ламає атрибут name у формі. Тому sleep_hours —
і жодних пробілів, крапок і дужок. Людське пояснення живе в
hint і в README.md, а не в назві колонки.~/data/.Ти хочеш спитати «скільки часу витрачаєш на дорогу до школи». Який тип поля обереш і що додаси, щоб дані потім можна було порахувати?
Виконуй по черзі. Галочки зберігаються — сторінку можна закрити.
form, а урок — третій
Практика зветься за темою, а не за номером уроку: усе кладемо в
~/www/form/. Теку lesson-03 шукати не треба —
її немає.check 03Система прочитає твої два файли, звірить їх зі схемою й перевірить права на теки. Спроби не обмежені.
Демонстраційний режим: результат згенеровано для показу. На сервері
ця кнопка викликає POST /api/check, який запускає чекер уроку
від імені учня.
$ і пояснюєш,
що станеться, якщо його прибрати;Це те, що бачить учитель, коли виставляє оцінку.
| Складник | Вага | Результат |
|---|
| Симптом | Причина й лікування |
|---|---|
| Не рахується середнє по колонці | колонка текстова або в ній є слово; дай колонці тип «число» і полагодь клітинку |
| Прізвище з'їхало у два стовпці | кома в полі без лапок; загорни поле в "…" |
| Дати сортуються неправильно | вони текстові й у форматі ДД.ММ.РРРР; перепиши в ISO РРРР-ММ-ДД |
| Замість тексту «РљРёС—РІ» | UTF-8 прочитали як Windows-1251; імпортуй із явним кодуванням |
| Скрипт не бачить першу колонку | BOM на початку файлу приклеївся до назви; збережи без BOM |
| Формула «з'їхала» при протягуванні | забув $ у посиланні на клітинку підсумку |
| У зведенні 11 гуртків замість 5 | вільний текст замість списку; зроби поле select з options |
Додай у fields.json ще одне поле. У
README.md напиши про нього два-три речення. Який тип ти обрав,
чому саме такий і яку формулу застосуєш, коли зберуться відповіді.
Якщо поле числове, постав min і max. І поясни,
від якої дурниці вони захищають.
Звідки взяті правила й числа на цій сторінці
csv — як розбір улаштований у справжній бібліотеці
(діалекти, quotechar, doublequote).EF BB BF і чому для UTF-8
позначка не обов'язкова.РРРР-ММ-ДД як єдиний однозначний запис дати.01.02.2026 у різних країнах
різні дні.SEPT1 → SEPTIN1, MARCH1 → MARCHF1; офіційний
реєстр символів — genenames.org
(HGNC)..xls
у 65 536 рядків.COUNTIF — зокрема те, що функція не розрізняє великі
й малі літери.XLOOKUP і
VLOOKUP — аргументи й межі застосування.$.$column_id.Дані в прикладах на цій сторінці вигадані для навчання: прізвища, години сну й дати не належать нікому з класу. Реальні дані з'являться на наступному уроці — і одразу поїдуть у приватну теку.