MySQL の日付フィールド。重複しないすべての行を検索し、差分のみを返します。

StackOverflow https://stackoverflow.com/questions/1333340

質問

これは私の最初の質問の 1 つですが、少しバリエーションがあります。

つまり、スケジュールがデータベースに登録されている 2 人の人がいます。スケジュールには、開始時刻、終了時刻、および両方のユーザーのさまざまなイベント/予定の説明が記録されるだけです。

担当者 A は担当者 B との約束を交換したいと考えています。personB と personA が交換できる回数をすべて返す MySQL クエリが必要です。

元々、クエリのパラメーターは、personA と重複する personB の予定をすべて破棄することであり、personB の予定は、personA が交換したい予定とまったく同じ長さである必要がありました。必要な結果を得るのに役立つ、時間算術/幾何学に関する素晴らしいアドバイスをいくつかもらいました。

次に、予定の長さが同じである必要がないように、1 対 1 パラメータを変更したいと思います。したがって、PersonA が月曜日の朝の予定 (午前 10 時から午前 11 時 30 分) を交換したい場合、クエリは次のようになります。

  • personA のいずれかの予定の間にある personB の予定を除外します。
  • 人物 A の予定以外の人物 B の予定を含める
  • 人物 A が空いている間の人物 B の予定の部分も含めますが、空いている部分のみを表示します。

したがって、担当者 A が上記の予定 (ここでも月曜日の午前 10 時から午前 11 時 30 分) を交換したい場合、担当者 A は火曜日の午後 1 時から午後 3 時までの予定があり、担当者 B は火曜日の午後 12 時からの予定を持っています。午後 00 時から午後 4 時までの場合、クエリは次を返します。

Possible_Swaps
==============
userID  | Start             | End             | Description
PersonB | Tuesday, 12:00 PM | Tuesday 1:00 PM | Cooking
PersonB | Tuesday,  4:00 PM | Tuesday 5:00 PM | Cooking

その他の可能性もあります。これはデータベースに期待しすぎでしょうか?もしそうなら、PHP スクリプトがそれらを処理できるように、少なくとも重複しているもののどちらかの側に時間がかかっているシフトを取得する方法について何か提案はありますか?


searlea のリクエストに従って、もう少し詳しいコンテキストを次に示します。

私は約束と言い続けましたが、私が本当に意味したのは「勤務シフト」のような「仕事」だったと思います。人物 A と人物 B は同じオフィスで働いています。vcalendar では、勤務シフトは通常「イベント」と呼ばれますが、場合によっては「アポイントメント」と呼ばれることもあります。私は後者を使用しました。2 人がフェアに行くような感じではないためです。

したがって、パーソン A は月曜日の午前 10 時から 11 時 30 分まで食器洗いのシフトを担当します。人 B は火曜日の午後 12 時から午後 5 時まで料理をしています。パーソン A は、月曜日に町を出る前に弟に会いたがっています。彼は月曜の午前中ずっと休みを取りたいのですが、シフトを 1 時間取得することで満足します。

私の古いモデルでは(1990年に育った) 私の最初の質問 ここ)、重複がなく、時間的に均等なシフトを探していました。しかし、それには 2 つの問題があります。

  1. 火曜日の私の2時間のシフトを誰かがカバーしてくれる必要があり、私は木曜日に4時間働き、ジョーは木曜日に8時間働いている場合、私は彼の時間を2時間交換することができ、彼は少し早く帰ることができ、私は少し残ることができます後で。

  2. 2 時間のシフトがあるが、時間通りに空港に着くためだけに喜んで 1 時間交換したい場合、週の後半にこれこれが私より 1 時間早く到着するかどうかを知りたいと考えています。彼のシフトのその部分を引き受けてください。

早い話が(手遅れですが)、私は明らかに として知られているものが欲しいのです。 相対補数 人 A から人 B へのシフトの回数 (基本的に、シフトが他の時点で重複しているかどうかに関係なく、人 B が働いていて人 A が働いていないとき)。

理想的には、人 B が働いていて人 A が働いていない部分 (上記の 2 つの 1 時間シフト) と、シフト全体 (シフトとして利用できないことを示す特別なタグ付き) を含む一連の結果を取得します。これにより、担当者 A がシフトの一部をカバーしていることがわかり、混乱して担当者 B がたまたま 1 時間のシフトを 2 つ勤務しているだけだと考えることがなくなります。

これだけでは少し複雑に思えてきます。基本的に、人 B のシフトを青、人 A のシフトを黄色にし、データベースが緑以外の部分をすべて返すようにします。

役に立ちましたか?

解決

SELECT * 
  FROM schedule AS s1
WHERE
  s1.user = 'Ondra'
