حقول التاريخ في MySQL، والعثور على جميع الصفوف غير المتداخلة وإرجاع الفرق فقط

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

سؤال

لذلك كان هذا أحد أسئلتي الأولى هنا، ولكن لدي اختلاف طفيف:

إذن لدي شخصان جداولهما موجودة في قاعدة بيانات.تسجل الجداول ببساطة وقت البدء ووقت الانتهاء ووصف الأحداث/المواعيد المختلفة لكلا المستخدمين.

يريد PersonA تبادل المواعيد مع PersonB.أريد استعلام MySQL يُرجع جميع الأوقات التي يمكن لـ PersonB وPersonA تبديلها.

في الأصل، كانت معلمات الاستعلام هي استبعاد أي مواعيد للشخص "ب" حيث كان هناك تداخل مع الشخص "أ" وكان تعيين الشخص "ب" يجب أن يكون بنفس طول الموعد الذي يريد الشخص "أ" تبديله.لقد حصلت على بعض النصائح الرائعة فيما يتعلق بحساب/هندسة الوقت والتي ساعدتني في الحصول على النتائج التي كنت بحاجة إليها.

أريد الآن تغيير المعلمة 1 إلى 1 بحيث لا تكون المواعيد متساوية في الطول.لذا، إذا أراد الشخص "أ" تبديل موعده صباح يوم الاثنين (10:00 صباحًا - 11:30 صباحًا)، فسيقوم الاستعلام بما يلي:

  • استبعاد أي من مواعيد الشخص "ب" التي تتم خلال أحد مواعيد الشخص "أ".
  • قم بتضمين أي من مواعيد الشخص "ب" التي تقع خارج مواعيد الشخص "أ".
  • قم بتضمين أجزاء مواعيد الشخص "ب" التي تكون عندما يكون الشخص "أ" مجانيًا، ولكن قم بإظهار الجزء المجاني فقط.

لذا، إذا أراد الشخص "أ" تبديل الموعد أعلاه (مرة أخرى، الاثنين 10:00 صباحًا - 11:30 صباحًا)، وكان لدى الشخص "أ" موعد يوم الثلاثاء من الساعة 1:00 ظهرًا إلى 3:00 مساءً، ولدى الشخص "ب" موعد يوم الثلاثاء من الساعة 12: من 00 مساءً إلى 4:00 مساءً، سيعود الاستعلام:

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، إليك المزيد من السياق:

لقد ظللت أقول المواعيد ولكن أعتقد أنني كنت أقصد حقًا "الوظائف" كما في "نوبات العمل".يعمل كل من PersonA وPersonA في نفس المكتب.في vcalendar، يُشار عادةً إلى نوبات العمل باسم "الأحداث" ولكن في بعض الأحيان "المواعيد" وقد اخترت الخيار الأخير لأنه يبدو أقل تشابهًا مع ذهاب الشخصين إلى المعرض.

إذن لدى PersonA مناوبة لغسل الأطباق يوم الاثنين من الساعة 10:00 إلى الساعة 11:30 صباحًا.يقوم PersonB بالطهي يوم الثلاثاء من الساعة 12:00 ظهرًا حتى 5:00 مساءً.يريد PersonA حقًا رؤية شقيقه قبل مغادرتهم المدينة يوم الاثنين.إنه يفضل الحصول على إجازة طوال صباح يوم الاثنين، لكنه يفضل الحصول على إجازة لمدة ساعة.

لذلك في نموذجي القديم (نشأ في سؤالي الأول هنا)، كنت أبحث عن أي تحولات لا يوجد فيها تداخل وتكون التحولات متساوية في الوقت المناسب.لكن ذلك ينطوي على مشكلتين:

  1. إذا كنت بحاجة إلى شخص لتغطية مناوبتي التي تبلغ ساعتين يوم الثلاثاء، وأنا أعمل لمدة 4 ساعات يوم الخميس، ويعمل جو لمدة 8 ساعات يوم الخميس، فيمكنني تبديل ساعتين من ساعاته ويمكنه المغادرة مبكرًا قليلاً ويمكنني البقاء قليلاً لاحقاً.

  2. إذا كان لدي مناوبة مدتها ساعتين، ولكنني سأستبدلها بكل سرور بساعة فقط للوصول إلى المطار في الوقت المحدد، أريد أن أعرف ما إذا كان كذا وكذا يأتي قبلي بساعة في وقت لاحق من الأسبوع حتى أتمكن من ذلك خذ هذا الجزء من تحوله.

