Комната-студия Android на несколько столов

Обновлено: 14.08.2022

Вывод: в этом руководстве показано, как использовать предложение внутреннего соединения SQLite для запроса данных из нескольких таблиц.

Введение в предложение внутреннего соединения SQLite

В реляционных базах данных данные часто распределяются по многим связанным таблицам. Таблица связана с другой таблицей с помощью внешних ключей.

Чтобы запросить данные из нескольких таблиц, используйте предложение INNER JOIN. Предложение INNER JOIN объединяет столбцы из связанных таблиц.

Предположим, у вас есть две таблицы: A и B.

A содержит столбцы a1, a2 и f. B имеет столбцы b1, b2 и f. Таблица A связывается с таблицей B с помощью столбца внешнего ключа с именем f.

Следующее иллюстрирует синтаксис внутреннего предложения соединения:

Для каждой строки в таблице A предложение INNER JOIN сравнивает значение столбца f со значением столбца f в таблице B. Если значение столбца f в таблице A равно значению столбца f в таблице B, он объединяет данные из столбцов a1, a2, b1, b2 и включает эту строку в результирующий набор.

Другими словами, предложение INNER JOIN возвращает строки из таблицы A, которым соответствует строка в таблице B.

Эта логика применяется, если вы присоединяетесь к более чем 2 таблицам.

См. следующий пример.

SQLite Inner Пример соединения

В результирующий набор включены только строки в таблице A: (a1,1), (a3,3) с соответствующими строками в таблице B (b1,1), (b2,3).

На следующей диаграмме показано предложение INNER JOIN:

 Диаграмма Венна внутреннего соединения SQLite

Примеры внутреннего соединения SQLite

Давайте взглянем на таблицы треков и альбомов в образце базы данных. Таблица треков связана с таблицей альбомов через столбец AlbumId.


В таблице треков столбец AlbumId является внешним ключом. И в таблице альбомов AlbumId является первичным ключом.

Чтобы запросить данные из таблиц треков и альбомов, используйте следующий оператор:

Пример SQLite Inner Join 2 Tables

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

Вы можете включить столбцы AlbumId из обеих таблиц в окончательный набор результатов, чтобы увидеть эффект.

SQLite Inner Пример соединения

Внутреннее соединение SQLite — пример с 3 таблицами

См. следующие таблицы: треки, альбомы и исполнители


Один трек принадлежит одному альбому, а в одном альбоме много треков. Таблица треков связана с таблицей альбомов через столбец id альбома.

Один альбом принадлежит одному исполнителю, а у одного исполнителя один или несколько альбомов. Таблица альбомов связана с таблицей исполнителей через столбец artistid.

Чтобы запросить данные из этих таблиц, вам нужно использовать два внутренних предложения соединения в операторе SELECT следующим образом:

 SQLite Inner Join 3 таблицы

Вы можете использовать предложение WHERE, чтобы получить треки и альбомы исполнителя с идентификатором 10 в виде следующего оператора:

SQLite INNER JOIN с предложением WHERE

В этом руководстве вы узнали, как использовать предложение SQLite INNER JOIN для запроса данных из нескольких таблиц.

Объекты доступа к данным, или DAO, используются в Room для доступа к сохраненным данным вашего приложения. По сравнению с построителями запросов или прямыми запросами они представляют собой лучший и более модульный способ доступа к вашей базе данных. Вы также должны отметить, что DAO не обязательно должен быть только классом. Если это абстрактный класс, он может иметь функцию Object() < [собственный код] >, которая принимает в качестве параметра только базу данных RoomDatabase. Во время компиляции Room создает каждую реализацию DAO.DAO позволяет выполнять различные операции, такие как вставка, обновление, удаление и выполнение необработанных запросов. Вы также можете легко интегрировать LiveData, RxJava Observables и Kotlin Coroutines в DAO.

Вставка

Комната создает реализацию, которая вставляет все параметры в базу данных в одной транзакции, когда вы аннотируете метод DAO с помощью @Insert.