AND
NOT EXISTS ( 
  SELECT * FROM schedule AS s2 
  WHERE
    s2.user = 'Zizka'
    AND (
      s2.start BETWEEN s1.start AND s1.end 
      OR
      s2.end BETWEEN s1.start AND s1.end 
      OR 
      s1.start > s2.start AND s1.end < s2.end 
    )
)

このはジシュカの日記の隙間に収まることができるOndraのイベントを選択します。

編集:もともとは交差していますが、相対的な補完をしたい場合は、これで十分です。

他のヒント

$shift_idは、ユーザーが交換したいシフトのIDでみましょう。

select swappable.shift_id, swappable.user_id, swappable.description,
    FROM_UNIXTIME(swappable.shiftstart) as start,
    FROM_UNIXTIME(swappable.shiftend) as end,
    (swappable.shiftend - swappable.shiftstart) -
        sum(coalesce(least(conflict.shiftend, swappable.shiftend) -
            greatest(conflict.shiftstart, swappable.shiftstart), 0))
        as swaptime,
    group_concat(conflict.shift_id) as conflicts,
    group_concat(concat(FROM_UNIXTIME(conflict.shiftstart), ' - ',
        FROM_UNIXTIME(conflict.shiftend))) as conflict_times
from shifts as problem
join shifts as swappable on swappable.user_id != problem.user_id
left join shifts as conflict on conflict.user_id = problem.user_id
    and conflict.shiftstart < swappable.shiftend
    and conflict.shiftend > swappable.shiftstart
where problem.shift_id = 1
group by swappable.shift_id
having swaptime > 0;

でテスト

CREATE TABLE `shifts` (
  `shift_id` int(10) unsigned NOT NULL auto_increment,
  `user_id` varchar(20) NOT NULL,
  `shiftstart` int unsigned NOT NULL,
  `shiftend` int unsigned NOT NULL,
  `description` varchar(32) default NULL,
  PRIMARY KEY  (`shift_id`)
);

insert  into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (1,'april', UNIX_TIMESTAMP('2009-04-04 10:00:00'),UNIX_TIMESTAMP('2009-04-04 12:00:00'),'Needs to be swapped');
insert  into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (2,'bill',  UNIX_TIMESTAMP('2009-04-04 10:30:00'),UNIX_TIMESTAMP('2009-04-04 11:30:00'),'Inside today');
insert  into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (3,'casey', UNIX_TIMESTAMP('2009-04-04 12:00:00'),UNIX_TIMESTAMP('2009-04-04 14:00:00'),'Immediately after today');
insert  into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (4,'casey', UNIX_TIMESTAMP('2009-04-04 08:00:00'),UNIX_TIMESTAMP('2009-04-04 10:00:00'),'Immediately before today');
insert  into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (5,'david', UNIX_TIMESTAMP('2009-04-04 11:00:00'),UNIX_TIMESTAMP('2009-04-04 15:00:00'),'Partly after today');

insert  into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (6,'april', UNIX_TIMESTAMP('2009-04-05 10:00:00'),UNIX_TIMESTAMP('2009-04-05 12:00:00'),'Tommorow');
insert  into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (7,'bill',  UNIX_TIMESTAMP('2009-04-05 09:00:00'),UNIX_TIMESTAMP('2009-04-05 11:00:00'),'Partly before tomorrow');
insert  into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (8,'casey', UNIX_TIMESTAMP('2009-04-05 10:00:00'),UNIX_TIMESTAMP('2009-04-05 12:00:00'),'Equals tomorrow');
insert  into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (9,'david', UNIX_TIMESTAMP('2009-04-05 10:30:00'),UNIX_TIMESTAMP('2009-04-05 11:30:00'),'Inside tomorrow');

insert  into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (10,'april',UNIX_TIMESTAMP('2009-04-11 10:00:00'),UNIX_TIMESTAMP('2009-04-11 12:00:00'),'Next week');
insert  into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (11,'april',UNIX_TIMESTAMP('2009-04-11 12:00:00'),UNIX_TIMESTAMP('2009-04-11 14:00:00'),'Second shift');
insert  into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (12,'bill', UNIX_TIMESTAMP('2009-04-11 11:00:00'),UNIX_TIMESTAMP('2009-04-11 13:00:00'),'Overlaps two');
insert  into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (13,'casey',UNIX_TIMESTAMP('2009-04-11 17:00:00'),UNIX_TIMESTAMP('2009-04-11 19:00:00'),'No conflict');

