Как найти друга базы данных

например, так.

  • первый запрос — общие друзья для пользователей с идентификаторами 1 и 2;
  • второй запрос — общие друзья для всех пар пользователей (у которых есть общие друзья).

SQL Fiddle

MySQL 5.6 Schema Setup:

create table u (id int, n text);

insert into u values
   (1, "вася")
  ,(2, "оля")
  ,(3, "юра")
  ,(4, "маша")
  ,(5, "борис")
  ,(6, "аня")
;

create table f (user int, friend int);

insert into f values
   (1, 3)
  ,(1, 4)
  ,(1, 5)
  ,(2, 3)
  ,(2, 4)
  ,(2, 6)
  ,(3, 4)
;

Query 1:

select f1.friend, u.n
from f f1
join f f2
  on f2.user = 2
  and f2.friend = f1.friend
join u
  on u.id = f1.friend
where f1.user = 1

Results:

| friend |    n |
|--------|------|
|      3 |  юра |
|      4 | маша |

Query 2:

select f1.user, f2.user, f1.friend, u.n
from f f1
join f f2
  on f2.friend = f1.friend
  and f2.user > f1.user
join u
  on u.id = f1.friend

Results:

| user | user | friend |    n |
|------|------|--------|------|
|    1 |    2 |      3 |  юра |
|    1 |    2 |      4 | маша |
|    1 |    3 |      4 | маша |
|    2 |    3 |      4 | маша |

MsGuns, допустим у нас в контексте есть две модели вида:

Кликните здесь для просмотра всего текста

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    public class Address
    {
        public int Id { get; set; }
        public string FullAddress { get; set; }
 
        public ICollection<User> Users { get; set; }
 
        //Ещё 100500 свойств
    }
 
    public class User
    {
        public int Id { get; set; }
        public string Nickname { get; set; }
 
        public int? Address_Id { get; set; }
        public Address Address { get; set; }
 
        //Ещё 100500 свойств
    }

В какой-то момент нам понадобилось выдернуть из базы только пары “Ник-Улица”. Мы можем вытащить весь агрегат и взять из него только нужное:

Кликните здесь для просмотра всего текста

C#
1
2
3
4
var user = cont.Users
                    .Include(x => x.Address)
                    .FirstOrDefault();
Console.WriteLine($"{user.Nickname} lives at {user.Address.FullAddress}");

Это сработает, но EF вытащит всю запись из таблицы пользователей и соответствующую её запись из таблицы адресов, хотя нам, по факту, нужно из каждой всего по одному полю (свойства в моделях: Nickname и FullAddress).

Но мы можем инструктировать EF о том, что конкретно нам надо, какие поля выдернуть, что нам не нужно ВСЁ. Для этого мы введём класс для нашей текущей хотелки (пара свойств: ник и адрес):

Кликните здесь для просмотра всего текста

C#
1
2
3
4
5
class UserAddress
{
    public string Nick { get; set; }
    public string Address { get; set; }
}

И попросим EF вытащить только два поля из связанных таблиц и запихнуть в наш специальный класс:

Кликните здесь для просмотра всего текста

C#
1
2
3
4
5
6
7
8
9
var dto = cont.Users
                    .Include(x => x.Address)
                    .Select(x => new UserAddress
                    {
                        Nick = x.Nickname,
                        Address = x.Address.FullAddress
                    })
                    .FirstOrDefault();
Console.WriteLine($"{dto.Nick} lives at {dto.Address}");

Всё. EF сходит в базу не за всеми полями записей, а только за двумя, что сильно эффективнее таскания всего подряд.

Добавлено через 6 минут
Такой подход не только позволяет конкретизировать запрашиваемые данные, но так же позволяет производить некие простые преобразования данных при выборке из базы.

Кликните здесь для просмотра всего текста

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
    class OtherUserAddress
    {
        public string UserAddress { get; set; }
    }
 
    var dto2 = cont.Users
        .Include(x => x.Address)
        .Select(x => new OtherUserAddress
        {
            UserAddress = "User " + x.Nickname + " lives at " + x.Address.FullAddress
        })
        .FirstOrDefault();
 
    Console.WriteLine(dto2.UserAddress);

  • ASP

