227

我的产品型号包含一些项目

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

我现在正在从另一个数据集中导入一些产品参数,但是名称的拼写不一致。例如,在另一个数据集中,Blue jeans可以拼写为Blue Jeans.

我想Product.find_or_create_by_name("Blue Jeans"),但这将创建一个新产品,几乎与第一个相同。如果我想查找和比较小写名称,我有什么选择。

性能问题在这里并不重要:只有 100-200 种产品,我想将其作为导入数据的迁移来运行。

有任何想法吗?

4

19 回答 19

395

你可能不得不在这里更详细

name = "Blue Jeans"
model = Product.where('lower(name) = ?', name.downcase).first 
model ||= Product.create(:name => name)
于 2010-02-08T09:35:23.083 回答
112

这是 Rails 中的完整设置,供我自己参考。如果它也对你有帮助,我很高兴。

查询:

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

验证者:

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

索引(来自Rails/ActiveRecord 中不区分大小写的唯一索引的答案? ):

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

我希望有一种更漂亮的方式来做第一个和最后一个,但话说回来,Rails 和 ActiveRecord 是开源的,我们不应该抱怨——我们可以自己实现它并发送拉取请求。

于 2013-03-06T11:07:20.173 回答
35

如果您使用 Postegres 和 Rails 4+,那么您可以选择使用列类型 CITEXT,这将允许不区分大小写的查询,而无需写出查询逻辑。

迁移:

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

为了测试它,您应该期待以下内容:

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">
于 2016-07-18T18:41:01.807 回答
22

您可能想要使用以下内容:

validates_uniqueness_of :name, :case_sensitive => false

请注意,默认设置为:case_sensitive => false,因此如果您没有更改其他方式,您甚至不需要编写此选项。

更多信息请访问: http ://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html#method-i-validates_uniqueness_of

于 2010-02-08T18:11:48.140 回答
20

有几条评论提到了 Arel,但没有提供示例。

这是不区分大小写搜索的 Arel 示例:

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

这种类型的解决方案的优点是它与数据库无关——它将为您当前的适配器使用正确的 SQL 命令(matchesILIKE用于 Postgres 和LIKE其他所有内容)。

于 2017-06-20T22:39:38.387 回答
14

在 postgres 中:

 user = User.find(:first, :conditions => ['username ~* ?', "regedarek"])
于 2012-03-05T21:43:47.690 回答
9

引用SQLite 文档

任何其他字符匹配自身或其小写/大写等效字符(即不区分大小写的匹配)

...我不知道。但它有效:

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

所以你可以做这样的事情:

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

不是#find_or_create,我知道,而且它可能对跨数据库不太友好,但值得一看吗?

于 2010-02-08T10:04:01.043 回答
7

Another approach that no one has mentioned is to add case insensitive finders into ActiveRecord::Base. Details can be found here. The advantage of this approach is that you don't have to modify every model, and you don't have to add the lower() clause to all your case insensitive queries, you just use a different finder method instead.

于 2010-04-03T19:55:57.837 回答
6

大写和小写字母仅相差一位。搜索它们最有效的方法是忽略该位,不转换大小写等。查看COLLATIONMSSQL的关键字,查看NLS_SORT=BINARY_CI是否使用Oracle等。

于 2010-07-29T18:32:21.070 回答
6

类似于#1的安德鲁斯:

对我有用的是:

name = "Blue Jeans"
Product.find_by("lower(name) = ?", name.downcase)

这消除了在同一个查询中执行#whereand的需要。#first希望这可以帮助!

于 2019-04-08T22:03:14.367 回答
5

Find_or_create 现在已弃用,您应该使用 AR Relation 加上 first_or_create,如下所示:

TombolaEntry.where("lower(name) = ?", self.name.downcase).first_or_create(name: self.name)

这将返回第一个匹配的对象,如果不存在,则为您创建一个。

于 2013-07-02T09:39:48.223 回答
3

Rails 内置了不区分大小写的搜索。它解释了数据库实现的差异。使用内置的 Arel 库,或者像 Squeel 这样的 gem

于 2013-12-06T20:53:26.063 回答
2

这里有很多很棒的答案,尤其是@oma's。但是您可以尝试的另一件事是使用自定义列序列化。如果您不介意所有内容都以小写形式存储在您的数据库中,那么您可以创建:

# 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

然后在你的模型中:

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

这种方法的好处是您仍然可以使用所有常规查找器(包括find_or_create_by),而无需使用自定义范围、函数或lower(name) = ?在您的查询中使用。

缺点是您会丢失数据库中的外壳信息。

于 2015-03-09T17:14:43.133 回答
2

一个替代方案可以是

c = Product.find_by("LOWER(name)= ?", name.downcase)
于 2019-04-26T16:15:46.557 回答
1

您还可以使用下面这样的范围并将它们放在关注点中并包含在您可能需要它们的模型中:

scope :ci_find, lambda { |column, value| where("lower(#{column}) = ?", value.downcase).first }

然后像这样使用: Model.ci_find('column', 'value')

于 2016-11-12T00:39:39.817 回答
0

假设您使用 mysql,您可以使用不区分大小写的字段:http: //dev.mysql.com/doc/refman/5.0/en/case-sensitive.html

于 2010-02-08T09:37:55.083 回答
0
user = Product.where(email: /^#{email}$/i).first
于 2014-09-10T10:30:08.893 回答
0

有些人使用 LIKE 或 ILIKE 显示,但那些允许正则表达式搜索。此外,您不需要在 Ruby 中进行小写。你可以让数据库为你做这件事。我认为它可能会更快。之后也first_or_create可以使用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"> 
于 2016-03-27T01:36:33.420 回答
-10

到目前为止,我使用 Ruby 制作了一个解决方案。将其放在 Product 模型中:

  #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

这将为我提供第一个名称匹配的产品。或无。

>> 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 :)
于 2010-02-08T09:38:36.627 回答