Kueri DB optimal untuk pencarian awalan

Saya memiliki kumpulan data yang merupakan daftar rentang awalan, dan ukuran awalan tidak semuanya sama. Berikut beberapa contohnya:

low: 54661601   high: 54661679   "bin": a
low: 526219100  high: 526219199  "bin": b
low: 4305870404 high: 4305870404 "bin": c

Saya ingin mencari "bin" mana yang sesuai dengan nilai tertentu dengan awalan yang sesuai. Misalnya, nilai 5466160179125211 akan sesuai dengan "bin" a. Jika terjadi tumpang tindih (yang jumlahnya sedikit), kita dapat mengembalikan awalan terpanjang atau semua awalan.

Algoritme optimal jelas merupakan semacam pohon di mana objek bin dapat disisipkan, di mana setiap tingkat pohon yang berurutan mewakili lebih banyak awalan.

Pertanyaannya adalah: bagaimana kita mengimplementasikan ini (dalam satu query) dalam database? Boleh mengubah/menambah kumpulan data. Apa desain data & kueri terbaik untuk ini? Jawaban menggunakan mongo atau MySQL adalah yang terbaik.


person Peyton    schedule 15.06.2012    source sumber


Jawaban (4)


Jika Anda membuat asumsi ringan tentang jumlah tumpang tindih dalam rentang awalan Anda, Anda dapat melakukan apa yang Anda inginkan secara optimal menggunakan MongoDB atau MySQL. Dalam jawaban saya di bawah ini, saya akan mengilustrasikannya dengan MongoDB, tetapi seharusnya cukup mudah untuk mem-porting jawaban ini ke MySQL.

Pertama, mari kita ulangi masalahnya sedikit. Saat Anda berbicara tentang pencocokan "rentang awalan", saya yakin apa yang sebenarnya Anda bicarakan adalah menemukan rentang yang benar berdasarkan pengurutan leksikografis (secara intuitif, ini hanyalah pengurutan string berdasarkan abjad alami). Misalnya, himpunan bilangan yang awalannya cocok dengan 54661601 hingga 54661679 adalah himpunan bilangan yang, jika ditulis sebagai string, secara leksikografis lebih besar atau sama dengan "54661601", tetapi secara leksikografis lebih kecil dari "54661680". Jadi, hal pertama yang harus Anda lakukan adalah menambahkan 1 ke semua batas tinggi Anda, sehingga Anda bisa mengungkapkan pertanyaan Anda dengan cara ini. Di mongo, dokumen Anda akan terlihat seperti ini

{low: "54661601", high: "54661680", bin: "a"}
{low: "526219100",  high: "526219200",  bin: "b"}
{low: "4305870404", high: "4305870405", bin: "c"}

Sekarang masalahnya menjadi: jika diberikan himpunan interval satu dimensi dalam bentuk [rendah, tinggi), bagaimana kita dapat dengan cepat menemukan interval mana yang memuat suatu titik tertentu ? Cara termudah untuk melakukan hal ini adalah dengan indeks pada bidang rendah atau tinggi. Mari kita gunakan bidang tinggi. Di cangkang mongo:

db.coll.ensureIndex({high : 1})

Untuk saat ini, anggap saja intervalnya tidak tumpang tindih sama sekali. Jika hal ini terjadi, maka untuk titik kueri "x" tertentu, satu-satunya interval yang mungkin berisi "x" adalah interval dengan nilai tinggi terkecil yang lebih besar dari "x". Jadi kita bisa menanyakan dokumen itu dan memeriksa apakah nilainya rendah juga kurang dari "x". Misalnya, ini akan mencetak interval yang cocok, jika ada:

db.coll.find({high : {'$gt' : "5466160179125211"}}).sort({high : 1}).limit(1).forEach(
       function(doc){ if (doc.low <= "5466160179125211") printjson(doc) } 
  )

Misalkan sekarang, alih-alih mengasumsikan interval tidak tumpang tindih sama sekali, Anda berasumsi bahwa setiap interval tumpang tindih dengan interval tetangga yang kurang dari k (saya tidak tahu berapa nilai k akan menjadikan hal ini benar bagi Anda, namun mudah-mudahan ini hanya masalah kecil). Dalam hal ini, Anda cukup mengganti 1 dengan k pada "batas" di atas, yaitu.

 db.coll.find({high : {'$gt' : "5466160179125211"}}).sort({high : 1}).limit(k).forEach(
       function(doc){ if (doc.low <= "5466160179125211") printjson(doc) } 
  )

Berapa waktu berjalan algoritma ini? Indeks disimpan menggunakan pohon-B, jadi jika ada interval n dalam kumpulan data Anda, diperlukan waktu O(log n) untuk mencari dokumen pertama yang cocok dengan < nilai kuat>tinggi, lalu O(k) waktu untuk melakukan iterasi pada dokumen k berikutnya, dengan total O(log n + k) kali. Jika k konstan, atau bahkan kurang dari O(log n), maka ini optimal secara asimtotik (ini ada dalam model komputasi standar; saya tidak menghitung jumlah transfer memori eksternal atau apa pun yang mewah).

