-1

同じ主題内で、考えられるすべての順次(連続および順方向のみ)の合計の組み合わせをリストアップしようとしています。row_idと合計に含まれる行数をリストします。

Sample :

Input (Source Table :) 

DLID    Subject Total   
1   Science 70  
2   Science 70  
3   Science 70  
4   Science 70  
5   Maths   80  
6   Maths   80  
7   English 90  
8   English 90  
9   English 90  
10  Science 75  

Expected Result :           
ID  Number of Rows  Subject Total
1   1   Science 70
2   1   Science 70
3   1   Science 70
4   1   Science 70
5   1   Maths   80
6   1   Maths   80
7   1   English 90
8   1   English 90
9   1   English 90
10  1   Science 75
1   2   Science 140
2   2   Science 140
3   2   Science 140
5   2   Maths   160
7   2   English 180
8   2   English 180
1   3   Science 210
2   3   Science 210
7   3   English 270
1   4   Science 280

VBSriptコード:

'myarray-アクセスデータベースからテーブル全体を読み取ります'"i"は読み取られた行の総数です'"j"各行に1つずつアクセスする場合'"m"は同じ件名の後続の行の数です。チェックする'"n"は、各行から開始し、m-1行までチェックするカウンターです。同じサブ'"k"が結果を"resultarray"に格納するために使用されているかどうかをチェックします。

myarray(0,j) = holds the row_id
myarray(1,j) = holds the subject
myarray(2,j) = holds the score
myarray(3 4 5 6 are other details
i is the total number of rows - around 80,000
There can be conitnuous records from the same subject as many as 700 - 800
m = is the number of rows matching / number of rows leading to the sum



For m = 1 to 700
For j = 0 to i-m
matchcount = 1

For n = 1 to m-1 
if  myarray(1,j) = myarray (1,j+n) Then 
matchcount = matchcount + 1
Else
Exit For
End If
Next

If matchcount = m Then
resultarray(2,k) = 0
For o = 0 to m - 1
resultarray(2,k) = CDbl(resultarray(2,k)) + CDbl (myarray (2,j+o))
resultarray(1,k) = m
resultarray(0,k) = ( myarray (0,j) )
resultarray(3,k) = ( myarray (3,j) )
resultarray(4,k) = ( myarray (4,j) )
resultarray(5,k) = ( myarray (1,j) )
resultarray(7,k) = ( myarray (5,j) )
resultarray(8,k) = ( myarray (6,j) )
Next
resultarray(2,k) = round(resultarray(2,k),0)
k = k + 1
ReDim Preserve resultarray(8,k)
End If

Next
Next

コードは完璧に機能していますが、非常に遅いです。同じ主題の80,000行と5から900の連続した行を扱っています。したがって、組み合わせの数は数百万になります。80,000行の1セットに数時間かかります。毎日たくさんのセットをしなければなりません。

これをスピードアップする方法を提案してください。より良いアルゴリズム/コードの改善/コードする言語の違い支援してください。

4

2 に答える 2

3

あなたの質問は、「これをスピードアップする方法を提案してください。より良いアルゴリズム/コードの改善/コードする言語の違いを助けてください。」

簡単に言えば、あなたの質問の一部にすばやく答えることができます。「コーディングする言語が異なる」==SQL。

詳細に:

達成しようとしているものが何であれ、データセットを多用するように見えます。この処理は、データを格納するDBMS内でより効率的に処理されるとほぼ確信しています。これは、DBMSが(適度に適切に記述された)SQLクエリを取得し、問い合わせているデータに関する独自の知識に基づいて最適化できるためです。データの大規模なセット/サブセットに対して非常に迅速かつ効率的に集計を実行します。

大きなデータセットを行ごとに繰り返して値を累積することは、許容できるパフォーマンスをもたらすことはめったにありません(あえて言うまでもありません)。これが、DBMSがネイティブにこれを行わない理由です(反復コード、またはVBコードなど、各行を調査する必要のあるコードを使用して強制しない場合)。


さて、より良いアルゴリズム/コードの改善/異なる言語の実装のために。

私はこれをSQLで実行しましたが、私のソリューションを使用するかどうかに関係なく、MS Accessの使用がユーザーを拘束していることがわかった場合は、データをMS SQL、Oracle、mySqlなどに移行することを強くお勧めします。反復的なアプローチ(これは、それがそれ行っていることを示唆するものではありません...これが当てはまるかどうかはわかりません)。

しかし、これが本当に宿題ではない場合、および/またはあなたが本当にMS Accessに縛られている場合、おそらくこれをMSAccessに変換するための努力の投資はパフォーマンスの点で実り多いかもしれません。原則はすべて同じである必要があります。これはリレーショナルデータベースであり、これはすべてかなり標準的なSQLであるため、ここで行ったことと同等のAccessがあると思います。

これに失敗すると、Accessプロバイダーを介したリンクサーバーとして、MSSQLインスタンスをMSAccessファイルに「ポイント」できるはずです。これについてアドバイスが必要な場合は、私に知らせてください。

セットベースの操作を使用してシーケンスの重労働の集計を実行できるようにする「ヘルパー」テーブルを設定するために、本質的に手続き型のコードがここにあります。

ソーステーブルを「Your_Source_Table」と呼びました。すべてのインスタンスで検索置換を実行して、名前を変更します。

また、私は何にもインデックスを設定していないことに注意してください...これを行う必要があります。結合に関係するすべての列に対してインデックスを作成する必要があると思います。実行プランをチェックして、不要なテーブルスキャンがないことを確認するのが賢明です。

Your_Source_Tableを作成するために以下を使用しました。

-- Create Your apparent table structure
IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Your_Source_Table]') AND type in (N'U'))
    DROP TABLE [dbo].[Your_Source_Table]
