Как стать автором
Обновить

Комментарии 26

BLOB — булевая переменная

Булевая - это true/false. Блобы - это массивы двоичных данных обычно.

Спасибо, исправил

Цитата:
"В sqlite3 существуют: INTEGER — вещественное число с указанной точностью, TEXT — текст, BLOB — двоичные данные, REAL — число с плавающей запятой(float24), NUMERIC — то же, что и INTEGER.
...
У каждого из них есть параметры NULL, UNIQUE, DEFAULT, "
Вопросы:

  1. Существуют кто? Типы данных полей таблиц?

  2. У кого есть параметры у типов данных, или конкретных полей таблиц?

INTEGER — вещественное число с указанной точностью,

а это вот совсем не смущает?

Автору платят за буквы, а не за фидбэки.
Не стреляйте в пианиста.
PS. и не ищите смысла там, где его нет

У каждого из них есть параметры NULL, UNIQUE, DEFAULT

Во-первых, это не параметры поля, а ограничения таблицы (CHECK constraints). Просто указанные ограничения ограничивают значения только в одном поле.

Во-вторых, корректно [NOT] NULL. А с учётом того, что при отсутствии этого ограничения вообще значением по умолчанию является именно NULL, то корректнее указать хотя бы только NOT NULL. Но уж никак не только NULL.

В третьих, поле может иметь "параметр" CHECK. Допускаю, что он опущен по причине того, что его обработка куда как сложнее указанных "параметров", да и вообще он по сути есть ограничение именно таблицы, а не поля, но вообще о нём не сказать - неправильно. То же касается и PRIMARY / FOREIGN KEY.

IMHO для sqlite проще писать запросы на sql. Мне кажется, что так удобнее, чем изучать сто двадцать пятый по счету ORM.

К сожалению без знания того же Room будет трудно вписаться в команду какой-нибудь андроид разработки. Мне иногда кажется, что sql воспринимается более сложным для многих начинающих разработчиков, чем основной язык программирования, и ORM в какой-то степени решает эту проблему, наряду с другими.

мне однажды пришлось переносить базу из sqlite на postgresql, а потом соответственно исправлять код приложения, и знаете, я очень пожалел что не использовал ORM, переписывать пришлось абсолютно все запросы и код обрабатывавший результат

Ну, если в будущем стоит вопрос масшабирования, то имеет смысл писать запросы на чистом SQL, не используя диалекты. Это минимизирует подобные проблемы. Но это исключительно мое мнение и мое видение. Конечно, ситуации могут быть разными.

Много ли на "чистом" sql можно написать такого что нельзя сделать через orm? Зато orm даёт больше безопасности.

Много ли на "чистом" sql можно написать такого что нельзя сделать через orm?

Ой, много! Достаточно регулярны вопросы на форумах типа "как вот это реализовать в моём ОРМ" - и изрядная их часть решается исключительно использованием RAW SQL..

В целом очень хорошая демонстрация, как НЕ нужно реализовывать ORM:

  • ActiveRecord довольно проблемная вещь, но даже закрывая глаза на это:

  • Первичных ключей нет, как ваш INSERT OR REPLACE поймет, что заменять? Точнее он-то поймет по UNIQUE, но у вас в примерах он нигде не указан. А если будет несколько полей с UNIQUE?

  • Внешние ключи реализованы через JSON, ломая весь смысл проверки целостности. Ваш же пример с удалением Box ломает данные.

  • Из-за этого же гарантированный N+1 на любой запрос.

  • filter сразу же выполняет запрос, последующие фильтры выполняются на стороне ORM. То есть Foo.objects.filter(a=1).filter(b=2) сначала прочитает лишние записи (еще помним про N+1), съест RAM, потому будет тратить такты CPU на фильтрацию.

  • filter поддерживает только сравнение на =, а как быть с другими операторами?

  • На каждый запрос делается новое подключение к БД.

  • ... тут можно еще писать и писать ...

Если это учебный материал, то ИМХО стоит в нем показывать как делать правильно, а не писать код ради "использования много сложных инструментов языка python, которые помогли написать короткий и красивый код, решающий довольно сложную задачу", тем более что код не слишком "короткий и красивый" и задачу решает, мягко говоря, так себе.

Если задача была продемонстрировать, как устроены другие ORM, то с уверенностью могу заявить, что ни одна популярная ТАК не устроена.

Хотя SQLite называют СУБД, фактически это просто библиотека для работы с массивом данных через SQL подобный интерфейс. Поэтому реализация некоторых аспектов ORM на уровне приложения может быть не ошибкой, а необходимостью.

Вот для примера, несколько интересных моментов по вашим замечаниям:

  • SQLite может вообще не контролировать соответствие схемы таблицы и типов передаваемых данных. Если добавить сюда динамичную природу Python, то инкапсуляция всей работы с данными в ActiveRecord уже не плохая идея.

  • Первичного ключа может не быть, физически даже ROWID в таблице может не быть. Так что использование UNIQUE частая и уместная практика в SQLite.

  • Проверка целостности по внешним ключам - опциональная для SQLite фича, полностью по желанию клиента. Поэтому использование собственной реализации через JSON может быть оправдано.

  • Подключение к SQLite это просто получение ручки на файл или оперативную память. Дорогостоящих операций установки соединения или прогрева отдельного процесса тут нет. Зато бывают сценарии когда нужно закрывать соединение для слаженной работы с локом файла.

Стоит учитывать специфику. Некоторые практики, привычные нам по опыту работы только с "большими СУБД", могут быть просто не применимы для SQLite.

То что специфику учитывать нужно спору нет, но я процитирую автора:

sqlite3 выбран из-за простоты, нетрудно заменить обращения к нему на обращения к любой удобной для вас базе данных

