17

3列のテーブルがあるとします。

  • id(PK、int)
  • タイムスタンプ(日時)
  • タイトル(テキスト)

私は次の記録を持っています:

1, 2010-01-01 15:00:00, Some Title
2, 2010-01-01 15:00:02, Some Title
3, 2010-01-02 15:00:00, Some Title

互いに3秒以内のGROUPBYレコードを実行する必要があります。このテーブルでは、行1と2がグループ化されます。

ここにも同様の質問があります: MysqlDateTimeグループ15分

私もこれを見つけました: http ://www.artfulsoftware.com/infotree/queries.php#106

これらのメソッドを数秒間機能するものに変換する方法がわかりません。SO質問のメソッドの問題は、既知のポイントで開始する時間のビン内にあるレコードに対してのみ機能するように思われることです。たとえばFLOOR()、5秒間隔で秒単位で作業する場合、15:00:04の時間は15:00:01とグループ化されますが、15:00:06とはグループ化されません。

これは意味がありますか?さらに詳しい説明が必要な場合はお知らせください。

編集:数字のセット{1、2、3、4、5、6、7、50、51、60}については、それらをグループ化するのが最善のようです{1、2、3、4、5 6、7}、{50、51}、{60}であるため、各グループ化行は、その行が前の3秒以内にあるかどうかによって異なります。私はこれが物事を少し変えることを知っています、これについて意地悪になってすみません。

異なるサーバーからのログをあいまい一致させようとしています。サーバー#1はアイテム「アイテム#1」をログに記録し、サーバー#2はサーバー#1から数秒以内に同じアイテム「アイテム#1」をログに記録します。両方のログ行でいくつかの集計関数を実行する必要があります。残念ながら、サーバーソフトウェアの性質上、私には続行するタイトルしかありません。

4

5 に答える 5

18

私はTomH.の優れたアイデアを使用していますが、ここでは少し異なる方法で実行しています。

チェーンの始まりであるすべての行を見つける代わりに、チェーンの始まりであるすべての時間を見つけてから、戻って時間に一致する行を見つけることができます。

ここでのクエリ#1は、3秒以内にどの時間がチェーンの始まりであるかを見つけることによって、どの時間がチェーンの始まりであるかを教えてくれるはずです。

SELECT DISTINCT Timestamp
FROM Table a
LEFT JOIN Table b
ON (b.Timestamp >= a.TimeStamp - INTERVAL 3 SECONDS
    AND b.Timestamp < a.Timestamp)
WHERE b.Timestamp IS NULL

次に、各行について、クエリ#2のタイムスタンプよりも小さい最大のチェーン開始タイムスタンプを見つけることができます。

