Bagaimana cara membuat indeks bersyarat di MySQL?

24

Bagaimana cara membuat indeks untuk memfilter rentang atau subset tabel tertentu di MySQL? AFAIK tidak mungkin dibuat secara langsung, tetapi saya pikir mungkin untuk mensimulasikan fitur ini.

Contoh: Saya ingin membuat indeks untuk NAMEkolom hanya untuk baris denganSTATUS = 'ACTIVE'

Fungsi ini akan disebut indeks yang difilter dalam SQL Server dan sebagian indeks dalam Postgres.

Maniero
sumber

Jawaban:

9

MySQL saat ini tidak mendukung indeks bersyarat.

Untuk mencapai apa yang Anda minta (bukan bahwa Anda harus melakukannya;)) Anda bisa mulai membuat tabel tambahan:

CREATE TABLE  `my_schema`.`auxiliary_table` (
   `id` int unsigned NOT NULL,
   `name` varchar(250), /* specify the same way as in your main table */
   PRIMARY KEY (`id`),
   KEY `name` (`name`)
);

Kemudian Anda menambahkan tiga pemicu di tabel utama:

delimiter //

CREATE TRIGGER example_insert AFTER INSERT ON main_table
FOR EACH ROW
BEGIN
   IF NEW.status = 'ACTIVE' THEN
      REPLACE auxiliary_table SET
         auxiliary_table.id = NEW.id,
         auxiliary_table.name = NEW.name;
   END IF;
END;//

CREATE TRIGGER example_update AFTER UPDATE ON main_table
FOR EACH ROW
BEGIN
   IF NEW.status = 'ACTIVE' THEN
      REPLACE auxiliary_table SET
         auxiliary_table.id = NEW.id,
         auxiliary_table.name = NEW.name;
   ELSE
      DELETE FROM auxiliary_table WHERE auxiliary_table.id = OLD.id;
   END IF;
END;//

CREATE TRIGGER example_delete AFTER DELETE ON main_table
FOR EACH ROW
BEGIN
   DELETE FROM auxiliary_table WHERE auxiliary_table.id = OLD.id;
END;//

delimiter ;

Kami perlu delimiter //karena kami ingin menggunakan ;di dalam pemicu.

Dengan begitu, tabel tambahan akan berisi persis ID yang sesuai dengan baris tabel utama yang berisi string "AKTIF", sedang diperbarui oleh pemicu.

Untuk menggunakannya pada select, Anda dapat menggunakan yang biasa join:

SELECT main_table.* FROM auxiliary_table LEFT JOIN main_table
   ON auxiliary_table.id = main_table.id
   ORDER BY auxiliary_table.name;

Jika tabel utama sudah berisi data, atau jika Anda membuat beberapa operasi eksternal yang mengubah data dengan cara yang tidak biasa (EG: di luar MySQL), Anda bisa memperbaiki tabel tambahan dengan ini:

INSERT INTO auxiliary_table SET
   id = main_table.id,
   name = main_table.name,
   WHERE main_table.status="ACTIVE";

Tentang kinerja, mungkin Anda akan memiliki sisipan, pembaruan, dan penghapusan yang lebih lambat. Ini bisa masuk akal hanya jika Anda benar-benar menangani beberapa kasus di mana kondisi yang diinginkan adalah positif. Meski begitu, mungkin hanya menguji Anda dapat melihat apakah ruang yang disimpan benar-benar membenarkan pendekatan ini (dan jika Anda benar-benar menghemat ruang sama sekali).

Bacco
sumber
7

Jika saya memahami pertanyaan dengan benar, saya pikir apa yang akan mencapai apa yang Anda coba lakukan adalah membuat indeks pada kedua kolom, NAMA dan STATUS. Itu akan secara efisien memungkinkan Anda untuk menanyakan di mana NAME = 'SMITH' dan STATUS = 'ACTIVE'