Satu-satunya kasus di mana hal ini gagal adalah ketika k besar, misalnya jika suatu interval besar memuat hampir semua interval lainnya. Dalam hal ini, waktu berjalannya adalah O(n). Jika data Anda terstruktur seperti ini, Anda mungkin ingin menggunakan metode lain. Salah satu pendekatannya adalah dengan menggunakan pengindeksan "2d" mongo, dengan nilai rendah dan tinggi yang mengkodekan koordinat x dan y . Maka kueri Anda akan berhubungan dengan kueri titik di wilayah tertentu pada bidang x - y. Hal ini mungkin berhasil dalam praktiknya, meskipun dengan penerapan pengindeksan 2d saat ini, kasus terburuknya masih O(n).

Ada sejumlah hasil teoritis yang mencapai kinerja O(log n) untuk semua nilai k. Mereka diberi nama seperti Pohon Pencarian Prioritas, Pohon Segmen, Pohon Interval, dll. Namun, ini adalah struktur data tujuan khusus yang harus Anda terapkan sendiri. Sejauh yang saya tahu, saat ini tidak ada database populer yang mengimplementasikannya.

person matulef    schedule 16.06.2012

"Optimal" dapat memiliki arti yang berbeda bagi orang yang berbeda. Tampaknya Anda dapat melakukan sesuatu seperti menyimpan nilai rendah dan tinggi sebagai varchars. Maka yang perlu Anda lakukan hanyalah

select bin from datatable where '5466160179125211' between low and high

Atau jika Anda memiliki alasan untuk mempertahankan nilai sebagai bilangan bulat dalam tabel, Anda dapat melakukan CASTing dalam kueri.

Saya tidak tahu apakah ini akan memberi Anda kinerja buruk dengan kumpulan data yang besar. Dan saya harap saya mengerti apa yang ingin Anda lakukan.

person Chud    schedule 15.06.2012

Dengan MySQL Anda mungkin harus menggunakan prosedur tersimpan, yang Anda panggil untuk memetakan nilai ke bin. Prosedur tersebut akan menanyakan daftar keranjang untuk setiap baris dan melakukan operasi aritmatika atau string untuk menemukan keranjang yang cocok. Anda dapat menyempurnakan desain ini dengan menggunakan awalan dengan panjang tetap, disusun dalam jumlah lapisan yang tetap. Anda dapat menetapkan kedalaman tetap pada pohon Anda dan setiap lapisan memiliki tabel. Anda tidak akan mendapatkan performa seperti pohon dengan salah satu pendekatan ini.

Jika Anda ingin melakukan sesuatu yang lebih canggih, saya rasa Anda harus menggunakan platform lain.

Sql Server memiliki tipe data Hierarki: http://technet.microsoft.com/en-us/library/bb677173.aspx

PostgreSQL memiliki tipe data cidr. Saya tidak paham dengan tingkat dukungan kueri yang dimilikinya, namun secara teori Anda bisa membuat tabel perutean di dalam db Anda dan menggunakannya untuk menetapkan keranjang: http://www.postgresql.org/docs/7.4/static/datatype-net-types.html#DATATYPE-CIDR

person Codure    schedule 15.06.2012

Peyton! :)

Jika Anda perlu menyimpan semuanya sebagai bilangan bulat, dan ingin semuanya berfungsi dengan satu kueri, ini akan berfungsi:

select bin from datatable where 5466160179125211 between 
      low*pow(10, floor(log10(5466160179125211))-floor(log10(low))) 
   and ((high+1)*pow(10, floor(log10(5466160179125211))-floor(log10(high)))-1);

Dalam hal ini, ia akan mencari antara angka 5466160100000000 (angka terendah dengan awalan rendah & jumlah digit yang sama dengan angka yang dicari) dan 546616799999999 (angka tertinggi dengan awalan tinggi & jumlah digit yang sama dengan angka tersebut mencari). Ini tetap berfungsi jika awalan tinggi memiliki lebih banyak digit daripada awalan rendah. Ini juga harus berfungsi (menurut saya) dalam kasus di mana angkanya lebih pendek dari panjang awalan, di mana kode varchar dalam solusi sebelumnya dapat memberikan hasil yang salah.

Anda sebaiknya bereksperimen untuk membandingkan kinerja memiliki banyak matematika sebaris dalam kueri (seperti dalam solusi ini) vs. kinerja menggunakan varchars.

Sunting: Performa tampaknya sangat bagus bahkan pada tabel besar tanpa indeks; jika Anda dapat menggunakan varchars maka Anda mungkin dapat lebih meningkatkan kinerja dengan mengindeks kolom rendah dan tinggi. Perhatikan bahwa Anda pasti ingin menggunakan varchars jika ada awalan yang memiliki awalan nol. Berikut perbaikan untuk memungkinkan kasus di mana angkanya lebih pendek dari awalan saat menggunakan varchars:

select * from datatable2 where '5466' between low and high
    and length('5466') >= length(high);
person Jamie    schedule 15.06.2012