insert  into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (14,'april',UNIX_TIMESTAMP('2009-05-04 10:00:00'),UNIX_TIMESTAMP('2009-05-04 12:00:00'),'Next month');
insert  into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (15,'april',UNIX_TIMESTAMP('2009-05-04 13:00:00'),UNIX_TIMESTAMP('2009-05-04 15:00:00'),'After break');
insert  into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (16,'bill', UNIX_TIMESTAMP('2009-05-04 11:00:00'),UNIX_TIMESTAMP('2009-05-04 14:00:00'),'Middle okay');

insert  into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (17,'april',UNIX_TIMESTAMP('2010-04-04 10:00:00'),UNIX_TIMESTAMP('2010-04-04 11:00:00'),'Next year');
insert  into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (18,'april',UNIX_TIMESTAMP('2010-04-04 11:30:00'),UNIX_TIMESTAMP('2010-04-04 12:00:00'),'After break');
insert  into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (19,'april',UNIX_TIMESTAMP('2010-04-04 12:30:00'),UNIX_TIMESTAMP('2010-04-04 13:30:00'),'Third part');
insert  into `shifts`(`shift_id`,`user_id`,`shiftstart`,`shiftend`,`description`) values (20,'bill', UNIX_TIMESTAMP('2010-04-04 10:30:00'),UNIX_TIMESTAMP('2010-04-04 13:00:00'),'Two parts okay');

結果:

'shift_id', 'user_id', 'description',              'start',               'end',                 'swaptime', 'conflicts', 'conflict_times'
 '3',       'casey',   'Immediately after today',  '2009-04-04 12:00:00', '2009-04-04 14:00:00', '7200',       NULL,       NULL
 '4',       'casey',   'Immediately before today', '2009-04-04 08:00:00', '2009-04-04 10:00:00', '7200',       NULL,       NULL
 '5',       'david',   'Partly after today',       '2009-04-04 11:00:00', '2009-04-04 15:00:00', '10800',     '1',        '2009-04-04 10:00:00 - 2009-04-04 12:00:00'
 '7',       'bill',    'Partly before tomorrow',   '2009-04-05 09:00:00', '2009-04-05 11:00:00', '3600',      '6',        '2009-04-05 10:00:00 - 2009-04-05 12:00:00'
'13',       'casey',   'No conflict',              '2009-04-11 17:00:00', '2009-04-11 19:00:00', '7200',       NULL,       NULL
'16',       'bill',    'Middle okay',              '2009-05-04 11:00:00', '2009-05-04 14:00:00', '3600',      '15,14',    '2009-05-04 13:00:00 - 2009-05-04 15:00:00,2009-05-04 10:00:00 - 2009-05-04 12:00:00'
'20',       'bill',    'Two parts okay',           '2010-04-04 10:30:00', '2010-04-04 13:00:00', '3600',      '19,18,17', '2010-04-04 12:30:00 - 2010-04-04 13:30:00,2010-04-04 11:30:00 - 2010-04-04 12:00:00,2010-04-04 10:00:00 - 2010-04-04 11:00:00'

これは方法など、任意の部分(単数または複数)をスワップすることができるため、すべてのシフトを示します (秒)くらいの合計時間は、スワップが可能です。最後の列、conflict_times、 スワッピングユーザーがすでに動作するようにスケジュールされた時刻を示しています。 アプリケーションは、そこから利用可能時間を抽出することは簡単なはずです。 それは、MySQLで、可能性が、非常にトリッキーです。

タスク

は、彼らが重複する部分を除いて2人の異なるユーザのすべての間隔を返します。

表とテストデータ