Котлин

  1. OnConflictStrategy.REPLACE: для замены старых данных и продолжения транзакции.
  2. OnConflictStrategy.ROLLBACK: для отмены транзакции.
  3. OnConflictStrategy.ABORT: для отмены транзакции. Транзакция была отменена.
  4. OnConflictStrategy.FAIL: для сбоя транзакции. Транзакция была отменена.
  5. OnConflictStrategy.NONE: игнорировать конфликт.

Примечание. Стратегии ROLLBACK и FAIL больше не поддерживаются. Вместо этого используйте ABORT.

Котлин

Удаление

Когда вы создаете метод DAO и аннотируете его с помощью @Delete, Room создает реализацию, которая удаляет набор сущностей из базы данных в соответствии с параметрами. Он ищет объекты для удаления с помощью первичных ключей.

Котлин

Простые вопросы

Основной аннотацией, используемой в классах DAO, является @Query. Он позволяет читать и записывать данные из базы данных. Поскольку каждый метод @Query проверяется во время компиляции, при возникновении проблемы с запросом возникает ошибка компиляции, а не сбой во время выполнения. Room также проверяет возвращаемое значение запроса, поэтому, если имя поля в возвращаемом объекте не соответствует именам соответствующих столбцов в ответе на запрос, Room уведомляет вас одним из двух способов: выдает предупреждение, если только подмножество имена полей совпадают. Если имена полей не совпадают, возвращается ошибка.

Котлин

Включение параметров в запрос

Параметры, передаваемые методам DAO, можно использовать в запросах, написанных с аннотацией @Query.

Котлин

Возвращаются подмножества столбцов

В комнате вы также можете возвращать подмножества столбцов из запроса.

Котлин

Запросы к нескольким таблицам

Некоторым из ваших запросов может потребоваться доступ к нескольким таблицам для вычисления результата. Room позволяет писать любые запросы, в том числе соединения таблиц. Кроме того, если ответ представляет собой наблюдаемый тип данных, например Flowable или LiveData, Room ищет недействительные данные во всех таблицах, на которые есть ссылки в запросе.

Котлин

Типы возвращаемых запросов

Комната поддерживает широкий спектр типов возвращаемых данных методов запросов, включая специальные типы возвращаемых данных для взаимодействия с определенными платформами или API. Методы запроса могут возвращать объекты LiveData, Observable и Flow. Вы также можете создать функцию приостановки метода DAO. Каждой из этих тем посвящены отдельные статьи.

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

Мой подход

Я хочу использовать базу данных комнат. Поэтому я создал объект GardenVisit, у которого есть свой уникальный идентификатор и дата посещения. Тогда мне понадобится объект GardenAnnotation. У этого объекта будет строка для каждого растения в саду с его идентификатором и признаками, аннотированными в день посещения. Я думал создать таблицу для каждого посещения сада и связать их отношением один к одному, но не нашел способа сделать это.

Почему я хочу создавать таблицу GardenAnnotation для каждого посещения сада?

В приложении можно удалить посещение сада. Таким образом, при его удалении он также должен удалить свою таблицу GardenAnnotation. Это казалось самым простым способом получить эту функцию.

Заключение

Как создать несколько таблиц одного и того же объекта в базе данных комнат и связать их с другой таблицей?

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

Решение

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

Судя по вашему описанию, у вас есть общие черты:-

  • Сады.
  • Растения.
  • Посещения
  • Черты.
  • Аннотации (выводы/характеристики за посещение).

Соответственно я предлагаю таблицы.

Таблица сада, которая, вероятно, имеет человеческий идентификатор сада (но не ограничивается им) (Кью, Висячие сады Вавилона….) (поскольку он уже существует и является эффективным) идентификатором (id).

Таблица растений (одуванчик, роза...) со столбцами для идентификатора, названия и, возможно, другой информации о растении.

Таблица (не упоминается), которая сопоставляет/связывает/связывает растение с садом, допуская связь многие ко многим (в садах может быть много растений, растение может использоваться во многих садах). 2 столбца один для карты в сад другой в завод.