Как реализовать хранение друзей в БД?

Как хранить связи вида пользователь-друг — понятно. Создаем таблицу friend в которой два столбца — user и friend. Делаем ключ по двум полям. Соответственно, пользователь А, добавил в друзья пользователя B — появилась соответствующая запись. Когда пользователь В подтвердил заявку в друзья — создается симметричная запись.

Далее, если хотим вывести друзей пользователя, пишем что-то вроде
select f1.friend from friends f1
join friends f2 on f1.user=f2.friend and f2.user=f1.friend
where f1.user=:user_id

Но что делать если мы хотим вывести не только т.н «взаимных друзей» но и заявки в друзья и не подтвержденные заявки в друзья.
т.е например:
Пользователь user1 добавил в друзья пользователей user2, user3. user2 подтвердил заявку. user4 добавил в друзья user1. И в профиле user1 должно выводиться:
user2 (удалить из друзей)
user3 (отозвать заявку)
user4 (принять заявку)

Как правильно написать запрос? Или одним запросом не обойтись?


  • Вопрос задан

    более трёх лет назад

  • 10965 просмотров

А что если хранить немного по другому?

Например не создавать дублирующую запись в обратную сторону, а изначально использовать еще одно поле в троичной системе счисления: ± 1 когда один пользователь добавил другого (знак указывает направление заявки) и 0 когда заявка подтверждена.

Тогда запрос будет один на выборку пары, а состояния отозвать/принять заявку и удалить из друзей будут определяться знаком числа в дополнительном поле.

Из очевидных плюсов. Места занимать будет примерно в два раза меньше — мелочь, а приятно.
Минусов не сразу не соображу.

Пригласить эксперта

Добавить ещё одну колонку — тип связи. Будет — user, friend, relation_type, где relation_type, например, friend/not_approved_friend/declined_friend/follower. А в выборку добавиться просто ограничение на тип связи.

Так хранить друзей это не очень хорошая идея. Если на сайте будет 1000 пользователей и у каждого по 100 друзей, то у вас будет таблица на 200000 записей, и довольно медленные запросы для такой простой штуки как список друзей.
Я бы сделал денормализацию, то есть просто хранил бы список друзей строкой в поле модели user 🙂
Быстрые запросы, так как пропарсить строку в несколько сотен символов будет быстрее, чем делать запрос к таблице в несколько сотен тысяч записей, да и в разработке такой способ проще.
Хотя возможно я ошибаюсь и разница в производительности будет не такой большой.

Собственно, отвечаю на ваш вопрос. Нужно просто получить список всех записей, где user1 есть в поле user или friend, затем уже в коде определить, взаимные это друзья (есть запись где user1 в поле friend, и запись где user1 в поле user, второй пользователь одинаковый в обоих запросах) или кто-то из них только отправил запрос. Запрос будет чем-то вроде:
select user, friend from friends where user=:user_id or friend=:user_id

теоретически вам надо два множества — друзья юзера, и обратное — те кто позвал в друзья вашего юзера.

1. select f1.friend from friends f1 where f1.user=:user_id
2. select f1.user from friends f1 where f1.friend=:user_id

пересечение будет давать взаимных френдов, вычитания — невзимных с одной и с другой стороны.
синтаксис SQL говорит что это легко можно сделать полным join, который обычно не имплементируется но эмулируется обьединением left и right. То бишь:

select * from friends f1 LEFT join friends f2 on f1.user=f2.friend and f2.user=f1.friend where f1.user=:user_id
UNION
select * from friends f1 RIGHT join friends f2 on f1.user=f2.friend and f2.user=f1.friend where f2.friend=:user_id

и будет 3 варианта — обе f1 и f2 не null — взаимные друзья или один из них null

Но это не самый эффективный способ, я бы согласился с serso и посоветовал иметь доп колонку с типом ( заодно можно их иметь несколько — друзья, супруги/любовники, коллеги ) и при добавлении проверять обратное отношение и сразу заполнять колонку. На нагруженной системе это можно делать в отложенном режиме.


  • Показать ещё
    Загружается…

