16

次のようなテーブルを想定します。

UID     Name        Datetime                Users
4       Room 4      2012-08-03 14:00:00     3
2       Room 2      2012-08-03 14:00:00     3
3       Room 3      2012-08-03 14:00:00     1
1       Room 1      2012-08-03 14:00:00     2

3       Room 3      2012-08-03 14:15:00     1
2       Room 2      2012-08-03 14:15:00     4
1       Room 1      2012-08-03 14:15:00     3

1       Room 1      2012-08-03 14:30:00     6

1       Room 1      2012-08-03 14:45:00     3
2       Room 2      2012-08-03 14:45:00     7
3       Room 3      2012-08-03 14:45:00     8
4       Room 4      2012-08-03 14:45:00     4

午後 2 時から午後 3 時までの各部屋 (1、2、3、4) の平均ユーザー数を取得したかったのです。問題は、ルームが 15 分のインターバル時間で「チェックイン」しない場合があることです。そのため、以前の最後の既知のユーザー数がまだ有効であると仮定する必要があります。

たとえば、部屋 4 のチェックインはチェックインされていないため2012-08-03 14:15:00、部屋 4 には 3 人のユーザーがいたと想定する必要があります。2012-08-03 14:15:002012-08-03 14:00:00

これは、私が探している平均ユーザー数が次のようになるように続きます。

ルーム 1: (2 + 3 + 6 + 3) / 4 = 3.5
ルーム 2: (3 + 4 + 4+ 7) / 4 = 4.5
ルーム 3: (1 + 1 + 1+ 8) / 4 = 2.75
ルーム 4: ( 3 + 3+ 3+ 4) / 4 = 3.25

ここ#で、以前の既知のチェックインに基づいた推定数です。

SQLだけでこれが可能かどうか疑問に思っていますか? そうでない場合は、私の簡単な不正確な擬似コードのように、単なる力ずくの数学ではない独創的な PHP ソリューションに興味があります。

foreach ($rooms_id_array as $room_id) {
    $SQL = "SELECT * FROM `table` WHERE (`UID` == $room_id && `Datetime` >= 2012-08-03 14:00:00 && `Datetime` <= 2012-08-03 15:00:00)";
    $result = query($SQL);
    if ( count($result) < 4 ) {
        // go through each date and find what is missing, and then go to previous date and use that instead
    } else {
        foreach ($result)
            $sum += $result;
        $avg = $sum / 4;
    }

}
4

4 に答える 4

6

あなたの難しさ(最も費用のかかるステップ)は、空白を埋めることです。ソース データの「空白を埋める」ことができない場合は、結合するテンプレートが必要になる可能性があります。次に、corrated-sub-queries を使用して、そのテンプレートに関連付けられたデータを検索します。

多くの場合、これは実際のテーブルで最適ですが、代わりにハードコーディングされたインラインビューを使用した例を次に示します...

SELECT
  `room`.`uid`           `uid` ,
  AVG(`data`.`users`)    `average_users`
FROM
  (SELECT 1 `UID`  UNION ALL
   SELECT 2 `UID`  UNION ALL
   SELECT 3 `UID`  UNION ALL
   SELECT 4 `UID`)                                     `room`
CROSS JOIN
  (SELECT '2012-08-03 14:00:00' `datetime`  UNION ALL
   SELECT '2012-08-03 14:15:00' `datetime`  UNION ALL
   SELECT '2012-08-03 14:30:00' `datetime`  UNION ALL
   SELECT '2012-08-03 14:45:00' `datetime`)            `checkin`
LEFT JOIN
  data
    ON  `data`.`uid`      = `room`.`uid`
    AND `data`.`datetime` = (SELECT MAX(`datetime`)
                               FROM `data`
                              WHERE `uid`       = `room`.`uid`
                                AND `datetime` <= `checkin`.`datetime`)
GROUP BY
  `room`.`uid`

-CROSS JOINは、すべての部屋のすべてのチェックイン スロットの記録を常に保持するようにテンプレートを作成します。

-correlated sub-query時間をさかのぼって検索し、その時点でその部屋の最新のチェックインを見つけます。

于 2012-08-04T13:37:13.233 に答える
5

このソリューションを使用できます:

SELECT   b.Name, 
         AVG(b.Users) avg_users
