@Alk90
php, mysql, jquery, css, html, api

Как эффективно вывести статус друга?

Всем привет! У меня есть таблица пользователей и таблица друзей. При запросе пользователя мне нужно получить статус является ли текущий пользователь другом выбранных из таблицы. Для этого я использовал подзапрос, потому что LEFT JOIN при таком условии не использует индексы и пробегает все поля. Это видно на картинке ниже:
5b7e790692963956763081.pngГораздо лучше работает подзапрос:
5b7e79847233f227837373.png

Но проблема появляется, когда мне нужно вывести не только статус дружбы (f.`status`), но и поле f.`accept`, чтобы уже на строне php определить, что если f.`status` == 0 и f.`accept` == 0, значит пользователь f.`user_two` еще не отреагировал на заявку в друзья.
Ну или если f.`status` == 0 и f.`accept` == 1, значит пользователь f.`user_two` отреагировал на заявку, но не принял её.

Проблема в том, что с помощью подзапроса нельзя вывести два поля в таблицу. Приходится делать либо два подзапроса, либо JOIN. Но в случае с двумя подзапросами ситуация становится хуже если вывести нужно не 2 поля, а 4. Не делать же 4 подзапроса?

У меня уже была мысль изменить поле `status` и сделать его по принципу:
  • NULL - (в случае если нет записи в таблице друзей) - небыло заявок в друзья между пользователями.
  • 0 - была заявка, но не было реакции второго пользователя
  • 1 - заявка была принята - пользователи друзья
  • 2 - заявка была отклонена


Прошу помощи... как сделать правильно, красиво и производительно?

Структура таблиц:

Users
CREATE TABLE `users` (
  `id` int(11) NOT NULL,
  `group_id` smallint(2) NOT NULL DEFAULT '4',
  `email` varchar(50) NOT NULL,
  `login` varchar(64) NOT NULL,
  `name` varchar(32) NOT NULL,
  `sex` tinyint(1) DEFAULT NULL,
  `bdate` date DEFAULT NULL,
  `country_id` int(11) DEFAULT NULL,
  `state_id` int(11) DEFAULT NULL,
  `city_id` int(11) DEFAULT NULL,
  `pwd` varchar(64) DEFAULT NULL,
  `salt` varchar(100) DEFAULT NULL,
  `reg_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  `last_visited` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  `foto` varchar(32) DEFAULT NULL,
  `provider` enum('native','vk','ok','go','fb','tw') DEFAULT 'native',
  `social_id` bigint(32) DEFAULT NULL,
  `link_fb` varchar(40) DEFAULT NULL,
  `link_in` varchar(40) DEFAULT NULL,
  `link_tw` varchar(40) DEFAULT NULL,
  `link_yo` varchar(40) DEFAULT NULL,
  `rating` float DEFAULT NULL,
  `albums` int(11) DEFAULT NULL,
  `timezone` varchar(255) DEFAULT NULL,
  `is_banned` tinyint(1) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;

ALTER TABLE `users`
  ADD PRIMARY KEY (`id`),
  ADD KEY `login` (`login`) USING BTREE,
  ADD KEY `username` (`name`(5));


Friends:
--
-- Структура таблицы `friends`
--
CREATE TABLE `friends` (
  `id` int(11) NOT NULL,
  `user_one` int(11) NOT NULL,
  `user_two` int(11) NOT NULL,
  `status` int(1) NOT NULL,
  `accept` tinyint(1) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

--
-- Индексы таблицы `friends`
--
ALTER TABLE `friends`
  ADD PRIMARY KEY (`id`),
  ADD KEY `user_one` (`user_one`),
  ADD KEY `user_two` (`user_two`);
  • Вопрос задан
  • 94 просмотра
Пригласить эксперта
Ответы на вопрос 2
Rsa97
@Rsa97
Для правильного вопроса надо знать половину ответа
Подзапрос в списке выборки SELECT - это самый наихудший вариант. Такой подзапрос выполняется для каждой строки, попавшей в выборку.
SELECT *
  FROM (
    SELECT `f`.`user_one` AS `id`, `f`.`status` AS `status`, `f`.`accept` AS `accept`
      FROM `friends` 
      WHERE `f`.`user_two` = :userId
    UNION SELECT `f`.`user_two` AS `friend`, `f`.`status` AS `status`, `f`.`accept` AS `accept`
      FROM `friends`
      WHERE `f`.`user_one` = :userId
  ) AS `f`
  JOIN `users` AS `u`
  ON `u`.`id` = `f`.`id`
Ответ написан
Исходя из собственного понимания задачи, позволю немного вмешаться в структуру
1. Таблица пользователей
CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(45) COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;


2. Таблица друзей
Добавлены внешние ключи на таблицу пользователей
Поскольку статусов ограниченное количество, то статусы вынес в отдельные атрибуты
CREATE TABLE `friends` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_one` int(11) NOT NULL,
  `user_two` int(11) NOT NULL,
  `is_accepted` tinyint(1) NOT NULL DEFAULT '0',
  `is_rejected` tinyint(1) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  KEY `fk_user_one_idx` (`user_one`),
  KEY `fk_user_two_idx` (`user_two`),
  CONSTRAINT `fk_user_one` FOREIGN KEY (`user_one`) REFERENCES `users` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
  CONSTRAINT `fk_user_two` FOREIGN KEY (`user_two`) REFERENCES `users` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;


3. Запрос со статусом заявок для первого пользователя будет не очень сложным
SELECT
    u1.name,
    IF (
        u2.id IS NOT NULL,
        IF (
            f1.is_accepted,
            CONCAT('Дружит c ', u2.name),
            CONCAT (
                'Не дружит c ',
                u2.name,
                ': ',
                IF(
                    f1.is_rejected,
                    'заявка отклонена',
                    'заявка ожидает рассмотрения'
                )
            )
        ),
        'Ни с кем не дружит'
	) status
FROM
	user u1
LEFT JOIN
	friends f1
    ON f1.user_one = u1.id
LEFT JOIN
	user u2
    ON u2.id = f1.user_two
WHERE
	u1.id = 1


Если я неправильно понял вашу задачу, готов обсудить и запрос и структуру таблиц.
Не исключено, что если я лучше пойму задачу, то можно будет создать лучшую структуру для хранения заявок в друзья, по которой получится сделать простой запрос
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы