MongoDB: Apakah mungkin membuat permintaan case-insensitive?


304

Contoh:

> db.stuff.save({"foo":"bar"});

> db.stuff.find({"foo":"bar"}).count();
1
> db.stuff.find({"foo":"BAR"}).count();
0

3
Karena MongoDB 3.2 Anda dapat melakukan pencarian yang tidak peka huruf besar kecil $caseSensitive: false. Lihat: docs.mongodb.org/manual/reference/operator/query/text/…
martin

4
Perhatikan bahwa itu hanya pada indeks teks.
Willem D'Haeseleer

1
@martin: $caseSensitivesudah salah secara default, dan itu tidak menjawab pertanyaan, karena hanya berfungsi pada bidang yang diindeks. OP sedang mencari perbandingan string case-insensitive.
Dan Dascalescu

Jawaban:


343

Anda bisa menggunakan regex .

Dalam contoh Anda itu akan menjadi:

db.stuff.find( { foo: /^bar$/i } );

Namun, saya harus mengatakan, mungkin Anda bisa menurunkan (atau menaikkan) nilai di jalan daripada mengeluarkan biaya tambahan setiap kali Anda menemukannya. Jelas ini tidak akan bekerja untuk nama orang dan semacamnya, tapi mungkin menggunakan case seperti tag.


27
Ini bekerja dengan sempurna. Dapat berfungsi di PHP dengan: $ collection-> find (array ('key' => MongoRegex baru ('/'.$ val.' / I ')));
Luke Dennis

