3

Django には、次の例のようなモデルがあります。

class Currency(models.Model):
    name = models.CharField(max_length=3, unique=True)
    full_name = models.CharField(max_length=20)


class ExchangeRate(models.Model):
    currency = models.ForeignKey('Currency')
    start_date = models.DateFiled()
    end_date = models.DateField()
    exchange_rate = models.DecimalField(max_digits=12, decimal_places=4)

これを単純化して、通貨が 1 つしかなく、ExchangeRateテーブルが次のようになっていると仮定します。

+---------------------+-------------------+------------+------------+---------------+
| currency_from__name | currency_to__name | start_date |  end_date  | exchange_rate |
+---------------------+-------------------+------------+------------+---------------+
|        PLN          |        USD        | 2014-03-01 | 2014-08-01 |    3.00000    |
|        PLN          |        USD        | 2014-08-01 | 2014-12-01 |    6.00000    |
+---------------------+-------------------+------------+------------+---------------+

これは数学演算を単純化するための例であることに注意してください!

このテーブルでは、データ密度は月に 1 回で、1 か月の有効なレコードは、たとえば、いつstart_date = 2014.03.01end_date = 2014.04.01であるため、start_date包括的end_dateで排他的です。

期間の平均為替レートを計算したい:

[2014.06.01 ;  2012.09.01)

つまり、次のことを意味>= 2014.06.01します。< 2014.09.01

Django では次のように書きます。

start_date = date(2014, 6, 1)
end_date = date(2014, 9, 1)

ExchangeRate.objects.all().filter(
        (
            Q(start_date__lt=start_date) & 
            Q(end_date__gt=start_date)
        ) | (
            Q(start_date__gte=start_date) & 
            Q(start_date__lt=end_date) & 
            Q(end_date__gt=start_date) 
        )
).annotate(
    currency_from_name = 'currency_from__name', 
    currency_to_name = 'currency_to__name'
).values(  # GROUP BY
    'currency_from_name',
    'currency_to_name'
).aggregate(
    F('currency_from_name'), 
    F('currency_to_name'), 
    Avg('exchange_rate')
)

このクエリの後4.5000、時間範囲に注意する必要がある場合、数学的な理由から正しいが間違っている値を受け取ります。
正解は4.000です。

この式で余分な列に注釈を付けてから、この列から平均値を計算するというこのソリューションを思いつきました:

https://www.codecogs.com/eqnedit.php?latex=\inline&space;Abs&space;\left&space;(&space;\frac{months&space;\left&space;(&space;greater(ER_{start_date}\&space;,\&space) ;start_date),&space;smaller(ER_{start_date}\&space;,\&space;end_date)&space;\right&space;)&space;}{months(start_date\&space;,\&space;end_date)}&space;\right&space;) &space;*&space;ER_{exchange_rate}

どこ:

  • Absは絶対値関数abs()
  • months2 つの日付の間の月を計算する関数ですmonths_between()
  • greatersmaller引数からそれに応じて大きい値と小さい値を選択する関数です - greatest()least()
  • ERからの列を意味しますExchangeRate-例F('exchange_rate')

私は9.3 PostgreSQL DBDjango 1.8.4を使用しています。

多分そのための簡単な機能がありますか?
多分私はこれを過度に複雑にしていますか?

4

3 に答える 3

3

1. months_between():

create function months_of(interval)
 returns int strict immutable language sql as $$
  select extract(years from $1)::int * 12 + extract(month from $1)::int
$$;

create function months_between(date, date)
 returns int strict immutable language sql as $$
   select months_of(age($1, $2))
$$;

2.average_weight():

create function average_weight(numeric, date, date, date, date)
 returns numeric(9,2) strict immutable language sql as $$
   select abs(months_between(GREATEST($2, $4), LEAST($3, $5))/months_between($4, $5))*$1
$$;

3.AverageWeight:

from django.db.models.aggregates import Func
from django.db.models.fields import FloatField

class AverageWeight(Func):
    function = 'average_weight'

    def __init__(self, *expressions):
        super(AverageWeight, self).__init__(*expressions, output_field=FloatField())

あなたの見解では:

