ColdFusionクエリが遅すぎます

cfloop内にクエリがあるため、処理が非常に遅くなります。このクエリを速くする方法はありますか?


    SELECT * FROM CheckRegister, ExpenseType 
    Where PropertyID=10
    and ExpenseType.ExpenseTypeID=CheckRegister.ExpenseTypeID 



     SELECT * FROM Vendors WHERE VendorID=#VendorID#
    <!--- I use the vendor name here --->

    
  
    Select TenantTransactionDate as fromDate From TenantTransactions
    Where CheckRegisterID = #CheckRegisterID#
    Order By TenantTransactionDate Limit 1
  
  
    Select TenantTransactionDate as ToDate From TenantTransactions
    Where CheckRegisterID = #CheckRegisterID#
    Order By TenantTransactionDate desc Limit 1
  
  
    
      
    
      
    
  

  <!--- I use the local.CreditDate here  --->

  <!--- Here goes a table with the data --->

cfoutputはループのように機能します。

3
MySQLとColdFusionのバージョンは?
追加された 著者 Shawn,
あなたが SELECT している両方のデータベース( myDBrent )は同じサーバー上にありますか?サーバーにはいくつの行があり、 GetgCheckRegister には何行が返されますか。これらのクエリを組み合わせてデータを1回のパスで取得し、次にそれらの結果を繰り返して必要なものを出力する必要があります。また、日付マスクに気をつけてください。それらは言語と機能の間で変わることができて、 'yyyy'は 'YYYY'とは違う何かを意味することができます。
追加された 著者 Shawn,
すみません、myDBにすべてがあります
追加された 著者 Max Boy,

5 答え

他の人が言ったように、あなたはループを取り除き結合を使うべきです。内側のループを見ると、コードは各CheckRegisterIDの最も早い日付と最も遅い日付を取得します。 LIMITを使用する代わりに、集計関数を使用してください。 MINとMAX、およびGROUP BY CheckRegisterID。その結果を派生クエリでラップして、結果をCheckRegister ONに戻すことができます。 id。

元のクエリの一部の列はスコープ指定されていないため、いくつか推測しました。改善の余地はありますが、開始するには何かが十分です。

-- select only needed columns
SELECT cr.CheckRegisterID, ... other columns
FROM CheckRegister cr 
       INNER JOIN ExpenseType ex ON ex.ExpenseTypeID=cr.ExpenseTypeID 
       INNER JOIN Vendors v ON v.VendorID = cr.VendorID
       LEFT JOIN 
       (
            SELECT CheckRegisterID
              , MIN(TenantTransactionDate) AS MinDate
              , MAX(TenantTransactionDate) AS MaxDate
            FROM  TenantTransactions
            GROUP BY CheckRegisterID
       ) tt ON tt.CheckRegisterID = cr.CheckRegisterID

WHERE cr.PropertyID = 10

JOINはあらゆるWebアプリケーション、IMOにとって重要なので、JOINを読むことを強くおすすめします。

6
追加された
@ Shawn - あなたは正しいかもしれません。テーブルの列と関係が明確ではなかったので、私はそれを残しました。グループ化すると結果が変わることがあります。それがそれらのケースのうちの1つであるかどうかわからなかった。そうでなければ、そうです。そうです。副照会は実行できます。
追加された 著者 Ageax,
「JOINはあらゆるWebアプリケーションにとって重要なので、JOINについて読むことを強くお勧めします」<< YES 。また、 SELECT *FROM CheckRegister、ExpenseType が問題を引き起こす可能性がある理由もあります。
追加された 著者 Shawn,
そして、あなたはここに副問い合わせを必要としません。これらの結果を JOIN に結合してから、同じ min()max()の集合体を追加し、さらに GROUPを追加することができます。同じ結果が得られます。
追加された 著者 Shawn,

すべてのデータを1回のクエリで取得してから、そのデータを操作して必要なものを出力する必要があります。データベースへの複数の接続は、ほとんどの場合、1回の旅行でデータを取得して処理するよりもリソースを多く消費します。あなたの結果を得るために:

SQLフィドル

Initial Schema Setup:

CREATE TABLE CheckRegister ( checkRegisterID int, PropertyID int, VendorID int, ExpenseTypeID int ) ;
CREATE TABLE ExpenseType ( ExpenseTypeID int ) ;
CREATE TABLE Vendors ( VendorID int ) ;
CREATE TABLE TenantTransactions ( checkRegisterID int, TenantTransactionDate date, note varchar(20) );