Таблица посещений, в которой указаны дата/время посещения, возможно, начало/окончание, а также карта/ссылка…. в сад.

Таблица характеристик, например. хорошо поливают, мертвые (если я ухаживаю за растением) …. Столбцы будут идентификатором и чертой (точные требования)

Таблица аннотаций, которая будет ссылаться на посещение (и, следовательно, на сад) и на растение в саду, а также на присваиваемые признаки.

Таким образом, схема может быть основана на SQLite (чтобы продемонстрировать, как работает база данных/взаимосвязи с точки зрения SQLite):-

Результат запроса: -

введите здесь описание изображения

И допустим, посещение с идентификатором 1 удалено (хотя вы, возможно, могли бы считать, что значение visit_done истинно как эффективное удаление (чтобы вы всегда могли вернуться назад во времени)), например. используя :-

Тогда тот же запрос возвращает:-

введите здесь описание изображения

т.е. три аннотации для посещения 3 были удалены

Игнорируя удаление, т. е. с оставшимся посещением с идентификатором visit_id, равным 3, таблицы выглядят следующим образом: -

сад

введите здесь описание изображения

завод

введите здесь описание изображения

черта

введите здесь описание изображения

посетить

введите здесь описание изображения

garden_plant_map

введите здесь описание изображения

аннотация

введите здесь описание изображения

Этот ответ, полученный из stackoverflow, находится под лицензией cc by-sa 2.5 , cc by-sa 3.0 и cc by-sa 4.0

В этом посте мы собираемся изучить некоторые дополнительные концепции библиотеки сохраняемости комнаты. Room — отличный инструмент для хранения сложных данных для ваших приложений Android в базе данных SQLite. Однако по мере того, как вы начинаете хранить больше данных в своих приложениях, может быть трудно определить, как их организовать.

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

Это сообщение в блоге адаптировано из видео YouTube, которое я сделал для AsyncAndroid.

Для начала давайте рассмотрим очень распространенное ограничение, с которым мы сталкиваемся при работе с базой данных SQLite. Вот базовая сущность Room с тремя полями:

Room сможет взять этот объект и успешно сопоставить его с таблицей SQLite с тремя столбцами:

Android Essence

Это работает, потому что Room может принимать примитивные типы данных (числа, строки, логические значения) и сопоставлять их с допустимыми типами данных SQLite. Мы не можем хранить что-то более сложное, чем это, в одной таблице. Давайте попробуем сделать Student более сложным и включить поле Address:

Room не сможет преобразовать это в таблицу SQLite, поскольку адрес не является допустимым типом данных SQLite:

Android Essence

Это основа для остальной части этого поста. Хотя SQLite не может хранить сложные данные в одной таблице, нашим приложениям по-прежнему необходимо хранить информацию о студентах, такую ​​как адреса и занятия, которые они посещают. Решение для хранения этих данных может быть разным, поэтому давайте рассмотрим каждый из имеющихся вариантов.

Предыдущий пример сохранения студента с его адресом на самом деле имеет быстрое, однострочное решение, которое представляет собой аннотацию @Embedded. Это укажет библиотеке Room взять каждое поле из свойства @Embedded и сопоставить его со столбцом внутри объекта, который мы создаем. Мы можем наглядно продемонстрировать это, также указав префикс для встроенных столбцов.

Это означает, что мы можем взять этот код:

Чтобы создать таблицу базы данных с этими столбцами:

Android Essence

Очень здорово, что мы можем получить однострочное решение этой проблемы со встроенными свойствами, но также важно понимать их собственные ограничения.Поскольку это всего лишь дополнительные столбцы в нашей таблице учеников, у нас нет способа безопасного обмена этими данными между сущностями. Рассмотрим двух студентов, Адама и Джона, которые живут по одному адресу:

имя_ученика номер_улицы название_улицы
Адам 111 Уолл-стрит
Джон 111 Уолл-стрит

Если мы зайдем в базу данных, возможно, мы обновим только одну из строк, в результате чего у учеников будут разные адреса:

