4

Ich habe eine MS SQL Server 2008 Datenbank, wo ich Orte, die Essen (Cafés, Restaurants, Restaurants etc.) servieren. Auf einer Website, die mit dieser Datenbank verbunden ist, können Personen die Orte auf einer Skala von 1 bis 3 bewerten.Berechnen Sie eine gewichtete (Bayesian) durchschnittliche Punktzahl/Index in der gespeicherten Prozedur?

Auf der Website gibt es eine Seite, wo die Leute eine Top-Liste mit den besten 25 (am besten bewerteten) Orten in einem anzeigen können bestimmte Stadt. Die Datenbankstruktur sieht ungefähr so ​​aus (es sind mehr Informationen in den Tabellen gespeichert, aber hier sind die relevanten Informationen): Database structure: Cities->Places->Votes

Ein Ort befindet sich in einer Stadt und Stimmen werden auf einen Platz platziert.

Bis jetzt habe ich nur eine durchschnittliche Stimme Punktzahl für jeden Ort berechnet, wo ich die Summe aller Stimmen für einen bestimmten Ort mit der Zahl der Stimmen für diesen Ort, so etwas wie dieses (Pseudo-Code) unterteilen:

vote_count = total number of votes for the place 
vote_sum = total sum of all the votes for the place 

vote_score = vote_sum/vote_count 

Ich muss auch Division durch Null behandeln, wenn ein Ort keine Stimmen hat. All dies geschieht innerhalb der gespeicherten Prozedur, die die anderen Daten abruft, die ich in der obersten Liste anzeigen möchte. Hier ist die aktuelle gespeicherte Prozedur, die die Top-25-Plätze mit der höchsten Stimme Punktzahl holt:

ALTER PROCEDURE [dbo].[GetTopListByCity] 
    (
    @city_id Int 
    ) 
AS 
    SELECT TOP 25 dbo.Places.place_id, 
      dbo.Places.city_id, 
      dbo.Places.place_name, 
      dbo.Places.place_alias, 
      dbo.Places.place_street_address, 
      dbo.Places.place_street_number, 
      dbo.Places.place_zip_code, 
      dbo.Cities.city_name, 
      dbo.Cities.city_alias, 
      dbo.Places.place_phone, 
      dbo.Places.place_lat, 
      dbo.Places.place_lng, 
      ISNULL(SUM(dbo.Votes.vote_score),0) AS vote_sum, 
      (SELECT COUNT(*) FROM dbo.Votes WHERE dbo.Votes.place_id = dbo.Places.place_id) AS vote_count, 
      COALESCE((CONVERT(FLOAT,SUM(dbo.Votes.vote_score))/(CONVERT(FLOAT,(SELECT COUNT(*) FROM dbo.Votes WHERE dbo.Votes.place_id = dbo.Places.place_id)))),0) AS vote_score 

    FROM dbo.Places INNER JOIN dbo.Cities ON dbo.Places.city_id = dbo.Cities.city_id 
    LEFT OUTER JOIN dbo.Votes ON dbo.Places.place_id = dbo.Votes.place_id 
    WHERE dbo.Places.city_id = @city_id 
    AND dbo.Places.hidden = 0 
    GROUP BY dbo.Places.place_id, 
      dbo.Places.city_id, 
      dbo.Places.place_name, 
      dbo.Places.place_alias, 
      dbo.Places.place_street_address, 
      dbo.Places.place_street_number, 
      dbo.Places.place_zip_code, 
      dbo.Cities.city_name, 
      dbo.Cities.city_alias, 
      dbo.Places.place_phone, 
      dbo.Places.place_lat, 
      dbo.Places.place_lng 
    ORDER BY vote_score DESC, vote_count DESC, place_name ASC 

    RETURN 

Wie Sie sehen können es mehr holt als nur die Stimme Partitur - Ich muss die Daten über den Ort, die Stadt es gelegen ist in und so weiter. Das funktioniert gut, aber es gibt ein großes Problem: Die Stimmenzahl ist zu einfach, weil sie die Anzahl der Stimmen nicht berücksichtigt. Mit der einfachen Berechnungsmethode eines Ort, der eine Stimme mit der Partitur hat 3 wird am Ende in der Liste höher als ein Ort, der vierzehn Stimmen mit der Partitur hat 3 und eine Stimme mit dem Ergebnis 2:

3/1 = 3 
(14*3 + 1*2) = 44/15 = 2.933333333333 

