Bester Weg in MySQL oder Rails, um innerhalb eines bestimmten Datumsbereichs einen Durchschnitt pro Tag zu erhalten

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

Frage

Ich versuche, ein Diagramm in Schienen zu erstellen, zum Beispiel den AVG -Verkaufsbetrag pro Tag für jeden Tag in einem bestimmten Datumsbereich

Sagen Sie, ich habe ein Products_Sold -Modell, das ein "Sales_price" -Float -Attribut hat. Wenn jedoch ein bestimmter Tag keinen Verkauf hat (z. B. keine im Modell/DB), möchte ich einfach 0 zurückkehren.

Was ist der beste Weg in MySQL/Rails, um dies zu erledigen? Ich weiß, dass ich so etwas tun kann:

(Diese SQL -Abfrage ist möglicherweise der völlig falsche Weg, um das zu bekommen, was ich möchte, auch)

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;

Und erhalten Sie wie diese Ergebnisse:

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

Was ich gerne bekommen möchte ist Folgendes:

| 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
  ...       ...

Kann ich dies mit SQL tun oder muss ich die Ergebnisse nachbearbeiten, um herauszufinden, welche Daten im Daterange nicht im SQL-Ergebnissatz sind? Vielleicht brauche ich ein paar Subselekten oder wenn Aussagen?

Vielen Dank für jede Hilfe alle.

War es hilfreich?

Lösung

Gibt es einen Grund (abgesehen vom Datum, der bereits erwähnt wurde), warum Sie die integrierten Gruppenfunktionsfunktionen in ActivereCord nicht verwenden würden? Sie scheinen besorgt über "Nachbearbeitung" zu sein, worüber ich nicht wirklich etwas zu sorgen denke.

Sie sind in Rails, also sollten Sie wahrscheinlich zuerst nach einer Rails -Lösung suchen [1]. Mein erster Gedanke wäre, so etwas zu tun

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

Welches Activerecord wurde so ziemlich die SQL, die Sie beschrieben haben? Angenommen, es gibt eine deklarierte has_many Assoziation zwischen Händler und Produkt, dann würden Sie das wahrscheinlich besser verwenden, also so etwas wie:

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

(Ich hoffe, dass Ihre Beschreibung des Modells als "products_sold" eine Art Transkriptionsfehler ist, übrigens - wenn nicht, sind Sie mit Ihrer Klasse -Benennung etwas außerhalb der Message!)

Nach all dem sind Sie wieder dort, wo Sie angefangen haben, aber Sie sind auf konventionellere Rails -Art und Weise dort angekommen (und Schienen schätzen die Konventionen wirklich!). Jetzt müssen wir die Lücken füllen.

Ich gehe davon aus, dass Sie Ihren Datumsbereich kennen, sagen wir, er ist als alle Daten aus definiert from_date zu to_date.

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

Das erstellt die vollständige Liste der Daten als Array. Wir brauchen nicht die Daten, an denen wir einen Durchschnitt haben:

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

Das Los sieht für mich ein bisschen überfüllt aus: Ich denke, es könnte eintrieben und sauberer sein. Ich würde den Bau eines Hashs oder einer Struktur untersuchen, anstatt in Arrays zu bleiben.


1] Ich sage nicht find_by_sql. Das ist in Ordnung, es soll so sein, aber ich denke, Sie sollten versuchen, es nur als letztes Ausweg zu verwenden.

Andere Tipps

Für eine solche Abfrage müssen Sie einen Mechanismus finden, um eine Tabelle mit einer Zeile für jedes Datum zu generieren, über den Sie melden möchten. Anschließend werden Sie eine äußere Verbindung dieser Tabelle mit der von Ihnen analysierenden Datentabelle durchführen. Möglicherweise müssen Sie auch mit NVL oder Koalesce spielen, um Nulls in Nullen umzuwandeln.

Der schwierige Teil besteht darin, herauszufinden, wie Sie die (temporäre) Tabelle erstellen, die die Liste der Daten für den Bereich enthält, den Sie analysieren müssen. Das ist DBMS-spezifisch.

Ihre Idee, Datum/Uhrzeitwerte zu einem einzigen Datum zuzuordnen, ist jedoch genau richtig. Sie müssten einen ähnlichen Trick machen - alle Daten auf ein ISO 8601 -Datumsformat wie 2009 -W01 für Woche 01 zuzuordnen - wenn Sie den wöchentlichen Umsatz analysieren möchten.

Außerdem sollten Sie Ihr Datumsformat besser auf die Notation 2009-01-08 abbilden, da Sie dann mit einer einfachen Charakter-Sortierung in Datumsreihenfolge sortieren können.

Ein bisschen austrocknen:

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]}

Hat MySQL Set-Returning-Funktionen? IE Funktionen, die in jeder Zeile einer Abfrage unterschiedliche Werte zurückgeben? Als Beispiel von PostgreSQL können Sie dies tun:

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

Dadurch wird ein Ergebnissatz erzeugt, das aus 2 Spalten und 3 Zeilen besteht, wobei die linke Spalte in jeder Zeile und die rechte Spalte 3, 4 und 5 enthält.

Angenommen, Sie haben ein Äquivalent von generate_series() In MySQL und Unterabfragen: Was Sie brauchen, ist a LEFT OUTER JOIN Von dieser Funktion bis zur Abfrage, die Sie bereits haben. Dadurch wird sichergestellt, dass jedes Datum in der Ausgabe angezeigt wird:

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;

Möglicherweise müssen Sie ein wenig mit diesem Richtfest verfolgen, um die Syntax für MySQL zu erhalten.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top