Most efficient way to structure segmented data
One possibility, stated simply, is the events are all in a calendar table, along with any non-category-specific attributes... then tables for the separate types of events to store the category-specific attributes.

tbl_calendar
------------------
event_id (PK)
user_id (FK -&gt; user.id)
start_time
end_time

tbl_cat1_event_info
------------------
event_id (FK -&gt; tbl_calendar.event_id)
cat1_foo1
cat1_foo2

tbl_cat2_event_info
------------------
event_id (FK -&gt; tbl_calendar.event_id)
cat2_foo3
cat2_foo4


This query would return essentially the same result as a union query of the two original tables.  

SELECT *
  FROM tbl_calendar tc
  LEFT JOIN tbl_cat1_event_info c1 ON c1.event_id = tc.event_id
  LEFT JOIN tbl_cat1_event_info c2 ON c2.event_id = tc.event_id
 WHERE c1.event_id IS NOT NULL OR c2.event_id IS NOT NULL;
-- see the expanded version of this query below


That last line isn't technically necessary but it assures that no events that aren't either in c1 or c2 don't get returned.

Getting just the cat1 events:

SELECT *
  FROM tbl_calendar tc
  JOIN tbl_cat1_event_info c1 ON c1.event_id = tc.event_id;




Follow-up: Avoiding events inappropriately being assocated with more than one category

The business rules say that an event is always either a cat1 event or a cat2 event, never both... but the structure I suggested above does not contain any mechanism for avoiding the inconsistency of the same event_id being inserted in both the cat1 and cat2 event info tables, which would then indicate that one event was both types of events... which can't be true.

If we add an event_type_id column to tbl_calendar, we can also modify the joins so that only the appropriate *event_info table is joined.

-- assumes tbl_calendar has an event_type_id column
SELECT *
  FROM tbl_calendar tc
  LEFT JOIN tbl_cat1_event_info c1 ON tc.event_type_id = 1 AND c1.event_id = tc.event_id
  LEFT JOIN tbl_cat1_event_info c2 ON tc.event_type_id = 2 AND c2.event_id = tc.event_id
 WHERE c1.event_id IS NOT NULL OR c2.event_id IS NOT NULL;


This would not actually prevent the event from being inserted but it would result in it not being evaluated if it appears in the wrong table; only the correct table according to event_type_id would be joined for each event.

A more aggressive (but perhaps also more correct) approach in addition to the above would be before-insert and before-update triggers on the *event_info tables that checked the other *event_info table and used SIGNAL (introduced in MySQL 5.5) to throw an exception to prevent the incorrect data from being inserted or updated.  One example:

DELIMITER //
CREATE TRIGGER tbl_cat1_event_info_bi BEFORE INSERT ON tbl_cat1_event_info FOR EACH ROW
BEGIN
    IF EXISTS (SELECT c2.id FROM cat2_event_info c2 WHERE c2.id = NEW.id) THEN
        SIGNAL SQLSTATE '45000'
               SET MESSAGE_TEXT = 'Conflicting value found in cat2_event_info';      
    END IF;
END;
//
DELIMITER ;