zu beheben Dies habe ich untersucht, indem ich irgendeine Form von gewichtetem Durchschnitt/gewichtetem Index verwendet habe. Ich habe ein Beispiel für eine wahre bayesische Schätzung gefunden, die vielversprechend aussieht. Es sieht wie folgt aus:

weighted rating (WR) = (v ÷ (v+m)) × R + (m ÷ (v+m)) × C 

where: 

R = average for the place (mean) = (Rating) 
v = number of votes for the place = (votes) 
m = minimum number of votes required to be listed in the Top 25 (unsure how many, but somewhere between 2-5 seems realistic) 
C = the mean vote across the whole database 

Die Probleme beginnen, wenn ich versuche, diese gewichtete Bewertung in einer gespeicherten Prozedur zu implementieren - es wird schnell kompliziert und ich verheddern in Klammern und den Überblick verlieren, was die gespeicherte Prozedur tut.

Jetzt brauche ich etwas Hilfe mit zwei Fragen:

Ist das ein geeignetes Verfahren für einen gewichteten Index für meine Seite Berechnung?

Wie sieht dieses (oder eine andere geeignete Berechnungsmethode) aus, wenn es in einer gespeicherten Prozedur implementiert wird?

Antwort

1

Ich kann kein Problem mit Ihnen Berechnungen sehen. Aber ich sehe, dass du oft dasselbe machst. Mein Vorschlag wird Ihnen helfen, die Aggregate an einem Ort zu machen, und dann ist die Auswahl ziemlich einfach.

;WITH CTE 
(
    SELECT 
     SUM(dbo.Votes.vote_score) AS SumOfVoteScore, 
     COUNT(*) AS CountOfVotes, 
     Votes.place_id 
    FROM 
     Votes 
    GROUP BY 
     Votes.place_id 
) 
SELECT TOP 25 
    dbo.Places.place_id, 
    dbo.Places.city_id, 
    dbo.Places.place_name, 
    dbo.Places.place_alias, 
    dbo.Places.place_street_address, 
    dbo.Places.place_street_number, 
    dbo.Places.place_zip_code, 
    dbo.Cities.city_name, 
    dbo.Cities.city_alias, 
    dbo.Places.place_phone, 
    dbo.Places.place_lat, 
    dbo.Places.place_lng, 
    ISNULL(CTE.SumOfVoteScore,0) AS vote_sum, 
    CTE.CountOfVotes AS vote_count, 
    COALESCE((CONVERT(FLOAT,CTE.SumOfVoteScore)/ 
    (CONVERT(FLOAT,CTE.CountOfVotes))),0) AS vote_score 

FROM dbo.Places INNER JOIN dbo.Cities ON dbo.Places.city_id = dbo.Cities.city_id 
LEFT JOIN CTE ON dbo.Places.place_id=CTE.place_id 
WHERE dbo.Places.city_id = @city_id 
AND dbo.Places.hidden = 0 
GROUP BY dbo.Places.place_id, 
     dbo.Places.city_id, 
     dbo.Places.place_name, 
     dbo.Places.place_alias, 
     dbo.Places.place_street_address, 
     dbo.Places.place_street_number, 
     dbo.Places.place_zip_code, 
     dbo.Cities.city_name, 
     dbo.Cities.city_alias, 
     dbo.Places.place_phone, 
     dbo.Places.place_lat, 
     dbo.Places.place_lng 
ORDER BY vote_score DESC, vote_count DESC, place_name ASC 

Die CTE-Funktion hilft uns, die Berechnungen wiederzuverwenden. Damit müssen wir SUM(vote_score) und SELECT COUNT(*) FROM Votes WHERE... nicht mehrfach verwenden. So, wenn Sie die Berechnungen auswählen, ist es recht einfach zu folgen.

Ich hoffe, das

bearbeiten

hilft müssen Sie nicht die Tabellenspalten im WAK definieren. Diese CTE (SumOfVoteScore, CountOfVotes, place_id) AS funktioniert so gut wie diese CTE AS. Sie müssen die Spalten definieren, wenn Sie eine rekursive CTE verwenden. Weil du mit dem anderen Teil union bist.

Als Referenz here und here Sie einige Informationen über CTE Funktionen

0

Dank Arion finden!

Ich war auf der Suche nach etwas im Sinne von CTE, aber ich wusste einfach nicht, dass ich gesucht habe! Es ist immer schön, etwas Neues zu lernen und ich weiß, dass ich CTE's in anderen Projekten nutzen werde. Wenn ich Ihren CTE in meiner gespeicherten Prozedur implementieren, erhalte ich diesen Code:

ALTER PROCEDURE dbo.GetTopListByCityCTE 
    (
    @city_id Int 
    ) 