имя_ученика номер_улицы название_улицы
Адам 111 Уолл-стрит
Джон 112 Уолл-стрит

Глядя на базу данных, мы не знаем, какой из них правильный. Эта проблема называется аномалией обновления. Теперь, если вы не беспокоитесь об обмене информацией между строками, а просто ищете способ лучше структурировать объекты Room, идеальным решением станут встроенные свойства.

Если вам нужна более высокая целостность данных, нам нужно переместить эту информацию в другой объект и создать связь между ними. Эта связь между двумя сущностями называется отношениями сущностей. В этом посте мы рассмотрим три типа отношений.

Первая связь, которую следует обсудить, — это связь "один ко многим". Это когда у вас есть ровно один экземпляр родительского объекта и ноль или более экземпляров дочернего объекта, который связан с ним. Примером может быть Student и любой Vehicle , который они зарегистрировали для парковки в своем университете. Используя наши знания о создании объектов Room, мы можем прийти к решению, подобному этому:

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

  1. Мы можем добавлять объекты транспортных средств с недействительными идентификаторами учащихся, которых на самом деле не существует.
  2. Если мы удаляем учащегося, связанные с ним транспортные средства все еще существуют в базе данных, и в результате мы получаем потерянные записи.

Создание отношений «один ко многим»

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

  1. Сущность, на которую мы хотим сослаться.
  2. Столбцы в дочернем объекте, которые будут использоваться для определения этой связи.
  3. Столбцы в родительском объекте, которые должны совпадать с дочерними столбцами, чтобы обеспечить взаимосвязь.

В итоге мы получим этот код:

Это означает, что мы не сможем выполнить вставку в объект Vehicle, если не используем идентификатор ownerId, который уже существует в таблице Student.

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

Запрос связи "один ко многим"

Общим запросом с этой структурой является запрос учащегося и транспортных средств, связанных с ним. Это может показаться пугающей задачей в библиотеке Room, например, вам, возможно, придется изучить запросы на соединение и написать какой-нибудь сложный SQL, но библиотека Room позаботится обо многом за нас. Давайте попробуем построить запрос, который возвращает следующий класс ответов:

Чтобы Room возвращал этот объект из запроса, нам нужно предоставить две аннотации.

  1. Аннотация @Embedded для студента, чтобы Room могла сопоставлять каждый столбец ответа с сущностью студента, как мы видели со встроенными свойствами.
  2. Аннотация @Relation к транспортным средствам, чтобы Room понимал, как связаны эти два объекта.

Результирующий код выглядит следующим образом:

Далее мы можем войти в наш DAO и написать запрос для получения каждого студента и их транспортных средств:

Здесь вы также заметите две аннотации:

  1. Аннотация @Query(), определяющая каждого учащегося, которого мы хотим вернуть по запросу.
  2. Аннотация @Transaction требуется, потому что Room на самом деле будет выполнять два запроса за кулисами, поэтому они должны выполняться внутри транзакции базы данных.

Комната за кулисами

Вы редко когда-либо погружаетесь в сгенерированный код из такой библиотеки, как Room, но в таких сценариях может быть полезно понять, что на самом деле делает каждая из аннотаций. Мы можем увидеть весь сгенерированный код для файла UniversityDAO_Impl.java в этом списке, но давайте просто выделим, что на самом деле происходит, когда мы вызываем fetchStudentsWithVehicles() .

  1. Сначала мы запускаем SQL-запрос, указанный в нашей аннотации @Query(), который в этом примере должен запрашивать всех учащихся. Видно в строке 30.
  2. Далее мы получаем индекс всех соответствующих столбцов Student благодаря аннотации @Embedded в нашем объекте ответа. Видно в строках с 39 по 43.
  3. После запроса всех учащихся мы запрашиваем все транспортные средства, принадлежащие одному из этих учащихся. Room знает, как связать две таблицы благодаря нашей аннотации @Relation. Это второй запущенный запрос к базе данных, а также то, почему нам понадобилась аннотация @Transaction. Видно в строке 132.
  4. После выполнения этих запросов библиотека Room примет ответы и сопоставит их с определенным классом данных, который мы определили, и вернет их список.

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