GO
CREATE TABLE [dbo].[Your_Source_Table](
    [DLID] [int] NOT NULL,
    [Subject] [nchar](10) NOT NULL,
    [Total] [int] NOT NULL
) ON [PRIMARY]
GO

そしてそれを次のように入力しました:

DLID        Subject    Total
----------- ---------- -----------
1           Science    70
2           Science    70
3           Science    70
4           Science    70
5           Maths      80
6           Maths      80
7           English    90
8           English    90
9           English    90
10          Science    75

次に、次の「ヘルパー」を作成しました。コードでの説明。

-- Set up helper structures.

-- Build a number table
if object_id('tempdb..##numbers') is not null
    BEGIN DROP TABLE ##numbers END
SELECT TOP 10000 IDENTITY(int,1,1) AS Number -- Can be 700, 800, or 900 contiguous rows, depending on which comment I read.  So I'll run with 100000 to be sure :-)
    INTO ##numbers
    FROM sys.objects s1
    CROSS JOIN sys.objects s2
ALTER TABLE ##numbers ADD CONSTRAINT PK_numbers PRIMARY KEY CLUSTERED (Number)


-- Determine where each block starts.
if object_id('tempdb..#tempGroups') is not null
    BEGIN DROP TABLE #tempGroups END
GO
CREATE TABLE #tempGroups (
    [ID] [int] NOT NULL IDENTITY,
    [StartID] [int] NULL,
    [Subject] [nchar](10) NULL
) ON [PRIMARY]

INSERT INTO #tempGroups
    SELECT t.DLID, t.Subject FROM Your_Source_Table t WHERE DLID=1
    UNION
    SELECT 
        t.DLID, t.Subject
    FROM
        Your_Source_Table t
        INNER JOIN Your_Source_Table t2 ON t.DLID = t2.DLID+1 AND t.subject != t2.subject

-- Determine where each block ends
if object_id('tempdb..##groups') is not null
    BEGIN DROP TABLE ##groups END
CREATE TABLE ##groups (
    [ID] [int] NOT NULL,
    [Subject] [nchar](10) NOT NULL,
    [StartID] [int] NOT NULL,
    [EndID] [int] NOT NULL
) ON [PRIMARY]
INSERT INTO ##groups
SELECT
    g1.id as ID,
    g1.subject,
    g1.startID as startID,
    CASE
        WHEN g2.id is not null THEN g2.startID-1
        ELSE (SELECT max(dlid) FROM Your_Source_Table) -- Boundary case when there is no following group (ie return the last row)
    END as endID
