Aufbau eines Wörterbuches
Ursprüngliche Intension war es eine Art Wörterbuch aufzubauen, um Texte „taggen“ zu können bzw. eine Art „Volltextsuche“ der minimalistischen Art Marke „Eigenbau“ zu realisieren und das möglichst performant. Dazu kam dann noch eine Forum-Anfrage, die in eine ähnliche Richtung geht und eine sportliche Herausforderung für mich darstellte.
Vereinfacht gesagt war es Ziel: Man hat Texte, die es in einzelne Wörter zu zergliedern gilt, welche man dann in einer Tabelle als Referenz ablegt.
Das Script ist wie bei mir üblich so aufgebaut, das man es beliebig oft ausführen kann, ohne das es zu Problemen führt.
Als Quelle dienen dabei Plain Text Dateien; sorry, aber ich hatte einfach keine sonderliche Lust nun auch noch HTML-Tag und ähnliches Gedöns raus zu parsen; man (ich) ist halt doch etwas bequem.
Womit sich das nächste Problem stellt: Woher brauchbare Texte finden, die man als Quelle nutzen kann? Das ist das eigentliche Problem an der Geschichte, hier muss jeder selbst zusehen, was er nutzen kann und will. Für mein Beispiel hier habe ich mir im Netz ein paar Dateien dazu rausgesucht; wie gesagt, ist nicht einfach heutzutage noch „Plain Text“ zu finden.
Zudem vereinfache ich mir einiges, was nun wirklich nicht gerade zur Qualität des Ergebnisses bei trägt. Wohl oder übel muss man die Wörterliste selbst sichten und unbrauchbares entfernen.
Lange Rede, wenig Sinn, fangen wir einfachen Mal an; Optimierungspotenzial gibt es allemal, was jeder für sich umsetzen mag.
Zunächst brauchen wir Tabellen zum Speichern; logo. Eine temporäre Staging Tabelle für den Import und natürlich eine finale Tabelle für die „Wörter“. Sinnig wäre es die Tabelle für Wörter um ein Feld für die Sprache zu ergänzen; aktuell ist es für mich aber eher ein „Proof of Concept“ und überhaupt; automatisiert die Sprache zu ermitteln ist ein noch größeres Problem. Aber Probleme löst man Step-by-Step; fangen wir erst mal an:
SET NOCOUNT ON;
-- Word-Tabelle anlegen, wenn nicht vorhanden
IF OBJECT_ID('dbo.Words') IS NULL
BEGIN CREATE TABLE dbo.Words (ID int NOT NULL IDENTITY(1, 1),
Word varchar(100) NOT NULL); ALTER TABLE dbo.Words ADD CONSTRAINT PK_Words PRIMARY KEY CLUSTERED (ID); CREATE UNIQUE NONCLUSTERED INDEX IX_Words_Word ON dbo.Words (Word); END
GO
-- Temporäre Staging Tabelle für BLOB Import
CREATE TABLE #Staging (Doc varchar(max));
GO
Nun ist die Staging-Tabelle recht leer, die gilt es nun mit Texten zu füllen. Für das Beispiel habe ich mir im Netz ein paar Plain Text Dateien rausgesucht und an den Link angeben, damit Ihr es auch analog nachvollziehen könnt. Es sind gemischt (im wahrsten Worte) DE/EN Texte, das Ergebnis ist qualitative nicht so übertrieben toll; was soll‘s. Der Import erfolgt per BULK INSERT, je Zeile wird ein DataSet in die Staging-Tabelle weg geschrieben. BULK ist sehr effizient:
-- Text-Datei Zeilenweise ins Staging per BULK importieren
-- http://www.gnu.org/licenses/lgpl.txt
BULK INSERT #Staging FROM 'D:\Projekte\lgpl.txt';
-- http://www.davros.org/misc/iso3166.txt
BULK INSERT #Staging FROM 'D:\Projekte\iso3166.txt';
-- http://werbach.com/barebones/barebones.txt
BULK INSERT #Staging FROM 'D:\Projekte\barebones.txt';
-- http://www.w3.org/TR/2000/WD-smil-boston-dom-20000225/SYMM.txt
BULK INSERT #Staging FROM 'D:\Projekte\SYMM.txt';
-- http://www.astro.caltech.edu/~george/qqq/qqq_pr.txt
BULK INSERT #Staging FROM 'D:\Projekte\qqq_pr.txt';
-- http://www.valeriodistefano.com/gutenberg/00_multilingual_dvd/etext93/badge10.txt
BULK INSERT #Staging FROM 'D:\Projekte\badge10.txt';
-- http://www.ojp.usdoj.gov/bjs/pub/ascii/ecp.txt
BULK INSERT #Staging FROM 'D:\Projekte\ecp.txt';
-- http://opensource.linux-mirror.org/licenses/cuaoffice.txt
BULK INSERT #Staging FROM 'D:\Projekte\cuaoffice.txt';
-- Fast DE nur ohne Umlaute
-- http://www.classicistranieri.com/deutsch/etext05/7kndr10.txt
BULK INSERT #Staging FROM 'D:\Projekte\7kndr10.txt';
-- http://www.ibiblio.org/pub/docs/books/gutenberg/etext90/const11.txt
BULK INSERT #Staging FROM 'D:\Projekte\const11.txt'
-- Schon eher richtig DE
-- http://www.stefanbion.de/cueltool/readme.txt
BULK INSERT #Staging FROM 'D:\Projekte\readme.txt'
-- http://www.dcoul.de/faq/text/A.txt
BULK INSERT #Staging FROM 'D:\Projekte\A.txt'
GO
Nun haben wir temporär Daten = Texte vorhanden.
(!! Bitte bei der Aktion die tempdb kontrollieren, bevor sie „platzt“; sind zwar keine wirklichen Datenmenge, aber Vorsicht ist besser als die Nachsicht zu haben!!).
Die sollen nun in einzelne Worte aufgesplittet werden, mit der Annahme, das ein Wort mit dem nächsten Leerzeichen endet (eine vereinfache, aber nicht korrekte Annahme). Dazu verwende ich eine ebenso temporäre Stored Procedure:
-- Temporäre SPs für die Verarbeitung anlegen
IF NOT OBJECT_ID('tempdb..#spGetWordsFromStaging') IS NULL
DROP PROCEDURE #spGetWordsFromStaging; GO CREATE PROCEDURE #spGetWordsFromStaging
AS BEGIN DECLARE @Text varchar(max); DECLARE @Word varchar(100); DECLARE @NextSpace int; DECLARE Line CURSOR LOCAL FORWARD_ONLY FOR SELECT Doc FROM #Staging OPEN LINE FETCH NEXT FROM Line INTO @Text WHILE @@FETCH_STATUS = 0 BEGIN
-- Überflüssige SPACEs entfernen und trimmen
SET @Text = RTRIM(LTRIM(REPLACE(ISNULL(@Text, ''), ' ', ' ')));
SET @NextSpace = CHARINDEX(' ', @Text); WHILE @NextSpace > 0 BEGIN SET @Word = LEFT(@Text, @NextSpace); -- Unerwünschte Sonderzeichen entfernen -- Nums
SET @Word = REPLACE(@Word, '0', '');
SET @Word = REPLACE(@Word, '1', '');
SET @Word = REPLACE(@Word, '2', ''); SET @Word = REPLACE(@Word, '3', ''); SET @Word = REPLACE(@Word, '4', ''); SET @Word = REPLACE(@Word, '5', ''); SET @Word = REPLACE(@Word, '6', ''); SET @Word = REPLACE(@Word, '7', ''); SET @Word = REPLACE(@Word, '8', ''); SET @Word = REPLACE(@Word, '9', ''); -- Interpunktion SET @Word = REPLACE(@Word, '.', '');
SET @Word = REPLACE(@Word, ',', '');
SET @Word = REPLACE(@Word, ';', ''); SET @Word = REPLACE(@Word, ':', ''); SET @Word = REPLACE(@Word, '!', ''); SET @Word = REPLACE(@Word, '?', ''); -- Und sonstige Sonderzeichen SET @Word = REPLACE(@Word, '-', '');
SET @Word = REPLACE(@Word, '+', '');
SET @Word = REPLACE(@Word, '=', ''); SET @Word = REPLACE(@Word, '"', ''); SET @Word = REPLACE(@Word, '´', ''); SET @Word = REPLACE(@Word, '`', ''); SET @Word = REPLACE(@Word, '#', ''); SET @Word = REPLACE(@Word, '$', ''); SET @Word = REPLACE(@Word, '€', ''); SET @Word = REPLACE(@Word, '*', ''); SET @Word = REPLACE(@Word, '_', ''); SET @Word = REPLACE(@Word, '$', ''); SET @Word = REPLACE(@Word, '&', ''); -- Html, XML und Wiki kommen auch vor SET @Word = REPLACE(@Word, '(', '');
SET @Word = REPLACE(@Word, ')', ''); SET @Word = REPLACE(@Word, '[', ''); SET @Word = REPLACE(@Word, ']', ''); SET @Word = REPLACE(@Word, '<', ''); SET @Word = REPLACE(@Word, '>', ''); -- Na Hochkommas sowieso SET @Word = REPLACE(@Word, '''', '');
-- Noch mal trimmen
SET @Word = LTRIM(RTRIM(@Word));
-- Nur "nützliche" Wörter mit >= 4 Zeichen
-- und keine Nums/Dates, kein (Back)-Slash
-- Emails machen acuh keinen Sinn
IF LEN(@Word) >= 4
AND ISNUMERIC(@Word) = 0
AND ISDATE(@Word) = 0 AND NOT @Word LIKE 'WWW%' AND NOT @Word LIKE '%/%' AND NOT @Word LIKE '%\%' AND NOT @Word LIKE '%@%' AND NOT @Word LIKE '%|%' BEGIN IF NOT EXISTS(SELECT Word FROM Words WHERE Word = @Word) INSERT INTO Words (Word) VALUES (@Word); END;
-- Verwendeten Text abschnippeln
SET @Text = SUBSTRING(@Text, @NextSpace + 1, LEN(@Text));
SET @NextSpace = CHARINDEX(' ', @Text); END; FETCH NEXT FROM Line INTO @Text END CLOSE Line; DEALLOCATE Line; END;
GO
Das Aufsplitten selbst ist wirklich recht Simple.
Aber wie man sehen auch kann: Ich mache es mir sehr einfach und ignoriere diverses Sonderfälle; es ist suboptimal und wie bereits erwähnt gibt es reichlich Optimierungspotenzial; ein jeder möge sich bitte nach Lust & Laune hier austoben und sich kreativ betätigen; über Feedback hierzu würde ich mich freuen.
Nun muss das ganze auch ausgeführt werden; also ein EXEC auf die #SP, anschließend die #Temps abräumen und das Ergebnis kontrollieren:
-- Nun ausführen EXEC #spGetWordsFromStaging;
GO -- TEMPs abräumen DROP PROCEDURE #spGetWordsFromStaging;
DROP TABLE #Staging;
-- Bitte die tempdb kontrollieren
-- bevor sie "platzt"
GO
-- Wie sieht das Ergebnis aus?
SELECT * FROM Words ORDER BY Word;
Wie gehts weiter? … Schauen wir mal .. die Basis haben wir jedenfalls schon!