FROM     (
         SELECT     a.UID, 
                    MAX(c.Datetime) last_date
         FROM       (SELECT DISTINCT UID FROM tbl) a
         CROSS JOIN (
                    SELECT '14:00:00' intrvl UNION ALL
                    SELECT '14:15:00'        UNION ALL
                    SELECT '14:30:00'        UNION ALL
                    SELECT '14:45:00'
                    ) b
         JOIN       tbl c ON a.UID           = c.UID
                         AND TIME(b.intrvl) >= TIME(c.Datetime)
         GROUP BY   a.UID,
                    b.intrvl
         ) a
JOIN     tbl b ON a.UID       = b.UID
              AND a.last_date = b.Datetime
GROUP BY b.UID,
         b.Name

クエリの内訳:


ステップ1:

最初に行う必要があるのは、各部屋を各時間間隔に関連付けることです。たとえば、サンプル データでは、 は間隔および とRoom 4関連付けられていませんが、何らかの方法でそれらの関連付けを表す必要があります。14:15:0014:30:00

関連する時間間隔で各部屋のデカルト積を作成することにより、これを実現します。

SELECT     a.UID, 
           b.intrvl
FROM       (SELECT DISTINCT UID FROM tbl) a
CROSS JOIN (
           SELECT '14:00:00' intrvl UNION ALL
           SELECT '14:15:00'        UNION ALL
           SELECT '14:30:00'        UNION ALL
           SELECT '14:45:00'
           ) b
ORDER BY   b.intrvl, a.UID DESC --Ordering for display purposes

レンダリング:

UID | intrvl
--------------
4   | 14:00:00
3   | 14:00:00
2   | 14:00:00
1   | 14:00:00
4   | 14:15:00
3   | 14:15:00
2   | 14:15:00
1   | 14:15:00
4   | 14:30:00
3   | 14:30:00
2   | 14:30:00
1   | 14:30:00
4   | 14:45:00
3   | 14:45:00
2   | 14:45:00
1   | 14:45:00

SQLFiddle デモ


ステップ2:

次に、これらの関連付けを取得したらtbl、メイン テーブルのフィールドの時間部分がDatetimeそれぞれのデカルト結合時間よりも小さいという条件で、結果をメイン テーブル ( ) に結合しますUIDUIDこれが行うことは、 ->関連付けごとでありintrvl、その時点またはそれ以前に発生したすべてのエントリが表示されますintrvl

したがって、たとえば、には intrvlRoom 3のエントリがないため、その14:30:00intrvl に参加するエントリは 2 つだけ14:15:00です14:00:00

これで、私たちがどこに向かっているのかがわかります。このステップの結果、各 intrvl の最新のエントリにアクセスできるようになります。

SELECT     a.UID, 
           b.intrvl,
           c.*
FROM       (SELECT DISTINCT UID FROM tbl) a
CROSS JOIN (
           SELECT '14:00:00' intrvl UNION ALL
           SELECT '14:15:00'        UNION ALL
           SELECT '14:30:00'        UNION ALL
           SELECT '14:45:00'
           ) b
JOIN       tbl c ON a.UID           = c.UID
                AND TIME(b.intrvl) >= TIME(c.Datetime)
ORDER BY   b.intrvl, a.UID DESC, c.Datetime --Ordering for display purposes

Nameレンダリング (列を除く):

