Il modo migliore in MySQL for Rails per ottenere MEDIA al giorno entro un intervallo di date specifico

StackOverflow https://stackoverflow.com/questions/423212

Domanda

Sto cercando di creare un grafico in Rails, ad esempio l'importo delle vendite medie al giorno per ogni giorno in un determinato intervallo di date

Supponi di avere un modello products_sold che ha un "prezzo_vendita" attributo float. Ma se un giorno specifico non ha vendite (ad esempio nessuno nel modello / db), voglio restituire semplicemente 0.

Qual è il modo migliore in MySQL / Rails per farlo? So di poter fare qualcosa del genere:

( Questa query SQL potrebbe essere il modo completamente sbagliato di ottenere anche quello che desidero )

SELECT avg(sales_price) AS avg, DATE_FORMAT(created_at, '%m-%d-%Y') AS date
    FROM products_sold WHERE merchant_id = 1 GROUP BY date;

E ottieni risultati come questo:

| avg |    date    |
  23    01-03-2009
  50    01-05-2009 
  34    01-07-2009
  ...       ...

Quello che vorrei ottenere è questo:

| avg |    date    |
  23    01-03-2009
   0    01-04-2009
  50    01-05-2009
   0    01-06-2009 
  34    01-07-2009
   0    01-08-2009
  ...       ...

Posso farlo con SQL o dovrò postelaborare i risultati per trovare quali date nel daterange non sono nel set di risultati SQL? Forse ho bisogno di alcune sottoselezioni o dichiarazioni IF?

Grazie per l'aiuto a tutti.

È stato utile?

Soluzione

C'è un motivo (diverso dalla data già menzionata) per cui non dovresti usare le funzionalità di funzione di gruppo integrate in ActiveRecord? Sembra che tu sia preoccupato per "post-elaborazione", che non credo sia davvero qualcosa di cui preoccuparti.

Sei su Rails, quindi probabilmente dovresti prima cercare una soluzione Rails [1]. Il mio primo pensiero sarebbe di fare qualcosa del genere

Product.average(:sales_price, :group => "DATE(created_at)", :conditions => ["merchant_id=?", 1])

quale ActiveRecord ha praticamente trasformato l'SQL che hai descritto. Supponendo che ci sia un'associazione has_many dichiarata tra Commerciante e Prodotto, probabilmente sarebbe meglio usarla, quindi qualcosa del tipo:

ave_prices = Merchant.find(1).products.average(:sales_price, :group => "DATE(created_at)")

(Spero che la tua descrizione del modello come "prodotti_venduti" sia una sorta di errore di trascrizione, tra l'altro - in caso contrario, sei un po 'fuori messaggio con il tuo nome di classe!)

Dopo tutto, sei tornato da dove hai iniziato, ma ci sei arrivato in un modo più convenzionale di Rails (e Rails apprezza davvero le convenzioni!). Ora dobbiamo colmare le lacune.

Suppongo che tu conosca il tuo intervallo di date, supponiamo che sia definito come tutte le date da from_date a to_date .

date_aves = (from_date..to_date).map{|dt| [dt, 0]}

Questo crea l'elenco completo delle date come un array. Non abbiamo bisogno delle date in cui abbiamo ottenuto una media:

ave_price_dates = ave_prices.collect{|ave_price| ave_price[0]} # build an array of dates
date_aves.delete_if { |dt| ave_price.dates.index(dt[0]) } # remove zero entries for dates retrieved from DB
date_aves.concat(ave_prices)     # add the query results
date_aves.sort_by{|ave| ave[0] } # sort by date

Quella roba mi sembra un po 'ingombra: penso che potrebbe essere più tersa e pulita. Avrei indagato sulla costruzione di un hash o di un Struct piuttosto che stare in array.


[1] Non sto dicendo di non usare SQL: si verificano situazioni in cui ActiveRecord non è in grado di generare la query più efficiente e si ricorre a find_by_sql . Va bene, dovrebbe essere così, ma penso che dovresti provare a usarlo solo come ultima risorsa.

Altri suggerimenti

Per qualsiasi query di questo tipo, dovrai trovare un meccanismo per generare una tabella con una riga per ogni data su cui vuoi riferire. Quindi eseguirai un join esterno di quella tabella con la tabella di dati che stai analizzando. Potrebbe anche essere necessario giocare con NVL o COALESCE per convertire i null in zero.

La parte difficile sta studiando come generare la tabella (temporanea) che contiene l'elenco delle date per l'intervallo che è necessario analizzare. Questo è specifico per DBMS.

La tua idea di mappare i valori di data / ora su una singola data è perfetta, però. Avresti bisogno di fare un trucco simile - mappando tutte le date in un formato di data ISO 8601 come 2009-W01 per la settimana 01 - se desideri analizzare le vendite settimanali.

Inoltre, faresti meglio a mappare il tuo formato DATA alla notazione del 2009-01-08 perché puoi quindi ordinare in ordine di data usando un ordinamento di caratteri semplice.

Per asciugare un po ':

ave_prices = Merchant.find(1).products.average(:sales_price, :group => "DATE(created_at)")
date_aves = (from_date..to_date).map{|dt| [dt, ave_prices[dt.strftime "%Y-%m-%d"] || 0]}

MySQL ha funzioni di ritorno? Cioè funzioni che restituiscono valori diversi su ogni riga di una query? Come esempio di PostgreSQL, puoi fare:

select 'foo', generate_series(3, 5);

Questo produrrà un set di risultati composto da 2 colonne e 3 righe, in cui la colonna di sinistra contiene "pippo" su ciascuna riga e la colonna di destra contiene 3, 4 e 5.

Quindi, supponendo che tu abbia un equivalente di generate_series () in MySQL e sottoquery: quello di cui hai bisogno è un LEFT OUTER JOIN da questa funzione alla query che hai già hanno. Ciò ti garantirà la visualizzazione di ogni data nell'output:

SELECT
    avg(sales_price) as avg,
    DATE_FORMAT(the_date, '%m-%d-%Y') as date
FROM (select cast('2008-JAN-01' as date) + generate_series(0, 364) as the_date) date_range
LEFT OUTER JOIN products_sold on (the_date = created_at)
WHERE merchant_id = 1
GROUP BY date;

Potrebbe essere necessario giocherellare un po 'con questo per ottenere la sintassi giusta per MySQL.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top