Поиск сериализованных данных с использованием активной записи
Я пытаюсь сделать простой запрос сериализованного столбца, как вы это делаете?
serialize :mycode, Array
1.9.3p125 :026 > MyModel.find(104).mycode
MyModel Load (0.6ms) SELECT `mymodels`.* FROM `mymodels` WHERE `mymodels`.`id` = 104 LIMIT 1
=> [43565, 43402]
1.9.3p125 :027 > MyModel.find_all_by_mycode("[43402]")
MyModel Load (0.7ms) SELECT `mymodels`.* FROM `mymodels` WHERE `mymodels`.`mycode` = '[43402]'
=> []
1.9.3p125 :028 > MyModel.find_all_by_mycode(43402)
MyModel Load (1.2ms) SELECT `mymodels`.* FROM `mymodels` WHERE `mymodels`.`mycode` = 43402
=> []
1.9.3p125 :029 > MyModel.find_all_by_mycode([43565, 43402])
MyModel Load (1.1ms) SELECT `mymodels`.* FROM `mymodels` WHERE `mymodels`.`mycode` IN (43565, 43402)
=> []
10 ответов
По сути, вы не можете. Недостатком #serialize является то, что вы игнорируете абстракции своей базы данных. Вы в значительной степени ограничены загрузкой и сохранением данных.
Тем не менее, один очень хороший способ замедлить ваше приложение для сканирования может быть:
MyModel.all.select { |m| m.mycode.include? 43402 }
Мораль истории: не используйте #serialize для каких-либо данных, к которым вам нужно обращаться.
Это просто хитрость, чтобы не замедлять работу вашего приложения. Вы должны использовать .to_yaml
,
Точный результат:
MyModel.where("mycode = ?", [43565, 43402].to_yaml)
#=> [#<MyModel id:...]
Проверено только для MySQL.
Сериализованный массив хранится в базе данных определенным образом, например:
[1, 2, 3, 4]
in
1\n 2\n 3\n etc
следовательно, запрос будет
MyModel.where("mycode like ?", "% 2\n%")
поставить пространство между %
а также 2
,
Нудл ответ правильный, но не совсем правильный.
Это действительно зависит от используемого вами адаптера базы данных /ORM: например, PostgreSQL теперь может хранить и искать хэши /json - проверить hstore. Я помню, что читал, что адаптер ActiveRecord для PostgreSQl теперь обрабатывает его правильно. И если вы используете mongoid или что-то в этом роде - тогда вы используете неструктурированные данные (например, json) на уровне базы данных везде.
Однако, если вы используете базу данных, которая не может действительно обрабатывать хэши - например, комбинацию MySQL / ActiveRecord - тогда единственная причина, по которой вы бы использовали сериализованное поле, - это некоторые данные, которые вы можете создавать / записывать в некотором фоновом процессе и отображать / выводить по требованию - только два использования, которые я нашел в своем опыте, - это некоторые отчеты (например, поле статистики в модели продукта - где мне нужно хранить некоторые средние и медианы для продукта) и пользовательские параметры (например, предпочитаемый ими цвет шаблона - я действительно не нужно спрашивать об этом) - однако пользовательская информация - например, их подписка на список рассылки - должна быть доступна для поиска по электронной почте.
PostgreSQL
hstore
Пример ActiveRecord:MyModel.where("mycode @> 'KEY=>\"#{VALUE}\"'")
ОБНОВЛЕНИЕ Начиная с 2017 года MariaDB и MySQL поддерживают типы полей JSON.
Хорошие новости! Если вы используете PostgreSQL с hstore (что очень легко с Rails 4), теперь вы можете полностью искать сериализованные данные. Это удобное руководство, а вот синтаксическая документация от PG.
В моем случае у меня есть словарь, хранящийся в виде хэша в столбце hstore с именем amenities
, Я хочу проверить пару запрашиваемых удобств, которые имеют значение 1
в хеше я просто делаю
House.where("amenities @> 'wifi => 1' AND amenities @> 'pool => 1'")
Ура для улучшений!
Вы можете запросить сериализованный столбец с помощью оператора SQL LIKE.
MyModel.where("mycode LIKE '%?%'", 43402)
Это быстрее, чем использование include?, Однако вы не можете использовать массив в качестве параметра.
В 2009 году от FriendFeed есть запись в блоге, в которой рассказывается, как использовать сериализованные данные в MySQL.
Что вы можете сделать, это создать таблицы, которые функционируют как индексы для любых данных, которые вы хотите найти.
Создайте модель, которая содержит доступные для поиска значения / поля
В вашем примере модели будут выглядеть примерно так:
class MyModel < ApplicationRecord
# id, name, other fields...
serialize :mycode, Array
end
class Item < ApplicationRecord
# id, value...
belongs_to :my_model
end
Создание "индексной" таблицы для полей поиска
Когда вы сохраняете MyModel, вы можете сделать что-то вроде этого для создания индекса:
Item.where(my_model: self).destroy
self.mycode.each do |mycode_item|
Item.create(my_model: self, value: mycode_item)
end
Запросы и поиск
Затем, когда вы хотите сделать запрос и выполнить поиск, просто выполните:
Item.where(value: [43565, 43402]).all.map(&:my_model)
Item.where(value: 43402).all.map(&:my_model)
Вы можете добавить метод в MyModel, чтобы сделать это проще:
def find_by_mycode(value_or_values)
Item.where(value: value_or_values).all.map(&my_model)
end
MyModel.find_by_mycode([43565, 43402])
MyModel.find_by_mycode(43402)
Чтобы ускорить процесс, вы захотите создать индекс SQL для этой таблицы.
Используя следующие комментарии в этом сообщении
/questions/22596987/poisk-serializovannyih-dannyih-s-ispolzovaniem-aktivnoj-zapisi/22597002#22597002
/questions/22596987/poisk-serializovannyih-dannyih-s-ispolzovaniem-aktivnoj-zapisi/22596993#22596993
Мне удалось запросить сериализованный хэш в моей модели
class Model < ApplicationRecord
serialize :column_name, Hash
end
когда column_name
имеет хэш вроде
{ my_data: [ { data_type: 'MyType', data_id: 113 } ] }
мы можем запросить его следующим образом
Model.where("column_name = ?", hash.to_yaml)
Это генерирует SQL-запрос вроде
Model Load (0.3ms) SELECT "models".* FROM "models" WHERE (column_name = '---
:my_data:
- :data_type: MyType
:data_id: 113
')
Если кто-то заинтересован в выполнении сгенерированного запроса в терминале SQL, он должен работать, однако следует позаботиться о том, чтобы значение было в точном формате, хранящемся в БД. Однако есть еще один простой способ, который я нашел в символе новой строки PostgreSQL, чтобы использовать необработанную строку, содержащую символы новой строки.
select * from table_name where column_name = E'---\n:my_data:\n- :data_type: MyType\n :data_id: 113\n'
Самая важная часть в приведенном выше запросе - E
.
Примечание. База данных, на которой я выполнил выше, - PostgreSQL.
Если у вас есть сериализованный столбец json, и вы хотите применить к нему подобный запрос. сделай так
YourModel.where("hashcolumn like ?", "%#{search}%")
Для поиска в сериализованном списке вам нужно добавить к данным префиксы и постфиксы с уникальными символами.
Пример:
А не что-то вроде:
2345,12345,1234567
что может вызвать проблемы, которые вы пытались найти
2345
вместо этого вы делаете что-то вроде
<2345>,<12345>,<1234567>
и искать
<2345>
(поисковый запрос преобразуется). Конечно, выбор символов префикса / постфикса зависит от допустимых данных, которые будут храниться. Вместо этого вы можете использовать что-то вроде
|||
если вы ожидаете
<
для использования и потенциально|
быть использованным. Конечно, это увеличивает объем данных, используемых полем, и может вызвать проблемы с производительностью.
Использование индекса триграмм или чего-то еще позволит избежать потенциальных проблем с производительностью.
Вы можете сериализовать его как
data.map { |d| "<#{d}>" }.join(',')
и десериализуйте его через
data.gsub('<').gsub('>','').split(',')
. Класс сериализатора достаточно хорошо справится с задачей загрузки / извлечения данных.
Это можно сделать, задав в поле базы данных текст и используя параметр rail
serialize
model с настраиваемым классом библиотеки. Класс lib должен реализовать два метода:
def self.dump(obj) # (returns string to be saved to database)
def self.load(text) # (returns object)
Пример с продолжительностью. Взято из статьи, чтобы гнили ссылки не попадали, пожалуйста, посетите эту статью для получения дополнительной информации. В примере используется одно значение, но довольно просто сериализовать список значений и десериализовать список с помощью методов, упомянутых выше.
class Duration
# Used for `serialize` method in ActiveRecord
class << self
def load(duration)
self.new(duration || 0)
end
def dump(obj)
unless obj.is_a?(self)
raise ::ActiveRecord::SerializationTypeMismatch,
"Attribute was supposed to be a #{self}, but was a #{obj.class}. -- #{obj.inspect}"
end
obj.length
end
end
attr_accessor :minutes, :seconds
def initialize(duration)
@minutes = duration / 60
@seconds = duration % 60
end
def length
(minutes.to_i * 60) + seconds.to_i
end
end