2011-03-10 3 views
15

J'utilise les rails 3 avec mongoïde. J'ai une collection de stocks avec une collection intégrée de prix:Interrogation d'objets incorporés dans Mongoid/rails 3 ("Inférieur à", opérateurs Min et tri)

class Stock 
    include Mongoid::Document 
    field :name, :type => String 
    field :code, :type => Integer 
    embeds_many :prices 

class Price 
    include Mongoid::Document 
    field :date, :type => DateTime 
    field :value, :type => Float 
    embedded_in :stock, :inverse_of => :prices 

Je voudrais obtenir les actions dont le prix minimum depuis une date donnée est inférieure à un prix donné p, puis être en mesure de trier les prix pour chaque stock.

Mais il semble que Mongodb ne le permette pas. Parce que cela ne fonctionnera pas:

@stocks = Stock.Where(:prices.value.lt => p) 

En outre, il semble que MongoDB ne peut pas trier les objets incorporés.

Alors, existe-t-il une alternative pour accomplir cette tâche?

Peut-être que je devrais mettre tout dans une collection pour que je puisse facilement exécuter la requête suivante:

@stocks = Stock.Where(:prices.lt => p) 

Mais je veux vraiment obtenir des résultats regroupés par noms d'actions après ma requête (stocks distincts avec un tableau de prix commandés par exemple). J'ai entendu parler de map/reduce avec la fonction de groupe mais je ne suis pas sûr de savoir comment l'utiliser correctement avec Mongoid.

http://www.mongodb.org/display/DOCS/Aggregation

L'équivalent dans SQL serait quelque chose comme ceci:

SELECT name, code, min(price) from Stock WHERE price<p GROUP BY name, code 

Merci pour votre aide.

Répondre

20

MongoDB/MongoId ne vous permettent de le faire. Votre exemple fonctionnera, la syntaxe est juste incorrecte.

@stocks = Stock.Where(:prices.value.lt => p) #does not work 

@stocks = Stock.where('prices.value' => {'$lt' => p}) #this should work 

Et, il est chainables encore si vous pouvez commander par son nom ainsi:

@stocks = Stock.where('prices.value' => {'$lt' => p}).asc(:name) 

Hope this helps.

+0

Notez que des requêtes plus complexes peuvent encore être effectuées, comme ceci: 'all_of ({: 'books.name' => 'name'}, {: 'books.author' =>/joe/i})' –

1

MongoDB ne permet l'interrogation des documents incorporés, http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-ValueinanEmbeddedObject

Qu'est-ce que vous manque est portée sur le modèle de prix, quelque chose comme ceci:

scope :greater_than, lambda {|value| { :where => {:value.gt => value} } } 

Cela vous permettra de passer dans tous les valeur que vous voulez et retourner une collection de prix Mongoid avec la valeur plus grande que ce que vous avez passé. Ce sera une collection non triée, vous devrez donc le trier dans Ruby.

prices.sort {|a,b| a.value <=> b.value}.each {|price| puts price.value} 

MongoId a un map_reduce méthode à laquelle vous passez deux variables de chaîne contenant les fonctions Javascript pour exécuter map/reduce, et ce serait sans doute la meilleure façon de faire ce que vous avez besoin, mais le code ci-dessus travaillera pour l'instant.

+0

Merci pour votre réponse. Mais je ne suis pas vraiment sûr de savoir comment utiliser cette fonctionnalité de portée. J'ai mis la fonction scope dans mon modèle Price, et en interrogeant @Stocks = Stock.where (: prices.greater_than => 200), cela me donne une erreur: "méthode non définie' greater_than 'pour: les prix: Symbol " – mathieurip

+0

La portée devrait être invoqué sur un seul stock, pas la collection. Donc, votre code ne fonctionnera pas, en effet. Parce que vous cherchez à filtrer comme ça, vous êtes mieux avec un appel MapReduce. Je verrai si je peux poster un exemple quelque part. –

+0

Oui, c'est ce que je viens de comprendre. Néanmoins, j'ai trouvé une solution stupide et lente qui fonctionne pour obtenir une liste de stocks dont les prix sont entre 100 et 200 par exemple, mais je ne suis pas content: 'i = 100 tandis que je <200 résultats = Stock.where ("prices.value" => i) si (@stocks == nil) @stocks = résultats autre @stocks + = résultats fin i + = 1 end' – mathieurip

2

J'ai eu un problème similaire ... voici ce que je propose:

scope :price_min, lambda { |price_min| price_min.nil? ? {} : where("price.value" => { '$lte' => price_min.to_f }) } 

Placez ce champ dans le modèle parent . Cela vous permettra de faire des requêtes comme:

Stock.price_min(1000).count 

Notez que ma portée ne fonctionne que lorsque vous y insérez réellement des données. C'est très pratique si vous construisez des requêtes complexes avec Mongoid.

Bonne chance!

mieux, Ruy