17 мая 2023, в 21:58

1000 руб./за проект

17 мая 2023, в 21:51

800 руб./за проект

17 мая 2023, в 21:34

500 руб./в час

Минуточку внимания

Речь сегодня пойдет об очень полезной возможности в SQL это межбазовый запрос. Данный вид запроса просто незаменим, если у Вас существует несколько баз данных на одном сервере или даже на разных серверах, а так как иногда требуется получить данные сразу отовсюду, например, для отчета, то межбазовый запрос лучшее решение этой задачи.

Примечание! Сразу хочу сказать, что все примеры будем пробовать на Transact-SQL MS Sql Server 2008 в Management Studio, так как в других СУБД синтаксис будет отличаться. Также хочу заметить, что все примеры ниже требуют начальных знаний SQL, поэтому советую для начала ознакомиться с материалами: Язык запросов SQL – Оператор SELECT, Добавляем в таблицу новую колонку на SQL, Сочетание строковых функций на Transact-SQL, Transact-sql – Табличные функции и временные таблицы эти статьи помогут Вам приобрести начальные знания в SQL.

И так приступим, сначала как обычно немного теории, для того чтобы понять, что такое межбазовый запрос и для чего он служит, а потом как обычно рассмотрим несколько практических примеров.

Межбазовый запрос

Межбазовый запрос – это запрос, который в процессе своего выполнения подключается к разным базам данных, а также в некоторых случаях к разным серверам баз данных.

А теперь давайте определимся, для каких целей могут служить межбазовые запросы, допустим, у Вас есть 3 базы данных, 2 из них расположены на одном MSSQL сервере, а одна на другом. Все они служат для какой-то определенной задачи, может быть у них даже схожая структура, но это не важно и Вам как программисту иногда требуется выгружать данные из всех баз, например, для того чтобы предоставить эти данные начальству, и Вы скорей всего запускаете запросы из каждой базы или переключаетесь из менеджера запросов на работу с той ли иной базой, но гораздо удобней было бы  запустить один запрос и получить сразу все данные. Именно для этого я пользуюсь данного вида запросами, но Вы, наверное, можете найти применение и для других задач. Если Вы сталкивались с такого рода задачами, то Вам просто необходимо узнать что такое межбазовый запрос.

Примеры межбазовых запросов

И первый пример он достаточно простой, требуется тогда когда необходимо получить данные из нескольких баз расположенных на одном сервере. Для объединения этих данных будем использовать конструкцию union all, которую мы рассматривали в статье – union и union all на Transact-SQL .

И для начала, допустим, у нас есть две базы данных (test и test2), схемы dbo в которых мы создали вот такие таблицы:

Таблица в базе test