AS 

;WITH CTE (SumOfVoteScore, CountOfVotes, place_id) AS 
(
    SELECT 
     SUM(dbo.Votes.vote_score) AS SumOfVoteScore, 
     COUNT(*) AS CountOfVotes, 
     Votes.place_id 
    FROM 
     Votes 
    GROUP BY 
     Votes.place_id 

) 

SELECT TOP 25 
    dbo.Places.place_id, 
    dbo.Places.city_id, 
    dbo.Places.place_name, 
    dbo.Places.place_alias, 
    dbo.Places.place_street_address, 
    dbo.Places.place_street_number, 
    dbo.Places.place_zip_code, 
    dbo.Cities.city_name, 
    dbo.Cities.city_alias, 
    dbo.Places.place_phone, 
    dbo.Places.place_lat, 
    dbo.Places.place_lng, 
    ISNULL(CTE.SumOfVoteScore,0) AS vote_sum, 
    CTE.CountOfVotes AS vote_count, 
    COALESCE((CONVERT(FLOAT,CTE.SumOfVoteScore)/ 
    (CONVERT(FLOAT,CTE.CountOfVotes))),0) AS vote_score 

FROM dbo.Places INNER JOIN dbo.Cities ON dbo.Places.city_id = dbo.Cities.city_id 
LEFT JOIN CTE ON dbo.Places.place_id = CTE.place_id 
WHERE dbo.Places.city_id = @city_id 
AND dbo.Places.hidden = 0 
GROUP BY dbo.Places.place_id, 
     dbo.Places.city_id, 
     dbo.Places.place_name, 
     dbo.Places.place_alias, 
     dbo.Places.place_street_address, 
     dbo.Places.place_street_number, 
     dbo.Places.place_zip_code, 
     dbo.Cities.city_name, 
     dbo.Cities.city_alias, 
     dbo.Places.place_phone, 
     dbo.Places.place_lat, 
     dbo.Places.place_lng, 
     CTE.SumOfVoteScore, 
     CTE.CountOfVotes 
ORDER BY vote_score DESC, vote_count DESC, place_name ASC 

Eine schnelle Überprüfung zeigt, dass es das gleiche Ergebnis wie der vorherigen Code gibt, aber es ist viel einfacher zu lesen und zu befolgen und hoffentlich viel effiziente .

Nun muss ich etwas experimentieren, um die alte (einfache) Berechnungsmethode durch eine neue zu ersetzen, die die Anzahl der Stimmen berücksichtigt.

+0

Tun Sie das .. Glücklich, Ihnen zu helfen. Wenn es Ihnen mit meiner Antwort gut geht, könnten Sie diese akzeptieren? – Arion

+0

Und auch wenn Sie meine Antwort sehen Ich habe es aktualisiert – Arion

+0

Ich möchte nur sicherstellen, dass CTE hilft mir, das ursprüngliche Problem zu lösen (Implementierung eines komplexeren Score-Index), bevor ich Ihre Antwort als Lösung markieren. Ich arbeite jetzt an der neuen gespeicherten Prozedur ... – tkahn

0

Ok - hier ist also die gespeicherte Prozedur kam ich mit:

ALTER PROCEDURE dbo.GetTopListByCityCTE 
    (
    @city_id Int 
    ) 
AS 

DECLARE @MinimumNumber float; 
DECLARE @TotalNumberOfVotes int; 
DECLARE @AverageRating float; 
DECLARE @AverageNumberOfVotes float; 

/* MINIMUM NUMBER */ 
SET @MinimumNumber = 1; 

/* TOTAL NUMBER OF VOTES -- ALL PLACES */ 
SET @TotalNumberOfVotes = (
    SELECT COUNT(*) FROM Votes 
); 

/* AVERAGE RATING -- ALL PLACES */ 
SET @AverageRating = (
    SELECT 
     CONVERT(FLOAT,(SUM(dbo.Votes.vote_score)))/CONVERT(FLOAT,COUNT(*)) AS AverageRating 
    FROM 
     Votes); 

/* AVERAGE NUMBER OF VOTES -- ALL PLACES */ 
/* CURRENTLY NOT USED IN INDEX - KEPT FOR REFERENCE */ 
SET @AverageNumberOfVotes = (
    SELECT AVG(CONVERT(FLOAT,NumberOfVotes)) FROM (SELECT COUNT(*) AS NumberOfVotes FROM Votes GROUP BY place_id) AS AverageNumberOfVotes 

); 
/* SUM OF ALL VOTE SCORES AND COUNT OF ALL VOTES -- INDIVIDUAL PLACES */ 
WITH CTE AS (
    SELECT 
     CONVERT(FLOAT, SUM(dbo.Votes.vote_score)) AS SumVotesForPlace, 
     CONVERT(FLOAT, COUNT(*)) AS CountVotesForPlace, 
     Votes.place_id 
    FROM 
     Votes 
    GROUP BY 
     Votes.place_id 
) 