ExchangeRate.objects.all().filter(
        (
            Q(start_date__lt=start_date) & 
            Q(end_date__gt=start_date)
        ) | (
            Q(start_date__gte=start_date) & 
            Q(start_date__lt=end_date) & 
            Q(end_date__gt=start_date) 
        )
).annotate(
    currency_from_name = 'currency_from__name', 
    currency_to_name = 'currency_to__name',
    weight_exchange = AverageWeight(
        F('exchange_rate'),
        start_date,
        end_date,
        F('start_date'),
        F('end_date'),
    )
).values(  # GROUP BY
    'currency_from_name',
    'currency_to_name'
).aggregate(
    F('currency_from_name'), 
    F('currency_to_name'), 
    Avg('weight_exchange')
)
于 2015-09-08T20:09:27.493 に答える
2

アプリケーションの問題は、為替レートを保存する方法です。したがって、あなたの質問に答えるには、はい、これを複雑にしすぎています。

「The Math」は、平均為替レートが 4.5 であることを示しています。

(3 + 6) /2 == 4.5 

選択した開始日または終了日に関係なく、システムは同じ値を取得します。

根本原因に対処するために、別のアプローチを試してみましょう。(簡単にするために、特定の日付範囲内の平均を取得することに関係のない外部キーとその他の詳細を残します。後でそれらを追加できます)

このモデルで:

class ExchangeRate(models.Model):
    currency1 = models.CharField(max_length=3)
    currency2 = models.CharField(max_length=3)
    start_date = models.DateField()
    exchange_rate = models.DecimalField(max_digits=12, decimal_places=4)

そしてこのデータ:

INSERT INTO exchange_rate_exchangerate(currency1, currency2, start_date, exchange_rate) VALUES ('PLN', 'USD', '2014-03-01', 3);
INSERT INTO exchange_rate_exchangerate(currency1, currency2, start_date, exchange_rate) VALUES ('PLN', 'USD', '2014-04-01', 3);
INSERT INTO exchange_rate_exchangerate(currency1, currency2, start_date, exchange_rate) VALUES ('PLN', 'USD', '2014-05-01', 3);
INSERT INTO exchange_rate_exchangerate(currency1, currency2, start_date, exchange_rate) VALUES ('PLN', 'USD', '2014-06-01', 3);
INSERT INTO exchange_rate_exchangerate(currency1, currency2, start_date, exchange_rate) VALUES ('PLN', 'USD', '2014-07-01', 3);
INSERT INTO exchange_rate_exchangerate(currency1, currency2, start_date, exchange_rate) VALUES ('PLN', 'USD', '2014-08-01', 6);
INSERT INTO exchange_rate_exchangerate(currency1, currency2, start_date, exchange_rate) VALUES ('PLN', 'USD', '2014-09-01', 6);
INSERT INTO exchange_rate_exchangerate(currency1, currency2, start_date, exchange_rate) VALUES ('PLN', 'USD', '2014-10-01', 6);
INSERT INTO exchange_rate_exchangerate(currency1, currency2, start_date, exchange_rate) VALUES ('PLN', 'USD', '2014-11-01', 6);

このクエリを実行できます。

from django.db.models import Avg
from datetime import date

first_date = date(2014, 6, 1)
last_date = date(2014, 9, 1)
er.models.ExchangeRate.objects.filter(
    start_date__gte = first_date,
    start_date__lt = last_date

).aggregate(Avg('exchange_rate'))

この出力を取得するには:

{'exchange_rate__avg': 4.0}
于 2015-09-07T15:50:20.487 に答える
0

これは加重平均と考える必要があるため、各行の重みを計算してから合計します。

私はあなたを助けるのに十分なDjangoを知りませんが、SQLではこれは次のようになります(私は今これをテストすることはできませんが、正しい考えを与えると思います):

SELECT SUM((LEAST(end_date, @end_date) - GREATEST(start_date, @start_date)) * exchange_rate) / (@end_date - @start_date) AS weighted_avg
FROM 
  ExchangeRate
WHERE
  (start_date, end_date) OVERLAPS (@start_date, @end_date)

これは、OVERLAPS 演算子を使用して、期間が重複しているかどうかを確認します。重みの計算にof by 1の間違いがあるかどうかはわかりませんが、これは入力変数の定義(@end_date = @end_date - 1)で処理する必要があると思います

于 2015-09-02T11:33:59.280 に答える