CREATE TABLE [dbo].[test_table_base_1](
        [id] [bigint] NOT NULL,
        [text] [varchar](50) NULL,
 CONSTRAINT [PK_test_table] PRIMARY KEY CLUSTERED 
(
        [id] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, 
ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

Таблица в базе test2

CREATE TABLE [dbo].[test_table_base_2](
        [id] [bigint] NOT NULL,
        [text] [varchar](50) NULL,
 CONSTRAINT [PK_test_table] PRIMARY KEY CLUSTERED 
(
        [id] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, 
ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

Я их заполнил тестовыми данными, сейчас увидите какими, и для того чтобы получить данные из этих таблиц напишем вот такой запрос:

Код:

select * from test.dbo.test_table_base_1 – Первая база
select * from test2.dbo.test_table_base_2 –Вторая база

Как видите синтаксис очень простой:

Select * from [база].[схема].[таблица]

Но результата в вышеуказанном запросе будет два, и для того чтобы объединить эти запросы, используем конструкцию union all.

Код:

select * from test.dbo.test_table_base_1 – Первая база
 union all
select * from test2.dbo.test_table_base_2 –Вторая база

И результат будет уже один. И с помощью данного межбазового запроса Вы легко можете объединить данные, а еще для удобства, чтобы каждый раз не писать текст запроса можете создать представление views, для того чтобы обращаться напрямую к этому представлению.

Теперь давайте рассмотрим пример посложней, когда требуется получить данные из базы, которая располагается на другом сервере.

Курс по SQL для начинающих

Для этого мы будем использовать конструкцию opendatasource.

Сразу скажу, что opendatasource работает, только если на сервере выставлен параметр Ad Hoc Distributed Queries со значением 1. Для того чтобы посмотреть этот параметр выполните процедуру sp_configure и посмотрите значение данного параметра:

Где,

  • config_value  — это значение которое внеслось но еще не сохранилось, т.е. сервер еще не переконфигурировался;
  • run_value – текущее значение данного параметра, т.е. с которым работает сервер в данный момент.

Кстати данная процедура возвращает очень много конфигурационных параметров, которые Вы можете посмотреть.

И для того чтобы изменить данный параметр, используем туже самую процедуру, синтаксис:

exec sp_configure [Название параметра],[Значение]

А для того чтобы сконфигурировать сервер с новым значением, запустим процедуру reconfigure, и весь запрос будет выглядеть вот так:

exec sp_configure 'Ad Hoc Distributed Queries',1
reconfigure
exec sp_configure

Ну а теперь можем приступать непосредственно к запросу, который подключится к серверу и получит необходимые данные. Для примера я буду подключаться сам к себе к тем же таблицам.

Код:

select * from  
 opendatasource ('sqlncli','Data Source=myserver;Integrated Security=SSPI')
 .test.dbo.test_table_base_1
 union all
select * from  
 opendatasource ('sqlncli','Data Source=myserver;Integrated Security=SSPI')
 .test2.dbo.test_table_base_2

Как видите результат тот же самый.

Здесь мы указали в первом параметре провайдер источника данных, т.е. SQL server (‘sqlncli’) и задали строку подключения:

Где,

  • Data Source – это адрес сервера баз данных;
  • Integrated Security=SSPI – при подключении использовать проверку подлинности Windows, т.е. аутентификация и авторизация пользователя будет проходить по учетным данным Windows, отлично подходит, если в сети развернута AD(Active Directory).

А если Вы хотите использовать проверку подлинности на уровне SQL сервера, то придется писать имя пользователя и пароль (которые должны быть созданы на SQL сервере) в строке подключения, например, абсолютно такой же результат, как и выше, получится, если мы напишем вот такой запрос:

select * from  
 opendatasource ('sqlncli','Data Source= myserver;user id = username; pwd=password')
 .test.dbo.test_table_base_1
 union all
select * from  
 opendatasource ('sqlncli','Data Source= myserver;user id = username; pwd=password')
 .test2.dbo.test_table_base_2

Т.е. вместо параметра Integrated Security мы укажем параметры:

  • user id  — логин на SQL сервере;
  • pwd – соответственно пароль.

Примечание! Opendatasource может подключаться и другим отличным от SQL сервера источникам для этого в параметрах указываете нужный Вам провайдер, например, для подключения к Excel документу можете использовать вот такой запрос (Синтаксис):

SELECT * FROM OPENDATASOURCE('Microsoft.Jet.OLEDB.4.0',
'Data Source=C:TestExcel.xls; Extended Properties=EXCEL 5.0')...[Sheet1$] ;

По межбазовым запросам все, надеюсь, данный материал был Вам интересен, и пригодится Вам на практике.

Заметка! Для комплексного изучения языка T-SQL рекомендую посмотреть мои видеокурсы по T-SQL, в которых используется последовательная методика обучения и рассматриваются все конструкции языка SQL и T-SQL.

Мы уже публиковали несколько уроков, посвящённых проектированию различных модулей, подобных тем, что используются на Facebook. Сегодня мы продолжим эту практику и рассмотрим как спроектировать БД для создания системы запросов в друзья.

Проект БД

Для реализации поставленной задачи нам понадобится три таблицы:Users, Updates и Friends.

Таблица Users

Таблица пользователей будет содержать данные, которые юзеры оставляют при регистрации.

CREATE TABLE `users` (
`user_id` INT(11) NOT NULL AUTO_INCREMENT ,
`username` VARCHAR(45) ,
`password` VARCHAR(100) ,
`email` VARCHAR(45) ,
`friend_count` INT(11) ,
`profile_pic` VARCHAR(150),
PRIMARY KEY (`user_id`));

Пароль будем хранить после хеширования функцией MD5.

Таблица Updates

Данная таблица будет хранить статусы пользователей. В данном случае user_id_fk это внешний ключ, который относится к users.user_id

CREATE TABLE `updates` (
`update_id` INT(11) AUTO_INCREMENT ,
`update` VARCHAR(45),
`user_id_fk` VARCHAR(45),
`created` INT(11) ,
`ip` VARCHAR(45),
PRIMARY KEY (`update_id`),
FOREIGN KEY (user_id_fk) REFERENCES users(user_id));

Таблица Friends

Данная таблица будет хранить связи пользователей. В данном контексте friend_one и friend_two это внешние ключи к users.user_id

CREATE TABLE `friends` (
`friend_one` INT(11) ,
`friend_two` INT(11) ,
`status` ENUM('0','1','2') DEFAULT '0',
PRIMARY KEY (`friend_one`,`friend_two`),
FOREIGN KEY (friend_one) REFERENCES users(user_id),
FOREIGN KEY (friend_two) REFERENCES users(user_id));

0, 1 и 2 – это статусы: “В ожидании”, “Запрос дружбы”, “Подтверждение дружбы”.

friendsTable.png

Добавление в друзья

Для того, чтобы отправить запрос в друзья, воспользуйтесь следующим insert запросом:

INSERT INTO friends
(friend_one,friend_two)
VALUES
('$user_id','$friend_id');

Подтверждение дружбы

Для подтверждения дружбы или игнорирования запроса, выставите статус в 0 или 1:

UPDATE friends
SET status="1"
WHERE
(friend_one="$user_id" OR friend_two="$user_id")
AND
(friend_one="$friend_id" OR friend_two="$friend_id");

Проверка дружат ли пользователи или нет

В данном случае, friend_one это инициатор запроса:

SELECT 'friend_one','friend_two','status' FROM friends
WHERE
(friend_one="$user_id" OR friend_two="$user_id")
AND
(friend_one="$friend_id" OR friend_two="$friend_id")

PHP

Вот небольшой PHP код, который поможет вам вывести соответствующие кнопки на страницах пользователей.

<?php
include 'db.php';
$user_id=user session value;
$friend_id=frined id value;
$result=mysqli_query($connection,"SELECT 'friend_one','friend_two','status' FROM friends WHERE (friend_one="$user_id" OR friend_two="$user_id") AND (friend_one="$friend_id" OR friend_two="$friend_id")");
$row=mysqli_fetch_array($result,MYSQLI_ASSOC);

if($row['friend_one']=='$user_id' && $row['status']=='0')
{

Обновление списка друзей

Выборка данных согласно связям таблиц users, friends и updates.

SELECT U.username, U.email, D.update_id, D.update, D.created
FROM users U, updates D, friends F
WHERE
D.user_id_fk = U.user_id
AND

CASE
WHEN F.friend_one = '$user_id'
THEN F.friend_two = D.user_id_fk
WHEN F.friend_two= '$user_id'
THEN F.friend_one= D.user_id_fk
END

AND
F.status > '0'
ORDER BY D.update_id DESC;

Списки друзей

В данном запросе мы отобразим связь между таблицами users и friends, для отображения списка друзей.

SELECT F.status, U.username, U.email
FROM users U, friends F
WHERE
CASE

WHEN F.friend_one = '$user_id'
THEN F.friend_two = U.user_id
WHEN F.friend_two= '$user_id'
THEN F.friend_one= U.user_id
END

AND
F.status='1';

db.php

Напоследок приведём пример файла конфигурации базы данных.

define('DB_SERVER', 'localhost');
define('DB_USERNAME', 'username');
define('DB_PASSWORD', 'password');
define('DB_DATABASE', 'database');
$connection = @mysqli_connect(DB_SERVER,DB_USERNAME,DB_PASSWORD,DB_DATABASE);

Добавить комментарий