UID |  intrvl    |  Datetime             |  Users
---------------- --------------------------------
4   |  14:00:00  |  2012-08-03 14:00:00  |  3   <-- Most recent entry up until 14:00:00
3   |  14:00:00  |  2012-08-03 14:00:00  |  1   <-- Most recent entry up until 14:00:00
2   |  14:00:00  |  2012-08-03 14:00:00  |  3   <-- Most recent entry up until 14:00:00
1   |  14:00:00  |  2012-08-03 14:00:00  |  2   <-- Most recent entry up until 14:00:00
4   |  14:15:00  |  2012-08-03 14:00:00  |  3   <-- Most recent entry up until 14:15:00
3   |  14:15:00  |  2012-08-03 14:00:00  |  1
3   |  14:15:00  |  2012-08-03 14:15:00  |  1   <-- Most recent entry up until 14:15:00
2   |  14:15:00  |  2012-08-03 14:00:00  |  3
2   |  14:15:00  |  2012-08-03 14:15:00  |  4   <-- Most recent entry up until 14:15:00
1   |  14:15:00  |  2012-08-03 14:00:00  |  2
1   |  14:15:00  |  2012-08-03 14:15:00  |  3   <-- Most recent entry up until 14:15:00
4   |  14:30:00  |  2012-08-03 14:00:00  |  3   <-- Most recent entry up until 14:30:00
3   |  14:30:00  |  2012-08-03 14:00:00  |  1   
3   |  14:30:00  |  2012-08-03 14:15:00  |  1   <-- Most recent entry up until 14:30:00
2   |  14:30:00  |  2012-08-03 14:00:00  |  3
2   |  14:30:00  |  2012-08-03 14:15:00  |  4   <-- Most recent entry up until 14:30:00
1   |  14:30:00  |  2012-08-03 14:00:00  |  2
1   |  14:30:00  |  2012-08-03 14:15:00  |  3
1   |  14:30:00  |  2012-08-03 14:30:00  |  6   <-- Most recent entry up until 14:30:00
4   |  14:45:00  |  2012-08-03 14:00:00  |  3
4   |  14:45:00  |  2012-08-03 14:45:00  |  4   <-- Most recent entry up until 14:45:00
3   |  14:45:00  |  2012-08-03 14:00:00  |  1
3   |  14:45:00  |  2012-08-03 14:15:00  |  1
3   |  14:45:00  |  2012-08-03 14:45:00  |  8   <-- Most recent entry up until 14:45:00
2   |  14:45:00  |  2012-08-03 14:00:00  |  3
2   |  14:45:00  |  2012-08-03 14:15:00  |  4
2   |  14:45:00  |  2012-08-03 14:45:00  |  7   <-- Most recent entry up until 14:45:00
1   |  14:45:00  |  2012-08-03 14:00:00  |  2
1   |  14:45:00  |  2012-08-03 14:15:00  |  3
1   |  14:45:00  |  2012-08-03 14:30:00  |  6
1   |  14:45:00  |  2012-08-03 14:45:00  |  3   <-- Most recent entry up until 14:45:00

SQLFiddle デモ


ステップ 3:

Datetime次のステップは、上記の結果セットを取得し、 intrvl ごとに最新の結合のみを取得することです。これは、集計関数GROUP BYと組み合わせて使用​​することで実現できます。MAX()

残念ながら、 の動作が原因Usersで、選択した のそれぞれと一緒にの値を正しく取得することもできません。DatetimeGROUP BY

SELECT     a.UID, 
           b.intrvl,
           MAX(c.Datetime) last_date
FROM       (SELECT DISTINCT UID FROM tbl) a
CROSS JOIN (
           SELECT '14:00:00' intrvl UNION ALL
           SELECT '14:15:00'        UNION ALL
           SELECT '14:30:00'        UNION ALL
           SELECT '14:45:00'
           ) b
JOIN       tbl c ON a.UID           = c.UID
                AND TIME(b.intrvl) >= TIME(c.Datetime)
GROUP BY   a.UID,
           b.intrvl
ORDER BY   b.intrvl, a.UID DESC --Again, for display purposes

レンダリング:

UID |  intrvl    |  last_date
---------------------------------------
4   |  14:00:00  |  2012-08-03 14:00:00
3   |  14:00:00  |  2012-08-03 14:00:00
2   |  14:00:00  |  2012-08-03 14:00:00
1   |  14:00:00  |  2012-08-03 14:00:00
4   |  14:15:00  |  2012-08-03 14:00:00
3   |  14:15:00  |  2012-08-03 14:15:00
2   |  14:15:00  |  2012-08-03 14:15:00
1   |  14:15:00  |  2012-08-03 14:15:00
4   |  14:30:00  |  2012-08-03 14:00:00
3   |  14:30:00  |  2012-08-03 14:15:00
2   |  14:30:00  |  2012-08-03 14:15:00
1   |  14:30:00  |  2012-08-03 14:30:00
4   |  14:45:00  |  2012-08-03 14:45:00
3   |  14:45:00  |  2012-08-03 14:45:00
2   |  14:45:00  |  2012-08-03 14:45:00
1   |  14:45:00  |  2012-08-03 14:45:00

SQLFiddle デモ


ステップ 4

ここで、Usersfor eachの値を取得してlast_date、これらの値の平均を取得する必要があります。これを行うには、最後のステップでクエリを句内の副選択としてラップし、一致する->関連ごとに の値を取得するFROMという条件で、もう一度メイン テーブルに結合します。UIDlast_dateUsers

SELECT   a.UID,
         a.last_date,
         b.Users
