Bester Weg in MySQL oder Rails, um innerhalb eines bestimmten Datumsbereichs einen Durchschnitt pro Tag zu erhalten
-
05-07-2019 - |
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.
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.