Es hitam
sumber
1
Ok, tapi ini bukan ruang efisien jika Anda memiliki beberapa baris dengan status AKTIF.
Maniero
Tidak, tidak, tapi itu bukan persyaratan dalam pertanyaan, dan tidak disebutkan bahwa tabel itu sangat berbobot dengan salah satu nilai. Untuk itu saya akan membuat pandangan terwujud dari STATUS yang Anda cari, tetapi MySQL tidak mendukungnya.
BlackICE
dan ruang disk murah ...
BlackICE
2
Ya, ini bukan persyaratan langsung, jadi saya memulai komentar dengan OK. Saya mencari beberapa alternatif profesional. Dan alternatif profesional selalu mencari cara paling efisien untuk melakukan tugas Anda. Jawaban Anda mungkin yang paling jelas. Tidak masalah dengan itu. Tapi saya benar-benar tidak setuju dengan "ruang disk yang murah", bukan karena itu mahal, tentu saja murah tetapi memori tidak begitu murah, memori memiliki batas rendah dan indeks harus hidup terutama pada memori agar lebih efisien. Akses disk tidak begitu murah. Jawaban Anda tentu adalah salah satu cara yang benar untuk mencapai tujuan tetapi saya ragu itu yang terbaik.
Maniero
Saya akan tidak setuju pada memori juga, ini cukup murah hari ini juga (tentu tidak semurah ruang disk, tetapi pada $ 10 / manggung untuk beberapa itu, saya akan mengatakan Anda dapat berbelanja secara royal sedikit :)
BlackICE
6

Anda tidak dapat melakukan pengindeksan bersyarat, tetapi untuk contoh Anda, Anda dapat menambahkan indeks multi-kolom pada ( name,status ).

Meskipun akan mengindeks semua data di kolom tersebut, tetap akan membantu Anda menemukan nama yang Anda cari dengan status "aktif".

Jonathan
sumber
4

Anda bisa melakukan ini dengan memisahkan data antara dua tabel, menggunakan tampilan untuk menyatukan dua tabel ketika semua data diperlukan, dan mengindeks hanya satu tabel di kolom itu - tapi saya pikir ini akan menyebabkan masalah kinerja untuk kueri yang perlu menabrak seluruh tabel kecuali perencana kueri lebih pintar dari yang saya berikan kredit untuk. Pada dasarnya Anda akan mempartisi tabel secara manual (dan menerapkan indeks hanya pada salah satu partisi).

Sayangnya fitur partisi tabel bawaan tidak akan membantu Anda dalam pencarian Anda karena Anda tidak dapat menerapkan indeks ke partisi tunggal.

Anda bisa mempertahankan kolom tambahan dengan indeks dan hanya memiliki nilai di kolom itu ketika kondisi yang Anda inginkan berdasarkan indeks itu benar, tetapi ini cenderung padat karya dan memiliki nilai terbatas (atau negatif) dalam hal efisiensi permintaan dan penghematan ruang.

David Spillett
sumber
Saya TIDAK akan memiliki dua tabel hanya untuk memiliki pengindeksan yang lebih baik, karena bergabung masih akan mahal bukan?
jcolebrand
@ jcolebrand: itu akan lebih mahal untuk permintaan umum (atas pandangan melakukan penyatuan), Anda harus memilih secara khusus dari tabel partisi untuk menggunakan indeks. Partisi bawaan akan melakukan ini untuk Anda secara efisien, tetapi hanya cara yang diinginkan Bigown (untuk menghemat ruang) jika mendukung indeks spesifik partisi. Saya bilang dia bisa melakukannya, bukan dia mau!
David Spillett
0

MySQL sekarang memiliki kolom virtual, yang dapat digunakan untuk indeks.

druud62
sumber
3
Bagaimana fitur ini dapat digunakan untuk mensimulasikan indeks yang difilter?
ypercubeᵀᴹ
1
@ yper-trollᵀᴹ, druud62 mungkin berpikir tentang Oracle: dbfiddle.uk/… - MySQL tidak terlihat memperlakukan NULL dengan cara yang sama: dbfiddle.uk/…
Jack Douglas
@ JackDouglas mungkin. (bukankah itu hanya pengoptimalan indeks yang menghemat ruang? Dengan kata lain dapat select count(*) from foo where id is null ;menggunakan indeks?)
ypercubeᵀᴹ
@ yper-trollᵀᴹ Oracle tidak mengindeks baris di mana semua kolom yang diindeks NULL ( use-the-index-luke.com/sql/where-clause/null/index ) - dan kolom virtual bisa di decode(status,'ACTIVE',name,null)misalnya.
Jack Douglas
Thnx, saya pikir itu telah berubah dalam versi terbaru (dan nol diindeks).
ypercubeᵀᴹ