Пересмотрено после получения дополнительной информации Чтобы сделать именно так, как вы просите, я бы посоветовал следующее. Добавьте исходную таблицу для дальнейшей нормализации
CREATE TABLE vote_sources (
source_id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
// 0 will be treated as anonymous, as NULL can have issues on UNIQUE indexes
user_id INT(11) UNSIGNED NOT NULL REFERENCES users(user_id) ON DELETE SET 0,
ip VARCHAR(15) NOT NULL,
dupeCheck VARCHAR(15) NOT NULL,
PRIMARY KEY(source_id),
UNIQUE INDEX (dupeCheck)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_general_ci
CREATE TABLE votes
(
vote_id int(11) NOT NULL AUTO_INCREMENT,
post_id int(11) REFERENCES posts(post_id) ON DELETE CASCADE,
source_id int(11) NOT NULL REFERENCES vote_sources(source_id) ON DELETE CASCADE,
vote ENUM('Up', 'Down'),
PRIMARY KEY(vote_id),
UNIQUE INDEX(post_id,source_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_general_ci
А потом бороться с двойными проверками этого триггера
CREATE TRIGGER dupeSourceCheck
BEFORE INSERT ON vote_sources
FOR EACH ROW SET NEW.dupeCheck = IF(NEW.user_id>0,CAST(NEW.user_id AS CHAR),NEW.ip)
Что, в свою очередь, должно приводить к ошибке дублирующегося ключа, если дублированный источник детализирован, при этом все еще отображается числовой идентификатор_пользователя для более эффективных объединений.