قصة قصيرة طويلة (متأخرة جدًا)، أريد ما يُعرف على ما يبدو باسم تكملة نسبية من تحولات PersonA إلى PersonB (بشكل أساسي في أي وقت يعمل فيه PersonB ولا يعمل PersonA، بغض النظر عما إذا كانت التحولات تتداخل في نقطة أخرى.)

من الناحية المثالية، سأحصل على مجموعة من النتائج التي تتضمن البتات التي كان الشخص B يعمل بها والشخص A لم يكن يعمل (النوبتان المذكورتان أعلاه لمدة ساعة واحدة)، بالإضافة إلى الوردية بأكملها (مع علامة خاصة للإشارة إلى أنها غير متوفرة كـ كامل) بحيث يرى الشخص "أ" أنه كان يغطي جزءًا من وردية العمل ولا يرتبك ويظن أن الشخص "ب" كان يعمل في نوبتين مدتهما ساعة واحدة.

بدأ كل هذا يبدو معقدًا بعض الشيء.في الأساس، أريد أن تكون تحولات PersonB باللون الأزرق، وأن تكون تحولات PersonA باللون الأصفر، وأريد أن تقوم قاعدة البيانات بإرجاع كافة الأجزاء غير الخضراء.

هل كانت مفيدة؟

المحلول

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 التي يمكن أن تتناسب مع فجوة في مذكرات Zizka.

تم التعديل: في الأصل كان ذلك متقاطعا، ولكن إذا كنت ترغب في الاستكمال النسبي، فهذا يكفي.

نصائح أخرى

يترك $shift_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.

مهمة

قم بإرجاع جميع الفواصل الزمنية لمستخدمين مختلفين باستثناء الأجزاء التي تتداخل فيها.

بيانات الجدول والاختبار

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;

الفكرة الأساسية هي سرد ​​الجدول مرتين مرتبة حسب الوقت بحيث يظهر كل سجل مرتين.مرة واحدة لوقت البداية ثم لوقت النهاية.ثم أستخدم المتغيرات المعرفة من قبل المستخدم لتتبع الحالة أثناء اجتياز السجلات وإرجاع سجلات "وقت الانتهاء" فقط مع ضبط وقت البدء ووقت الانتهاء للفواصل الزمنية المتداخلة.

الافتراضات

الافتراض الوحيد هو أنه لا توجد فترة زمنية للشخص x تتداخل مع فترة زمنية أخرى لنفس الشخص.

سلوك

حالات قليلة ونتائجها:

<  (   >   )
<  >   (   )

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

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

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

تحفظات

لا بد لي من استخدام الطابع الزمني لنظام التشغيل Unix نظرًا لأنه لم يتمكن خادم MySQL الخاص بي من إجراء مقارنة بين DATETIME المحفوظ في المتغير المحدد من قبل المستخدم وشيء آخر.

إيجابيات وسلبيات

إنها تؤدي مهمتها في تمريرة واحدة دون أي وصلات لذا يجب أن تستغرق وقتًا O(N).ولا يمكنه استرجاع جميع أجزاء الفاصل الزمني للشخص "أ" المقطوعة بفواصل مغلقة للشخص "ب".يستخدم وظائف MySQL المحددة.

للرجوع إلى رمز مقدم من التعليمات البرمجية التي استخدمتها مؤخرا. يمكن استخدامه للتحقق من نطاقات تاريخ التداخل. تتم كتابته في Ruby على القضبان، ولكن يمكن بسهولة ترجمة الفكرة (بيان 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

كالعادة مع Scopes المسماة، يمكن إعادة استخدام هذا النطاق مع أي نطاقات أخرى.

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