ruby-on-rails - ignorar acentos sql server




Búsqueda de mayúsculas y minúsculas en el modelo Rails (12)

Mi modelo de producto contiene algunos artículos

 Product.first
 => #<Product id: 10, name: "Blue jeans" >

Ahora estoy importando algunos parámetros del producto de otro conjunto de datos, pero hay inconsistencias en la ortografía de los nombres. Por ejemplo, en el otro conjunto de datos, Blue jeans podría deletrear Blue Jeans .

Quería Product.find_or_create_by_name("Blue Jeans") , pero esto creará un nuevo producto, casi idéntico al primero. ¿Cuáles son mis opciones si quiero encontrar y comparar el nombre en minúsculas?

Los problemas de rendimiento no son realmente importantes aquí: solo hay 100-200 productos, y quiero ejecutar esto como una migración que importa los datos.

¿Algunas ideas?


Algunas personas muestran usando LIKE o ILIKE, pero esas permiten búsquedas de expresiones regulares. Además, no es necesario bajar en Ruby. Puedes dejar que la base de datos lo haga por ti. Creo que puede ser más rápido. También se puede usar first_or_create después de where .

# app/models/product.rb
class Product < ActiveRecord::Base

  # case insensitive name
  def self.ci_name(text)
    where("lower(name) = lower(?)", text)
  end
end

# first_or_create can be used after a where clause
Product.ci_name("Blue Jeans").first_or_create
# Product Load (1.2ms)  SELECT  "products".* FROM "products"  WHERE (lower(name) = lower('Blue Jeans'))  ORDER BY "products"."id" ASC LIMIT 1
# => #<Product id: 1, name: "Blue jeans", created_at: "2016-03-27 01:41:45", updated_at: "2016-03-27 01:41:45"> 

Citando de la documentación de SQLite :

Cualquier otro carácter coincide con sí mismo o con su equivalente en mayúsculas / minúsculas (es decir, coincidencia que no distingue mayúsculas y minúsculas)

... que no sabía. Pero funciona:

sqlite> create table products (name string);
sqlite> insert into products values ("Blue jeans");
sqlite> select * from products where name = 'Blue Jeans';
sqlite> select * from products where name like 'Blue Jeans';
Blue jeans

Así que podrías hacer algo como esto:

name = 'Blue jeans'
if prod = Product.find(:conditions => ['name LIKE ?', name])
    # update product or whatever
else
    prod = Product.create(:name => name)
end

No #find_or_create , lo sé, y puede que no sea muy #find_or_create varias bases de datos, pero vale la pena verlo.



Esta es una configuración completa en Rails, para mi propia referencia. Estoy feliz si te ayuda también.

la consulta:

Product.where("lower(name) = ?", name.downcase).first

el validador

validates :name, presence: true, uniqueness: {case_sensitive: false}

el índice (respuesta del índice único insensible a mayúsculas en Rails / ActiveRecord? ):

execute "CREATE UNIQUE INDEX index_products_on_lower_name ON products USING btree (lower(name));"

Me gustaría que hubiera una forma más bella de hacer la primera y la última, pero nuevamente, Rails y ActiveRecord son de código abierto, no deberíamos quejarnos, podemos implementarlo nosotros mismos y enviar una solicitud de extracción.


Hasta ahora, hice una solución usando Ruby. Coloque esto dentro del modelo del producto:

  #return first of matching products (id only to minimize memory consumption)
  def self.custom_find_by_name(product_name)
    @@product_names ||= Product.all(:select=>'id, name')
    @@product_names.select{|p| p.name.downcase == product_name.downcase}.first
  end

  #remember a way to flush finder cache in case you run this from console
  def self.flush_custom_finder_cache!
    @@product_names = nil
  end

Esto me dará el primer producto donde coinciden los nombres. O nada.

>> Product.create(:name => "Blue jeans")
=> #<Product id: 303, name: "Blue jeans">

>> Product.custom_find_by_name("Blue Jeans")
=> nil

>> Product.flush_custom_finder_cache!
=> nil

>> Product.custom_find_by_name("Blue Jeans")
=> #<Product id: 303, name: "Blue jeans">
>>
>> #SUCCESS! I found you :)

Hay muchas respuestas geniales aquí, particularmente las de @ oma. Pero otra cosa que podría intentar es usar la serialización de columnas personalizada. Si no le importa que todo esté almacenado en minúsculas en su base de datos, puede crear:

# lib/serializers/downcasing_string_serializer.rb
module Serializers
  class DowncasingStringSerializer
    def self.load(value)
      value
    end

    def self.dump(value)
      value.downcase
    end
  end
end

Luego en tu modelo:

# app/models/my_model.rb
serialize :name, Serializers::DowncasingStringSerializer
validates_uniqueness_of :name, :case_sensitive => false

El beneficio de este enfoque es que aún puede usar todos los buscadores regulares (incluido find_or_create_by ) sin usar ámbitos personalizados, funciones, o tener lower(name) = ? en sus consultas.

El inconveniente es que se pierde información de la carcasa en la base de datos.


Las letras mayúsculas y minúsculas difieren solo en un bit: la forma más eficiente de buscarlas es ignorar este bit, no convertir las letras inferiores o superiores, etc. Consulte las palabras clave COLLATION para MS SQL, consulte NLS_SORT = BINARY_CI si usa Oracle. etc.


Otro enfoque que nadie ha mencionado es agregar buscadores que no distinguen entre mayúsculas y minúsculas en ActiveRecord :: Base. Los detalles se pueden encontrar here . La ventaja de este enfoque es que no tiene que modificar todos los modelos, y no tiene que agregar la cláusula lower() a todas las consultas que no distinguen entre mayúsculas y minúsculas, sino que simplemente utiliza un método de búsqueda diferente.


Si está utilizando Postegres y Rails 4+, entonces tiene la opción de usar el tipo de columna CITEXT, que permitirá consultas que no distinguen entre mayúsculas y minúsculas sin tener que escribir la lógica de consulta.

La migración:

def change
  enable_extension :citext
  change_column :products, :name, :citext
  add_index :products, :name, unique: true # If you want to index the product names
end

Y para probarlo debes esperar lo siguiente:

Product.create! name: 'jOgGers'
=> #<Product id: 1, name: "jOgGers">

Product.find_by(name: 'joggers')
=> #<Product id: 1, name: "jOgGers">

Product.find_by(name: 'JOGGERS')
=> #<Product id: 1, name: "jOgGers">


Varios comentarios se refieren a Arel, sin dar un ejemplo.

Aquí hay un ejemplo de Arel de una búsqueda que no distingue entre mayúsculas y minúsculas:

Product.where(Product.arel_table[:name].matches('Blue Jeans'))

La ventaja de este tipo de solución es que es ILIKE de la base de datos: utilizará los comandos SQL correctos para su adaptador actual (las matches usarán ILIKE para Postgres y LIKE para todo lo demás).


user = Product.where(email: /^#{email}$/i).first






case-insensitive