2016-06-25 21 views
1

売り注文を格納するMySQL DBテーブルでは、LastReviewedカラムに売り注文が変更された最後の日付と時刻を保持します(timestamp、デフォルト値CURRENT_TIMESTAMP )。私は、特定のユーザーのために、過去90日間、毎日変更された売上の数をプロットしたいと思います。SQL:サブクエリを使用しないクエリでの再利用関数の結果

LastReviewedからの日数と、その範囲内のレコード数を返すSELECTを作成しようとしています。私はDATEDIFF()だけでなく、各レコードのCURDATE()複数回計算しています

SELECT DATEDIFF(CURDATE(), LastReviewed) AS days, COUNT(*) AS number FROM sales 
WHERE UserID=123 AND DATEDIFF(CURDATE(),LastReviewed)<=90 
GROUP BY days 
ORDER BY days ASC 

注意:以下は正常に動作します私のクエリは、あります。これは実際には効果がないようですので、前回の計算の結果をどのように再利用できるかを知りたいと思います。私が試した最初のものでした:

SELECT DATEDIFF(CURDATE(), LastReviewed) AS days, COUNT(*) AS number FROM sales 
WHERE UserID=123 AND days<=90 
GROUP BY days 
ORDER BY days ASC 

エラー:Unknown column 'days' in 'where clause'。だから私はネットの周りを見始めた。別の議論(Can I reuse a calculated field in a SELECT query?)に基づいて、私は次の次のことを試してみました:

SELECT DATEDIFF(CURDATE(), LastReviewed) AS days, COUNT(*) AS number FROM sales 
WHERE UserID=123 AND (SELECT days)<=90 
GROUP BY days 
ORDER BY days ASC 

エラー:Unknown column 'days' in 'field list'。私も次のことを試してみましたよ:

SELECT @days := DATEDIFF(CURDATE(), LastReviewed) AS days, 
     COUNT(*) AS number FROM sales 
WHERE UserID=123 AND @days <=90 
GROUP BY days 
ORDER BY days ASC 

クエリがゼロ結果を返しますので、@days<=90が、私はSELECT句に入れてWHERE句を削除した場合、私は@daysでいくつかの結果を見ることができるにもかかわらずfalseを返すように見えます90.

以下の値は、私は、サブクエリを使用して動作するように物事を得ている:

SELECT * FROM (
    SELECT DATEDIFF(CURDATE(),LastReviewed) AS sales , 
     COUNT(*) AS number FROM sales 
    WHERE UserID=123 
    GROUP BY days 
) AS t 
WHERE days<=90 
ORDER BY days ASC 

は、しかし、私はそれが最も効率的な方法だかどうかを知るodn't。たとえその値がクエリの最初から最後まで同じであっても、このソリューションでもレコードごとに1回、 CURDATE()を計算することは言うまでもありません。それは無駄ではないですか?私はこれを熟考していますか?ヘルプは大歓迎です。

注:これはCodeReviewにある必要がありますか?私が使用しようとしているコードが実際に動作しないため、ここに投稿しました

+1

FWIWでは、クエリ+サブクエリは安価ではなく、クエリで 'WHERE days <90'を実行するか、' DATEDIFF(CURDATE()、LastReviewed)を繰り返すと結果がより速くなるかどうかを確認できます。 – aneroid

+0

私のポストでは言及していませんでしたが(1分後に追加します)、バグのもう一つのことは、最初から最後まで変更しなくても、すべてのステップで 'CURDATE() 'を計算することですクエリの終わり。 – BeetleJuice

答えて

1

あなたの質問には実際に2つの問題があります。

まず、WHERESELECTに先行することを見落としています。サーバがWHERE <expression>を評価すると、計算された計算の値はすでに<expression>となり、SELECTの値を使用できます。

しかし、それよりも悪いことに、通常はサーバが各行の式を評価する必要があるため、関数の引数としてカラムを使用するクエリを書くことはほとんどありません。これは、DATE_SUB(CURDATE(), INTERVAL 90 DAY)<比較の片側に使用することができます定数に解決することができるため、オプティマイザは、これを見ると、すべてが興奮するだろう

WHERE LastReviewed < DATE_SUB(CURDATE(), INTERVAL 90 DAY) 

代わりに、あなたはこれを使用する必要がありますインデックスがLastReviewedで存在する場合、サーバーは、インデックスを使用してLastReviewed >=の定数値を持つすべての行を直ちに削除できます。

DATEDIFF(CURDATE(), LastReviewed) AS days(まだSELECTに必要)は、私たちが望むことがわかっている行に対してのみ評価されます。

(UserID、LastReviewed)に1つのインデックスを追加すると、サーバーは関連する行を正確に正確に正確に特定できます。

+0

あなたの投稿は私にとって本当に良いレッスンでした。あなたは正しいところです。私は、 'WHERE'が最初に実行されたことや、フィールドを関数の引数として使用することの意味を考えなかったのです。 'CURDATE()'と拡張子 'DATE_SUB'は一度だけ計算されることを正しく理解しましたか?最後に、 'UserID、LastReviewed'のインデックスを提案しました。同じユーザーが同じ「LastReviewed」を持つ複数のレコードを持つことができるということをあなたのアドバイスは依然として適用しますか? – BeetleJuice

+0

これがあなたの答えに関係する場合、そのテーブルにはすでに「一意のユーザーID、オーダーID」というインデックスがあります。 'orderID'自体は一意ではありません(複数のユーザーが同じものを持つことができます) – BeetleJuice

+0

このようなインデックスは、一致するユーザーに行を絞り込むために使用できますが、サーバーは一致するすべての行をスキャンして日付をチェックする必要があります。何もしていないものの、それがどれだけ役に立つかは、ユーザーあたりの行数に依存します。 –

1

組み込み関数は、たとえばローをフェッチするよりもはるかにコストがかかりません。

あなたは、以下の「複合」指数で、より多くのパフォーマンスの向上を得ることができ

INDEX(UserID, LastReviewed) 

変更あなたの製剤は、関数呼び出しにLastRevieded「隠れ」さ

WHERE UserID=123 
    AND LastReviewed >= CURRENT_DATE() - INTERVAL 90 DAY 

へインデックスでは使用できなくなります。

まだ改善されていない場合は、昨日の統計を計算して「要約表」に入れる夜間のクエリを検討してください。そこからあなたが言及したSELECTはさらに高速に実行できます。

+0

同じユーザーには同じ「LastReviewed」を持つ複数のレコードを持たせることができ、異なるユーザーは同じ「LasteReviewed」を持つレコードを持つことができます。テーブルには既に一意のUserID、orderIDのインデックスがあります。これにより、インデックス 'UserID、LastReviewed'の追加に関する記述が変更されますか? – BeetleJuice

+0

提案された変更点_plus_は、私の推奨するインデックスにより速く実行されるはずです。 'UNIQUE(UserID、orderID)'を使うことができますが、 'UserID'部分だけを使います。 'UserID'と' LastReviewed'の両方を使う方が良いでしょう。 [_複合インデックスの詳細_](http://mysql.rjweb.org/doc.php/index1) –

関連する問題