it-swarm.asia

مطابقة النمط مع LIKE أو SIMILAR TO أو التعبيرات العادية في PostgreSQL

كان علي أن أكتب استعلامًا بسيطًا حيث أذهب للبحث عن اسم الأشخاص الذي يبدأ بالحرف B أو D:

SELECT s.name 
FROM spelers s 
WHERE s.name LIKE 'B%' OR s.name LIKE 'D%'
ORDER BY 1

كنت أتساءل عما إذا كانت هناك طريقة لإعادة كتابة هذا ليصبح أكثر أداء. لذا يمكنني تجنب or و/أو like؟

103
Lucas Kauffman

استفسارك هو الأفضل إلى حد كبير. بناء الجملة لن يكون أقصر بكثير ، لن يصبح الاستعلام أسرع بكثير:

SELECT name
FROM   spelers
WHERE  name LIKE 'B%' OR name LIKE 'D%'
ORDER  BY 1;

إذا كنت تريد حقًا اختصار البنية ، فاستخدم تعبيرًا عاديًا مع الفروع:

...
WHERE  name ~ '^(B|D).*'

أو أسرع قليلاً مع فئة الأحرف:

...
WHERE  name ~ '^[BD].*'

اختبار سريع بدون فهرس يعطي نتائج أسرع من SIMILAR TO في كلتا الحالتين بالنسبة لي.
مع وجود مؤشر B-Tree المناسب ، LIKE يفوز بهذا السباق بأحجام كبيرة.

اقرأ الأساسيات عن مطابقة النمط في الدليل .

مؤشر للأداء المتفوق

إذا كنت مهتمًا بالأداء ، فقم بإنشاء فهرس مثل هذا للجداول الأكبر:

CREATE INDEX spelers_name_special_idx ON spelers (name text_pattern_ops);

يجعل هذا النوع من الاستعلام أسرع بأوامر من الحجم. تنطبق اعتبارات خاصة لترتيب الفرز الخاص بلغة معينة. اقرأ المزيد عن فئات المشغل في الدليل . إذا كنت تستخدم اللغة القياسية "C" (معظم الناس لا يفعلون) ، فسيعمل مؤشر عادي (مع فئة عامل التشغيل الافتراضية).

مثل هذا الفهرس جيد فقط للأنماط الراسية اليسرى (المطابقة من بداية السلسلة).

SIMILAR TO أو التعبيرات العادية ذات التعبيرات الأساسية القائمة على اليسار يمكنها استخدام هذا الفهرس أيضًا. ولكن not ​​مع الفروع (B|D) أو فئات الأحرف [BD] (على الأقل في اختباراتي على PostgreSQL 9.0).

تستخدم مطابقات Trigram أو البحث النصي فهارس GIN أو Gist خاصة.

نظرة عامة على عوامل تشغيل مطابقة الأنماط

  • LIKE (~~) بسيطة وسريعة ولكنها محدودة في قدراتها.
    ILIKE (~~*) المتغير غير الحساس لحالة الأحرف.
    pg_trgm يمد دعم الفهرس لكليهما.

  • ~ (مطابقة التعبير العادي) قوية ولكنها أكثر تعقيدًا وقد تكون بطيئة لأي شيء أكثر من أساسي التعبيرات.

  • SIMILAR TO فقط لا طائل منه. سلالة غريبة من LIKE والتعبيرات العادية. لم أستخدمها مطلقا. انظر أدناه.

  • ٪ هي عامل "التشابه" الذي توفره الوحدة الإضافية pg_trgm. انظر أدناه.

  • @@ هو عامل البحث عن النص. انظر أدناه.

pg_trgm - مطابقة الترام

بدءًا بـ PostgreSQL 9.1 يمكنك تسهيل التمديد pg_trgm لتوفير دعم الفهرس أيLIKE/ILIKE النمط (وأنماط regexp البسيطة مع ~) باستخدام فهرس GIN أو Gist.

التفاصيل والمثال والروابط:

pg_trgm يوفر أيضًا هؤلاء المشغلون :

  • % - عامل التشغيل "التشابه"
  • <% (المبدل: %>) - عامل "Word_similarity" في Postgres 9.6 أو أحدث
  • <<% (المبدل: %>>) - عامل التشغيل "الصارم_السمعي" في Postgres 11 أو أحدث

البحث عن النص

هو نوع خاص من الأنماط المطابقة مع بنية أساسية وأنواع فهرس منفصلة. يستخدم القواميس والأصل وهو أداة رائعة للعثور على الكلمات في المستندات ، خاصة للغات الطبيعية.

مطابقة البادئة مدعوم أيضًا:

وكذلك عبارة بحث منذ Postgres 9.6:

النظر في المقدمة في الدليل و نظرة عامة على العوامل والوظائف .

أدوات إضافية لمطابقة السلسلة الغامضة

تقدم الوحدة الإضافية fuzzystrmatch بعض الخيارات الإضافية ، ولكن الأداء أقل عمومًا من كل ما سبق.

على وجه الخصوص ، قد تكون عمليات التنفيذ المختلفة لوظيفة levenshtein() مفيدة.

لماذا تكون التعبيرات العادية (~) دائمًا أسرع من SIMILAR TO؟

الجواب بسيط. SIMILAR TO تتم إعادة كتابة التعبيرات في التعبيرات العادية داخليًا. لذلك ، لكل تعبير SIMILAR TO ، هناك على الأقل تعبير عادي أسرع (يحفظ عبء إعادة كتابة التعبير). لا يوجد مكاسب في الأداء باستخدام SIMILAR TO على الإطلاق .

والتعبيرات البسيطة التي يمكن إجراؤها باستخدام LIKE (~~) أسرع مع LIKE على أي حال.

SIMILAR TO مدعوم فقط في PostgreSQL لأنه انتهى به في المسودات الأولى لمعيار SQL. ما زالوا لم يتخلصوا منه. ولكن هناك خطط لإزالتها وتضمين تطابقات regexp بدلاً من ذلك - أو هكذا سمعت.

يكشف EXPLAIN ANALYZE ذلك. حاول فقط مع أي طاولة بنفسك!

EXPLAIN ANALYZE SELECT * FROM spelers WHERE name SIMILAR TO 'B%';

يكشف:

...  
Seq Scan on spelers  (cost= ...  
  Filter: (name ~ '^(?:B.*)$'::text)

SIMILAR TO تمت إعادة كتابته بتعبير عادي (~).

الأداء النهائي لهذه الحالة بالذات

لكن EXPLAIN ANALYZE يكشف المزيد. جرب ، مع وجود الفهرس المذكور أعلاه في المكان:

EXPLAIN ANALYZE SELECT * FROM spelers WHERE name ~ '^B.*;

يكشف:

...
 ->  Bitmap Heap Scan on spelers  (cost= ...
       Filter: (name ~ '^B.*'::text)
        ->  Bitmap Index Scan on spelers_name_text_pattern_ops_idx (cost= ...
              Index Cond: ((prod ~>=~ 'B'::text) AND (prod ~<~ 'C'::text))

داخليًا ، باستخدام فهرس غير مدرك للغة المحلية (text_pattern_ops أو باستخدام الإعدادات المحلية C) ، تتم إعادة كتابة التعبيرات البسيطة القائمة على اليسار باستخدام عوامل تشغيل نمط النص هذه: ~>=~ ، ~<=~ ، ~>~ ، ~<~. هذه هي حالة ~ أو ~~ أو SIMILAR TO على حد سواء.

وينطبق الشيء نفسه على الفهارس على أنواع varchar مع varchar_pattern_ops أو char مع bpchar_pattern_ops.

لذا ، بتطبيقه على السؤال الأصلي ، هذه هي أسرع طريقة ممكنة :

SELECT name
FROM   spelers  
WHERE  name ~>=~ 'B' AND name ~<~ 'C'
    OR name ~>=~ 'D' AND name ~<~ 'E'
ORDER  BY 1;

بالطبع ، إذا كان من المفترض أن تبحث عن الأحرف الأولى المتجاورة ، يمكنك زيادة التبسيط:

WHERE  name ~>=~ 'B' AND name ~<~ 'D'   -- strings starting with B or C

إن مكسب ~ أو ~~ سهل للغاية. إذا لم يكن الأداء هو الشرط الأساسي الخاص بك ، فيجب عليك فقط الالتزام بالمشغلات القياسية - للوصول إلى ما لديك بالفعل في السؤال.

171
Erwin Brandstetter

ماذا عن إضافة عمود إلى الجدول. بناءً على متطلباتك الفعلية:

person_name_start_with_B_or_D (Boolean)

person_name_start_with_char CHAR(1)

person_name_start_with VARCHAR(30)

لا يدعم PostgreSQL الأعمدة المحسوبة في الجداول الأساسية خادم SQL ولكن يمكن الحفاظ على العمود الجديد عبر المشغل. من الواضح أن هذا العمود الجديد سيتم فهرسته.

بدلا من ذلك ، فهرس على تعبير سيعطيك نفس ، أرخص. على سبيل المثال:

CREATE INDEX spelers_name_initial_idx ON spelers (left(name, 1)); 

يمكن للاستعلامات التي تطابق التعبير في شروطها استخدام هذا الفهرس.

بهذه الطريقة ، يتم تسجيل نتيجة الأداء عند إنشاء البيانات أو تعديلها ، لذلك قد تكون مناسبة فقط لبيئة نشاط منخفضة (أي عمليات كتابة أقل بكثير من عمليات القراءة).

11
onedaywhen

يمكنك المحاولة

SELECT s.name
FROM   spelers s
WHERE  s.name SIMILAR TO '(B|D)%' 
ORDER  BY s.name

ليس لدي أي فكرة عما إذا كان التعبير أعلاه أو التعبير الأصلي ساريًا في Postgres أم لا.

إذا قمت بإنشاء الفهرس المقترح ، فسيكون مهتمًا أيضًا بمعرفة كيفية مقارنة ذلك بالخيارات الأخرى.

SELECT name
FROM   spelers
WHERE  name >= 'B' AND name < 'C'
UNION ALL
SELECT name
FROM   spelers
WHERE  name >= 'D' AND name < 'E'
ORDER  BY name
8
Martin Smith

للتحقق من الأحرف الأولى ، غالبًا ما أستخدم الإرسال إلى "char" (مع علامات الاقتباس المزدوجة). إنها ليست محمولة ، ولكنها سريعة جدًا. داخليًا ، يقوم ببساطة بتفكيك النص وإرجاع الحرف الأول ، وتكون عمليات المقارنة "char" سريعة جدًا لأن النوع هو طول ثابت 1 بايت:

SELECT s.name 
FROM spelers s 
WHERE s.name::"char" =ANY( ARRAY[ "char" 'B', 'D' ] )
ORDER BY 1

لاحظ أن الإرسال إلى "char" يكون أسرع من انحراف ascii() بواسطة @ Sole021 ، ولكنه غير متوافق مع UTF8 (أو أي ترميز آخر لهذه المسألة) ، حيث يرجع ببساطة البايت الأول ، لذا يجب يمكن استخدامها فقط في الحالات التي تكون فيها المقارنة مقابل 7-bit القديمة العادية ASCII حرفاً.

2
Ziggy Crueltyfree Zeitgeister

سؤال قديم جدًا ، لكني وجدت حلاً سريعًا آخر لهذه المشكلة:

SELECT s.name 
FROM spelers s 
WHERE ascii(s.name) in (ascii('B'),ascii('D'))
ORDER BY 1

نظرًا لأن الدالة ascii () تبدو فقط في الحرف الأول من السلسلة.

2
Sole021

ما فعلته في الماضي ، والذي واجهته مشكلة أداء مشابهة ، هو زيادة حرف ASCII الحرف الأخير ، ثم إجراء أفضل. ثم تحصل على أفضل أداء لمجموعة فرعية وظائف LIKE. بالطبع ، لا يعمل إلا في حالات معينة ، ولكن بالنسبة لمجموعات البيانات الضخمة للغاية التي تبحث فيها عن اسم على سبيل المثال ، فإنه يجعل الأداء ينتقل من السوء إلى المقبول.

2
Mel Padden

هناك طريقتان لم يتم ذكرهما بعد للتعامل مع مثل هذه الحالات:

  1. الفهرس الجزئي (أو المقسم - إذا تم إنشاؤه لنطاق كامل يدويًا) - يكون مفيدًا للغاية عندما تكون هناك حاجة فقط لمجموعة فرعية من البيانات (على سبيل المثال أثناء بعض الصيانة أو مؤقتة لبعض التقارير):

    CREATE INDEX ON spelers WHERE name LIKE 'B%'
    
  2. تقسيم الجدول نفسه (باستخدام الحرف الأول كمفتاح التقسيم) - هذه التقنية تستحق النظر بشكل خاص في PostgreSQL 10+ (التقسيم الأقل إيلاما) و 11+ (التقسيم أثناء تنفيذ الاستعلام).

علاوة على ذلك ، إذا تم فرز البيانات في الجدول ، يمكن للمرء الاستفادة من استخدام فهرس BRIN (فوق الحرف الأول).

1
Tomasz Pala