# Fixer le site's Search: PostgreSQL FullMSC2Text Search M SK3and Where It BreaksMST4

<!--category-- ASP.NET, PostgreSQL, Search, RRF -->
<datetime class="hidden">2026-01-14T12:00</datetime>

La recherche est l’une de ces fonctions que tout le monde sous-estime. Il semble trivial jusqu’à ce que les utilisateurs réels commencent à écrire des questions réelles. *de façon conceptuelle* correct but textually wrong. Lorsque la recherche a échoué dans ces cas, les utilisateurs ne pensent pas que le site est bloqué.

Il est également sométhique que ' plague sur ce site depuis sa création. [Recherche ouverte](/blog/textsearchingpt2), ont travaillé sur PostgreSQL à la recherche de textes complets avec elle' sa fantaisie stuff vector mais n'a jamais été vraiment heureux avec elle **abusé** je suis particulièrement maintenant en train de construire un outil de recherche dans [***lucide*RAG**](https://www.lucidrag.com) well I thought I should FINALLY fix it.
Que ce soit ou non, c’est une autre question.

Cet article n’est pas sur la construction d’un moteur de recherche à partir du zéro, ., mais sur la correction des bords étroits de PostgreSQL complet, -, de la recherche textuelle dans un système de production réel.., j’aborderai les échecs spécifiques que j’ai rencontrés et ,, pourquoi ils se sont produits, MSC7 et les corrections pragmatiques requises pour faire en sorte que la recherche se comporte comme les utilisateurs l’attendent déjà.

**Renforcement des travaux antérieurs**: Cet article étend la [Mise en œuvre de la recherche sémantique](/blog/semantic-search-in-action) qui a ajouté une recherche hybride avec la fusion de rangs réciproques (RRF). Cet article antérieur couvrait les fondements -combinant PostgreSQL fullM SK3text search with Qdrant vector searchMSC4 Ce article corrige les cas étroits que la mise en œuvre a manqués