FROM
    #tempGroups g1
    LEFT JOIN #tempGroups g2 ON g1.id = g2.id-1

DROP TABLE #tempGroups;
GO

-- We now have a helper table called ##groups, that identifies the subject, start DLID and end DLID of each continuous block of a particular subject in your dataset.
-- So now, we can build up the possible sequences within each group, by joining to a number table.
if object_id('tempdb..##sequences') is not null
    BEGIN DROP TABLE ##sequences END
CREATE TABLE ##sequences (
    [seqID] [int] NOT NULL IDENTITY,
    [groupID] [int] NOT NULL,
    [start_of_sequence] [int] NOT NULL,
    [end_of_sequence] [int] NOT NULL
) ON [PRIMARY]
INSERT INTO ##sequences
SELECT
    g.id,
    ns.number start_of_sequence,
    ne.number end_of_sequence
FROM
    ##groups g

    INNER JOIN ##numbers ns
    ON ns.number <= (g.endid - g.startid + 1) -- number is in range for this block

    INNER JOIN ##numbers ne
    ON ne.number <= (g.endid - g.startid + 1) -- number is in range for this block
    and ne.number >= ns.number -- end after start
ORDER BY
    1,2,3

次に、1つのセットベースの操作で目的の結果を得ることができます。

-- By joining groups to your dataset we can add a group identity to each record.
-- By joining sequences we can generate copies of the rows for aggregation into each sequence.
select 
    min(t.dlid) as ID, -- equals (s.start_of_sequence + g.startid - 1) (sequence positions offset by group start position)
    count(t.dlid) as number_of_rows,    
    g.subject,
    sum(t.total) as total

--select *
from
    Your_Source_Table t

    inner join ##groups g
    on t.dlid >= g.startid and t.dlid <= g.endid -- grouping rows into each group.

    inner join ##sequences s
    on s.groupid = g.id -- get the sequences for this group.
    and t.dlid >= (s.start_of_sequence + g.startid - 1) -- include the rows required for this sequence (sequence positions offset by group start position)
    and t.dlid <= (s.end_of_sequence + g.startid - 1)

group by
    g.subject,
    s.seqid

order by 2, 1

ただし、注: この結果は、「期待される結果」とまったく同じではありません。行1から始まる1行シーケンスの重複インスタンスを誤って含めましたが(科学の場合、合計1 * 70 = 70)、行1から始まる4行シーケンスを含めませんでした(科学の場合、合計4 * 70 = 280)。

正しい結果、私見は次のとおりです。

ID          number_of_rows subject    total
----------- -------------- ---------- -----------
1           1              Science    70 <-- You've got this row twice.
2           1              Science    70
3           1              Science    70
4           1              Science    70
5           1              Maths      80
6           1              Maths      80
7           1              English    90
8           1              English    90
9           1              English    90
10          1              Science    75
1           2              Science    140
2           2              Science    140
3           2              Science    140
5           2              Maths      160
7           2              English    180
8           2              English    180
1           3              Science    210
2           3              Science    210
7           3              English    270
1           4              Science    280 <-- You don't have this row.

(20 row(s) affected)
于 2013-03-23T10:11:07.770 に答える
3

「実際の」アクセス(SQL)ソリューションの構成要素は次のとおりです。

観察#1

最初のステップとして、[SourceTable]に2つの数値(長整数)列を追加することをお勧めします。

[SubjectBlock]は、件名が同じである行の「ブロック」に番号を付けます

[SubjectBlockSeq]は、各ブロック内の行に順番に番号を付けます

両方ともインデックスを付ける必要があります(複製OK)。これらの列に入力するコードは次のようになります...

Public Sub UpdateBlocksAndSeqs()
Dim cdb As DAO.Database, rst As DAO.Recordset
Dim BlockNo As Long, SeqNo As Long, PrevSubject As String

Set cdb = CurrentDb
Set rst = cdb.OpenRecordset("SELECT * FROM [SourceTable] ORDER BY [DLID]", dbOpenDynaset)