Связь "один к одному" – это когда у вас есть ровно один экземпляр родительской и дочерней сущности. Примером может служить студент и его заявление в университет, если они могут подать заявку только один раз.

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

  1. Размер данных. Допустим, мы хотим запросить информацию обо всех студентах, но нам не нужно заботиться об их заявлениях. Разделение данных на два объекта позволяет нам запрашивать только те данные, которые нам действительно нужны.
  2. Безопасность. Это может быть необычно для приложений Android, но в других системах баз данных может потребоваться, чтобы некоторая информация находилась в отдельной базе данных с другими разрешениями, но при этом сохранялась связь один к одному с информацией в другой базе данных.

Создание отношений «один к одному»

Этот тип отношений сложно обеспечить идеальным соблюдением. Мы не можем создать связь между двумя объектами напрямую, потому что у нас нет способа вставить данные в две таблицы одновременно. Однако мы можем приблизиться к этому, ограничив количество приложений, которые могут относиться к учащемуся.

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

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

Запрос отношений "один к одному"

Код запроса для этого будет очень похож на последний пример! Есть только одно отличие:

  1. Объект ответа ссылается на одну запись приложения, а не на список, как в последнем примере.

Вот код:

  1. Если с учащимся не связано ни одно приложение, этот запрос вернет значение null и может привести к сбою. Решение состоит в том, чтобы сделать свойство приложения обнуляемым внутри StudentWithApplication .
  2. Если с учащимся связано только одно приложение, оно вернется, как и ожидалось.
  3. Если на одного учащегося приходится более одного приложения (что невозможно в нашем примере), Room вернет последнее найденное приложение.

Последний тип отношений, который следует обсудить, — это отношения «многие ко многим». Это когда у вас есть много экземпляров родительского объекта и много экземпляров дочернего объекта. Примером могут быть студенты и классы. Учащийся может посещать несколько занятий, а в классе может быть более одного ученика.

Чтобы создать этот объект, нам нужно нечто, называемое соединительной таблицей. Мы не можем связать студентов и классы напрямую, нам нужно пройти через третью таблицу, которая определяет отношения между ними. Мы можем сделать это с помощью третьей сущности ClassEnrollments. Вот как будут выглядеть эти отношения:

Android Essence

Создание отношений «многие ко многим»

Давайте посмотрим на код сущности, необходимый для ClassEnrollments . Нам нужно сделать следующее:

  1. Создайте отношение ForeignKey между this и Student .
  2. Создайте отношение ForeignKey между this и Class .
  3. Создайте составной первичный ключ между (studentId, classId), чтобы гарантировать, что учащийся не посещает занятия дважды.

Вот получившийся объект:

Запрос отношений «многие ко многим»

Как вы уже догадались, код запроса здесь снова почти идентичен предыдущему примеру. Единственным новым дополнением для запроса ответа «многие ко многим» является указание соединяемой таблицы с помощью свойства AssociateBy внутри аннотации @Relation:

Сам запрос остается в соответствии с тем, что мы уже сделали:

Теперь у вас есть все инструменты, чтобы понять, как структурировать сложные данные в базе данных SQLite с помощью room. Мы знаем варианты использования для каждого времени отношений, как применять их на уровне базы данных и как запрашивать каждый из них. Если у вас есть какие-либо вопросы или вы хотите узнать больше о библиотеке Room, дайте мне знать в комментариях ниже! Вы также можете связаться со мной в Twitter.

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

Базы данных

Обновлено 27 апреля 2020 г., Адам Макнейли

Адам Макнейли

Адам – эксперт Google по разработке приложений для Android. Он занимается разработкой приложений с 2015 года и путешествует по миру, чтобы представлять и учиться у других разработчиков Android.

Руководство самозванца по внедрению зависимостей

Внедрение зависимостей — одна из самых горячих тем в Android и разработке программного обеспечения в целом. Это также тема, которая может предоставить lo. … Продолжить чтение

Читайте также: