InitalCommit
This commit is contained in:
182
services/searchService.js
Normal file
182
services/searchService.js
Normal file
@@ -0,0 +1,182 @@
|
||||
const { sql, getConnection } = require('../config/database');
|
||||
|
||||
/**
|
||||
* Definieren Sie hier die Tabellen und Spalten, die durchsucht werden sollen
|
||||
*
|
||||
* Format:
|
||||
* {
|
||||
* tableName: 'TabellenName',
|
||||
* columns: ['Spalte1', 'Spalte2', 'Spalte3'],
|
||||
* primaryKey: 'ID' // Primärschlüssel der Tabelle
|
||||
* }
|
||||
*/
|
||||
// Gemeinsame Spalten für die TEILE Tabelle
|
||||
// WICHTIG:
|
||||
// - Die Teilenummer steht in der Spalte `Teil`
|
||||
// - Es gibt KEINE Spalte `WarenNr` in TEILE, daher darf diese hier NICHT verwendet werden
|
||||
// - Spaltennamen müssen exakt den vorhandenen Spalten in TEILE entsprechen,
|
||||
// sonst schlägt die Suche mit "invalid column name" fehl.
|
||||
const COMMON_COLUMNS = [
|
||||
'Teil', // Teilenummer
|
||||
'Art', // Art
|
||||
'Gruppe', // Gruppe
|
||||
'Bez', // Bezeichnung
|
||||
'Bez2', // Bezeichnung 2
|
||||
'Hersteller' // Hersteller
|
||||
// Weitere Felder (z.B. SuchL, SuchR, EUWarenNr, Ben-Felder) können bei Bedarf ergänzt werden
|
||||
];
|
||||
|
||||
// Erweiterte Spalten (falls benötigt) für spezielle Varianten mit zusätzlichen Feldern
|
||||
// Basieren auf den gemeinsamen Spalten der TEILE Tabelle
|
||||
const COLUMNS_EL = [
|
||||
...COMMON_COLUMNS,
|
||||
'AUF', // Auftragsfeld
|
||||
'AUF1', // Auftragsfeld 1
|
||||
'AUF2', // Auftragsfeld 2
|
||||
'AUF3' // Auftragsfeld 3
|
||||
];
|
||||
|
||||
const SEARCH_CONFIG = [
|
||||
{
|
||||
tableName: 'TEILE',
|
||||
columns: COMMON_COLUMNS,
|
||||
primaryKey: 'ISN' // technisch primärer Schlüssel der Tabelle
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* Erstellt eine SQL WHERE-Klausel für die Volltextsuche
|
||||
* Findet Teilübereinstimmungen in allen Spalten (case-insensitive)
|
||||
* Optimiert durch:
|
||||
* - Verwendung von COLLATE für case-insensitive Vergleich (schneller als LOWER)
|
||||
* - Effiziente CAST-Größen statt NVARCHAR(MAX)
|
||||
* - Vermeidung von redundanten Funktionsaufrufen
|
||||
* @param {Array} columns - Array von Spaltennamen
|
||||
* @param {string} searchTerm - Suchbegriff
|
||||
* @returns {string} WHERE-Klausel
|
||||
*/
|
||||
const buildSearchCondition = (columns, searchTerm) => {
|
||||
return columns
|
||||
.map(col => `[${col}] LIKE @searchPattern COLLATE SQL_Latin1_General_CP1_CI_AS`)
|
||||
.join(' OR ');
|
||||
};
|
||||
|
||||
/**
|
||||
* Führt eine Volltextsuche über alle konfigurierten Tabellen durch
|
||||
* Findet alle Datensätze, die den Suchbegriff als Teilstring enthalten (case-insensitive)
|
||||
*
|
||||
* Optimierungen:
|
||||
* - Parallele Suche über alle Tabellen (Promise.all statt sequentiell)
|
||||
* - Effiziente WHERE-Klauseln ohne CAST zu NVARCHAR(MAX)
|
||||
* - Case-insensitive Suche via COLLATE statt LOWER()
|
||||
* - Optimierte Query-Struktur mit TOP für bessere Performance
|
||||
*
|
||||
* @param {string} searchTerm - Der zu suchende Begriff
|
||||
* @returns {Promise<Array>} Array mit Suchergebnissen
|
||||
*/
|
||||
const fullTextSearch = async (searchTerm) => {
|
||||
if (!searchTerm || searchTerm.trim() === '') {
|
||||
throw new Error('Suchbegriff darf nicht leer sein');
|
||||
}
|
||||
|
||||
const pool = await getConnection();
|
||||
|
||||
// Pattern für Teilübereinstimmungen: findet den Begriff überall im Text
|
||||
// % = beliebige Zeichen davor/dahinter (SQL Wildcard)
|
||||
const searchPattern = `%${searchTerm.trim()}%`;
|
||||
|
||||
// OPTIMIERUNG: Parallele Suche über alle Tabellen statt sequentiell
|
||||
const searchPromises = SEARCH_CONFIG.map(async (tableConfig) => {
|
||||
try {
|
||||
const whereClause = buildSearchCondition(tableConfig.columns, searchTerm);
|
||||
|
||||
// Optimierte Query:
|
||||
// - Verwendet COLLATE für case-insensitive Vergleich (schneller als LOWER)
|
||||
// - TOP 100 limitiert Ergebnisse früh im Execution Plan
|
||||
// - WITH (NOLOCK) für Read-Uncommitted (schneller, da keine Locks)
|
||||
const query = `
|
||||
SELECT TOP 100 *
|
||||
FROM [${tableConfig.tableName}] WITH (NOLOCK)
|
||||
WHERE ${whereClause}
|
||||
`;
|
||||
|
||||
const request = pool.request();
|
||||
request.input('searchPattern', sql.NVarChar, searchPattern);
|
||||
|
||||
const result = await request.query(query);
|
||||
|
||||
if (result.recordset && result.recordset.length > 0) {
|
||||
return {
|
||||
tableName: tableConfig.tableName,
|
||||
matchCount: result.recordset.length,
|
||||
records: result.recordset
|
||||
};
|
||||
}
|
||||
return null; // Keine Ergebnisse gefunden
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Fehler beim Durchsuchen der Tabelle ${tableConfig.tableName}:`, error.message);
|
||||
// Tabelle existiert möglicherweise nicht - Fehler zurückgeben
|
||||
return {
|
||||
tableName: tableConfig.tableName,
|
||||
matchCount: 0,
|
||||
records: [],
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// Warte auf alle Suchanfragen parallel
|
||||
const allResults = await Promise.all(searchPromises);
|
||||
|
||||
// Filtere null-Werte (Tabellen ohne Ergebnisse) heraus
|
||||
const results = allResults.filter(result => result !== null);
|
||||
|
||||
return results;
|
||||
};
|
||||
|
||||
/**
|
||||
* Holt alle verfügbaren Tabellen aus der Datenbank
|
||||
* @returns {Promise<Array>} Array mit Tabellennamen
|
||||
*/
|
||||
const getAvailableTables = async () => {
|
||||
const pool = await getConnection();
|
||||
const query = `
|
||||
SELECT TABLE_NAME
|
||||
FROM INFORMATION_SCHEMA.TABLES
|
||||
WHERE TABLE_TYPE = 'BASE TABLE'
|
||||
ORDER BY TABLE_NAME
|
||||
`;
|
||||
|
||||
const result = await pool.request().query(query);
|
||||
return result.recordset.map(row => row.TABLE_NAME);
|
||||
};
|
||||
|
||||
/**
|
||||
* Holt die Spalten einer spezifischen Tabelle
|
||||
* @param {string} tableName - Name der Tabelle
|
||||
* @returns {Promise<Array>} Array mit Spalteninformationen
|
||||
*/
|
||||
const getTableColumns = async (tableName) => {
|
||||
const pool = await getConnection();
|
||||
const query = `
|
||||
SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_NAME = @tableName
|
||||
ORDER BY ORDINAL_POSITION
|
||||
`;
|
||||
|
||||
const request = pool.request();
|
||||
request.input('tableName', sql.NVarChar, tableName);
|
||||
|
||||
const result = await request.query(query);
|
||||
return result.recordset;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
fullTextSearch,
|
||||
getAvailableTables,
|
||||
getTableColumns,
|
||||
SEARCH_CONFIG
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user