INSERT INTO CheckRegister ( checkRegisterID, PropertyID, VendorID, ExpenseTypeID )
VALUES (1,10,1,1),(1,10,1,1),(1,10,2,1),(1,10,1,2),(1,5,1,1),(2,10,1,1),(2,5,1,1) 
;

INSERT INTO ExpenseType ( ExpenseTypeID ) VALUES (1), (2) ;
INSERT INTO Vendors ( VendorID ) VALUES (1), (2) ;

INSERT INTO TenantTransactions ( checkRegisterID, TenantTransactionDate, note )
VALUES 
    (1,'2018-01-01','start')
  , (1,'2018-01-02','another')
  , (1,'2018-01-03','another')
  , (1,'2018-01-04','stop')
  , (2,'2017-01-01','start')
  , (2,'2017-01-02','another')
  , (2,'2017-01-03','another')
  , (2,'2017-01-04','stop')
 ;

Main Query:

SELECT cr.*
  , max(tt.TenantTransactionDate) AS startDate
  , min(tt.TenantTransactionDate) AS endDate
FROM CheckRegister cr 
INNER JOIN ExpenseType et ON cr.ExpenseTypeID = et.ExpenseTypeID
INNER JOIN Vendors v ON cr.vendorID = v.VendorID 
LEFT OUTER JOIN TenantTransactions tt ON cr.checkRegisterID = tt.CheckRegisterID
WHERE cr.PropertyID = 10
GROUP BY cr.CheckRegisterID, cr.PropertyID, cr.VendorID, cr.ExpenseTypeID

Results:

| checkRegisterID | PropertyID | VendorID | ExpenseTypeID |  startDate |    endDate |
|-----------------|------------|----------|---------------|------------|------------|
|               1 |         10 |        1 |             1 | 2018-01-04 | 2018-01-01 |
|               1 |         10 |        1 |             2 | 2018-01-04 | 2018-01-01 |
|               1 |         10 |        2 |             1 | 2018-01-04 | 2018-01-01 |
|               2 |         10 |        1 |             1 | 2017-01-04 | 2017-01-01 |

2つのチェックレジスタを追加しただけですが、CheckRegisterID 1には2つのベンダーと2つのベンダー1の経費タイプがあります。データがそのように設定されていない場合は、最後のクエリで心配する必要はありません。

Use proper JOIN syntax to get the related data you need. Then you can aggregate that data to get the fromDate and toDate. If your data is more complex, you may need to look at Window Functions. https://dev.mysql.com/doc/refman/8.0/en/window-functions.html

最終的な出力がどのようになるかわかりませんが、上記のクエリではすべてのクエリデータが1回のパスで得られます。そして、そのデータの各行はあなたが出力する必要があるものをあなたに与えるべきです、それで今あなたはループするための1つのクエリしか持っていません。

5
追加された
MySQLを使用すると、必ずしもグループ内のすべての同じ列を含めずに集計できます。
追加された 著者 Ageax,

私がColdFusionを開発してから長い時間が経ちましたが、一般的な経験則は、ループ内でクエリを呼び出さないことです。何をしているかにもよりますが、ループはRBAR(行ごとに行を並べる)操作と見なすことができます。

あなたは基本的に1つのクエリを定義し、各レコードをループしています。各レコードについて、3つの追加のデータベースネットワーク呼び出し( 1レコードあたり)として3つの追加クエリを実行しています。私がそれを見る方法では、あなたはいくつかの選択肢があります:

  1. 最初のクエリを書き換えて、それぞれに必要なデータが既に含まれているようにします。 記録チェック。
  2. 最初のクエリをそのままにして、ユーザーがレコードを操作したときに詳細な情報を提供する機能を作成し、それを非同期に実行します。 「クレジットの日付を表示」リンクのようなもので、必要に応じてデータが表示されます。
  3. ループ内のクエリを結合して、2つの getTenantTransaction ... ではなく1つのクエリにして、パフォーマンスが向上するかどうかを確認します。これにより、RBARデータベースの呼び出しが3回から2回に減ります。
