The Case
Πρόσφατα χρειάστηκε να βοηθήσω ένα πελάτη που αντιμετώπιζε ένα πρόβλημα απόδοσης σε ένα query του.
Το συγκεκριμένο query διάβαζε δεδομένα από ένα πίνακα που είχε αρκετές εγγραφές και οι indexes που υπήρχαν χρησιμοποιούταν ιδανικά.
Το πρόβλημα στο query αυτό ήταν ότι χρησιμοποιούσε μια UDF που υπολόγιζε τον αριθμό των λέξεων που είχε μια κολώνα NVARCHAR(MAX).
Βλέποντας την συγκεκριμένη UDF αναρωτήθηκα για ποιο λόγο επιλέχθηκε η υλοποίηση αυτή. Σε σχετική ερώτηση μου, η απάντηση ήταν ότι βρήκαν και αντέγραψαν αυτή από γνωστό site που παρέχει τέτοιες λύσεις και που οι περισσότεροι χρησιμοποιούν για να γράψουν κώδικα.
Δεν θα δείξω την υλοποίηση της συγκεκριμένης UDF, απλά θα πω ότι έκανε loop διαβάζοντας χαρακτήρα-χαρακτήρα και ελέγχοντας το κενό, την τελεία, το κόμμα και κάποια ακόμα για να αποφανθεί ότι έχει διαβάσει μια λέξη. Σε πολύ μεγάλα strings η διαδικασία είναι αργή, και αυτή επιβάρυνε την απόδοση.
My impementation
Αλλάζοντας το κώδικα της συγκεκριμένης UDF όπως παρακάτω το αποτέλεσμα στην απόδοση του συγκεκριμένου query ήταν θεαματικό.
SQL Script
CREATE FUNCTION dbo.fnStringWordCount (@string NVARCHAR(MAX))
RETURNS INT
AS
BEGIN
DECLARE @strlen INT = 0;
SET @string = REPLACE(@string,CHAR(10),' ');
SET @string = REPLACE(@string,CHAR(13),' ');
SET @string = REPLACE(@string,' ',' '); -- 5 spaces
SET @string = REPLACE(@string,' ',' '); -- 4 spaces
SET @string = REPLACE(@string,' ',' '); -- 3 spaces
SET @string = REPLACE(@string,' ',' '); -- 2 spaces
SET @strlen = LEN(@string)
SET @string = REPLACE(@string,' ',''); -- 1 spaces
RETURN (@strlen-LEN(@string))+1
END
GO
NOTE
Επέλεξα την καθαρότητα της ανάγνωσης του κώδικα για αυτό τον έγραψα με αυτό τον τρόπο. Έτσι και αλλιώς δεν υπάρχει διαφορά στην απόδοση του αν όλα τα REPLACE ήταν στην ίδια γράμμη.
How it works
Η συγκεκριμένη λύση χρησιμοποιεί απλή λογική η οποία λέει ότι μεταξύ των λέξεων υπάρχει πάντα ένας κενός χαρακτήρας.
Αυτό σημαίνει ότι αν από το αρχικό string length αφαιρέσω το string length που θα έχει μείνει αφού αφαιρέσω τον κενό χαρακτήρα έχω πρακτικά τον αριθμό των λέξεων.
Επειδή όμως χάνεται η τελευταία λέξη που δεν έχει κενό στο τέλος της πρέπει να προστεθεί +1 στο τελικό αποτέλεσμα.
Για να διασφαλίσω το ότι θα έχω πάντα ένα κενό χαρακτήρα ανάμεσα στις λέξεις πριν υπολογιστεί το αρχικό string length αφαιρώ τα CRLF και την τυχόν ύπαρξη περισσότερων κενών χαρακτήρων.
Τα υπόλοιπα τα βλέπετε στον κώδικα παραπάνω και το παράδειγμα κλήσης της UDF παρακάτω.
Execution Examaple
SELECT [ProductReviewID]
,[ProductID]
,[Comments]
,dbo.fnStringWordCount (Comments) as WordCount
FROM [AdventureWorks].[Production].[ProductReview];
GO
//Antonios Chatzipavlis