このようにテーブルの行の列に文字列がある場合
1 2 2 2 2 2 2
文字列内の部分文字列2
の出現をどのようにカウントしますか?スペース区切り文字" "
以外は何も想定していない。
この目的のために、数値を部分文字列として扱いましょう
CREATE TABLE foo
AS
SELECT 1 AS id, '1 2 2 2 2 2 2'::text AS data;
TABLE foo
id | data
----+---------------
1 | 1 2 2 2 2 2 2
あなたはこれで解決することができます
[〜#〜] fast [〜#〜]以下に説明するパターンなどの文字列関数
length(str) - length(*replace(str, replaceStr))
/ length(replaceStr)
length
とregexp_replace
の使用ほとんどのRDBMSは、このような部分文字列の出現を計算する方法を提供しています。
SELECT length(data) - length(replace(data, '2', ''))
/ length('2')
FROM foo;
アンカーがないと、something-space-delimited内の部分文字列を置き換えているかどうかわからないため、このメソッドはここでは適用できません。例として、上記は2
の329
を置き換えます。これを修正するには、regexp_replace
を使用して部分文字列を固定します。
SELECT length(data) - length(regexp_replace(data, '\m2\M', '', 'g'))
/ length('2')
FROM foo;
より複雑にすることもできますが、単純なスペース( '')で分割していないため、 次のように異なる長さのサブストリングに対応することもできます)この質問 。これが/ length('2')
を明示的に含める理由です。これは何もしないことになりますが、1文字より長いものを検索する場合は必須です。
SELECT length(data) - length(regexp_replace(data, '\m42\M', '', 'g'))
/ length('42')
FROM foo;
ARRAY[]
に分割するここでは、1つの一致を減算して文字列を2つのフラグメントに分割する必要があるため、発生はフラグメントカウントより1つ少なくなります。このxyx
でのy
の分割は{'x', 'x'}
を生成し、y
の発生に対応する長さを1
にする必要があります。
SELECT array_length(x, 1) - 1
FROM foo
CROSS JOIN LATERAL regexp_split_to_array(data, '\m2\M') AS t(x);
-- un-anchored version for reference.
-- CROSS JOIN LATERAL string_to_array(data, '2') AS t(x);
または、string_to_array
を使用して、スペースで区切られたものを分離し、一致をカウントすることもできます。
SELECT id, array_length(array_positions(x, '2'), 1)
FROM foo
CROSS JOIN LATERAL string_to_array(data, ' ') AS t(x);
regexp_split_to_table
でテーブルに分割するここでは、正規表現をテーブルに分割しています。このメソッドでは、GROUP BY
とcount()
を使用しています。
SELECT id, x
FROM foo
CROSS JOIN LATERAL regexp_split_to_table(data, ' ')
AS t(x);
id | x
----+---
1 | 1
1 | 2
1 | 2
1 | 2
1 | 2
1 | 2
1 | 2
(7 rows)
そして、そこから通常のSQLを実行できます。
SELECT id, x, count(*)
FROM (
SELECT id, x
FROM foo
CROSS JOIN LATERAL regexp_split_to_table(data, ' ')
AS t(x)
) AS t(id,x)
GROUP BY id, x;
id | x | count
----+---+-------
1 | 1 | 1
1 | 2 | 6
regex_matches
の使用ここでは、分割を回避し、代わりにWord境界に\m
および\M
アンカーを使用します。
SELECT count(*)
FROM foo
CROSS JOIN LATERAL regexp_matches(data, '\m2\M', 'g');
この方法は全体的に最速であることが判明しました。
CREATE LANGUAGE plperl
CREATE FUNCTION count_occurances(inputStr text, regex text)
RETURNS smallint
AS $BODY$
scalar @{[ $_[0] =~ m/$_[1]/g ]}
$BODY$
LANGUAGE plperl
IMMUTABLE;
同じ形式のデータに従うと、パフォーマンスへの影響が次のように得られます。
CREATE TABLE foo
AS
SELECT
1 AS id,
array_to_string(
ARRAY(SELECT trunc(random()*100+1)::int % 100 FROM generate_series(1,5000) AS t(x)),
' '
) AS data
;
これらの制約の下で、plperlを使用した手続き型メソッドが最速であることがわかりました。次に、次のものが最速のネイティブメソッドであることがわかりました。
length(str) - regexp_replace(str, replacement, g)
/ length(replacement)
文字列をアンカーする必要がないことを除いて、試行錯誤された文字列置換の方法は、いちばん不格好かもしれませんが、いちばん速くて最も効率的なネイティブ方法です。
length(str) - replace(str, replacement)
/ length(replacement)
つまり、ARRAY[]
メソッドは、テーブルに分割するよりも大幅に高速です。
完全一致を検索(スタンドアロン2
対。 2
以内に 329
)
@AndriyMによる改善
with foo (data) as (select '1 2 329 2 2 2 272 2 22 2 2'::text)
select (
length (data) + 2
- length (replace (replace (' ' || data || ' ',' 2 ',' '),' 2 ',' '))
) / length('2')
from foo
;
7
元のソリューション
with foo (data) as (select '1 2 329 2 2 2 272 2 22 2 2'::text)
select (
length (replace (data,' ',' ')) + 2
- length (replace (' ' || replace (data,' ',' ') || ' ', ' 2 ', ''))
) / length (' 2 ')
from foo
;
7