L’infrastructure de recherche sémantique sert à deux fins : *et* fournit la couche de récupération pour [Avocat GPT](/blog/building-a-lawyer-gpt-for-your-blog-part1) Système RAG -un assistant à l'écriture qui rédige de nouveaux postes en utilisant le site'le contenu existant comme base de connaissances . Pour des plongées plus poussées dans la technologie sous-jacente, voir [Établissement d’une partie "avocat GPTM SK1 –](/blog/building-a-lawyer-gpt-for-your-blog-part3) sur les embeddings et la recherche par vecteur.

## Les problèmes

### 1. Les résultats vides montrent des articles aléatoires

Lorsque une recherche n'a révélé aucun résultat, le système a montré des articles anciens au hasard plutôt que des suggestions utiles . Cela violait le principe de la moindre surprise - les utilisateurs attendaient soit des résultats pertinents, soit un résultat clair MSC3 aucune correspondance trouvéeMSC4 message avec des messages récents en tant que suggestionsMST5 Ceci a fait apparaître que la recherche avait fonctionné SMC6 simplement malS MC7

**La solution**: Modifié `BlogSearchService.HybridSearchWithPagingAsync()` pour détecter quand la recherche retourne des résultats nuls et revenir à l’affichage de nouveaux postes commandés par date descendante. Ajout d’un `NoMatchFound` Le groupe de travail a examiné les questions suivantes : `BasePagingModel` de sorte que l'interface puisse afficher un message approprié comme "No match found. Avez-vous dit un de ces messages?

```csharp
// No match found - return recent posts as suggestions
if (noMatchFound)
{
    Log.Logger.Information("No search results for '{Query}', returning recent posts as suggestions", query);
    return await GetRecentPostsAsSuggestions(targetLanguage, startDate, endDate, page, pageSize);
}
```

### 2. Acronymes comme "DiSE" WerenM SK3t Correspondant

PostgreSQL' sa configuration par défaut de recherche textuelle en anglaise indexe techniquement les acronymes, mais la normalisation et le démarrage rendent les termes significatifs peu fiables dans la pratiqueM SK4 Quand vous recherchez MSC5DiSEMska6, il est ramené à "diseMka8 puis le dictionnaire anglais peut l'abandonner ou attribuer un faible poidsMske9 ce qui fait que la recherche entièreMKA10texte manque des articles qui contiennent clairement |MKA11DiSeMSKA12 dans le titre et le contenuM KA13

**La solution**: Ajout de détection d'acronymes (termes ≤6 caractères avec lettres majusculesM SK3 et case-insensitive substring fallback à l'aide de PostgreSQL `ILIKE`. Il s'agit de compléter la recherche complète -texte plutôt que de le remplacer (voir les considérations sur le rendement ci-dessous pour savoir pourquoi cela est acceptable

```csharp
// Detect if query looks like an acronym or short term
var isAcronymLike = query.Length <= 6 && query.Any(char.IsUpper);

// Add substring search for acronyms
searchQuery = searchQuery.Where(x =>
    EF.Functions.ILike(x.Title, $"%{acronym}%")
    || EF.Functions.ILike(x.PlainTextContent, $"%{acronym}%"));
```

### 3. Termes techniques comportant des caractères spéciaux Recherche défectueuse

Les recherches pour "ASPM SK1NET" ou "CMSC4 seraient échouées parce que le parser de la recherche textuelle PostgreSQL\' traite les périodes et les symboles de hash comme des délimiteursMSSK6 en divisant |"\ASP\M SK8\NET\"\ en |"\Asp\MST11\ et |m"\NET+MST13 as tokens distinctsMST14 Cela signifiait que la recherche du terme exact ne fonctionnerait pas.

**La solution**: Création d’un `SearchQueryParser` qui reconnaît les termes techniques communs et les remplace par des versions recherchables:

```csharp
private static readonly Dictionary<string, string> TechnicalTerms = new(StringComparer.OrdinalIgnoreCase)
{
    ["asp.net"] = "aspnet",
    ["c#"] = "csharp",
    [".net"] = "dotnet",
    ["f#"] = "fsharp",
    ["node.js"] = "nodejs",
    // ... more terms
};
```

### 4. Demandes similaires à "ASPM SK2NET et Alpine" Ne fonctionnent pas

PostgreSQL traite "et" comme mot d'arrêt et le supprime des recherches.

**La solution**: Mise en œuvre d’un analyseur de recherche Google de type - qui gère intelligentement les mots stop et appuie des exploitants de recherche avancés

## Les solutions

### Opérations de recherche Google-Style

J'ai mis en œuvre un `SearchQueryParser` classe qui analyse les demandes avec l’appui de:

1. **Phrases citées**: `"exact match"` - recherche la phrase exacte
2. **Conditions exclues**: `-unwanted` - exclut les résultats contenant ce terme
3. **Cartes sauvages**: `ASP*` - correspond aux matches "ASP",
4. **Conditions techniques**: Manipule automatiquement "ASPM SK2NET", "CMSC5 ≥".NET\" etc.

Le parteur utilise un modèle de regex compilé pour marquer la demande:

```csharp
[GeneratedRegex(@"""([^""]+)""|(-)?(\S+)", RegexOptions.Compiled)]
private static partial Regex QueryTokenRegex();

public ParsedQuery Parse(string query)
{
    var matches = QueryTokenRegex().Matches(processedQuery);

    foreach (Match match in matches)
    {
        // Quoted phrase
        if (match.Groups[1].Success)
        {
            var phrase = match.Groups[1].Value.Trim();
            result.Phrases.Add(phrase);
            continue;
        }

        // Excluded term (starts with -)
        if (match.Groups[2].Success)
        {
            var term = match.Groups[3].Value.Trim();
            result.ExcludeTerms.Add(term.ToLowerInvariant());
            continue;
        }

        // Regular term or wildcard
        var token = match.Groups[3].Value.Trim();
        if (token.Contains('*'))
        {
            result.WildcardTerms.Add(token.Replace("*", ""));
        }
        else if (!StopWords.Contains(token))
        {
            result.IncludeTerms.Add(token.ToLowerInvariant());
        }
    }
}
```

### Établissement de l'esquisse PostgreSQL

Le parteur produit des données structurées qui sont ensuite converties en PostgreSQL. `to_tsquery` car nous générons une syntaxe structurée - et non `websearch_to_tsquery` car nous avons déjà analysé les opérateurs eux-mêmes, ce qui nous donne plus de contrôle sur la façon dont les termes sont combinés.

```csharp
public string BuildTsQuery(ParsedQuery parsed)
{
    var queryParts = new List<string>();

    // Add include terms with AND
    foreach (var term in parsed.IncludeTerms)
    {
        queryParts.Add(term);
    }

    // Add wildcard terms with :* suffix
    foreach (var term in parsed.WildcardTerms)
    {
        queryParts.Add($"{term}:*");
    }

    return queryParts.Count > 0 ? string.Join(" & ", queryParts) : string.Empty;
}
```

> **Pourquoi ne pas simplement utiliser `websearch_to_tsquery`?**
> Parce qu’il ne fonctionne toujours pas sur le plan technique, acronymes, et domaineM SK2syntaxe spécifique MSC3et n’offre aucun crochets pour la classement hybride ou les défaillances . En nous analysant nous-mêmesMska5, nous pouvons traiter ces cas avant qu’ils ne parviennent à PostgreSQLMske6

### Enhanced BuildSearchQuery

L'article 5 de la Loi sur l'immigration et le statut des réfugiés `BuildSearchQuery` la méthode a été complètement rédigée pour utiliser la structure de demande parlée. Notez que `baseQuery` already filters by language, date range, and visibility - weM SK3re adding searchMska4specific conditions on topM Ska5

```csharp
private IOrderedQueryable<BlogPostEntity> BuildSearchQuery(
    string query,
    string language,
    DateTime? startDate,
    DateTime? endDate,
    string order)
{
    var parsed = _queryParser.Parse(query);
    IQueryable<BlogPostEntity> searchQuery = baseQuery;

    // Handle phrases (exact substring matching)
    foreach (var phrase in parsed.Phrases)
    {
        searchQuery = searchQuery.Where(x =>
            EF.Functions.ILike(x.Title, $"%{phrase}%")
            || EF.Functions.ILike(x.PlainTextContent, $"%{phrase}%")
            || x.Categories.Any(c => EF.Functions.ILike(c.Name, $"%{phrase}%")));
    }

    // Build tsquery for include terms and wildcards
    var tsQuery = _queryParser.BuildTsQuery(parsed);

    // Apply full-text search if we have terms
    if (!string.IsNullOrWhiteSpace(tsQuery))
    {
        searchQuery = searchQuery.Where(x =>
            x.SearchVector.Matches(EF.Functions.ToTsQuery("english", tsQuery))
            || x.Categories.Any(c =>
                EF.Functions.ToTsVector("english", c.Name)
                    .Matches(EF.Functions.ToTsQuery("english", tsQuery))));
    }

    // Handle acronyms with case-insensitive substring search
    // This supplements full-text search (additive OR), not replaces it
    var acronymTerms = parsed.IncludeTerms
        .Concat(parsed.WildcardTerms)
        .Where(t => _queryParser.IsAcronymLike(t))
        .ToList();

    foreach (var acronym in acronymTerms)
    {
        searchQuery = searchQuery.Where(x =>
            EF.Functions.ILike(x.Title, $"%{acronym}%")
            || EF.Functions.ILike(x.PlainTextContent, $"%{acronym}%"));
    }

    // Handle excluded terms (must NOT contain these)
    foreach (var excludeTerm in parsed.ExcludeTerms)
    {
        searchQuery = searchQuery.Where(x =>
            !EF.Functions.ILike(x.Title, $"%{excludeTerm}%")
            && !EF.Functions.ILike(x.PlainTextContent, $"%{excludeTerm}%")
            && !x.Categories.Any(c => EF.Functions.ILike(c.Name, $"%{excludeTerm}%")));
    }

    return orderedQuery;
}
```

## Exemples de recherche

Voici quelques exemples de la fonctionnalité de recherche améliorée:

### Recherche fondamentale

```
DiSE
```

✅ Maintenant, les articles correspondent à "DiSEM SK2 dans le titre ou le contenu.

### Conditions techniques

```
ASP.NET
```

✅ Trouver des articles sur ASPM SK1NET (convert automatiquement en SSK3aspnet" pour une recherche complèteMSC5texte

### Recherche de phrases

```
"semantic search"
```

✅ Trouve la phrase exacte " recherche sémantiqueM SK2 dans les articles

### Conditions exclues

```
ASP.NET -Core
```

✅ Trouve les articles ASPM SK1NET mais exclut ceux qui mentionnent "Core"

### Cartes sauvages

```
ASP*
```

✅ Correspondences "ASP", "A SPNETM SK4

### Demande complexe

```
"full text search" PostgreSQL -MySQL
```

✅ Trouve des articles avec une phrase exacte "recherche de texte complet" ET mentions de PostgreSQLM SK3, mais À L'exclusion des articles qui mentionnent MySQL

### Exemple avant et après

**Demande de renseignements**: `ASP.NET and Alpine`

**Avant** (brokenM SK1

- "ASP.NETM SK2 divisé en "AspMSC4 + |
- "etM SK1 supprimés comme mot d'arrêt
- Résultat: Seules les recherches pour M SK1Alpine"

**Après** (fixedM SK1

- Convert à "aspnet"
- "et" préservés intelligentement dans le cadre de l'intention d'une demande d'utilisateur
- "AlpineM SK1 recherche normale
- Résultat: Trouve des articles sur ASP.NET et Alpine

## Intégration du classement RRF

La recherche s’intègre à la fusion de rangs réciproques (RRFM SK1 telle qu’elle est mise en œuvre dans le [article de recherche sémantique antérieure](/blog/semantic-search-in-action#hybrid-search-with-reciprocal-rank-fusion). Le RRF ici est utilisé pour *classement*, ne se rappelle pas - il combine déjà les résultats obtenus à partir de BMM SK3 complets- recherche textuelle et recherche sémantique vectorielle

```csharp
// Fuse using RRF with category/freshness boosts
var fusedDtos = _ranker.FuseResults(bm25Results, vectorResults, query);
```

L'algorithme RRF utilise `1/(k+rank)` où k=60 combine les résultats provenant de multiples sources , puis applique des stimulations pourM SK2

- **Correspondance des catégories**: +2.0
- **Match de titre**: +1.0
- **La fraîcheur**: Dégradation exponentielle au cours d’une année 1

Pour la mise en œuvre complète du RRF et comment fonctionne la recherche hybride, voir [Recherche sémantique en action](/blog/semantic-search-in-action). Pour des plongées plus profondes dans les emplacements et la similitude du vecteurM SK1 voir [Établissement d’une partie "avocat GPTM SK1 -](/blog/building-a-lawyer-gpt-for-your-blog-part3). La même infrastructure permet à l’utilisateur de faire une recherche face à - et à la récupération RAG pour AI-à l’aide d’un écriture assistéeM SK3

## Architecture technique

### Injection de dépendance

Les nouveaux éléments sont enregistrés comme services:

```csharp
services.AddSingleton<SearchQueryParser>();
services.AddSingleton<SearchRanker>();
services.AddScoped<BlogSearchService>();
```

`SearchQueryParser` et `SearchRanker` sont des singletons parce qu’ils' sont instÃ©rÃ©tÃ©s , fil- sécuritaireM SK3 et peuvent Ãatre rÃ©utilisÃ©es dans toutes les demandesMSC4 `BlogSearchService` est scoped parce qu’il accédera au contexte de la base de données.

### Flux de demandes

1. **L'utilisateur entre une demande de recherche** → Contrôleur de recherche
2. **Partage de la demande** → SearchQueryParser se divise en composants structurels
3. **Recherche sémantique (si activée)** → Base de données du vecteur Qdrant avec embeddings ONNX
4. **PostgreSQL complet-recherche de texte ( toujours disponibleM SK2** → BMM SK1connaissance des mots clés fondée
5. **Fusion RRF** → Combine les deux ensembles de résultats avec la catégorieM SK1accroissement de la fraîcheur
6. **Aucun résultat** → Retourne les articles récents comme suggestions

La même composante de recherche sémantique est également utilisée par [Avocat GPT](/blog/building-a-lawyer-gpt-for-your-blog-part1) système permettant de récupérer des articles d’un blog antérieur pertinents pour AI-assisted writing. Voir [Partie 4](/blog/building-a-lawyer-gpt-for-your-blog-part4) pour plus de détails sur le pipeline d’ingestion.

### Système de base de données

La recherche se fonde sur une présomption antérieure `SearchVector` dans la colonne `BlogPosts` Tableau:

```sql
ALTER TABLE mostlylucid."BlogPosts"
ADD COLUMN "SearchVector" tsvector
GENERATED ALWAYS AS (
    to_tsvector('english',
        coalesce("Title", '') || ' ' ||
        coalesce("PlainTextContent", '')
    )
) STORED;

CREATE INDEX idx_blog_posts_search_vector
ON mostlylucid."BlogPosts"
USING GIN ("SearchVector");
```

Le GIN (Index inversé généralisé) fournit une recherche rapide et complète de textes -à travers les grandes corporations de texteM SK3

## Considérations relatives au rendement

### Pourquoi ILIKE pour les acronymes?

Pendant `ILIKE` (caseM SK1insensitive LIKE) est plus lente que le texte complet -recherche de texte M SK4 ilMSC5est nécessaire pour les acronymes car

1. Full-text search normalizes/stems termsM SK2 breaking short uppercase strings
2. Les acronymes sont généralement courts (≤6 caractèresM SK1 limitant l'impact sur la performance
3. La condition n'est ajoutée que lorsque nécessaire (acronymes détectésM SK1

Cette approche ne serait pas adaptée à la recherche arbitraire de sous-strings sur des millions de lignes.

### Répartition de la densité des couvertures PostgreSQL (`ts_rank_cd`)

PostgreSQL offre deux fonctions de classement pour la recherche complète

- `ts_rank`: Fréquence de l'expression de base ( Combien de fois les termes apparaissent?
- `ts_rank_cd`: Répartition de la densité de couverture (de quelle façon les termes se présentent ensemble

Nous utilisons `ts_rank_cd` car elle fournit **Relevance similaire à BM25-** en tenant compte de la proximité du terme:

```csharp
// Order by cover density ranking - rewards term proximity
orderedQuery = searchQuery.OrderByDescending(x =>
    x.SearchVector.RankCoverDensity(EF.Functions.ToTsQuery("english", tsQuery)));
```

**Pourquoi? `ts_rank_cd` est supérieur:**

| Metrique  | `ts_rank` | `ts_rank_cd` |
|--------|-----------|--------------|
| **Algorithme** | Fréquence à terme | Densité de couverture MSC2proximité MSC3 SMC4
| **Demandes de mots multiples-** | Compte les termes séparément | Termes de récompense apparaissant ensemble |
| **Exemple: M SK1contenants de docker"** | Article avec "docker" 50 points élevés à plusieurs reprises
| **Rendement** | Fast | Marginally slowerM SK2 still levers GIN index |
| **Le comportement** | Compte simple | Moyenne BMM SK2 |

Il s’agit d’un **Optimisation rapide de la victoire** - meilleure classement de la pertinence avec application zéro-computation à niveauM SK2 Tous les rangs se font dans PostgreSQL en utilisant l’indice GIN existant sur `SearchVector`.

**Références:**

- [PostgreSQL ts_rank_cd documentation](https://www.postgresql.org/docs/current/textsearch-controls.html#TEXTSEARCH-RANKING)
- [EF Core RankCoverDensity](https://learn.microsoft.com/en-us/ef/core/providers/postgres/misc#full-text-search)

### Optimisations supplémentaires du rendement

Au-delà de fixer la fonctionnalité de recherche, plusieurs améliorations clés améliorent le rendement:

#### 1. Cache linguistique

Les langues disponibles changent rarement mais ont été demandées à chaque demande de recherche. Maintenant cachée avec double-enroulement vérifiéM SK2

```csharp
private static readonly TimeSpan LanguageCacheDuration = TimeSpan.FromHours(1);
private static List<string>? _cachedLanguages;
private static DateTime _languageCacheExpiry = DateTime.MinValue;
private static readonly SemaphoreSlim _cacheLock = new(1, 1);
```

**Incidences**: Élimine la question de base de données 1 par demande de recherche

#### 2. Demandes d'ILIKE en série

Code original utilisé `foreach` loops créant plusieurs clauses WHERE. Maintenant battues en expressions uniques:

```csharp
// BEFORE: Multiple WHERE clauses
foreach (var acronym in acronymTerms)
{
    searchQuery = searchQuery.Where(x =>
        EF.Functions.ILike(x.Title, $"%{acronym}%"));
}

// AFTER: Single batched WHERE
if (acronymTerms.Count > 0)
{
    searchQuery = searchQuery.Where(x =>
        acronymTerms.Any(acronym =>
            EF.Functions.ILike(x.Title, $"%{acronym}%")));
}
```

**Incidences**: SQL plus propre, ~5-10% plus rapide pour les demandes à plusieurs datesM SK3

#### 3. Index de couverture pour les demandes courantes

Indice partiel avec colonnes INCLUDE pour les modèles d’accès fréquents:

```sql
CREATE INDEX idx_blog_posts_search_covering
ON mostlylucid."BlogPosts" ("LanguageId", "IsHidden", "ScheduledPublishDate")
INCLUDE ("Id", "Slug", "Title", "PublishedDate")
WHERE "IsHidden" = false;
```

**Incidences**: Enable [index-scans seulement](https://www.postgresql.org/docs/current/indexes-index-only-scans.html) - PostgreSQL n’a pas besoin d’avoir accès à l’échelon de tableaux ', mais de réduire I/O par 20-30% pour les demandes courantes

#### 4. Retiré Inclut des éléments inutiles

EF Core's `Include()` charge les entités de navigation complètes. Supprimé lorsque seules les propriétés de navigation sont utilisées dans les clauses DE QUOIM SK1

```csharp
// BEFORE: Loads full LanguageEntity into memory
.Include(x => x.LanguageEntity)
.Where(x => x.LanguageEntity.Name == "en")

// AFTER: EF translates navigation property without loading entity
.Where(x => x.LanguageEntity.Name == "en")
```

**Incidences**: ~5-10% réduction de la mémoireM SK2 moins de données transférées de la base de données.

**Références:**

- [Index de PostgreSQL](https://www.postgresql.org/docs/current/indexes.html)
- [Rendement fondamental de l'EF](https://learn.microsoft.com/en-us/ef/core/performance/)

### Stratégie d'optimisation des demandes

Recherche construit Où les clauses sont incrémentiellement utilisées [EF Core's expression tree composition](https://learn.microsoft.com/en-us/ef/core/querying/):

```csharp
IQueryable<BlogPostEntity> searchQuery = baseQuery;

// Each filter added conditionally - PostgreSQL optimizes the final query
if (parsed.Phrases.Count > 0) { searchQuery = searchQuery.Where(...); }
if (!string.IsNullOrWhiteSpace(tsQuery)) { searchQuery = searchQuery.Where(...); }
if (acronymTerms.Count > 0) { searchQuery = searchQuery.Where(...); }
```

Il s'agit d'une **une seule demande SQL optimisée** rather than multiple round trips. PostgreSQL' le planificateur de demande peut utiliser les statistiques et les indices efficacement lorsqu’il voit la clause WHERE complète

## Résumé du rendement

Impact cumulatif de toutes les optimizations:

| Optimisation | Impact de la latence MSC2 Impact du chargement DB MSC3
|-------------|----------------|----------------|
| Cache de langues | Minimale CMS2 CMS3 demandeC MS4requête MCMS5
| Supprimer InclureM SK1 | | | 3 | 4 | Moins transfert de données | |
| Batch ILIKE | -5-10% ≥| Cleaner SQL MSC4
| Indice d'indice de couverture | MSC2 MSC3 IndicesMSc4scans seulement SMC5
| tsM SK1rank_cd | semblable MSC4 Importance accrue MSC5
| Établissement de la cache ( paramètres routiers ) M| NM SK4A P| Prévient les données bloquées R|

**Amélioration totale prévue**: **30-50% recherche plus rapide** avec une charge de base de données nettement réduite.

## Les leçons apprises

1. **Don't assume full-text search handles everything**: Les cases à bord comme les acronymes et les caractères spéciaux nécessitent une manipulation spéciale

2. **Combiner plusieurs approches**: Recherche textuelle complèteM SK1 recherche sémantique (BM25) + recherche semantice SSK5vectorsMSC6 \+ les retraits de sous-strings offrent une meilleure couverture que n'importe quelle méthode unique.

3. **Les attentes des utilisateurs formées par Google**: Soutenir les phrases citéesM SK1 exclusions , et cartes vives rend la recherche plus naturelle parce que les utilisateurs sont déjà familiers avec ces opérateurs.

4. **Fournir des répercussions utiles**: Lorsque la recherche se révèle insuffisante , ne montrent rien' ne font pas apparaître aucun message - montrent les articles récents et indiquent clairement qu'aucun match n'a été trouvéM SK4

5. **Parse, don't hack**: Un analyseur de demande approprié est plus propre et plus entretenable qu’une série d’ manipulations à chaînes.

6. **Leverage PostgreSQL's construits-in ranking**: Usage `ts_rank_cd` au lieu de `ts_rank` pour le BM25-relevance similaireM SK1Le classement de la densité de couverture tient compte de la proximité du terme -une victoire rapide qui améliore la qualité des résultats avec une application zéro

7. **Profil avant l'optimisation**: Les lacunes de l’analyse des SFT1obvious" ne constituaient pas le problème réel . Les recherches linguistiquesMSC7 incluent excessivementM SK8 et les indices manquants ont eu un impact plus important que prévuMST9

8. **Opérations de base de données par lots**: Multiple `foreach` loops créant des clauses distinctes POURQUOI les clauses génèrent un SQL suboptimal. Usage `Any()` ou `All()` pour les expressions uniques.

## Améliorations à venir

Possibilités d’amélioration pour l’avenir, catégorisée par préoccupationM SK1

**Amélioration de la recherche**:

- **Correspondance floue**: Distance de Levenshtein pour la tolérance au typo
- **Expansion synonyme**:

**Amélioration de l'analyse**:

- **Filtration des catégories**: `category:ASP.NET` opérateur
- **Répartition des dates**: `after:2025-01-01` opérateur

**Réfinitions du classement**:

- **Éclaircissement des résultats**: Afficher les parties de texte correspondantes dans les résultats
- **Cliquer-à travers le suivi**: Apprenez à partir du comportement des utilisateurs pour améliorer le classement

**Observabilité**:

- **Analyse de la recherche**: Suivi des demandes populaires et recherches manquantes pour identifier les lacunes

## Conclusion

Fabrication de bâtiments- recherche de classe exige la fixation des cases en bordure **et** Optimisation de la performance. Cet article a porté sur les deux éléments suivants : : fixer PostgreSQL complet, - cases de recherche à bord du texte, | ( acronymes, , termes techniques, ◦ , | Google, ─ - | exploitants de style, ‒ ) | et mettre en œuvre des améliorations clés de la rendement. `ts_rank_cd`).

La mise en œuvre **30-50% recherche plus rapide** tout en réduisant la charge de base de données grâce à:

- Classement de la densité de couverture (`ts_rank_cd`) pour une meilleure pertinence
- Cachement linguistique éliminant les demandes répétées
- Expressions ILIKE battues pour SQL plus propre
- Couverture des indices permettant les scans d'index-seulement
- Remplacement du noyau EF inutile inclut

L’influence clé: aucune approche unique ne traite de tous les cas . PostgreSQL FTS excelle en ce qui concerne le matchage des mots-clés, la recherche sémantique traite des demandes conceptuellesM SK3 et les reculs ciblés atteignent les cases à bordMSC4 La couche d’analyse propre l’intègre ensembleMST5 traite les opérateurs et les termes techniques avant qu’ils ne parviennent aux moteurs de rechercheM ST6

L’infrastructure de recherche sémantique sert à deux fins:

- **Utilisateur- Face à la recherche**: Combine le mot clé et la combinaison sémantique pour obtenir de meilleurs résultats
- **Récupération du RAG**: Fait fonctionner [Avocat GPT](/blog/building-a-lawyer-gpt-for-your-blog-part1) assistant à l'écriture

**Articles connexes:**

- [Recherche sémantique en action](/blog/semantic-search-in-action) - Fondation: recherche hybride avec RRF
- [Établissement d’une partie "avocat GPTM SK1 -](/blog/building-a-lawyer-gpt-for-your-blog-part3) Bases de données vectorielles & Embeddings
- [Établissement d’une partie "avocat GPTM SK1 -](/blog/building-a-lawyer-gpt-for-your-blog-part4) - Pipeline d'ingestion

**Documentation officielle:**

- [PostgreSQL Full-Suite de texte](https://www.postgresql.org/docs/current/textsearch.html)
- [Index de PostgreSQL](https://www.postgresql.org/docs/current/indexes.html)
- [Rendement fondamental de l'EF](https://learn.microsoft.com/en-us/ef/core/performance/)
- [Fournisseur de l'EF Core Npgsql](https://www.npgsql.org/efcore/index.html)

Tous les codes disponibles dans le [blog's GitHub repository](https://github.com/scottgal/mostlylucidweb).