2012-04-13 17 views
15

ho un modello di database Position(lat,lon) che detiene latitudes e longitudes.Rails nidificate query SQL

ho un'azione di controllo chiamato show_close_by che riceve una posizione in gradi (my_lat, my_lon), una tolleranza (in chilometri) e dovrebbe restituire l'elenco delle posizioni nel database che rientrano nell'intervallo di tolleranza.

Per questo, utilizzo la formula haversine_distance che calcola la distanza in chilometri (sulla superficie della Terra) tra due coordinate (lat1, lon1, lat2, lon2).

Per rendere la query più veloce, ho scritto tutto haversine_distance formula nella query:

... WHERE 2*6371*asin(sqrt(power(sin((:lat2-latitude)*pi()/(180*2)) ,2) + cos(latitude*pi()/180)*cos(:lat2*pi()/180)*power(sin((:lon2-longitude)*pi()/(180*2)),2))) < tolerance 

Le specifiche della query non contano. Il mio dubbio è: è necessario calcolare questa enorme funzione per OGNI posizione nel database? Posso filtrare alcune posizioni chiaramente troppo lontane con una funzione più semplice?

Bene, posso: Con una query SQL nidificata, posso interrogare il database per le posizioni che si trovano all'interno di un "quadrato" grande (nello spazio lat/lon) e quindi filtrare quelli con la funzione trigonometrica più costosa. Qualcosa di simile a quanto segue:

SELECT * FROM (SELECT * FROM Positions WHERE lat-latitude < some_reasonable_upper_bound AND lon-longitude < same_upper_bound) WHERE costly_haversine_distance < tolerance 

Infine, la mia domanda: come posso implementare questa in Rails (senza scrivere l'intera query me)? Positions.where(reasonable_upper_bound).where(costly_but_accurate_restriction) crea una query nidificata? Se no, come?

Grazie mille!

+0

Conoscete [Geocoder] (https://github.com/alexreisner/geocoder)? Tu installi la gemma, aggiungi una linea nel tuo modello di posizione, e poi puoi fare cose come 'Position.near ([40.71, 100.23], 20)'. Questa gemma è abbastanza popolare, quindi direi che fanno quello che vuoi fare abbastanza bene. (fa finta di sapere, il collegamento tra clausole 'where' non fa quello che vuoi.Penso che abbia la precedenza su quelli precedenti). – Robin

+0

Sì, conosco Geocoder e potrei finire per usarlo, ma mi è sembrato un po 'grande per quello che voglio (voglio solo le distanze tra gli oggetti). Quanto è più lenta un'applicazione che carica un intero oggetto Geocoder rispetto alla funzione haversine_distance barebones che ho programmato? – gdiazc

+0

@Robin Non sovrascrive il precedente, ma * fa * aggiunge le condizioni usando 'AND' alla query corrente, che non è esattamente ciò che il richiedente sta cercando. – nzifnab

risposta

25

Ecco come fare query nidificate:

LineItem.where(product_id: Product.where(price: 50)) 

rende la seguente richiesta:

SELECT "line_items".* FROM "line_items" 
WHERE "line_items"."product_id" IN 
(SELECT "products"."id" FROM "products" WHERE "products"."price" = 50) 

Nota che soloid s saranno recuperati dalla tabella products. Se stai cercando di creare un altro modo per connettere due entità e questa magia non è adatta, usa Product.select(:some_field).where(...).

+0

Grazie per il post, ma ho bisogno di un aiuto leggermente diverso .. Come scrivere query se ho bisogno di selezionare tutti i messaggi comunicati tra due utenti (ad esempio id1 e id2) e il messaggio ha sender_id e receiver_id. Quindi ho bisogno di selezionare "tutti i messaggi di cui ((sender_id = id1 e reciver_id = id2) o (sender_id = id2 e reciver_id = id1))" in binari. Ho bisogno di ordinarli e impaginare che non può essere fatto se faccio due query separate e poi le aggiungo (avrò il risultato desiderato ma l'impaginazione diventa difficile). Quindi, per favore aiutatemi a scrivere una sola riga per questo. Qualsiasi aiuto sarà apprezzato. – zeal

+0

@zeal Prova https: // github.com/activerecord-hackery/squeel per query complicate. – jdoe

Problemi correlati