# ORACLE - Stringfunktionen



## flowerpower (14. Mai 2006)

hallo, ich soll aus einem Satz, alle Worte entnehmen und in einer Tabelle mit Position ablegen, weitergehend, dann die Häufigkeit der Worte (wenn doppelt vorhanden).

Habe mit PL/SQL diverse String-Funktionen versucht (rtrim, ltrim), die Funktionen geben aber für mich keine verwertbaren Ausgaben. Mit PHP hätte ich die Funktion explode genommen, damit wäre es meiner Meinung nach relativ simpel. Unter PL/SQL komme ich nicht weiter.

Vielen Dank. flowerpower


----------



## Exceptionfault (14. Mai 2006)

Hi, ich habe leider auch keine Funktion wie EXPLODE gefunden und daher kurz was ausprogrammiert. 

Meine Datentabelle sah so aus:

```
create table words
(
	word		VARCHAR2(50),
	positions	VARCHAR2(100),
	wordcount	NUMBER(2)
);
```

Und hier die anonyme PL/SQL Prozedur die mir die Tabelle füllt:

```
declare
	s_sentence	VARCHAR2(100)	:= 'das ist mein Satz und das Wort das kommt drei mal vor';
	s_word		VARCHAR2(100);
	TYPE space_list IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
	spaces		space_list;
	pos_beg		PLS_INTEGER := -1;
	pos_cnt		PLS_INTEGER := 1;
begin

	LOOP
		pos_beg := INSTR( s_sentence, ' ', 1, pos_cnt );
		
		IF pos_beg = 0 THEN
			spaces( pos_cnt ) := LENGTH( s_sentence ) + 1;
			EXIT;
		ELSE
			spaces( pos_cnt ) := pos_beg;
			pos_cnt := pos_cnt + 1;
		END IF;

	END LOOP;

	pos_beg := 1;
	FOR i IN spaces.FIRST..spaces.LAST LOOP
		
		s_word  := TRIM( SUBSTR( s_sentence, pos_beg, spaces(i)-pos_beg ) );
		pos_beg := spaces(i);
		MERGE 	INTO words w
		USING	( SELECT s_word AS word FROM DUAL ) f
		ON ( w.word = f.word )
		WHEN MATCHED THEN UPDATE SET
			positions = positions || ' ' || TO_CHAR( i ),
			wordcount = wordcount + 1
		WHEN NOT MATCHED THEN INSERT VALUES ( s_word, TO_CHAR( i ), 1 );
	END LOOP;

end;
/
```

Das Ergebnis:

```
SELECT * FROM words;
WORD                                               POSITIONS             WORDCOUNT
-------------------------------------------------- -------------------- ----------
das                                                1 6 8                         3
ist                                                2                             1
mein                                               3                             1
Satz                                               4                             1
und                                                5                             1
Wort                                               7                             1
kommt                                              9                             1
drei                                               10                            1
mal                                                11                            1
vor                                                12                            1
```

So nun zu Erklärung, oder ist alles klar ? ;-)

In der ersten Schleife suche ich alle Leerzeichen im Satz und speicher mir die Positionen in einer Liste ab. In der zweiten Schleife laufe ich nun die Liste durch und hole mir per SUBSTR die einzelnen Wörter heraus.
Das MERGE Statement ersetzt ein INSERT und ein UPDATE, d.h. wenn ein Wort noch nicht in der Liste ist wird es mit seiner Position im Satz (i) und der Anzahl 1 aufgenommen, ansonsten wird die Anzahl um 1 erhöht und die Position im String angehängt. Hoffe das genügt so ?


----------



## flowerpower (14. Mai 2006)

Da bin ich erstmal platt. Hast Du das auf die Schnelle aus dem Ärmel geschüttelt?
Also erstmal vielen, vielen Dank. Ich schaue mir das nach einer Portion Schlaf an.

Danke auch für die Erklärung, falls sich meine Bildungslücken blicken lassen.:suspekt: 

Nochmal Danke. Wenn mir etwas unklar bleibt/wird darf ich sicher noch mal fragen.

Schönen Abend. Gruß. flowerpower


----------



## flowerpower (16. Mai 2006)

... bei mir kommt folgende Ausgabe.

1  das  1  
2  ist  1  
3  mein  1  
4  Satz  1  
5  und  1  
6  das  1  
7  Wort  1  
8  das  1  
9  kommt  1  
10  drei  1  
11  mal  1  
12  vor  1 

Leider werden die Ergebnisse nicht summiert und die Position  nicht angezeigt.


----------



## Exceptionfault (16. Mai 2006)

Hmmm das kann ich so leider nicht nachvollziehen. Das würde ja bedeuten er findet im Merge Statement *ON ( w.word = f.word ) * das vorhandene Wort nicht und fügt ein neues ein.
Das Problem hatte ich am Anfang auch, aber nur weil er das Leerzeichen vor den Wörtern hat stehen lassen, was ich mit dem *TRIM* umgangen habe.

Andere Frage, ich habs unter 10.2.0.1 getestet, nutzt du eine ältere Version ?

