2009-09-13 7 views
1

Ma table votes ressemble à ceci:requête SQL pour obtenir "score vote"

id: integer 
vote: boolean 
voteable_id: integer 
voteable_type: string 
voter_id: integer 
voter_type: string 

La colonne vote détermine si la ligne représente un "up vote" (vote = true) ou un "vote down" (vote = false) .

voteable_type est la classe de la chose étant votée, voteable_id est l'identifiant de la chose d'être voté, voter_type est la classe de l'électeur, et voter_id est l'identifiant de l'électeur.

Ce que je besoin est une requête pour obtenir le haut nposts dans l'ordre décroissant par « score vote », où « score de vote » est défini comme (le nombre maximum de votes le poste a) - (la nombre de votes négatifs que le poste a).

points bonus si votre solution ne me demande pas de recourir à find_by_sql() (je travaille dans Rails)

plus de points bonus si votre solution fonctionne de la même manière SQLite et PostgreSQL (si il est plus important que cela fonctionne dans PostgreSQL).

+0

Quelle base de données? . – cletus

+3

Vous rendriez également votre vie beaucoup plus facile si vous stockez ceci comme 1/-1 au lieu de vrai/faux aussi bien. – cletus

+0

Err, ne serait-ce pas 1 et 0? – Alex

Répondre

3

génériquement, vous pouvez le faire en utilisant une déclaration case avec un sum:

select 
    voteable_id, 
    sum(case 
     when vote then 1 
     else -1 
    end) as vote_score 
from 
    votes 
group by 
    voteable_id 

Notez que ceci est ANSI SQL, et fonctionnent donc sur SQLite, MySQL, Postgres, Oracle, SQL Server, DB2, etc., etc.

pour obtenir vos messages top N, vous pouvez tout simplement ajouter à la requête ci-dessus:

order by 
    vote_score desc 
limit 10 

limit est utilisé par Postgres et S QLite (un peu différemment dans MySQL), et pas dans Oracle ou SQL Server, en tant que FYI.

Ainsi, pour obtenir les informations des messages associés à ceci:

select 
    p.title, 
    p.author, 
    p.createdate, 
    sum(case 
      when v.vote then 1 
      else -1 
     end) as vote_score 
from 
    posts p 
    inner join votes v on 
     p.post_id = v.voteable_id 
group by 
    p.title, 
    p.author, 
    p.createdate 
order by 
    vote_score desc 
limit 10 
+0

Maintenant, ajoutez l'exigence N haut aussi? –

+0

Cool - comment puis-je ajouter un 'join' dans le mix pour obtenir les' posts' associés au 'voteable_id' retourné? –

+0

Merci, mais quand j'essaie cette deuxième requête, j'obtiens 'erreur SQL: près de" sum ": erreur de syntaxe' –

2

La fonction conditionnelle commune à SQLite et PostgreSQL (et d'autres implémentations SQL ANSI conformes) est CASE - voir par exemple here pour PostgreSQL et here pour SQLite. Ainsi, la partie intérieure pour « compter +1/-1 nombre de voix » va devoir être

SUM(CASE WHEN vote THEN 1 ELSE -1 END) 

et vous aurez également besoin inévitablement GROUP BY voteable_id pour faire de ce droit de travail SUM.

Cela devra être dans le ORDER BY pour le tri (avec un DESC); pas sûr si vous le voulez aussi dans les résultats, mais je suppose que vous le faites, auquel cas il doit également être dans le SELECT (et vous pouvez vous référer à son alias dans le ORDER BY). Enfin, LIMIT n fonctionne dans les deux moteurs.

Donc, mettre tous ensemble:

SELECT voteable_id, 
     SUM(CASE WHEN vote THEN 1 ELSE -1 END) AS vote_score 
    FROM votes 
    GROUP BY voteable_id 
    ORDER BY vote_score DESC 
    LIMIT 10 

devrait satisfaire tous vos besoins.

+0

Cool - comment puis-je ajouter un 'join' dans le mix pour obtenir les' posts' associés au votable_id' retourné? –

+0

entre FROM et GROUP BY, ajoutez 'JOIN JOINs USING (voteable_id)' (si c'est le nom du champ sur les deux tables, sinon utilisez une condition 'ON') et dans le SELECT sélectionnez les champs nécessaires de 'posts.'; assurez-vous également de désambiguïser 'votes.voteable_id' etc au besoin (une qualification explicite des champs est recommandée quand> 1 table est impliquée! -). –

0

Si vous utilisez VoteFu (dont vous avez l'air), je vous recommande de convertir en utilisant Integers pour stocker les valeurs de vote au lieu de: boolean. La seule raison pour laquelle les votes dans VoteFu sont stockés en tant que booléens est que j'ai senti que je devais maintenir la compatibilité ascendante avec Acts_As_Voteable. J'ai maintenant décidé que je mettais trop de poids sur cette préoccupation.

Je vais sortir une nouvelle version du plugin VoteFu qui gère la conversion pour vous, mais jusque là, je pense que changer cela vous-même est une bonne idée. Voici comment:

class VoteFuIntegerMigration < ActiveRecord::Migration 
    def self.up 
    add_column :votes, :vote_int, :integer 
    Vote.find(:all).each do |vote| 
     vote.vote_int = vote.vote? ? 1 : -1 
     vote.save! 
    end 
    remove_column :vote, :vote 
    rename_column :vote, :vote_int, :vote 
    end 
end 

Et puis:

module Juixe 
    module Acts 
    module Voteable 
     module InstanceMethods 
     def votes_for 
      Vote.sum(:vote, :conditions => [ 
      "voteable_id = ? AND voteable_type = ? AND vote > 0", 
      id, self.class.name]) 
     end 

     def votes_against 
      Vote.sum(:vote, :conditions => [ 
      "voteable_id = ? AND voteable_type = ? AND vote < 0", 
      id, self.class.name]) 
     end 

     def votes_total 
      Vote.sum(:vote, :conditions => [ 
      "voteable_id = ? AND voteable_type = ?", 
      id, self.class.name]) 
     end 
     end 
    end 
    end 
end 

Il est probablement une bonne idée d'introduire les changements du module comme monkey-patch. Le nouveau VoteFu aura ce code intégré (avec des tests, aussi.)

+0

Des progrès sur la prochaine version, Pete? (J'adore toujours ta gemme!) –

+0

J'ai été terriblement, terriblement négligent en codant ma prochaine version! Un autre projet m'a usurpé. Je suis actuellement à la recherche d'autres committers pour aider. Si vous voulez soumettre un patch de votre solution qui fait la conversion entière, je serais heureux de 1) vous créditer complètement et b) vous faire un committer sur le projet. Fais moi savoir! – Pete

Questions connexes