5

Railsでグラフを作成しようとしています。たとえば、特定の日付範囲の毎日の1日あたりの平均売上高です

「sales_price」float属性を持つproducts_soldモデルがあるとします。しかし、特定の日に売上がない場合 (たとえば、モデル/データベースに何もない)、単純に 0 を返したいと思います。

これを行うための MySQL/Rails での最善の方法は何ですか? 私はこのようなことができることを知っています:

このSQLクエリは、私が望んでいるものを取得するための完全に間違った方法かもしれません

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;

次のような結果が得られます。

| | 平均 | 日付 |
  23 01-03-2009
  50 2009 年 1 月 5 日
  34 01-07-2009
  ... ... ...

私が取得したいのはこれです:

| | 平均 | 日付 |
  23 01-03-2009
   0 01-04-2009
  50 2009 年 1 月 5 日
   0 01-06-2009
  34 01-07-2009
   0 01-08-2009
  ... ... ...

SQL でこれを行うことはできますか?それとも、結果を後処理して、日付範囲内のどの日付が SQL 結果セットにないかを見つける必要がありますか? おそらく、いくつかのサブセレクトまたは IF ステートメントが必要ですか?

助けてくれてありがとう。

4

4 に答える 4

7

ActiveRecord の組み込みグループ機能を使用しない理由 (既に述べた日付以外) はありますか? 「後処理」を気にされているようですが、特に気にする必要はないと思います。

あなたは Rails を使用しているので、まず Rails ソリューションを探す必要があります[1]。私の最初の考えは、次のようなことをすることです

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

どの ActiveRecord が、あなたが説明したほとんどの SQL に変わりました。Merchant と Product の間に宣言されたhas_many関連付けがあると仮定すると、おそらくそれを使用したほうがよいでしょう。

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

(モデルの「products_sold」としての説明が、ある種の転記エラーであることを願っています。そうでない場合は、クラスの命名について多少メッセージから外れています!)

結局、元の場所に戻りましたが、より従来の Rails の方法でそこにたどり着きました (そして、Rails は実際に規則を重視しています!)。次に、ギャップを埋める必要があります。

日付範囲を知っていると仮定します。たとえば、日付範囲が からfrom_dateまでのすべての日付として定義されているとしto_dateます。

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

これにより、日付の完全なリストが配列として構築されます。平均を取得した日付は必要ありません。

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

その多くは私には少し雑然としているように見えます.もっと簡潔できれいになると思います. 配列にとどまるのではなく、ハッシュまたは構造体の構築を調査します。


[1] SQL を使用するなと言っているわけではありません。ActiveRecord が最も効率的なクエリを生成できず、SQL に頼る状況が発生しますfind_by_sql。それはそれでいいのですが、あくまで最後の手段として使ってみてはいかがでしょうか。

于 2009-01-08T10:16:30.100 に答える
2

少し乾かすには:

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]}
于 2009-07-18T21:47:45.623 に答える
2

このようなクエリでは、レポートする日付ごとに 1 つの行を含むテーブルを生成するメカニズムを見つける必要があります。次に、分析しているデータ テーブルとそのテーブルの外部結合を行います。null をゼロに変換するには、NVL または COALESCE を使用する必要がある場合もあります。

難しいのは、分析する必要がある範囲の日付のリストを含む (一時的な) テーブルを生成する方法です。これは DBMS 固有です。

ただし、日付/時刻の値を単一の日付にマッピングするというあなたのアイデアは的を射ています。毎週の売り上げを分析したい場合は、すべての日付を ISO 8601 の日付形式 (週 01 の 2009-W01 など) にマッピングするという、同様のトリックを実行する必要があります。

また、DATE 形式を 2009-01-08 表記にマップすることをお勧めします。これは、単純な文字の並べ替えを使用して日付順に並べ替えることができるためです。

于 2009-01-08T06:18:42.000 に答える
0

MySQL にはセットを返す関数がありますか? つまり、クエリの各行で異なる値を返す関数ですか? PostgreSQL の例として、次のことができます。

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

これにより、2 列と 3 行で構成される結果セットが生成されます。左の列には各行の「foo」が含まれ、右の列には 3、4、および 5 が含まれます。

したがって、generate_series()MySQL とサブクエリに相当するものがあると仮定すると、必要なのは、LEFT OUTER JOINこの関数から既にあるクエリへの です。これにより、各日付が出力に表示されることが保証されます。

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;

MySQL の正しい構文を取得するには、これを少しいじる必要があるかもしれません。

于 2009-01-08T06:18:13.937 に答える