次の解決策は、1からの長さ(少なくとも)にシーケンス番号を含むテーブルを必要とするをしたいですあなたのword_column
word_column
がVARCHAR(190)
であると仮定すると、1から190までの番号の表が必要です。シーケンスプラグインでMariaDBを使用する場合は、表seq_1_to_190
を使用できます。あなたがそれを持っていない場合、それを作成する多くの方法があります。一つの簡単な方法は、information_schema.columns
テーブルを使用することです:
create table if not exists seq_1_to_190 (seq tinyint unsigned auto_increment primary key)
select null as seq from information_schema.columns limit 190;
ます。また、サブクエリでオンザフライでそれを作成することができますが、それはあなたのクエリが複雑になります。
セッション変数@word
を使用して検索文字列を保存します。
set @word = 'StackExch_bla_bla_bla';
しかし、すべての出現を定数検索文字列で置き換えることができます。
今、私たちは
select seq as l, left(@word, seq) as substr
from seq_1_to_190 s
where s.seq <= char_length(@word)
http://rextester.com/BWU18001
と、あなたのwords
テーブルとそれに参加するときLIKE
条件のためにそれを使用して、すべての接頭サブストリングを作成するために、シーケンステーブルを使用することができます。
select w.word_column
from (
select seq as l, left(@word, seq) as substr
from seq_1_to_190 s
where s.seq <= char_length(@word)
) s
join words w on w.word_column like concat(replace(s.substr, '_', '\_'), '%')
order by s.l desc
limit 1
http://rextester.com/STQP82942
_
はプレースホルダで、\_
で検索文字列にエスケープする必要があることに注意してください。あなたの文字列に含めることができる場合は%
のためにそれを行う必要がありますが、私は私の答えでこの部分をスキップします。
クエリはまた、サブクエリなしで書くことができます。
select w.word_column
from seq_1_to_190 s
join words w on w.word_column like concat(replace(left(@word, seq), '_', '\_'), '%')
where s.seq <= char_length(@word)
order by s.seq desc
limit 1
http://rextester.com/QVZI59071
これらのクエリは、仕事をしてtheorieに、彼らはまた、高速である必要があります。しかし、MySQL(私のケースではMariaDB 10.0.19)は、悪い実行計画を作成し、ORDER BY
節のインデックスを使用しません。両方のクエリは、100K行のデータセットで約1.8秒で実行されます。
私は、単一のクエリを使用してパフォーマンスを向上させるために何ができるベストは
select (
select word_column
from words w
where w.word_column like concat(replace(left(@word, s.seq), '_', '\_'), '%')
limit 1
) as word_column
from seq_1_to_190 s
where s.seq <= char_length(@word)
having word_column is not null
order by s.seq desc
limit 1
http://rextester.com/APZHA8471
あるこの1つは高速ですが、それでも670ミリ秒のような必要があります。 Gordons CASEクエリは125ミリ秒で実行されますが、フルテーブル/インデックススキャンとファイルセットが必要です。
私はインデックス付きの一時テーブルとORDER BY
句のインデックスを使用するためにエンジンを強制的に管理しかし:
drop temporary table if exists tmp;
create temporary table tmp(
id tinyint unsigned auto_increment primary key,
pattern varchar(190)
) engine=memory
select null as id, left(@word, seq) as pattern
from seq_1_to_190 s
where s.seq <= char_length(@word)
order by s.seq desc;
select w.word_column
from tmp force index for order by (primary)
join words w
on w.word_column >= tmp.pattern
and w.word_column < concat(tmp.pattern, char(127))
order by tmp.id asc
limit 1
http://rextester.com/OOE82089
このクエリは、上の「インスタント」(1ミリ秒未満)であります私の100K行のテストテーブル。 FORCE INDEX
を削除するか、LIKE
条件を使用すると、再び低速になります。
char(127)
がASCII文字列で機能するように見えることに注意してください。キャラクタセットに応じて別のキャラクタを探す必要があるかもしれません。
結局のところ、最初の考えはUNION ALL
というクエリを使用することでしたが、これはGordon Linoffによって提案されました。また、 "インスタント" である
set @subquery = '(
select word_column
from words
where word_column like {pattern}
limit 1
)';
set session group_concat_max_len = 1000000;
set @sql = (
select group_concat(
replace(
@subquery,
'{pattern}',
replace(quote(concat(left(@word, seq), '%')), '_', '\_')
)
order by s.seq desc
separator ' union all '
)
from seq_1_to_190 s
where s.seq <= char_length(@word)
);
set @sql = concat(@sql, ' limit 1');
prepare stmt from @sql;
execute stmt;
http://rextester.com/OPTJ37873
: - しかし、ここでSQL唯一のソリューションです。
あなたはstrored手続き/関数のような場合は、 - ここで、関数の:それはおそらく最速の方法です
select get_with_similar_begin('StackExch_bla_bla_bla');
select get_with_similar_begin('StackO_bla_bla_bla');
http://rextester.com/CJTU4629
として
create function get_with_similar_begin(search_str text) returns text
begin
declare l integer;
declare res text;
declare pattern text;
set l = char_length(search_str);
while l > 0 and res is null do
set pattern = left(search_str, l);
set pattern = replace(pattern, '_', '\_');
set pattern = replace(pattern, '%', '\%');
set pattern = concat(pattern, '%');
set res = (select word_column from words where word_column like pattern);
set l = l - 1;
end while;
return res;
end
はそれを使用してください。長い文字列の場合、の種類を分けて征服すると、平均ルックアップ数が減少することがあります()。しかし、ちょっと残忍かもしれません。
あなたは大きなテーブルの上にあなたのクエリをテストしたい場合は - 私は(シーケンスプラグインとMariaDBのための)私のテストテーブルを作成するには、次のコードを使用:
drop table if exists words;
create table words(
id mediumint auto_increment primary key,
word_column varchar(190),
index(word_column)
);
insert into words(word_column)
select concat('Stack', rand(1)) as word_column
from seq_1_to_100000;
insert into words(word_column)values('StackOferflow'),('StackExchange'),('MetaStackExchange');
'like'と' regexp'は適していませんなぜなら、私は入力単語と同じ方法で始まる単語を取得したいからです。 – Palindromer