FROM     (
         SELECT     a.UID, 
                    MAX(c.Datetime) last_date
         FROM       (SELECT DISTINCT UID FROM tbl) a
         CROSS JOIN (
                    SELECT '14:00:00' intrvl UNION ALL
                    SELECT '14:15:00'        UNION ALL
                    SELECT '14:30:00'        UNION ALL
                    SELECT '14:45:00'
                    ) b
         JOIN       tbl c ON a.UID           = c.UID
                         AND TIME(b.intrvl) >= TIME(c.Datetime)
         GROUP BY   a.UID,
                    b.intrvl
         ) a
JOIN     tbl b ON a.UID       = b.UID
              AND a.last_date = b.Datetime
ORDER BY a.UID DESC --Display purposes again

レンダリング:

UID | last_date           | Users
---------------------------------
4   | 2012-08-03 14:00:00 | 3
4   | 2012-08-03 14:00:00 | 3
4   | 2012-08-03 14:00:00 | 3
4   | 2012-08-03 14:45:00 | 4
3   | 2012-08-03 14:00:00 | 1
3   | 2012-08-03 14:15:00 | 1
3   | 2012-08-03 14:15:00 | 1
3   | 2012-08-03 14:45:00 | 8
2   | 2012-08-03 14:00:00 | 3
2   | 2012-08-03 14:15:00 | 4
2   | 2012-08-03 14:15:00 | 4
2   | 2012-08-03 14:45:00 | 7
1   | 2012-08-03 14:00:00 | 2
1   | 2012-08-03 14:15:00 | 3
1   | 2012-08-03 14:30:00 | 6
1   | 2012-08-03 14:45:00 | 3

SQLFiddle デモ


ステップ 5

Usersこれで、各部屋でグループ化し、列を平均化するだけの簡単な問題になります。

SELECT   b.Name, 
         AVG(b.Users) avg_users
FROM     (
         SELECT     a.UID, 
                    MAX(c.Datetime) last_date
         FROM       (SELECT DISTINCT UID FROM tbl) a
         CROSS JOIN (
                    SELECT '14:00:00' intrvl UNION ALL
                    SELECT '14:15:00'        UNION ALL
                    SELECT '14:30:00'        UNION ALL
                    SELECT '14:45:00'
                    ) b
         JOIN       tbl c ON a.UID           = c.UID
                         AND TIME(b.intrvl) >= TIME(c.Datetime)
         GROUP BY   a.UID,
                    b.intrvl
         ) a
JOIN     tbl b ON a.UID       = b.UID
              AND a.last_date = b.Datetime
GROUP BY b.UID,
         b.Name

レンダリング:

Name   | avg_users
------------------
Room 1 | 3.5
Room 2 | 4.5
Room 3 | 2.75
Room 4 | 3.25

最終結果の SQLFiddle デモ

于 2012-08-25T21:14:19.730 に答える
2

MySQL変数を少し試してみて、次のアイデアを思いつきました。

時間の経過に伴うユーザーの(個別の)積分を計算し、合計時間で割るだけです。

SET @avgSum := @lastValue := @lastTime := @firstTime := 0;
SELECT
  *,
  @firstTime := IF(@firstTime = 0, UNIX_TIMESTAMP(`DateTime`), @firstTime),
  @avgSum := @avgSum + (UNIX_TIMESTAMP(`DateTime`) - @lastTime) * @lastValue,
  @lastValue,
  @lastTime,
  @lastValue := `Users`,
  @lastTime := UNIX_TIMESTAMP(`DateTime`),
  @avgSum / (UNIX_TIMESTAMP(`DateTime`) - @firstTime) AS `average`
FROM
  `table`
WHERE
  `UID` = 1 AND
  UNIX_TIMESTAMP(`DateTime`) >= … AND
  UNIX_TIMESTAMP(`DateTime`) < …
ORDER BY
  UNIX_TIMESTAMP(`DateTime`) ASC;

@firstTime最初のユーザーレコードのタイムスタンプであり、@avgSum時間の経過に伴うユーザーの合計(積分)です。@lastValueおよび@lastTimeは、前のレコードの値と時刻です。この列averageは、ユーザーの合計を間隔全体で割ったものです(NULL最初のレコードのゼロによる除算によるものは気にしないでください)。

2つの制限がまだ存在します。指定された間隔の最初と最後のレコードが存在する必要があります。なしの場合、平均は最後に利用可能なレコードで「終了」します。

于 2012-08-27T21:36:47.993 に答える
1

チェックインの間隔が均等でなくても、これはすべての時間枠に対応するのにかなり良い仕事だと思います。また、あなたの例には誤りがあると思います。加重平均では、部屋2の最後の値は「7」ではなく「4」です。

