J'ai une table de plusieurs millions de chaînes que je veux comparer à une table d'environ vingt mille chaînes comme celle-ci:
#standardSQL WITH record AS ( SELECT LOWER(text) AS name FROM `bigquery-public-data.hacker_news.comments` ), fragment AS ( SELECT LOWER(name) AS name, COUNT(*) FROM `bigquery-public-data.usa_names.usa_1910_current` GROUP BY name ) SELECT record.* FROM `record` JOIN `fragment` ON record.name LIKE CONCAT('%', fragment.name, '%')
Malheureusement, cela prend énormément de temps. p >
Étant donné que la table fragment
ne contient que 20 000 enregistrements, puis-je la charger dans un tableau JavaScript en utilisant un UDF et la faire correspondre de cette façon? J'essaie de comprendre comment faire cela maintenant, mais peut-être qu'il y a déjà de la magie que je pourrais faire ici pour accélérer les choses. J'ai essayé un CROSS JOIN
et mes ressources ont été dépassées assez rapidement. J'ai également essayé d'utiliser EXISTS
mais je ne peux pas faire référence au record.name
à l'intérieur de WHERE
de cette sous-requête sans obtenir d'erreur. P >
Cela semble refléter à peu près la même quantité de données ...
#standardSQL SELECT record.* FROM `record` JOIN `fragment` ON record.name LIKE CONCAT('%', fragment.name, '%')
p >
3 Réponses :
Ci-dessous, pour BigQuery Standard SQL
#standardSQL WITH record AS ( SELECT LOWER(text) AS name FROM `bigquery-public-data.hacker_news.comments` ), fragment AS ( SELECT DISTINCT LOWER(name) AS name FROM `bigquery-public-data.usa_names.usa_1910_current` ), temp_record AS ( SELECT record, TO_JSON_STRING(record) id, name, item FROM record, UNNEST(REGEXP_EXTRACT_ALL(name, r'\w+')) item ), temp_fragment AS ( SELECT name, item FROM fragment, UNNEST(REGEXP_EXTRACT_ALL(name, r'\w+')) item ) SELECT AS VALUE ANY_VALUE(record) FROM ( SELECT ANY_VALUE(record) record, id, r.name name, f.name fragment_name FROM temp_record r JOIN temp_fragment f USING(item) GROUP BY id, name, fragment_name ) WHERE name LIKE CONCAT('%', fragment_name, '%') GROUP BY id
ci-dessus a été terminé en 375 secondes, alors que la requête d'origine est toujours en cours d'exécution à 2740 secondes et continue de s'exécuter, donc je n'attendrai même pas qu'elle se termine
ne devrait-il pas supprimer le 1er% pour utiliser l'index de la base de données puisque '%' fragment_name '%' effectuera une analyse complète de la table à chaque fois?
il utilise BigQuery - aucun index pertinent ici. Aussi name LIKE CONCAT ('%', fragment_name, '%')
signifie correspondre à tout le nom où la valeur de fragment_name est n'importe où à l'intérieur de la valeur du nom
La réponse de Mikhail semble être plus rapide - mais en avons une qui n'a pas besoin de SPLIT
ni de séparer le texte en mots.
Premièrement, calculez une expression régulière avec tous les mots à recherché:
#standardSQL WITH record AS ( SELECT text AS name FROM `bigquery-public-data.hacker_news.comments` ), largestring AS ( SELECT '(?i)(mary|margaret|helen|more_names|more_names|more_names|josniel|khaiden|sergi)' ) SELECT record.* FROM `record` WHERE REGEXP_CONTAINS(record.name, (SELECT * FROM largestring))
Vous pouvez maintenant prendre cette chaîne résultante et l'utiliser dans un REGEX
en ignorant la casse:
#standardSQL WITH record AS ( SELECT text AS name FROM `bigquery-public-data.hacker_news.comments` ), fragment AS ( SELECT name AS name, COUNT(*) FROM `bigquery-public-data.usa_names.usa_1910_current` GROUP BY name ) SELECT FORMAT('(%s)',STRING_AGG(name,'|')) FROM fragment
J'ai remarqué le LIMIT 10
qui réduirait le nombre de fragments. Je me demande quelle est la limite d'une agrégation de chaînes dans BigQuey.
Oops. Je viens de supprimer le LIMIT 10
. Cela a fonctionné pour moi (~ 510 secondes).
juste pour revérifier - avez-vous réexécuté la chaîne pour plus grand chaîne
car il a encore 10 éléments?
Oui. Cela prendrait beaucoup moins de ~ 510 secondes si je n'avais que 10 noms.
Comme éludé dans ma question, j'ai travaillé sur une version utilisant un JavaScript UDF qui résout ce problème bien que de manière plus lente que la réponse que j'ai acceptée. Pour être complet, je le publie ici car peut-être que quelqu'un (comme moi dans le futur) pourrait le trouver utile.
CREATE TEMPORARY FUNCTION CONTAINS_ANY(str STRING, fragments ARRAY<STRING>) RETURNS STRING LANGUAGE js AS """ for (var i in fragments) { if (str.indexOf(fragments[i]) >= 0) { return fragments[i]; } } return null; """; WITH record AS ( SELECT text AS name FROM `bigquery-public-data.hacker_news.comments` WHERE text IS NOT NULL ), fragment AS ( SELECT name AS name, COUNT(*) FROM `bigquery-public-data.usa_names.usa_1910_current` WHERE name IS NOT NULL GROUP BY name ), fragment_array AS ( SELECT ARRAY_AGG(name) AS names, COUNT(*) AS count FROM fragment GROUP BY LENGTH(name) ), records_with_fragments AS ( SELECT record.name, CONTAINS_ANY(record.name, fragment_array.names) AS fragment_name FROM record INNER JOIN fragment_array ON CONTAINS_ANY(name, fragment_array.names) IS NOT NULL ) SELECT * EXCEPT(rownum) FROM ( SELECT record.name, records_with_fragments.fragment_name, ROW_NUMBER() OVER (PARTITION BY record.name) AS rownum FROM record INNER JOIN records_with_fragments ON records_with_fragments.name = record.name AND records_with_fragments.fragment_name IS NOT NULL ) WHERE rownum = 1
L'idée est que la liste des fragments est assez petite pour pouvoir être traité dans un tableau, similaire à la réponse de Felipe en utilisant des expressions régulières. La première chose que je fais est de créer une table fragment_array
qui est groupée par la longueur des fragments ... un moyen peu coûteux d'empêcher un tableau surdimensionné qui que j'ai trouvé peut provoquer UDF timeouts.
Ensuite, je crée une table appelée records_with_fragments
qui joint ces tableaux aux enregistrements d'origine, ne trouvant que ceux qui contiennent un fragment correspondant à l'aide de l'UDF JavaScript CONTAINS_ANY ()
. Cela entraînera une table contenant des doublons, car un enregistrement peut correspondre à plusieurs fragments.
Le SELECT
final extrait ensuite la table record
d'origine, joint à records_with_fragments
pour déterminer quel fragment correspond, et utilise également la fonction ROW_NUMBER ()
pour éviter les doublons, par exemple ne montrant que la première ligne de chaque enregistrement identifiée de manière unique par son name
.
Maintenant, la raison pour laquelle je fais la jointure dans la requête finale est que dans mes données réelles, il y en a plus champs que je veux en plus de la chaîne correspondant. Plus tôt dans mes données réelles, je crée une table de chaînes DISTINCT
qui doivent ensuite être jointes à nouveau.
Voila! Pas le plus élégant mais il fait le travail.
pouvez-vous penser à des données publiques que vous pouvez utiliser comme exemple?
bon exemple - voir la réponse