CREATE TABLE IF NOT EXISTS `shifts` (
  `id` int(11) NOT NULL auto_increment,
  `name` varchar(1) NOT NULL,
  `start` datetime NOT NULL,
  `end` datetime NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=12 ;

INSERT INTO `shifts` (`id`, `name`, `start`, `end`) VALUES
(1, 'a', '2000-01-01 01:00:00', '2000-01-01 03:00:00'),
(2, 'a', '2000-01-01 06:00:00', '2000-01-01 07:30:00'),
(3, 'b', '2000-01-01 02:00:00', '2000-01-01 04:00:00'),
(4, 'b', '2000-01-01 05:00:00', '2000-01-01 07:00:00'),
(5, 'a', '2000-01-01 08:00:00', '2000-01-01 11:00:00'),
(6, 'b', '2000-01-01 09:00:00', '2000-01-01 10:00:00'),
(7, 'a', '2000-01-01 12:00:00', '2000-01-01 13:00:00'),
(8, 'b', '2000-01-01 14:00:00', '2000-01-01 14:30:00'),
(9, 'a', '2000-01-01 16:00:00', '2000-01-01 18:00:00'),
(10, 'a', '2000-01-01 19:00:00', '2000-01-01 21:00:00'),
(11, 'b', '2000-01-01 17:00:00', '2000-01-01 20:00:00');

試験結果

        id  name    start           end
        1   a   2000-01-01 01:00:00 2000-01-01 02:00:00
        3   b   2000-01-01 03:00:00 2000-01-01 04:00:00
        4   b   2000-01-01 05:00:00 2000-01-01 06:00:00
        2   a   2000-01-01 07:00:00 2000-01-01 07:30:00
        5   a   2000-01-01 10:00:00 2000-01-01 11:00:00
        7   a   2000-01-01 12:00:00 2000-01-01 13:00:00
        8   b   2000-01-01 14:00:00 2000-01-01 14:30:00
        9   a   2000-01-01 16:00:00 2000-01-01 17:00:00
        11  b   2000-01-01 18:00:00 2000-01-01 19:00:00
        10  a   2000-01-01 20:00:00 2000-01-01 21:00:00

ソリューション

私は、MySQLの機能は次のクエリとの目標を達成するために、ユーザー定義変数と呼ばれる使用します:

SET @inA=0, @inB=0, @lastAstart = 0, @lastBstart = 0, @lastAend = 0, @lastBend = 0;
SELECT id,name,start,end FROM (
    SELECT 
        id,name,
        IF(name='a',
          IF(UNIX_TIMESTAMP(start) > @lastBend, start, FROM_UNIXTIME(@lastBend)),
          IF(UNIX_TIMESTAMP(start) > @lastAend, start, FROM_UNIXTIME(@lastAend))
        ) as start,
        IF(name='a',
          IF(@inB,FROM_UNIXTIME(@lastBstart),end),
          IF(@inA,FROM_UNIXTIME(@lastAstart),end)
        )  as end,
        IF(name='a',
          IF(@inB AND (@lastBstart < @lastAstart), 1, 0),
          IF(@inA AND (@lastAstart < @lastBstart), 1, 0)
        ) as fullyEnclosed,
          isStart,
          IF(name='a',@inA:=isStart,0), 
          IF(name='b',@inB:=isStart,0), 
          IF(name='a',IF(isStart,@lastAstart:=t,@lastAend:=t),0), 
          IF(name='b',IF(isStart,@lastBstart:=t,@lastBend:=t),0)
    FROM (
            (SELECT *, UNIX_TIMESTAMP(start) as t, 1 as isStart FROM `shifts` WHERE name IN ('a', 'b'))
        UNION ALL 
            (SELECT *, UNIX_TIMESTAMP(end) as t, 0 as isStart FROM `shifts` WHERE name IN ('a', 'b'))
        ORDER BY t
    ) as sae
) AS final WHERE NOT isStart AND NOT fullyEnclosed;

基本的な考え方は、すべてのレコードが2回表示されるように2倍の時間でソートされたテーブルを一覧表示することです。開始時間に一度、その後、終了時間のために。それから私は、レコードを横断しながら、状態を追跡し、間隔の重複を調整し、開始時刻と終了時刻とだけ「末端の時間」のレコードを返すために、ユーザ定義の変数を使用しています。

仮定

のみ仮定は、人物Xのない間隔が同じ人物の別の間隔と重複しないことである。

行動

ほとんどの場合、およびその成果ます:

<  (   >   )
<  >   (   )

( < )  ( > )
( ) <  > ( )

<  (   )   >    // for this and similar cases only last part of interval is returned
       <   >

(   <  )   (   )  (  )  (   >   )  // like so
(   )                <  >   (   )

警告

それはDATETIMEとの比較を行うことができませんでした私のMySQLサーバは、ユーザ定義の変数と他の何かに保持するので、私はUNIXタイムスタンプを使用している必要があります。

長所と短所

これは、それがどんなことなく、シングルパスで仕事だんので、それはO(N)時間を取る必要があります参加します。 それは、人Aの区間のすべての部分を取得することができない人B.の囲まれた間隔で切り出さ これは、MySQLの特定の機能を使用します。

私は最近使用した

参考コード切り取ら。日付範囲の重複をチェックするために使用することができます。それは、

)はRuby on Railsで書かれていますが、アイデア(SQL文)は、簡単に他の言語に翻訳することができます
  class Absence
    named_scope :overlaps, lambda { |start, ende| { 
      :conditions =>
          ["   absences.start_date BETWEEN :start AND :end " +
           "OR absences.end_date   BETWEEN :start AND :end " +
           "OR :start BETWEEN absences.start_date AND absences.end_date " +
           "OR :end BETWEEN absences.start_date AND absences.end_date ",
              {:start => start, :end => ende } ]
      }}
  end

という名前のスコープといつものように、この範囲は、他のスコープと組み合わせて再利用することができます。

user = User.find(...)
today = Date.today
confirmed_absences = user.absences.confirmed.overlaps(today.beginning_of_month, today.end_of_month).count
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top