セットアップ:

if object_id(N'avgTbl', N'U') is not null
drop table avgTbl;

create table avgTbl (
    UserId int not null,
    RoomName nvarchar(10) not null,
    CheckInTime datetime not null,
    UserCount int not null,

    constraint pk_avgTbl primary key (UserId, RoomName, CheckInTime)
);

insert into avgTbl (UserId, RoomName, CheckInTime, UserCount) values
(4, 'Room 4', '2012-08-03 14:00:00', 3),
(2, 'Room 2', '2012-08-03 14:00:00', 3),
(3, 'Room 3', '2012-08-03 14:00:00', 1),
(1, 'Room 1', '2012-08-03 14:00:00', 2),

(3, 'Room 3', '2012-08-03 14:15:00', 1),
(2, 'Room 2', '2012-08-03 14:15:00', 4),
(1, 'Room 1', '2012-08-03 14:15:00', 3),

(1, 'Room 1', '2012-08-03 14:30:00', 6),

(1, 'Room 1', '2012-08-03 14:45:00', 3),
(2, 'Room 2', '2012-08-03 14:45:00', 7),
(3, 'Room 3', '2012-08-03 14:45:00', 8),
(4, 'Room 4', '2012-08-03 14:45:00', 4);

クエリ:

/* 
* You just need to enter the start and end times below.  
* They can be any intervals, as long as the start time is 
* before the end time.
*/
declare 
    @startTime datetime = '2012-08-03 14:00:00',
    @endTime datetime = '2012-08-03 15:00:00';

declare     
    @totalTime numeric(18,1) = datediff(MINUTE, @startTime, @endTime);

    /*
    * This orders the observations, and assigns a sequential number so we can 
    *join on it later.
    */
with diffs as (
    select 
        row_number() over (order by RoomName, CheckInTime) as RowNum,
        CheckInTime,
        UserCount,
        RoomName
    from avgTbl
),
/*
* Get the time periods, 
* calc the number of minutes, 
* divide by the total minutes in the period, 
* multiply by the UserCount to get the weighted value, 
* sum the weighted values to get the weighted avg.
*/
mins as (
    select 
        cur.RoomName,
        /*
        * If we do not have an observation for a given room, use "0" instead
        * of "null", so it does not affect calculations later.
        */
        case 
            when prv.UserCount is null then 0
            else prv.UserCount
            end as UserCount, 
        /* The current observation time. */            
        cur.CheckInTime as CurrentT,
        /* The prior observation time. */
        prv.CheckInTime as PrevT,
        /*
        * The difference in minutes between the current, and previous qbservation
        * times.  If it is the first observation, then use the @startTime as the
        * previous observation time.  If the current time is null, then use the
        * end time.
        */
        datediff(MINUTE, 
            case 
                when prv.CheckInTime is null then @startTime 
                else prv.CheckInTime 
                end, 
            case 
                when cur.CheckInTime is null then @endTime 
                else cur.CheckInTime 
                end) as Mins 
    from diffs as cur
        /*
        * Join the observations based on the row numbers.  This gets the current,
        * and previous observations together in the same record, so we can 
        * perform our calculations.
        */
        left outer join diffs as prv on cur.RowNum = prv.RowNum + 1
            and cur.RoomName = prv.RoomName
    union
    /*
    * Add the end date as a period end, assume that the user count is the same 
    * as the last observation.
    */
    select 
        d.RoomName, 
        d.UserCount, 
        @endTime,
        d.CheckInTime, -- The last recorded observation time.
        datediff(MINUTE, d.CheckInTime, @endTime) as Mins
    from diffs as d 
    where d.RowNum in (
        select MAX(d2.RowNum)
        from diffs as d2
        where d2.RoomName = d.RoomName
        )
    group by d.RoomName, d.CheckInTime, d.UserCount
)
/* Now we just need to get our weighted average calculations. */
select 
    m.RoomName, 
    count(1) - 1 as NumOfObservations,
    /*
    * m.Min = minutes during which "UserCount" is the active number.
    * @totalTime = total minutes between start and end.
    * m.Min / @totalTime = the % of the total time.
    * (m.Min / @totalTime) * UserCount = The weighted value.
    * sum(..above..) = The total weighted average across the observations.
    */
    sum((m.Mins/@totalTime) * m.UserCount) as WgtAvg
from mins as m
group by m.RoomName
order by m.RoomName;
于 2012-08-25T03:13:18.650 に答える