<!doctype html><html lang="ru"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="color-scheme" content="light"><meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; worker-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self'; img-src 'self' data:; object-src 'none'; base-uri 'none'"><title>DuckDB — SQL-мастерская: CSV, JSON, Parquet</title><link href="vendor/bootstrap.min.css" rel="stylesheet"><link href="style.css" rel="stylesheet"><script type="module" src="app.js"></script></head><body>
<main class="container-fluid workshop"><header class="hero"><span class="eyebrow">Лаборатория данных · ae-nekrasov.ru</span><h1>Из выгрузки — в понятный отчёт</h1><p>Настоящий DuckDB в браузере: девять SQL-задач на данных сервисной CRM.</p><p class="small" id="version">DuckDB-Wasm 1.32.0 · ядро 1.4.3 · загрузка по кнопке</p></header>
<div class="row g-3"><aside class="col-xl-4"><section class="card p-3"><h2>1. Выберите задачу</h2><label for="scenario" class="form-label">Практический сценарий</label><select id="scenario" class="form-select"></select><p id="why" class="mt-3"></p><div class="tip" id="task"></div><details class="mt-3"><summary>Какие данные внутри</summary><p class="small mt-2">24 строки CSV: 20 заказов после очистки, 8 клиентов, 6 JSON-событий. Есть повтор O004, неверная дата, сумма и пустой ID. Клиент 999 отсутствует в справочнике.</p><p class="small">Таблицы: <code>orders_raw</code>, <code>orders_clean</code>, <code>clients</code>, <code>events</code>, <code>parquet_orders</code>. Денежные суммы — целые копейки.</p><div class="links"><a href="fixtures/orders.csv" download>orders.csv</a><a href="fixtures/clients.csv" download>clients.csv</a><a href="fixtures/events.json" download>events.json</a><a href="fixtures/orders.parquet" download>orders.parquet</a></div></details></section>
<section class="card p-3 mt-3"><h2>Попробовать свою выгрузку</h2><label for="file" class="form-label">CSV с заголовком или JSON · UTF-8 · до 2 МиБ</label><input type="file" class="form-control" id="file" accept=".csv,.json"><p class="small mt-2" id="upload-note">Свой файл не выбран.</p><button class="btn btn-outline-secondary" id="clear-file">Убрать свой файл</button><p class="small mt-3 mb-0">Файл читается локально через File API, без отправки на сервер. CSV импортируется строками; JSON — с определением структуры. Перезагрузка страницы удалит его из памяти. Для крупных файлов используйте Node.js из исходников.</p></section></aside>
<section class="col-xl-8"><div class="card p-3"><h2>2. Измените и выполните SQL</h2><label for="sql" class="form-label">Один SELECT или WITH · до 16 КиБ</label><textarea id="sql" class="form-control sql" rows="13" spellcheck="false" autocomplete="off"></textarea>
<details class="mt-3" open><summary>Параметры запроса $1, $2, $3</summary><div class="form-check mt-2"><input class="form-check-input" type="checkbox" id="with-params"><label class="form-check-label" for="with-params">Передавать три параметра отдельно от SQL</label></div><div class="row g-2 mt-1"><div class="col-sm-4"><label for="from" class="form-label">$1 · начало включительно</label><input type="date" id="from" class="form-control"></div><div class="col-sm-4"><label for="to" class="form-label">$2 · конец не включается</label><input type="date" id="to" class="form-control"></div><div class="col-sm-4"><label for="minimum" class="form-label">$3 · минимум, коп.</label><input id="minimum" class="form-control" inputmode="numeric"></div></div></details>
<div class="actions mt-3"><button class="btn btn-primary" id="run">Выполнить SQL</button><button class="btn btn-outline-danger" id="stop" disabled>Остановить</button><button class="btn btn-outline-secondary" id="reset">Сбросить пример</button><button class="btn btn-outline-secondary" id="back">Назад к запросу</button></div>
<p class="small text-muted mt-3 mb-0">Первый запуск загружает около 44 МБ ядра и расширений до сжатия. Результат: до 500 строк / 1 МиБ; SQL: до 10 секунд. Отмена завершает Worker, следующий запуск восстановит таблицы. Внешнее чтение файлов и установка расширений после импорта отключены.</p></div>
<div id="status" class="alert alert-info mt-3" role="status" aria-live="polite"></div><section class="card p-3"><h2>3. Проверьте и сохраните результат</h2><p id="count" class="small text-muted"></p><div id="result" class="table-wrap" role="region" aria-label="Таблица результата с горизонтальной прокруткой" tabindex="0"></div><div id="chart" class="mt-3"></div><div class="actions mt-3"><button class="btn btn-outline-primary" id="json" disabled>Скачать JSON</button><button class="btn btn-outline-primary" id="csv" disabled>CSV для таблиц</button><button class="btn btn-outline-secondary" id="sql-file">Скачать SQL</button></div><p class="small text-muted mt-2 mb-0">CSV: кавычки, UTF-8 BOM и защита текстовых ячеек от формул. JSON: BIGINT и DECIMAL сохраняются строками; NaN и Infinity тоже. Учитывайте типы при чтении отчёта.</p></section></section></div>
<footer class="mt-4"><a href="../duckdb-project.zip" download>Скачать проект, тесты и инструкции Windows/Linux</a><p class="small mt-2">Демо не хранит данные постоянно и не заменяет транзакционную базу приложения. Авторские исходники MIT · <a href="licenses/THIRD-PARTY-NOTICES.txt">лицензии компонентов</a>.</p></footer></main></body></html>