SELECT Table.id, MAX(StartOfChains.TimeStamp) AS ChainStartTime
FROM Table
JOIN ([query #1]) StartofChains
ON Table.Timestamp >= StartOfChains.TimeStamp
GROUP BY Table.id

それができたら、必要に応じてグループ化できます。

SELECT COUNT(*) --or whatever
FROM Table
JOIN ([query #2]) GroupingQuery
ON Table.id = GroupingQuery.id
GROUP BY GroupingQuery.ChainStartTime

トムHさんの回答とは別に投稿するほど明確かどうかはわかりませんが、実装に問題があるようで、考えていたので、また投稿したいと思いました。幸運を!

于 2011-07-02T09:42:33.913 に答える
6

OMG Poniesへのコメントの回答に基づいて、あなたの問題を理解したと思うので、私はセットベースの解決策を持っていると思います。アイデアは、最初にタイトルに基づいてチェーンの開始点を見つけることです。チェーンの開始は、その行の前の3秒以内に一致がない行として定義されます。

SELECT
    MT1.my_id,
    MT1.title,
    MT1.my_time
FROM
    My_Table MT1
LEFT OUTER JOIN My_Table MT2 ON
    MT2.title = MT1.title AND
    (
        MT2.my_time < MT1.my_time OR
        (MT2.my_time = MT1.my_time AND MT2.my_id < MT1.my_id)
    ) AND
    MT2.my_time >= MT1.my_time - INTERVAL 3 SECONDS
WHERE
    MT2.my_id IS NULL

これで、チェーン以外のスターターは、その前に表示されたチェーンスターターに属していると想定できます。MySQLはCTEをサポートしていないため、上記の結果を一時テーブルにスローすることをお勧めします。これにより、以下の同じサブクエリへの複数の結合を節約できます。

SELECT
    SQ1.my_id,
    COUNT(*)  -- You didn't say what you were trying to calculate, just that you needed to group them
FROM
(
    SELECT
        MT1.my_id,
        MT1.title,
        MT1.my_time
    FROM
        My_Table MT1
    LEFT OUTER JOIN My_Table MT2 ON
        MT2.title = MT1.title AND
        (
            MT2.my_time < MT1.my_time OR
            (MT2.my_time = MT1.my_time AND MT2.my_id < MT1.my_id)
        ) AND
        MT2.my_time >= MT1.my_time - INTERVAL 3 SECONDS
    WHERE
        MT2.my_id IS NULL
) SQ1
INNER JOIN My_Table MT3 ON
    MT3.title = SQ1.title AND
    MT3.my_time >= SQ1.my_time
LEFT OUTER JOIN
(
    SELECT
        MT1.my_id,
        MT1.title,
        MT1.my_time
    FROM
        My_Table MT1
    LEFT OUTER JOIN My_Table MT2 ON
        MT2.title = MT1.title AND
        (
            MT2.my_time < MT1.my_time OR
            (MT2.my_time = MT1.my_time AND MT2.my_id < MT1.my_id)
        ) AND
        MT2.my_time >= MT1.my_time - INTERVAL 3 SECONDS
    WHERE
        MT2.my_id IS NULL
) SQ2 ON
    SQ2.title = SQ1.title AND
    SQ2.my_time > SQ1.my_time AND
    SQ2.my_time <= MT3.my_time
WHERE
    SQ2.my_id IS NULL

CTEを使用できる場合、または一時テーブルを使用する場合、これははるかに簡単に見えます。一時テーブルを使用すると、パフォーマンスが向上する場合もあります。

また、タイムスタンプを完全に一致させることができる場合は、これに問題があります。その場合は、IDとタイムスタンプの組み合わせを使用して、タイムスタンプ値が一致する行を区別するために、クエリを少し調整する必要があります。

編集:タイムスタンプによる完全一致を処理するようにクエリを変更しました。

于 2011-07-01T19:59:36.940 に答える
2

警告:長い答え。これは機能するはずであり、MySQLで再帰CTEを実行できないため、何も実行されなくなるまでINSERTステートメントを何度も実行する必要がある途中の1つのステップを除いて、かなり適切です。

例として、あなたのデータの代わりにこのデータを使用します。

id    Timestamp
1     1:00:00
2     1:00:03
3     1:00:06
4     1:00:10

これが最初に書くクエリです:

SELECT a.id as aid, b.id as bid
FROM Table a
JOIN Table b 
ON (a.Timestamp is within 3 seconds of b.Timestamp)

それは戻ります:

aid     bid
1       1
1       2
2       1
2       2
2       3
3       2
3       3
4       4

重複を許可しないものを保持するための素敵なテーブルを作成しましょう。

CREATE TABLE
Adjacency
( aid INT(11)
, bid INT(11)
, PRIMARY KEY (aid, bid) --important for later
)

ここでの課題は、その関係の推移閉包のようなものを見つけることです。

そのために、次のレベルのリンクを見つけましょう。つまり、隣接テーブルに1 2とがあるので、次を追加する必要があります。2 31 3

INSERT IGNORE INTO Adjacency(aid,bid)
SELECT adj1.aid, adj2.bid
FROM Adjacency adj1
JOIN Adjacency adj2
ON (adj1.bid = adj2.aid)

これはエレガントではない部分です。テーブルに行が追加されなくなるまで、上記のINSERTステートメントを繰り返し実行する必要があります。それを行うためのきちんとした方法があるかどうかはわかりません。

これが終了すると、次のような一時的に閉じた関係になります。

aid     bid
1       1
1       2
1       3     --added
2       1
2       2
2       3
3       1     --added
3       2
3       3
4       4

そして今、オチのために:

SELECT aid, GROUP_CONCAT( bid ) AS Neighbors
FROM Adjacency
GROUP BY aid

戻り値:

aid     Neighbors
1       1,2,3
2       1,2,3
3       1,2,3
4       4

それで

SELECT DISTINCT Neighbors
FROM (
     SELECT aid, GROUP_CONCAT( bid ) AS Neighbors
     FROM Adjacency
     GROUP BY aid
     ) Groupings

戻り値

Neighbors
1,2,3
4

ふぅ!

于 2011-07-01T18:49:22.297 に答える
2

私は@ChrisCunninghamの答えが好きですが、ここに別の見方があります。

まず、あなたの問題の説明についての私の理解(私が間違っている場合は私を訂正してください):

イベントログをイベントの時間順に並べられたシーケンスとして表示し、グループに分割して、シーケンス内の2つの隣接する行間の3秒を超える間隔として境界を定義します。

私は主にSQLServerで作業しているので、SQLServer構文を使用しています。MySQLSQLに変換するのはそれほど難しいことではありません。

したがって、最初にイベントログテーブルを作成します。

--
-- our event log table
--
create table dbo.eventLog
(
  id       int          not null ,
  dtLogged datetime     not null ,
  title    varchar(200) not null ,

  primary key nonclustered ( id ) ,
  unique clustered ( dtLogged , id ) ,

)

問題ステートメントの上記の理解を考えると、次のクエリはあなたのグループの上限と下限を与えるはずです。group byこれは、物事を折りたたむための2つの単純なネストされたselectステートメントです。

  • 最も内側selectは、各グループの上限を定義します。その上限はグループを定義します。
  • アウターselectは、各グループの下限を定義します。

テーブルのすべての行は、そのように定義されたグループの1つに分類される必要があり、特定のグループは、単一の日付/時刻値で構成されている可能性があります。

[編集:上限は、間隔が3秒を超える日付/時刻の最小値です]

select dtFrom = min( t.dtFrom ) ,
       dtThru =      t.dtThru
from ( select dtFrom = t1.dtLogged ,
              dtThru = min( t2.dtLogged )
       from      dbo.EventLog t1
       left join dbo.EventLog t2 on t2.dtLogged >= t1.dtLogged
                                and datediff(second,t1.dtLogged,t2.dtLogged) > 3
       group by t1.dtLogged
     ) t
group by t.dtThru

次に、イベントログから行をプルし、それらが属するグループでタグを付けることができます。

select *
from ( select dtFrom = min( t.dtFrom ) ,
              dtThru =      t.dtThru
       from ( select dtFrom = t1.dtLogged ,
                     dtThru = min( t2.dtLogged )
              from      dbo.EventLog t1
              left join dbo.EventLog t2 on t2.dtLogged >= t1.dtLogged
                                       and datediff(second,t1.dtLogged,t2.dtLogged) > 3
              group by t1.dtLogged
            ) t
       group by t.dtThru
     ) period
join dbo.EventLog t on t.dtLogged >=           period.dtFrom
                   and t.dtLogged <= coalesce( period.dtThru , t.dtLogged )
order by period.dtFrom , period.dtThru , t.dtLogged

各行は、返された列を介しdtFromてそのグループでタグ付けされます。dtThru必要に応じて、空想を得て、各グループに整数の行番号を割り当てることができます。

于 2011-07-01T19:27:30.637 に答える
2

簡単なクエリ:

SELECT * FROM time_history GROUP BY ROUND(UNIX_TIMESTAMP(time_stamp)/3);
于 2013-03-12T15:31:33.230 に答える