J'ai une requête quelque peu impliquée qui divise des chaînes et génère chaque mot comme un enregistrement.
J'ai fait un test rapide, un avec un CTE et une avec une sous-requête et j'ai quelque peu surpris de voir que le CTE prend deux fois plus de temps pour exécuter.
Voici le gist de ce que la requête fait:
-- 1. translate matches characters from comment to given list (of symbols) and replaces them with commas.
-- 2. string_to_array splits string by comma and puts in an array
-- 3. unnest unpacks the array into rows
SELECT
sub_query.Word,
sub_query._created_at
FROM
( SELECT unnest(string_to_array(translate(nps_reports.comment::text, ' ,.<>?/;:@#~[{]}=+-_)("*&^%$£!`\|}'::text, ',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,'::text), ','::text, ''::text)) AS Word,
nps_reports.comment,
nps_reports._id,
nps_reports._created_at
FROM nps_reports
WHERE nps_reports.comment::text <> 'undefined'::text
) sub_query
WHERE sub_query.Word IS NOT NULL AND NOT (sub_query.Word IN ( SELECT stop_words.stop_Word FROM stop_words))
ORDER BY sub_query._created_at DESC;
WITH split AS
(
SELECT unnest(string_to_array(translate(nps_reports.comment::text, ' ,.<>?/;:@#~[{]}=+-_)("*&^%$£!`\|}'::text, ',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,'::text), ','::text, ''::text)) AS Word,
nps_reports.comment,
nps_reports._id,
nps_reports._created_at
FROM nps_reports
WHERE nps_reports.comment::text <> 'undefined'::text
)
SELECT
split.Word,
split._created_at
FROM split
WHERE split.Word IS NOT NULL AND NOT (split.Word IN ( SELECT stop_words.stop_Word FROM stop_words))
ORDER BY split._created_at DESC;
Et voici les explications pour chacun:
Sort (cost=15921589.76..16082302.91 rows=64285258 width=40) (actual time=16299.150..17697.914 rows=4394788 loops=1)
Sort Key: sub_query._created_at DESC
Sort Method: external merge Disk: 116112kB
Buffers: shared hit=22915 read=7627, temp read=34281 written=34281
-> Subquery Scan on sub_query (cost=2.49..2311035.10 rows=64285258 width=40) (actual time=0.177..13274.895 rows=4394788 loops=1)
Filter: ((sub_query.Word IS NOT NULL) AND (NOT (hashed SubPlan 1)))
Rows Removed by Filter: 3676303
Buffers: shared hit=22915 read=7627
-> Seq Scan on nps_reports (cost=0.00..695825.11 rows=129216600 width=88) (actual time=0.073..9781.244 rows=8071091 loops=1)
Filter: ((comment)::text <> 'undefined'::text)
Rows Removed by Filter: 844360
Buffers: shared hit=22914 read=7627
SubPlan 1
-> Seq Scan on stop_words (cost=0.00..2.19 rows=119 width=4) (actual time=0.016..0.034 rows=119 loops=1)
Buffers: shared hit=1
Planning time: 0.115 ms
Execution time: 18451.245 ms
Sort (cost=17213755.76..17374468.91 rows=64285258 width=40) (actual time=44008.467..45508.786 rows=4394788 loops=1)
Sort Key: split._created_at DESC
Sort Method: external merge Disk: 116112kB
Buffers: shared hit=23031 read=7531, temp read=34281 written=353942
CTE split
-> Seq Scan on nps_reports (cost=0.00..695825.11 rows=129216600 width=135) (actual time=0.057..10451.951 rows=8071091 loops=1)
Filter: ((comment)::text <> 'undefined'::text)
Rows Removed by Filter: 844360
Buffers: shared hit=23027 read=7531
-> CTE Scan on split (cost=2.49..2907375.99 rows=64285258 width=40) (actual time=0.162..37888.364 rows=4394788 loops=1)
Filter: ((Word IS NOT NULL) AND (NOT (hashed SubPlan 2)))
Rows Removed by Filter: 3676303
Buffers: shared hit=23028 read=7531, temp written=319661
SubPlan 2
-> Seq Scan on stop_words (cost=0.00..2.19 rows=119 width=4) (actual time=0.009..0.030 rows=119 loops=1)
Buffers: shared hit=1
Planning time: 0.649 ms
Execution time: 46297.825 ms
CTE à PostgreSQL est une clôture d'optimisation. Cela signifie que le planificateur de requête ne pousse pas les optimisations sur une limite CTE.
Je pense que beaucoup de ceci est stupide si vous pouvez simplement l'écrire comme ça .. Ici, nous utilisons CROSS JOIN LATERAL
plutôt que l'emballage complexe et NOT EXISTS
plutôt que NOT IN
SELECT Word,
_created_at
FROM nps_reports
CROSS JOIN LATERAL unnest(regexp_split_to_array(
nps_reports.comment,
'[^a-zA-Z0-9]+'
)) AS Word
WHERE nps_reports.comment <> 'undefined'
AND nps_reports.comment IS NOT NULL
AND NOT EXISTS (
SELECT 1
FROM stop_words
WHERE stop_words.stop_Word = Word
)
ORDER BY _created_at DESC;
Tout cela dit, tout ce que vous faites semble réinventer FTS. C'est aussi une mauvaise idée.
@Évan Carroll a expliqué pourquoi le CTE prend plus de temps, mais voici une requête améliorée, plus rapide que toutes les solutions énumérées ci-dessus.
Voir Cette question pour plus de fond.
-- create custom dict (you don't necessarily need to do this)
CREATE TEXT SEARCH DICTIONARY simple_with_stop_words (TEMPLATE = pg_catalog.simple, STOPWORDS = english);
CREATE TEXT SEARCH CONFIGURATION public.simple_with_stop_words (COPY = pg_catalog.simple);
ALTER TEXT SEARCH CONFIGURATION public.simple_with_stop_words ALTER MAPPING FOR asciiword WITH simple_with_stop_words;
-- the actual query
SELECT
token.Word,
nps._created_at
FROM nps_reports nps CROSS JOIN LATERAL UNNEST(to_tsvector('simple_with_stop_words', nps.comment)) token(Word)
WHERE nps.comment IS NOT NULL AND
nps.comment <> 'undefined' AND
nps.language = 'en-US';
Ceci utilise l'OPTGRESQL to_tsvector
Fonction qui fait plusieurs choses en fonction de la configuration qui lui est donnée. Si utilisé avec le dictionnaire simple
, au lieu de la personnalisation que j'ai faite, elle scindera simplement n'importe quelle chaîne en mots.
J'utilise également une fonctionnalité de Postgres 9.3+, le mot-clé LATERAL
, ce qui me permet de passer un argument de la partie gauche de la jointure à droite de la jointure, c'est-à-dire: je peux passer le comment
dans UNNEST
.
Cela prend environ 10 seconds
Pour exécuter sur toute la base de données. Comparer à la méthode la plus rapide précédente (sous-requête) qui a pris 18 seconds
.