SELECT 
    dbo.Places.place_id, 
    dbo.Places.city_id, 
    dbo.Places.place_name, 
    dbo.Places.place_alias, 
    dbo.Places.place_street_address, 
    dbo.Places.place_street_number, 
    dbo.Places.place_zip_code, 
    dbo.Cities.city_name, 
    dbo.Cities.city_alias, 
    dbo.Places.place_phone, 
    dbo.Places.place_lat, 
    dbo.Places.place_lng, 
    ISNULL(CTE.SumVotesForPlace,0) AS vote_sum, 
    ISNULL(CTE.CountVotesForPlace,0) AS vote_count, 
    COALESCE((CTE.SumVotesForPlace/ 
    CTE.CountVotesForPlace),0) AS vote_score, 
    ISNULL((CTE.CountVotesForPlace/(CTE.CountVotesForPlace + @MinimumNumber)) * (COALESCE((CTE.SumVotesForPlace/CTE.CountVotesForPlace),0)) + (@MinimumNumber/(CTE.CountVotesForPlace + @MinimumNumber)) * @AverageRating,0) AS WeightedIndex 

FROM dbo.Places INNER JOIN dbo.Cities ON dbo.Places.city_id = dbo.Cities.city_id 
LEFT JOIN CTE ON dbo.Places.place_id = CTE.place_id 
WHERE dbo.Places.city_id = @city_id 
AND dbo.Places.hidden = 0 
GROUP BY dbo.Places.place_id, 
     dbo.Places.city_id, 
     dbo.Places.place_name, 
     dbo.Places.place_alias, 
     dbo.Places.place_street_address, 
     dbo.Places.place_street_number, 
     dbo.Places.place_zip_code, 
     dbo.Cities.city_name, 
     dbo.Cities.city_alias, 
     dbo.Places.place_phone, 
     dbo.Places.place_lat, 
     dbo.Places.place_lng, 
     CTE.SumVotesForPlace, 
     CTE.CountVotesForPlace 
ORDER BY WeightedIndex DESC, vote_count DESC, place_name ASC 

Es gibt eine Variable @AverageNumberOfVotes genannt, die bei der Berechnung verwendet wird, aber ich hielt es dort bei Referenz es erforderlich sein könnte .

Wenn ich dies mit den Daten mache, die ich habe, bekomme ich Ergebnisse, die ein wenig anders sind als zuvor, aber es ist keine Revolution und es ist nicht ganz das, was ich brauchte. Hier sind die Top 10 Zeilen, die zurückgegeben werden, wenn ich ausführen die SP über:

vote_sum  vote_count vote_score   WeightedIndex 
1110   409   2,71393643031785 2,7140960047496 
807    310   2,60322580645161 2,60449697749787 
38    15   2,53333333333333 2,56708633093525 
25    10   2,5     2,55442722744881 
2    1   2     2,55188848920863 
2    1   2     2,55188848920863 
2    1   2     2,55188848920863 
2    1   2     2,55188848920863 
2    1   2     2,55188848920863 
2    1   2     2,55188848920863 

Das Problem hier zu sein scheint, dass, wo es nur eine Stimme und die Partitur 2, der gewichtete Index wird 2,55188848920863?

Die Formel zur Berechnung dieses Index stammt aus IMDB (http://www.imdb.com/chart/top) und ich denke, entweder habe ich etwas falsch gemacht oder die Daten, die ich in meiner Datenbank habe, ist nicht vergleichbar mit den Daten (Anzahl der Stimmen oder Abstimmungsskala), die IMDB hat?

bearbeiten

Gibt es eine Möglichkeit, dass ich diese Funktion einstellen könnte, so dass es für mich besser funktioniert? Gibt es eine andere Funktion/Vorgehensweise, die besser funktionieren würde? Ich muss noch die Berechnungen in der gespeicherten Prozedur durchführen.

+0

Ich bin mir nicht sicher, was diese Formel (die IMDB nennt "wahre Bayessche Schätzung") ist, was ich brauche. Und es gab Kritik: http://en.wikipedia.org/wiki/Bayes_estimator#Practical_example_of_misapplication_of_Bayes_estimators – tkahn