PrevSubject = "(an impossible value)"
BlockNo = 0
SeqNo = 0
DBEngine.Workspaces(0).BeginTrans  ''speeds up bulk updates
Do While Not rst.EOF
    If rst!Subject <> PrevSubject Then
        BlockNo = BlockNo + 1
        SeqNo = 0
    End If
    SeqNo = SeqNo + 1
    rst.Edit
    rst!SubjectBlock = BlockNo
    rst!SubjectBlockSeq = SeqNo
    rst.Update
    PrevSubject = rst!Subject
    rst.MoveNext
Loop
DBEngine.Workspaces(0).CommitTrans
rst.Close
Set rst = Nothing
End Sub

...そして更新されたSourceTableは...

DLID    Subject   Total   SubjectBlock    SubjectBlockSeq
1       Science   70      1               1
2       Science   60      1               2
3       Science   75      1               3
4       Science   70      1               4
5       Maths     80      2               1
6       Maths     90      2               2
7       English   90      3               1
8       English   80      3               2
9       English   70      3               3
10      Science   75      4               1

(以下の結果を確認しやすくするために、テストデータを微調整したことに注意してください。)

増え続ける「合計に含まれるシーケンスの長さ」を繰り返すと、次のようなクエリを使用するだけで、関心のある「ブロック」をすばやく特定できます。

SELECT SubjectBlock FROM SourceTable WHERE SubjectBlockSeq=3

...これが返されます...

1
3

...「3回の実行」の合計を計算するときに、ブロック2(「数学」)と4(最後の「科学」の1つ)をまったく調べる必要がないことを示します。

観察#2

NumRows = 1の場合、最初の通過は特殊なケースです。[SourceTable]から[ExpectedResults]テーブルに行をコピーするだけです。単一のクエリでそれを行うことで時間を節約できます。

INSERT INTO ExpectedResult ( DLID, NumRows, Subject, Total, SubjectBlock, NextSubjectBlockSeq )
SELECT SourceTable.DLID, 1 AS Expr1, SourceTable.Subject, SourceTable.Total, 
    SourceTable.SubjectBlock, SourceTable.SubjectBlockSeq+1 AS Expr2
FROM SourceTable;

[ExpectedResult]テーブルに[SubjectBlock](以前と同じ)と[NextSubjetBlockSeq]([SubjectBlockSeq] +1)の2つの列を追加したことに気付くかもしれません。繰り返しになりますが、重複を許可するために、両方にインデックスを付ける必要があります。以下で使用します。

観察#3

合計するためのより長い「実行」を探し続けると、各実行は実際には、最後に追加の行が追加された、より早い(より短い)実行になります。結果を[ExpectedResults]テーブルに書き込むと、これらの値を再利用でき、実行全体の個々の値をわざわざ戻って合計する必要がなくなります。

NumRows = 2の場合、「アドオン」行はSubjectBlockSeq>=2...

SELECT SourceTable.*
FROM SourceTable
WHERE (((SourceTable.SubjectBlockSeq)>=2))
ORDER BY SourceTable.DLID;

...あれは...

DLID    Subject   Total SubjectBlock    SubjectBlockSeq
2       Science   60    1               2
3       Science   75    1               3
4       Science   70    1               4
6       Maths     90    2               2
8       English   80    3               2
9       English   70    3               3

...そして、追加の行を「タック」する「より早い(より短い)実行」の[ExpectedResult]行が次の行になります。

  • 同じ[SubjectBlock]から、

  • [NumRows] = 1で、

  • [ExpectedResult].[NextSubjectBlockSeq] = [SourceTable].[SubjectBlockSeq]

したがって、新しい合計を取得して、次のように[ExpectedResult]に追加できます。

INSERT INTO ExpectedResult ( DLID, NumRows, Subject, Total, SubjectBlock, NextSubjectBlockSeq )
SELECT SourceTable.DLID, 2 AS Expr1, SourceTable.Subject, 
    [ExpectedResult].[Total]+[SourceTable].[Total] AS NewTotal, 
    SourceTable.SubjectBlock, [SourceTable].[SubjectBlockSeq]+1 AS Expr2
FROM SourceTable INNER JOIN ExpectedResult 
    ON (SourceTable.SubjectBlockSeq = ExpectedResult.NextSubjectBlockSeq) 
        AND (SourceTable.SubjectBlock = ExpectedResult.SubjectBlock)
