shopkeeper
テーブルには次のフィールドがあります。
id (bigint),amount (numeric(19,2)),createddate (timestamp)
たとえば、上記の表があります。昨日のレコードを取得し、金額をセントに印刷してレポートを生成したいと思います。
実行方法の1つは、Javaアプリケーションで計算を実行し、簡単なクエリを実行することです
Date previousDate ;// $1 calculate in application
Date todayDate;// $2 calculate in application
select amount where createddate between $1 and $2
そして、レコードをループし、Javaアプリケーションで金額をセントに変換し、レポートを生成します
別の方法は、SQLクエリ自体で計算を実行するようなものです
select cast(amount * 100 as int) as "Cents"
from shopkeeper where createddate between date_trunc('day', now()) - interval '1 day' and date_trunc('day', now())
そして、レコードをループしてレポートを生成します
1つの方法では、すべての処理がJavaアプリケーションで行われ、単純なクエリが起動されます。それ以外の場合、すべての変換と計算はSQLクエリで行われます。
上記の使用例は単なる例であり、実際のシナリオでは、テーブルには、同様の種類の処理を必要とする多くの列を含めることができます。
パフォーマンスなどの面でどのアプローチが優れているか、そしてその理由を教えてください
それは多くの要因に依存します-しかし最も重要なことは:
いつものように、doを使用してデータをアプリサーバーに戻す場合、列と行を最小限に抑えることが有利になります。クエリが調整され、適切にインデックス付けされていることを確認すると、どちらのシナリオにも役立ちます。
メモを再:
そして、レコードをループします
Looping through recordsは、ほとんどの場合、sqlで行うのが間違っています-セットベースの操作を記述することをお勧めします。
原則として、データベースのジョブを最小限に抑えることをお勧めします。「このデータを保存し、このデータをフェッチします」-ただし、サーバーでのエレガントなクエリが多くの帯域幅。
また、考慮してください:これが計算上高価な場合、どこかにキャッシュできますか?
正確な "これは良い";両方の方法でコーディングし、比較します(どちらかの最初のドラフトは100%調整されていない可能性が高いことに注意してください)。しかし、通常の使用法を考慮してください。実際に一度に5回(別々に)呼び出される場合は、それをシミュレートします。「これらの1つと1つ」だけを比較しないでください。
比phorを使ってみましょう:パリで金のネックレスを購入したい場合、金細工師はケープタウンまたはパリに座ることができます、それは問題ですスキルと味。しかし、そのために南アフリカからフランスに大量の金鉱石を決して出荷しないでしょう。鉱石は採掘現場(または少なくとも一般的な地域)で処理され、金のみが出荷されます。同じことがアプリとデータベースにも当てはまります。
PostgreSQLに関する限り、サーバー上でほとんど何でも非常に効率的に実行できます。 RDBMSは、複雑なクエリに優れています。手続き上のニーズについては、さまざまな サーバー側スクリプト言語 :tcl、python、Perlなどから選択できます。ただし、ほとんどは PL/pgSQL を使用します。
最悪の場合のシナリオは、より大きなセットの各行ごとにサーバーに繰り返しアクセスすることです。 (それは、一度に1トンの鉱石を出荷するようなものです。)
2番目の行、前のクエリに依存するクエリのカスケードを送信する場合、すべてのクエリまたはプロシージャですべてを実行できますサーバー。 (これは、金と各宝石を別々の船で順番に出荷するようなものです。)
アプリとサーバーを行き来するのは高価です。サーバーおよびクライアントの場合。それを削減しようとすると、勝ちます-エルゴ:必要に応じてサーバー側の手順や洗練されたSQLを使用します。
ほぼすべての複雑なクエリをPostgres関数にパックするプロジェクトを終了しました。アプリはパラメーターを引き渡し、必要なデータセットを取得します。高速、クリーン、シンプル(アプリ開発者向け)、I/Oは最小限に抑えられています...カーボンフットプリントの少ない光沢のあるネックレス。
この場合、データベースエンジンはJavaよりも効率的な10進算術ルーチンを持っている可能性が高いため、SQLで計算を行う方がおそらくわずかに優れています。
一般に、行レベルの計算ではそれほど違いはありません。
違いを生むのは次のとおりです。
データアクセスロジックのどの部分をSQLで実行し、どの部分をアプリケーションで実行するかに関して、黒/白はありません。 Mark Gravell's 文言が好きで、区別します
SQLのパワーと表現力は、過小評価されています。 window functions が導入されたため、多くの非厳密セット指向の計算をデータベースで非常に簡単かつエレガントに実行できます。
アプリケーションアーキテクチャ全体に関係なく、常に3つの経験則に従う必要があります。
私の経験では、まともなDBAとまともなデータベースに関するある程度の知識があれば、すぐにDBのCPU制限に達することはありません。
これらの事柄が説明されているいくつかのさらなる読書:
一般に、同じまたは他のプロジェクトの他のモジュールまたはコンポーネントもそれらの結果を取得する必要がある場合は、SQLで処理を行います。サーバー側で実行されるアトミック操作も優れています。db管理ツールからストアドプロシージャを呼び出すだけで、さらに処理することなく最終的な値を取得できます。
場合によってはこれは適用されませんが、実行されると意味があります。また、一般的に、dbボックスは最高のハードウェアとパフォーマンスを備えています。
ORMの上に記述したり、カジュアルな低パフォーマンスアプリケーションを記述したりする場合は、アプリケーションを単純化するパターンを使用してください。高性能のアプリケーションを作成し、規模について慎重に考えている場合、処理をデータに移行することで勝ちます。処理をデータに移行することを強く推奨します。
これについて、2つのステップで考えてみましょう。(1)OLTP(レコードの少数)トランザクション。 (2)OLAP(多くのレコードの長時間スキャン)。
OLTPの場合、高速(1秒あたり1万-1万トランザクション)にしたい場合は、データベースからラッチ、ロック、およびデッドロックの競合を削除する必要があります。これは、トランザクションでの長いストールを排除する必要があることを意味します。クライアントからDBへのラウンドトリップは、クライアントに処理を移動することです。 (読み取り/更新をアトミックにするための)長期間存続するトランザクションを使用することはできず、スループットは非常に高くなります。
再:水平スケーリング。最新のデータベースは水平方向に拡張します。これらのシステムは、HAとフォールトトレランスを既に実装しています。それを活用して、アプリケーションのスペースを簡素化してください。
OLAPを見てみましょう-この場合、おそらくテラバイトのデータをアプリケーションにドラッグバックするのは恐ろしいアイデアであることは明らかです。これらのシステムは、圧縮された事前に編成された列データに対して非常に効率的に動作するように特別に構築されています。最新のOLAPシステムも水平方向にスケーリングし、作業を水平方向に分散させる高度なクエリプランナーを備えています(内部的に処理をデータに移動します)。
これに答える方法を簡素化するには、負荷分散を調べることです。負荷を最も容量の大きい場所に配置したい(それが理にかなっている場合)。ほとんどのシステムでは、すぐにボトルネックになるのはSQLサーバーなので、おそらく答えは、SQLが必要以上に1オンスの作業を行うことは望ましくないということです。
また、ほとんどのアーキテクチャでは、追加されるシステムと外部システムのコアを構成するのはSQLサーバーです。
しかし、上記の計算は非常に簡単なので、システムを限界までプッシュしない限り、配置するのに最適な場所は配置したい場所です。距離計算などのためにsin/cos/tanを計算するなど、数学が自明でない場合、努力は自明ではなくなり、慎重な計画とテストが必要になる可能性があります。
この質問に対処するために実際の例を挙げましょう
私はOHLCデータの加重移動平均を計算する必要がありました、それを行うためのシンボルを持つ約134000のキャンドルがあります
どちらが良いですか?
要件
励ましを与えるために、これは、加重移動平均を行うPythonバージョンです
コードを介して行われるWMA
import psycopg2
import psycopg2.extras
from talib import func
import timeit
import numpy as np
with psycopg2.connect('dbname=xyz user=xyz') as conn:
with conn.cursor() as cur:
t0 = timeit.default_timer()
cur.execute('select distinct symbol from ohlc_900 order by symbol')
for symbol in cur.fetchall():
cur.execute('select c from ohlc_900 where symbol = %s order by ts', symbol)
ohlc = np.array(cur.fetchall(), dtype = ([('c', 'f8')]))
wma = func.WMA(ohlc['c'], 10)
# print(*symbol, wma[-1])
print(timeit.default_timer() - t0)
conn.close()
WMA Through SQL
"""
if the period is 10
then we need 9 previous candles or 15 x 9 = 135 mins on the interval department
we also need to start counting at row number - (count in that group - 10)
For example if AAPL had 134 coins and current row number was 125
weight at that row will be weight = 125 - (134 - 10) = 1
10 period WMA calculations
Row no Weight c
125 1
126 2
127 3
128 4
129 5
130 6
131 7
132 8
133 9
134 10
"""
query2 = """
WITH
condition(sym, maxts, cnt) as (
select symbol, max(ts), count(symbol) from ohlc_900 group by symbol
),
cte as (
select symbol, ts,
case when cnt >= 10 and ts >= maxts - interval '135 mins'
then (row_number() over (partition by symbol order by ts) - (cnt - 10)) * c
else null
end as weighted_close
from ohlc_900
INNER JOIN condition
ON symbol = sym
WINDOW
w as (partition by symbol order by ts rows between 9 preceding and current row)
)
select symbol, sum(weighted_close)/55 as wma
from cte
WHERE weighted_close is NOT NULL
GROUP by symbol ORDER BY symbol
"""
with psycopg2.connect('dbname=xyz user=xyz') as conn:
with conn.cursor() as cur:
t0 = timeit.default_timer()
cur.execute(query2)
# for i in cur.fetchall():
# print(*i)
print(timeit.default_timer() - t0)
conn.close()
信じられないかもしれませんが、クエリは純粋なPythonバージョンよりも高速に実行されますWEIGHTED MOVING AVERAGE !!!私はそのクエリを段階的に作成しましたそこにハングアップして、あなたはうまくやるよ
速度
0.42141127300055814秒Python
0.23801879299935536秒SQL
私のデータベースには134000の偽OHLCレコードがあり、1000の株式に分割されています。これは、SQLがアプリサーバーよりも優れている例です
パフォーマンスの観点を形成する:これは、データベースの基になるディスクから実際にデータをフェッチするよりもはるかに高速に実行できる非常に単純な算術演算です。また、where句の値の計算は、実行時に非常に高速になる可能性があります。要約すると、ボトルネックは値の計算ではなく、ディスクIOである必要があります。
読みやすさから、ORMを使用する場合は、アプリケーションサーバー環境で実行する必要があると思います。ORMを使用すると、セットベースの操作を使用して、基になるデータを非常に簡単に操作できるからです。とにかく生のSQLを作成する場合、そこで計算を行うのに何も問題はありません。適切にフォーマットされていれば、SQLは少し見やすく、読みやすくなります。
この質問に対する他の回答は興味深いものです。 驚いたことに、誰もあなたの質問に答えていません。あなたは疑問に思っています:
詳細:質問1では、端数の集計が丸め誤差なしで機能することを確認する必要があります。私は、19,2という数値はお金に見合ったものだと思います。このため、お金にフロートを使用するのは間違っています。
質問2では、プログラマーとして「現在」と見なされる日付を完全に制御したいと思います。 now()のような関数を使用する場合、自動ユニットテストを記述するのは難しい場合があります。また、トランザクションスクリプトが長い場合は、変数をnow()に設定し、その変数を使用して、すべてのロジックでまったく同じ値を使用することをお勧めします。
重要なのは、「パフォーマンス」が定義されていないことです。
私にとって最も重要なのは開発者の時間です。
SQLクエリを記述します。遅すぎるか、DBがボトルネックになっている場合は、再検討してください。その時までに、2つのアプローチのベンチマークを行い、セットアップに関連する実際のデータ(ハードウェアと使用しているスタック)に基づいて決定を下せるようになります。
パフォーマンスの違いは、特定の例やベンチマークなしで推論できるとは思いませんが、別の見解があります。
どちらを維持できますか?たとえば、フロントエンドをJavaからFlash、HTML5、C++などに切り替えることができます。非常に多くのプログラムがこのような変更を経ており、複数のデバイスで動作する必要があるため、そもそも複数の言語で存在しています。
適切な中間層がある場合でも(与えられた例からそうではないようです)、その層が変更され、JBossがRuby/Railsになる可能性があります。
一方、SQLバックエンドをリレーショナルデータベースではないものでSQLに置き換えることはまずありませんし、たとえそれを行ってもフロントエンドをゼロから書き直さなければならないので、要点は重要ではありません。
私の考えは、DBで計算を行うと、すべてを再実装する必要がないため、後から2番目のフロントエンドまたは中間層を作成する方がはるかに簡単になるということです。ただし、実際には、「人々が理解できるコードでどこでこれを行うことができるか」が最も重要な要素だと思います。
フロントエンドで計算を実行するかバックエンドで計算を実行するかは、ビジネスの実装における目標を決定できる場合、非常に決定されます。時にJavaコードは、SQLコードよりも適切に記述されているか、その逆の場合があります。しかし、まだ混乱している場合は、最初に判断しようとすることができます-
コードを配置する場所を決定する前に、考えられる他の多くの側面があります。 1つの認識が完全に間違っています-すべてがJava(アプリコード)で最適に実行でき、かつ/またはすべてがdb(SQLコード)で最適です。