и вот тут с большими СУБД будут проблемы. Не хочется, чтобы новички переносили такой опыт на серьезные проекты.

А зачем лишать себя целостности по внешним ключам, если она есть из коробки?

Приведите пример когда нужен unique, но не primary key. По моему primary key и unique отличаются только тем, что unique может быть null

Приведите пример когда нужен unique, но не primary key.

Например, когда нужны два UNIQUE constraint на одной таблице.

Приведите пример когда нужен unique, но не primary key. По моему primary key и unique отличаются только тем, что unique может быть null

SQLite используют для конфигурации клиентских приложений, ручной работы с данными и даже как формат упаковки данных для передачи между приложениями.

Первичного ключа может не быть если есть:

  • Накопление данных, затем идентификация

  • Построение уникального индекса после того как заполнили таблицу

  • Проверка уникальности для колонки с динамической типизацией

  • Человекочитаемость и эргономика, несколько равноправных идентификаторов, явное указание колонок в ссылке и т.д.

Это конечно не значит что нужно использовать UNIQUE вместо первичного ключа в общем случае, но учитывать стоит если мы хотим получить гибкое и функциональное решение.

Иначе получится как у разработчика Tortoise ORM. Не знаю, как сейчас, но в начале он на полном серьёзе думал что у вас в каждой таблице есть колонка с названием "id" как первичный ключ. Все операции ORM были реализованы на этом допущении. Думаю не нужно пояснять чем такое решение плохо.

А зачем лишать себя целостности по внешним ключам, если она есть из коробки?

В SQLite проверка целостности опциональная, должна включаться при каждом подключении клиента специальной PRAGMA.

Если ваша система распределенная, есть много клиентов, или какие то из этих клиентов недоверенные, то считайте что проверки нет. Любой клиент может эту проверку не включать и таким образом испортить данные, несмотря на внешние ключи.

Это конечно не значит что всем нужно бежать и реализовывать свой механизм целостности, вместо штатного, на уровне приложения или ORM. Но как минимум такая попытка имеет смысл.

Можно было бы поспорить с тем, что это получился ORM, скорее это типобезопасный DSL для SQL-генератора (в Java например есть QueryDSL, очень похоже). Добавьте автогенератор классов по схеме, будет один в один, нишевая библиотека для своих задач.

Прямо сейчас пишу себе микро-орм для Монги с поддержкой motor_asyncio. Мне не нравится дизайн многих орм в которых параметры соединения и сессии передаются через глобальные переменные и плохо работают с asyncio. Поэтому я начал с класса Adapter, который хранит в себе сессию и является фабрикой для моделей. Хочу чтобы интерфейс выглядел примерно так:

class User(metaclass=orm.Model):
  id_ = orm.Field(index=ASCENDING)
  name = orm.Field()

adapter = Adapter("mongodb://localhost:27017")
# protected определяет, будут ли методы типа insert, delete, update среди методов user_class
user_class = adapter.retrieve_model(User, database="test", collection="users", protected=False)
await user = user_class.find(name="test")
await user_class.insert(User(name="test2"))

orm.Model делает свои классы наследуемыми от ProtectedModel / FreeModel, которые имеют разный набор CRUD операций. Adapter.retrieve_model добавляет инстанс AsyncIOMotorCollection в новый класс.
В принципе, всё вроде получается, но если кто-то подскажет хороший проект для референсов или ещё чего - будет круто.

Вы точно уверены, что NUMERIC это то же самое, что INTEGER? Потому, что авторы sqlite с вами категорически несогласны: "column with NUMERIC affinity may contain values using all five storage classes".
NUMERIC - специальный тип, который может хранить любой из всех остальных типов. Согласитесь, что это совсем не то же самое, что и "INTEGER".

SQLite умеет динамически выбирать размер целочисленных данных в зависимости от фактической размерности значений. Это актуально только для процесса сериализации данных на хранение.

Клиентский драйвер вам всегда будет распаковывать и выдавать 64 битное знаковое число.

Его название в схеме таблицы не более чем сахар для адаптации различных диалектов SQL, можете называть как хотите: NUMERIC, INTEGER, INT, BIGINT, etc.

А нет же. Сам сказанул не то, сам себя поправлю.

По официальной документации SQLite, действительно есть NUMERIC тип, и это совсем не то же самое что и INTEGER.

Мне как и автору видимо привычно числа с фиксированными точностью и масштабом называть DECIMAL, поэтому такая путаница возникла.

НЛО прилетело и опубликовало эту надпись здесь

Недостаточно. DS-ники еще более ленивы чем люди. Они неплохо знают SQL, но предпочитают выдернуть все данные по SELECT * FROM целиком. И вместо написания утомительных CTE/JOIN - всё остальное делают в Pandas/аналогах, в небольших ячейках кода Jupyter/Lab. Просто потому что там быстрее, удобнее, "однострочнее" выходит, не теряется контекст (вычисленные значения висят в RAM).

Трех-экранные SELECT-запросы в той же 1С задолбали, работать с ними невероятно тяжело, даже их авторам. Прямое подтверждение - в современных типовых конфигурация 1С количество таблиц/views превысило разумное (10000+). При том что бухучет на бумаге ведется всего в трех таблицах: Главной книге, пяти однотипных Журналах-ордерах и Кассовой книге. И с этим учетом были сделаны все великие стройки страны.

Поэтому ORM и Pandas - никуда не уйдут (как и SQL), т.к. по-своему удобны на разных задачах. Они дают вам те самые "три таблицы", если не перетягивать в одну сторону одеяло технологий, а использовать все.

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

Зарегистрируйтесь на Хабре, чтобы оставить комментарий