2
Terutama jika Anda menginterpolasi string ({foo: / # {x} / i}) yang dapat memiliki tanda tanya di dalamnya ..
Peter Ehrlich

17
Jangan lupa juga ^ dan $: MongoRegex ('/ ^'. Preg_quote ($ val). '$ / I')
Julien

20
Perhatikan bahwa ini akan melakukan fullscan daripada menggunakan indeks.
Martin Konicek

12
itu tidak akan berhasil sepenuhnya jika ia menggunakan jangkar pada awalnya, karena itulah pentingnya nasihat Julien.
Pax

198

MEMPERBARUI:

Jawaban asli sekarang sudah usang. Mongodb sekarang mendukung pencarian teks lengkap tingkat lanjut, dengan banyak fitur.

JAWABAN ASLI:

Perlu dicatat bahwa pencarian dengan case regex tidak peka / i berarti mongodb tidak dapat mencari berdasarkan indeks, sehingga permintaan terhadap dataset besar dapat memakan waktu lama.

Bahkan dengan dataset kecil, itu tidak terlalu efisien. Anda menerima pukulan cpu yang jauh lebih besar daripada waran permintaan Anda, yang bisa menjadi masalah jika Anda mencoba mencapai skala.

Sebagai alternatif, Anda dapat menyimpan salinan huruf besar dan mencarinya. Misalnya, saya memiliki tabel Pengguna yang memiliki nama pengguna yang merupakan case campuran, tetapi id adalah salinan huruf besar dari nama pengguna. Ini memastikan duplikasi case-sensitive tidak mungkin (memiliki "Foo" dan "foo" tidak akan diizinkan), dan saya dapat mencari berdasarkan id = username.toUpperCase () untuk mendapatkan pencarian case-sensitive untuk nama pengguna.

Jika bidang Anda besar, seperti badan pesan, duplikasi data mungkin bukan pilihan yang baik. Saya percaya menggunakan pengindeks luar seperti Apache Lucene adalah pilihan terbaik dalam kasus itu.


1
@Dan, hanya untuk info, dalam MongoDB terbaru, "Jika ada indeks untuk bidang tersebut, maka MongoDB mencocokkan ekspresi reguler terhadap nilai-nilai dalam indeks, yang bisa lebih cepat daripada pemindaian koleksi." - docs.mongodb.org/manual/reference/operator/query/regex/…
Sergiy Sokolenko

1
Documents mungkin sudah diperbarui. Mereka sekarang mengatakan "Untuk kueri ekspresi reguler case-sensitive, jika ada indeks untuk bidang, maka MongoDB cocok dengan ekspresi reguler terhadap nilai-nilai dalam indeks, yang bisa lebih cepat dari pemindaian koleksi."
Jeff Lewis

1
Batasan lain dengan indeks teks adalah Anda hanya dapat memiliki satu per koleksi (beberapa kolom), jadi tidak cocok jika Anda perlu mengisolasi pencarian pada bidang yang berbeda untuk kasus yang berbeda.
Paul Grimshaw

2
@SergiySokolenko: dokumen sekarang mengatakan (paragraf terakhir di bagian ini ): "Kueri ekspresi reguler kasus tidak sensitif umumnya tidak dapat menggunakan indeks secara efektif. Implementasi $ regex tidak sadar-collation dan tidak dapat memanfaatkan indeks case-insensitive."
Dan Dascalescu

1
Menggunakan pencarian teks lengkap adalah salah dalam hal ini (dan berpotensi berbahaya ), karena pertanyaannya adalah tentang membuat kueri yang tidak peka terhadap huruf besar-kecil, misalnya username: 'bill'mencocokkan BILLatau Bill, bukan kueri pencarian teks lengkap, yang juga akan cocok dengan kata - kata yang berasal dari kata tangkasbill , seperti Bills, billeddll.
Dan Dascalescu

70

Jika Anda perlu membuat regexp dari suatu variabel, ini adalah cara yang lebih baik untuk melakukannya: https://stackoverflow.com/a/10728069/309514

Anda kemudian dapat melakukan sesuatu seperti:

var string = "SomeStringToFind";
var regex = new RegExp(["^", string, "$"].join(""), "i");
// Creates a regex of: /^SomeStringToFind$/i
db.stuff.find( { foo: regex } );

Ini memiliki manfaat menjadi lebih terprogram atau Anda bisa mendapatkan peningkatan kinerja dengan kompilasi sebelumnya jika Anda sering menggunakannya kembali.


1
new RegExp("^" + req.params.term.toLowerCase(), "i") juga berfungsi dengan baik
Tahir Yasin

2
Anda harus mempertimbangkan melarikan diri dari string untuk meningkatkan keamanan jika variabel berasal dari permintaan: stackoverflow.com/a/50633536/5195127
davidivad

Dimulai dengan MongoDB 3.4, ada dukungan asli untuk Case Insensitive Indexes
Dan Dascalescu

64

Ingatlah bahwa contoh sebelumnya:

db.stuff.find( { foo: /bar/i } );

akan menyebabkan setiap entri yang berisi bilah cocok dengan kueri (bar1, barxyz, openbar), bisa sangat berbahaya bagi pencarian nama pengguna pada fungsi auth ...

Anda mungkin perlu membuatnya hanya cocok dengan istilah pencarian dengan menggunakan sintaksis regexp yang sesuai seperti:

db.stuff.find( { foo: /^bar$/i } );

Lihat http://www.regular-expressions.info/ untuk bantuan sintaks pada ekspresi reguler


Jawaban ini seperti komentar.
Dan Dascalescu

62

Dimulai dengan MongoDB 3.4, cara yang disarankan untuk melakukan pencarian case-insensitive cepat adalah menggunakan Case Insensitive Index .

Saya secara pribadi mengirim email kepada salah satu pendiri untuk membuatnya bekerja, dan dia mewujudkannya! Itu adalah masalah pada JIRA sejak 2009 , dan banyak yang meminta fitur tersebut. Begini cara kerjanya:

Indeks case-insensitive dibuat dengan menentukan collation dengan kekuatan 1 atau 2. Anda dapat membuat indeks case-insensitive seperti ini:

db.cities.createIndex(
  { city: 1 },
  { 
    collation: {
      locale: 'en',
      strength: 2
    }
  }
);

Anda juga dapat menentukan susunan default per koleksi saat Anda membuatnya:

db.createCollection('cities', { collation: { locale: 'en', strength: 2 } } );

Dalam kedua kasus tersebut, untuk menggunakan indeks case-insensitive, Anda perlu menentukan susunan yang sama dalam findoperasi yang digunakan saat membuat indeks atau koleksi:

db.cities.find(
  { city: 'new york' }
).collation(
  { locale: 'en', strength: 2 }
);

Ini akan mengembalikan "New York", "new york", "New york" dll.

Catatan lain

  • Jawaban yang menyarankan untuk menggunakan pencarian teks lengkap salah dalam hal ini (dan berpotensi berbahaya ). Pertanyaannya adalah tentang membuat kueri yang tidak peka huruf besar-kecil, misalnya username: 'bill'mencocokkan BILLatau Bill, bukan kueri penelusuran teks lengkap, yang juga akan cocok dengan kata-kata yang berasal dari bill, seperti Bills, billeddll.

  • Jawaban yang menyarankan untuk menggunakan ekspresi reguler lambat, karena bahkan dengan indeks, dokumentasi menyatakan :

    "Kueri ekspresi reguler case-insensitive umumnya tidak dapat menggunakan indeks secara efektif. Implementasi $ regex tidak sadar-kolasi dan tidak dapat memanfaatkan indeks case-insensitive."

    $regexjawaban juga menjalankan risiko injeksi input pengguna .


Bekerja dengan baik untuk saya, bahkan dengan pipa agregasi.
Morio

Saya pikir ini adalah jawaban yang tepat, karena kecepatan membaca data adalah penting
Rndmax

Sepertinya saya tidak dapat menemukan cara untuk menambahkan susunan default ke koleksi setelah itu dibuat. Apakah ada cara untuk melakukannya?
IncrediblePony

19
db.zipcodes.find({city : "NEW YORK"}); // Case-sensitive
db.zipcodes.find({city : /NEW york/i}); // Note the 'i' flag for case-insensitivity

1
@ OlegV.Volkov harus memiliki deskripsi tentang bagaimana jawaban Anda sesuai dan apa yang salah dalam kode penanya.
Parth Trivedi

1
Jawaban khusus kode ini tidak menambahkan apa pun ke yang diterima, yang telah diposting 6 tahun sebelumnya.
Dan Dascalescu

19

TL; DR

Cara yang benar untuk melakukan ini di mongo

Jangan Gunakan RegExp

Alami dan gunakan pengindeksan inbuilt mongodb, cari

Langkah 1 :

db.articles.insert(
   [
     { _id: 1, subject: "coffee", author: "xyz", views: 50 },
     { _id: 2, subject: "Coffee Shopping", author: "efg", views: 5 },
     { _id: 3, subject: "Baking a cake", author: "abc", views: 90  },
     { _id: 4, subject: "baking", author: "xyz", views: 100 },
     { _id: 5, subject: "Café Con Leche", author: "abc", views: 200 },
     { _id: 6, subject: "Сырники", author: "jkl", views: 80 },
     { _id: 7, subject: "coffee and cream", author: "efg", views: 10 },
     { _id: 8, subject: "Cafe con Leche", author: "xyz", views: 10 }
   ]
)
 

Langkah 2 :

Perlu membuat indeks pada bidang TEKS mana pun yang ingin Anda cari, tanpa kueri pengindeksan akan sangat lambat

db.articles.createIndex( { subject: "text" } )

langkah 3:

db.articles.find( { $text: { $search: "coffee",$caseSensitive :true } } )  //FOR SENSITIVITY
db.articles.find( { $text: { $search: "coffee",$caseSensitive :false } } ) //FOR INSENSITIVITY


 

1
Opsi bagus, tapi tidak ada yang lebih "benar" tentang penggunaan indeks teks versus regex, itu hanyalah pilihan lain. Ini berlebihan untuk kasus OP.
JohnnyHK

2
Kecuali regex secara signifikan lebih lambat. Pencarian teks lengkap juga lambat, tetapi tidak lambat. Cara tercepat (tetapi lebih besar) adalah bidang terpisah yang selalu diatur ke huruf kecil.
Tom Mettam

4
Menggunakan pencarian teks lengkap adalah salah dalam hal ini (dan berpotensi berbahaya ), karena pertanyaannya adalah tentang membuat kueri yang tidak peka terhadap huruf besar-kecil, misalnya username: 'bill'mencocokkan BILLatau Bill, bukan kueri pencarian teks lengkap, yang juga akan cocok dengan kata - kata yang berasal dari kata tangkasbill , seperti Bills, billeddll.
Dan Dascalescu

15
db.company_profile.find({ "companyName" : { "$regex" : "Nilesh" , "$options" : "i"}});

2
Sudahkah Anda melihat jawaban yang ada sebelum memposting yang ini? Alih-alih jawaban kuasi-duplikat hanya kode, Anda mungkin ingin menjelaskan bagaimana itu menambahkan sesuatu yang bernilai dibandingkan dengan jawaban sebelumnya.
Dan Dascalescu

1
Saya hanya ingin menambahkan bahwa jawaban inilah yang memberi saya solusi. Saya menggunakan kerangka kerja PHP dan ini cocok dengan sintaks ORM dengan baik sedangkan solusi lain di sini tidak. $existing = Users::masterFind('all', ['conditions' => ['traits.0.email' => ['$regex' => "^$value$", '$options' => 'i']]]);
Don Rzeszut

9

Mongo (versi saat ini 2.0.0) tidak mengizinkan pencarian case-sensitive terhadap bidang yang diindeks - lihat dokumentasi mereka . Untuk bidang yang tidak diindeks, regex yang terdaftar di jawaban lain harus baik-baik saja.


19
Hanya untuk memperjelas ini: pencarian case-insensitive diizinkan pada bidang yang diindeks, mereka tidak akan menggunakan indeks dan akan menjadi lambat seolah-olah bidang itu tidak diindeks.
heavi5ide

@ heavi5ide karena pertanyaan ini digunakan untuk menandai duplikat, saya pikir saya akan mengklarifikasi bahwa regexes (diperlukan untuk pencarian case sensitive) menggunakan indeks, tetapi mereka harus melakukan full index scan. Dengan kata lain mereka tidak dapat menggunakan indeks secara efisien . Untungnya dokumentasi tersebut telah diperbarui sejak 2011 tetapi masih bagus untuk dicatat di sini juga.
Sammaye

7

Satu hal yang sangat penting untuk diingat ketika menggunakan kueri berbasis Regex - Ketika Anda melakukan ini untuk sistem login, lepaskan setiap karakter tunggal yang Anda cari, dan jangan lupa ^ dan $ operator. Lodash memiliki fungsi yang bagus untuk ini , jika Anda sudah menggunakannya:

db.stuff.find({$regex: new RegExp(_.escapeRegExp(bar), $options: 'i'})

Mengapa? Bayangkan seorang pengguna memasukkan .*nama pengguna. Itu akan cocok dengan semua nama pengguna, memungkinkan login dengan hanya menebak kata sandi pengguna mana pun.


6

Metode terbaik adalah dalam bahasa pilihan Anda, saat membuat pembungkus model untuk objek Anda, minta metode save () Anda beralih melalui seperangkat bidang yang akan Anda cari yang juga diindeks; kumpulan bidang tersebut harus memiliki mitra huruf kecil yang kemudian digunakan untuk pencarian.

Setiap kali objek disimpan lagi, properti huruf kecil kemudian diperiksa dan diperbarui dengan perubahan apa pun pada properti utama. Ini akan membuatnya sehingga Anda dapat mencari secara efisien, tetapi menyembunyikan pekerjaan tambahan yang diperlukan untuk memperbarui bidang lc setiap kali.

Bidang huruf kecil bisa menjadi kunci: menyimpan objek nilai atau hanya nama bidang dengan awalan lc_. Saya menggunakan yang kedua untuk menyederhanakan kueri (kueri objek dalam bisa membingungkan di kali)

Catatan: Anda ingin mengindeks bidang lc_, bukan bidang utama yang menjadi dasarnya.


Solusi yang bagus tapi untungnya dimulai dengan MongoDB 3.4, ada dukungan asli untuk Case Insensitive Indexes .
Dan Dascalescu

6

Misalkan Anda ingin mencari "kolom" di "Tabel" dan Anda ingin pencarian tidak case-case. Cara terbaik dan efisien adalah seperti di bawah ini;

//create empty JSON Object
mycolumn = {};

//check if column has valid value
if(column) {
    mycolumn.column = {$regex: new RegExp(column), $options: "i"};
}
Table.find(mycolumn);

Kode di atas hanya menambahkan nilai pencarian Anda sebagai RegEx dan mencari dengan kriteria tidak sensitif yang ditetapkan dengan "i" sebagai opsi.

Semua yang terbaik.


5

Menggunakan luwak ini bekerja untuk saya:

var find = function(username, next){
    User.find({'username': {$regex: new RegExp('^' + username, 'i')}}, function(err, res){
        if(err) throw err;
        next(null, res);
    });
}

8
Bukan itu .toLowerCase() berlebihan jika Anda menetapkan tanda case-insensitive flag of i?
k00k

Ya itu. Anda tidak perlu .toLowerCase (). Saya telah menghapusnya dari jawabannya.
ChrisRich

hmm haruskah ini bekerja seperti itu? Ketika saya mencari "mark" juga mendapat setiap record dengan "marko" - adakah cara hanya mengabaikan sensitivitas case?
Suisse

Ok menemukannya, regex yang benar adalah: '^' + serach_name + '$', "i"
Suisse

3
Ini berbahaya. Anda tidak keluar dari nama pengguna, sehingga regex sembarang dapat disuntikkan.
Tom Mettam

3

Kerangka agregasi diperkenalkan di mongodb 2.2. Anda dapat menggunakan operator string "$ strcasecmp" untuk membuat perbandingan case-insensitive antara string. Ini lebih direkomendasikan dan lebih mudah daripada menggunakan regex.

Berikut dokumen resmi pada operator perintah agregasi: https://docs.mongodb.com/manual/reference/operator/aggregation/strcasecmp/#exp._S_strcasecmp .


4
bagaimana cara menggunakan ini dalam permintaan find ()? db.stuff.find ({name: $ strcasecmp (name)})?
Suisse

3

Anda dapat menggunakan Indeks Tidak Sensitif Kasus :

Contoh berikut membuat koleksi tanpa susunan default, lalu menambahkan indeks pada bidang nama dengan susunan case sensitif. Komponen Internasional untuk Unicode

/* strength: CollationStrength.Secondary
* Secondary level of comparison. Collation performs comparisons up to secondary * differences, such as diacritics. That is, collation performs comparisons of 
* base characters (primary differences) and diacritics (secondary differences). * Differences between base characters takes precedence over secondary 
* differences.
*/
db.users.createIndex( { name: 1 }, collation: { locale: 'tr', strength: 2 } } )

Untuk menggunakan indeks, kueri harus menentukan susunan yang sama.

db.users.insert( [ { name: "Oğuz" },
                            { name: "oğuz" },
                            { name: "OĞUZ" } ] )

// does not use index, finds one result
db.users.find( { name: "oğuz" } )

// uses the index, finds three results
db.users.find( { name: "oğuz" } ).collation( { locale: 'tr', strength: 2 } )

// does not use the index, finds three results (different strength)
db.users.find( { name: "oğuz" } ).collation( { locale: 'tr', strength: 1 } )

atau Anda dapat membuat koleksi dengan susunan standar:

db.createCollection("users", { collation: { locale: 'tr', strength: 2 } } )
db.users.createIndex( { name : 1 } ) // inherits the default collation

Tampaknya ada masalah sintaksis kecil (kawat gigi hilang). Harap perbarui kueri: db.users.createIndex( { name: 1 }, {collation: { locale: 'tr', strength: 2 } } )
Mohd Belal

3

Untuk mencari variabel dan menghindarinya:

const escapeStringRegexp = require('escape-string-regexp')
const name = 'foo'
db.stuff.find({name: new RegExp('^' + escapeStringRegexp(name) + '$', 'i')})   

Melarikan diri dari variabel melindungi permintaan terhadap serangan dengan '. *' Atau regex lainnya.

escape-string-regexp


1

Gunakan RegExp , Jika ada opsi lain yang tidak bekerja untuk Anda, RegExp adalah pilihan yang baik. Itu membuat case string tidak sensitif.

var username = new RegExp("^" + "John" + "$", "i");;

gunakan nama pengguna dalam kueri, dan kemudian selesai.

Saya harap ini juga akan berhasil untuk Anda. Semua yang terbaik.


0

Saya telah membuat Func sederhana untuk case regex tidak sensitif, yang saya gunakan dalam filter saya.

private Func<string, BsonRegularExpression> CaseInsensitiveCompare = (field) => 
            BsonRegularExpression.Create(new Regex(field, RegexOptions.IgnoreCase));

Maka Anda cukup memfilter pada bidang sebagai berikut.

db.stuff.find({"foo": CaseInsensitiveCompare("bar")}).count();

0

Menggunakan filter berfungsi untuk saya dalam C #.

string s = "searchTerm";
    var filter = Builders<Model>.Filter.Where(p => p.Title.ToLower().Contains(s.ToLower()));
                var listSorted = collection.Find(filter).ToList();
                var list = collection.Find(filter).ToList();

Bahkan mungkin menggunakan indeks karena saya percaya metode dipanggil setelah pengembalian terjadi tetapi saya belum menguji ini.

Ini juga menghindari masalah

var filter = Builders<Model>.Filter.Eq(p => p.Title.ToLower(), s.ToLower());

bahwa mongodb akan menganggap p.Title.ToLower () adalah properti dan tidak akan dipetakan dengan benar.


Terima kasih, Ini bekerja untuk Aku. Di sini kita perlu mendapatkan filter dalam variabel kemudian meneruskan metode Find ().
Nilay

0

Untuk siapa pun yang menggunakan Golang dan ingin memiliki pencarian teks lengkap yang peka huruf dengan mongodb dan perpustakaan mgo godoc globalsign .

collation := &mgo.Collation{
    Locale:   "en",
    Strength: 2, 
}


err := collection.Find(query).Collation(collation)

-1

Seperti yang dapat Anda lihat di mongo docs - karena $textindeks versi 3.2 tidak peka huruf besar-kecil: https://docs.mongodb.com/manual/core/index-text/#text-index-case-insensitivity

Buat indeks teks dan gunakan $ text operator dalam permintaan Anda .


Menggunakan pencarian teks lengkap adalah salah dalam hal ini (dan berpotensi berbahaya ), karena pertanyaannya adalah tentang membuat kueri yang tidak peka terhadap huruf besar-kecil, misalnya username: 'bill'mencocokkan BILLatau Bill, bukan kueri pencarian teks lengkap, yang juga akan cocok dengan kata - kata yang berasal dari kata tangkasbill , seperti Bills, billeddll.
Dan Dascalescu

-1

Ini telah diuji untuk pencarian string

{'_id': /.*CM.*/}               ||find _id where _id contains   ->CM
{'_id': /^CM/}                  ||find _id where _id starts     ->CM
{'_id': /CM$/}                  ||find _id where _id ends       ->CM

{'_id': /.*UcM075237.*/i}       ||find _id where _id contains   ->UcM075237, ignore upper/lower case
{'_id': /^UcM075237/i}          ||find _id where _id starts     ->UcM075237, ignore upper/lower case
{'_id': /UcM075237$/i}          ||find _id where _id ends       ->UcM075237, ignore upper/lower case

-1

Saya telah menghadapi masalah yang sama dan inilah yang bekerja untuk saya:

  const flavorExists = await Flavors.findOne({
    'flavor.name': { $regex: flavorName, $options: 'i' },
  });

Solusi ini sudah diberikan dua kali sebelumnya. Silakan periksa jawaban yang ada sebelum memposting yang baru.
Dan Dascalescu

@DanDascalescu tidak yakin apa yang Anda bicarakan, pada CTRL + F, solusi serupa dengan banyak upvotes mempostingnya pada September 2018. Saya memposting jawaban saya April 2018. Saya benar-benar memposting ini karena tidak ada pada saat itu. Periksa juga kapan diposting sebelum memperingatkan mereka yang benar-benar mencoba membantu.
Woppi

Saya sedang berbicara tentang jawaban ini dari April 2016, dan jawaban ini dari Mei 2016. Keduanya menggunakan $regexdan $options. Apa yang Anda Ctrl + F?
Dan Dascalescu

Juga, menggunakan $regextidak efisien dan berpotensi tidak aman, seperti yang saya jelaskan di edit saya untuk jawaban 2016 lainnya ini . Tidak ada salahnya menghapus jawaban jika mereka tidak lagi melayani komunitas!
Dan Dascalescu

Tercatat pada $ regex yang tidak efisien, terima kasih banyak. Opsi I Ctrl + F $. Kami hanya dua di sini tanpa Regexp baru dalam kode $ regex kami, Apr 2018 dan Sep 2018. Saya tidak menggunakan Regexp baru dalam jawaban saya. Saya lupa masalah khusus yang saya miliki dengan Regexp baru yang diselesaikan ketika saya menghapusnya dan gunakan saja solusi ini yang saya posting.
Woppi
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.