Mir würden jetzt erstmal nur 2 Dinge einfallen:
1.) Primary Key auf Spalte "word" legen, das verhindert zumindest, dass das Wort öfter auftaucht und zeigt uns ob es wirklich ein Fehler beim MERGE ist.
2.) Das MERGE Statement in 2 Statements umschreiben, d.h. erst ein UPDATE machen, und wenn *SQL%ROWCOUNT = 0* zurückkommt, dann ein INSERT mit neuen Werten.


----------



## flowerpower (16. Mai 2006)

ich nutze die 9.2.0.1.0 ( diese ist auch vorgegeben, bzw. wir arbeiten aussschliesslich auf dieser). Versuche nochmal etwas zu verändern und melde mich morgen noch einmal.

DANKE.

Gruß. flowerpower


----------



## flowerpower (17. Mai 2006)

Kurioserweise erscheint statt der Ausgabe des Wortes die Zahlen 1-12, als Position erscheint dann das Wort. Bin gerade mal auf "Fehlersuche". Ideen sind jederzeit willkommen. 

Grüsse. flowerpower


----------



## flowerpower (17. Mai 2006)

Ich bekomme zumindest einen reinen Insert in die Tabelle nun hin, auch werden nun die Spalten korrekt belegt. 
Könntest Du für einen Newbie noch mal die Merge-Funkton aus einander nehmen, habe heute schon den ganzen Tag hier dran gesessen. Mit deinem ersten Quelltext kommt immer obige Ausgabe  

Danke. Grüsse. flowerpower


----------



## Exceptionfault (18. Mai 2006)

flowerpower hat gesagt.:
			
		

> Könntest Du für einen Newbie noch mal die Merge-Funkton aus einander nehmen, habe heute schon den ganzen Tag hier dran gesessen.



Aber klar, also hier MERGE im einzelnen.

Also MERGE macht im Grunde folgendes:
1.) Ich gebe dem MERGE Statement eine Menge an Daten

```
USING   ( SELECT s_word AS word FROM DUAL ) f
```
In unserem Fall ist es einfach nur das eine Wort aus dem Satz,könnte aber z.B. auch ein Select aus einer Tabelle sein...

2.) Ich sage dem Statement in welcher Tabelle ich meine Daten haben möchte

```
MERGE   INTO words w
```

3.) Ich definiere woran MERGE erkennt, ob es den Satz schon gibt oder nicht, also im Normalfall meinen Primary Key

```
ON ( w.word = f.word )
```
Bei uns ist es das gefundene Wort im Satz und die Spalte mit den Wörtern in der Tabelle

4.) Ich beschreibe was MERGE machen soll, falls es den Eintrag schon gibt

```
WHEN MATCHED THEN UPDATE SET
            positions = positions || ' ' || TO_CHAR( i ),
            wordcount = wordcount + 1
```
Wir hängen die Position des Wortes an den vorhandenen String an und zählen die Wortanzahl um eins hoch

5.) Ich beschreibe was MERGE machen soll wenn das Wort nicht gefunden wird

```
WHEN NOT MATCHED THEN INSERT (word, positions, wordcount ) VALUES ( s_word, TO_CHAR( i ), 1 );
```
Klar, ein einfaches INSERT Statement

Somit macht das MERGE Statement also das Prüfen, "Ist es schon da?, dann UPDATE, sonst INSERT" überflüssig und vor allem schneller, da ich nicht jeden Satz prüfen muss,  sondern auch eine ganze Datenmenge "mergen" kann.

Ich hab das Beispiel übrigens nochmal getestet und bei mir gehts einwandfrei. Ich kann mir jetzt nur noch vorstellen, dass entweder deine Tabellenspalten anders rum definiert sind als bei mir ? (DESC) 
oder dass es tatsächlich Unterschiede wegen der Version gibt, wobei mir dann das Verhalten unter 9.2.0.1 nicht ganz klar wäre...?


----------



## flowerpower (26. Mai 2006)

Danke für die Erklärungen. Merge hatte nicht funktioniert, aber ich hatte es anders gelöst.
Die Worte als PrimaryKey und dann ein Insert (wenn Wort schon existiert, dann gab es eine DUP_VAL_ON_INDEX-Exception und es wurde ein Update gemacht, wobei die Position ergänzt und das Vorkommen hoch gezählt wurde). Problematisch war, dass ich bei der Exception aus dem gesamten Block geflogen bin. Da habe ich eine Weile gebraucht.

Eine Lösung habe ich dahingehend gefunden das Insert in einer Procedure aufzurufen, somit beendet die Exception nur die Prozedur und verbleibt in der eigentlichen Loop.

Gruß. flowerpower


----------



## Exceptionfault (29. Mai 2006)

Um in einer Funktion Fehler abzufangen kannst du intern auch BEGIN ... EXCEPTION ... END Blöcke definieren und verschachteln.


----------



## flowerpower (29. Mai 2006)

Danke für den Hinweis , ich werde es mal probieren.

Gruß. flowerpower


----------