3
追加された
それが問題です。最初のクエリを書き換えて、必要なデータをすべて含めることができます。 TenantTransactionsに参加してTenantTransactions.TenantTransactionDateを小さくして大きくする必要があります。
追加された 著者 Max Boy,

あなたは常にループの中でクエリを持つことを避けたいと思います。データベースを照会するときはいつでも、(サーバーからデータベースへ、そしてデータベースからサーバーへ戻る)ラウンドトリップがあります。

一般的なアプローチは、できるだけ少ない文で必要なすべての情報を照会することによってデータを一括処理することです。単一のステートメントですべてを結合するのが理想的ですが、これは明らかにあなたのテーブルスキーマに依存します。 SQLだけでは解決できない場合は、次のようにクエリを変換できます。

GetCheckRegister...

(no loop)


    SELECT * FROM Vendors WHERE VendorID IN (#valueList(GetCheckRegister.VendorID)#)



    Select TenantTransactionDate as fromDate From TenantTransactions
    Where CheckRegisterID IN (#valueList(GetCheckRegister.CheckRegisterID)#)


etc.

valueList(query.column) returns a comma delimited list of the specified column values. This list is then used with MySQL's IN (list) selector to retrieve all records that belong to all the listed values.

ループ内の各文に対して1つのクエリしかありません( GetCheckRegister の4倍のレコード数ではなく、合計4つのクエリ)。しかし、すべてのレコードはまとまっているので、それに応じてそれらを一致させる必要があります。これを行うには、ColdFusionのクエリーオブクエリー(QoQ)。既に取得したデータに対してクエリーを実行できます。取得したデータはメモリ内にあるため、それらへのアクセスは迅速です。

GetCheckRegister, GetVendorName, getTenantTransactionDateFrom, getTenantTransactionDateTo etc.



    <!--- query of queries --->
    
        SELECT * FROM [GetVendorName] WHERE VendorID = #GetCheckRegister.VendorID#
    

    etc.


実際には、実際のクエリをループの外に移動し、代わりにQoQを使用してループ内の実際のクエリの結果をクエリします。

Regardless of this, make sure your real queries are fast by profiling them in MySQL. Use indices!

2
追加された
大量のデータが問題になる場合は、QoQアプローチを実行することをお勧めしますが、それでもすべてのデータを一度にまとめて取得してから valuelist()を処理する必要はないでしょう。 s。適切なインデントについてのコメントを+100します。
追加された 著者 Shawn,

次の場合は、メインクエリとループを使用してデータを処理するほうが速くなります。

  • すべてのフィールドを使用しているのでない限り、(SELECT *ではなく)非常に多くの列をフェッチすることを避けるため、必要な特定のフィールドのみを使用してSELECTを使用します。

SELECT VendorID、CheckRegisterId、... from CheckRegister、ExpenseType ...

  • ループ内の少ないサブクエリを使用して、メインのクエリにテーブルを結合しようとしています。たとえば、メインクエリでVendorsテーブルを使用する(このテーブルに結合できる可能性がある場合)

SELECT VendorID、CheckRegisterId、VendorName ...からCheckRegister、ExpenseType、Vendors ...

  • Finally, you can estimate the time of the process and detect the performance problem:
    • ROWS = Number of rows of the result in the main query
    • TIME_V = Time (ms) to get the result of GetVendorName using a valid VendorId
    • TIME_TD1 = Time (ms) to get the result of getTenantTransactionDateFrom using a valid CheckRegisterID
    • TIME_TD2 = Time (ms) to get the result of getTenantTransactionDateTo using a valid CheckRegisterID
  • Then, you can calculate the resulting time using TOTAL = ROWS * (TIME_V+ TIME_TD1 + TIME_TD2).
  • For example, if ROWS=10000 , TIME_V = 30, TIME_TD1 = 15, TIME_TD2 = 15 : RESULT = 10000 * (30 + 15 + 15) = 10000 * 60 = 600000 (ms) = 600 (sec) = 10 min

したがって、10000行の場合、1ミリ秒のループで10秒かかります。

メインクエリに対して結果行が多数ある場合は、ループ内の各要素のクエリ時間を最小化する必要があります。各ミリ秒は、ループのパフォーマンスに影響します。そのため、ループ内の各クエリに対してフィルタ処理された各フィールドに正しいインデックスがあることを確認する必要があります。

0
追加された
TenantTransactionsにはまだループが必要です。右?
追加された 著者 Max Boy,