WHERE (((SourceTable.SubjectBlockSeq)>=2) AND (ExpectedResult.NumRows=1));

[ExpectedResult]に追加された行は

DLID    NumRows Subject   Total SubjectBlock    NextSubjectBlockSeq
2       2       Science   130   1               3
3       2       Science   135   1               4
4       2       Science   145   1               5
6       2       Maths     170   2               3
8       2       English   170   3               3
9       2       English   150   3               4

今、私たちは料理をしています...

以前と同じロジックを使用して、NumRows=3を処理できるようになりました。唯一の違いは、値3をNumRowsに挿入することであり、選択基準は次のようになります。

WHERE (((SourceTable.SubjectBlockSeq)>=3) AND (ExpectedResult.NumRows=2))

完全なクエリは

INSERT INTO ExpectedResult ( DLID, NumRows, Subject, Total, SubjectBlock, NextSubjectBlockSeq )
SELECT SourceTable.DLID, 3 AS Expr1, SourceTable.Subject, 
    [ExpectedResult].[Total]+[SourceTable].[Total] AS NewTotal, 
    SourceTable.SubjectBlock, [SourceTable].[SubjectBlockSeq]+1 AS Expr2
FROM SourceTable INNER JOIN ExpectedResult 
    ON (SourceTable.SubjectBlockSeq = ExpectedResult.NextSubjectBlockSeq) 
        AND (SourceTable.SubjectBlock = ExpectedResult.SubjectBlock)
WHERE (((SourceTable.SubjectBlockSeq)>=3) AND (ExpectedResult.NumRows=2));

[ExpectedResult]に追加された行は

DLID    NumRows Subject   Total SubjectBlock    NextSubjectBlockSeq
3       3       Science   205   1               4
4       3       Science   205   1               5
9       3       English   240   3               4

パラメータ化

連続する各クエリは非常に似ているので、一度だけ記述して繰り返し使用できれば、非常に便利です。幸い、これを「パラメータクエリ」に変換すると、次のことが可能になります。

PARAMETERS TargetNumRows Long;
INSERT INTO ExpectedResult ( DLID, NumRows, Subject, Total, SubjectBlock, NextSubjectBlockSeq )
SELECT SourceTable.DLID, [TargetNumRows] AS Expr1, SourceTable.Subject, 
    [ExpectedResult].[Total]+[SourceTable].[Total] AS NewTotal, 
    SourceTable.SubjectBlock, [SourceTable].[SubjectBlockSeq]+1 AS Expr2
FROM SourceTable INNER JOIN ExpectedResult 
    ON (SourceTable.SubjectBlock = ExpectedResult.SubjectBlock) 
        AND (SourceTable.SubjectBlockSeq = ExpectedResult.NextSubjectBlockSeq)
WHERE (((SourceTable.SubjectBlockSeq)>=[TargetNumRows]) 
    AND ((ExpectedResult.NumRows)=[TargetNumRows]-1));

新しいAccessクエリを作成し、上記をSQLペインに貼り付けて、として保存しますpq_appendToExpectedResult。(「pq_」は、パラメータークエリであることを示す単なる視覚的な手がかりです。)

VBAからのパラメータクエリの呼び出し

QueryDefオブジェクトを介してVBAでパラメータクエリを呼び出す(実行する)ことができます。

Dim cdb As DAO.Database, qdf As DAO.QueryDef
Set cdb = CurrentDb
Set qdf = cdb.QueryDefs("pq_appendToExpectedResult")
qdf!TargetNumRows = 4  '' parameter value
qdf.Execute
Set qdf = Nothing

いつ停止するか

NumRowsこれで、パラメータクエリをインクリメントして再実行するだけの問題であることがわかりますが、いつ停止するのでしょうか。簡単だ:

VBAでNumRows変数をインクリメントした後、テストします

DCount("DLID", "SourceTable", "SubjectBlockSeq=" & NumRows)

0に戻ったら、完了です。

(すべての)コードを見せて

申し訳ありませんが、すぐにではありません。;)これをいじって、どうなるか教えてください。

于 2013-03-23T11:59:40.963 に答える