Форум программистов, компьютерный форум, киберфорум
C#: Базы данных
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
 
Рейтинг 4.85/40: Рейтинг темы: голосов - 40, средняя оценка - 4.85
9 / 6 / 3
Регистрация: 10.01.2020
Сообщений: 330
MySQL

Как сделать оптимальный код для вставки строк (INSERT and FOREIGN KEY) (linq2db)

17.02.2021, 15:55. Показов 8392. Ответов 27

Студворк — интернет-сервис помощи студентам
Доброго всем!

Когда мелкое приложение, которое использует БД с парой таблиц, вопросов не возникает.
А когда приложение по крупнее, нужна правильно спроектированная БД.

Вот с самой проектировкой БД более-меннее понятно.

Но вопросов много возникает при вставке новых значений в таблицы с внешними ключами (FOREIGN KEY).

Все видеокурсы о SQL на 90+% состоят из уроков на SELECT.
В этих курсах или данные уже существуют, до начала урока, и делаются выборки, или добавляются данные с уже известными внешними ключами.
Но как оптимально поступать в реальных приложениях?



Это всё естественно примитивная модель БД, чтобы лучше объяснить что я хочу.
Мне нужно не создать структуру БД, а понять "правильный"|"оптимальный"|"лёгкий"|"над ёжный" способ работы с БД из .Net + linq2db и FOREIGN KEY.


Представьте вот такую ситуацию.
Есть некий сервис онлайн курсов "Online".
К нему есть ограниченный доступ по API (по 1 разу в 60 минут к Student и Course).

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

Чтобы не было многабукаф, спрячу код под спойлеры.


Сущности получаемые по API

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
namespace Online.API
{
    public class Student
    {
        /// <summary>
        /// Id Пользователя на сервисе API
        /// </summary>
        public long Id { get; set; }
        /// <summary>
        /// Имя пользователя на сервисе API
        /// </summary>
        public string FullName { get; set; }
        /// <summary>
        /// Количество уже затраченных часов обучения на всех курсах.
        /// </summary>
        public int TotalHours { get; set; }
        /// <summary>
        /// Все Id курсов в которых участвует пользователь
        /// </summary>
        public List<long> CourseIds { get; set; }
    }
 
    public class Course
    {
        /// <summary>
        /// Id конкретного курса, можно получить только через запрос к (API) Student
        /// Получить все остальные данные по Курсу можно только при наличии Id
        /// </summary>
        public long Id { get; set; }
        /// <summary>
        /// Название курса
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// Сколько часов длится курс
        /// </summary>
        public int Hours { get; set; }
    }
}


Из двух API объектов Student и Course получается три таблицы в БД, так как один студент может быть записан на несколько курсов.

`StudentCourse` таблица многие ко многим.
У одного студента может быть много курсов.
У одного курса можеть быть много студентов.

SQL создания таблиц

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
CREATE TABLE `Student` (
    `Id` BIGINT(20) UNSIGNED NOT NULL,
    `FullName` VARCHAR(255) NOT NULL,
    `TotalHours` INT(11) NOT NULL,
    PRIMARY KEY (`Id`)
);
 
CREATE TABLE `Course` (
    `Id` BIGINT(20) UNSIGNED NOT NULL,
    `Name` VARCHAR(255) NULL DEFAULT NULL,
    PRIMARY KEY (`Id`)
);
 
CREATE TABLE `StudentCourse` (
    `StudentId` BIGINT(20) UNSIGNED NOT NULL,
    `CourseId` BIGINT(20) UNSIGNED NOT NULL,
    PRIMARY KEY (`StudentId`, `CourseId`),
    CONSTRAINT `FK_studentcourse_student` FOREIGN KEY (`StudentId`) REFERENCES `Student` (`Id`),
    CONSTRAINT `FK_studentcourse_course` FOREIGN KEY (`CourseId`) REFERENCES `Course` (`Id`)
);
 
-- Так же Вопрос по SQL
-- Почему при создании таблицы `StudentCourse` этим запросом, появляется индекс
    INDEX `FK_studentcourse_course` (`CourseId`) USING BTREE,
-- Я же ограничил дублирование записей в "PRIMARY KEY (`StudentId`, `CourseId`),"


Соответственно для этих таблиц БД созданы классы-сущности в C#

Сущности C# для linq2db

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
namespace Online.Model
{
    public class Student
    {
        [PrimaryKey]
        public long Id { get; set; }
 
        [Column, NotNull]
        public string FullName { get; set; }
 
        [Column, NotNull]
        public int TotalHours { get; set; }
 
        [NotColumn]
        [Association(ThisKey = nameof(Id), OtherKey = nameof(Online.Model.StudentCourse.StudentId))]
        public List<StudentCourse> StudentCourses { get; set; }
    }
 
    public class Course
    {
        [PrimaryKey]
        public long Id { get; set; }
        [Column, Nullable]
        public string Name { get; set; }
        [Column, Nullable]
        public int Hours { get; set; }
    }
    
    public class StudentCourse
    {
        [PrimaryKey]
        public long StudentId { get; set; }
 
        [PrimaryKey]
        public long CourseId { get; set; }
 
        [NotColumn]
        [Association(ThisKey = nameof(StudentId), OtherKey = nameof(Online.Model.Student.Id))]
        public Student Student { get; set; }
 
        [NotColumn]
        [Association(ThisKey = nameof(CourseId), OtherKey = nameof(Online.Model.Course.Id))]
        public Course Course { get; set; }
    }
}



А теперь последовтельность действий.

Я могу сделать запрос и получить список пользователей (Online.API.Student -- get.Student?)
И получить объекты класса Online.API.Student.

К примеру получаю вот такого пользователя.
C#
1
2
3
4
5
6
7
Online.API.Student studentAPI = new Online.API.Student
{
    Id = 1234,
    FullName = "Иван Иванов",
    TotalHours = 67,
    CourseIds = new List<long> { 1245, 3287, 2456, 7643 },
};

Затем я конвертирую его в Online.Model.Student и добавляю в таблицу БД `Student`
C#
1
2
3
4
5
6
7
8
9
10
Online.Model.Student student = new Online.Model.Student
{
    Id = studentAPI.Id,
    FullName = studentAPI.FullName,
    TotalHours = studentAPI.TotalHours,
};
 
// ранее созданный объект БД (linq2db)
// Если существует, то обновить, так как могут быть изменения
db.InsertOrReplace(student);

И вот теперь мне нужно добавить данные в таблицу `StudentCourse`.

Но есть несколько вопросов.
Я не могу добавить с обновлением потому, что в таблице стоит PrimaryKey на оба поля.
А так же я не знаю существует ли Course.Id в таблице `Course`.

То есть мне нужно проверить существование Course.Id, затем отсутствие этой пары в `StudentCourse`.


С этого места мне кажетя всё идёт не очень гладко.
Может и до этого места тоже было не очень, но сейчас и я это вижу.


Реализация вставки.
С предварительной проверкой вставляем строки
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
foreach (var courseId in studentAPI.CourseIds)
{
    // Чекаем существование курса
    var check = db.Course
        .Where(x => x.Id == courseId)
        .Select(x => 1).FirstOrDefault();
 
    // Если не существует то добавить
    if (check != 1)
    {
        // Получаем по API всю информацию по Course.Id
        Online.Model.Course course = new Course
        {
            Id = courseId,
            Name = "Программирование Баз Данных",
            Hours = 23,
        };
 
        db.InsertOrReplace(course);
    }
}

И только теперь добавляем в таблицу, предварительно проверив на существование.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
foreach (var courseId in studentAPI.CourseIds)
{
    var check = db.StudentCource
        .Where(x => x.StudentId == studentAPI.Id)
        .Where(x => x.CourseId == courseId)
        .Select(x => 1).FirstOrDefault();
 
    if (check != 1)
    {
        var studentCourse = new StudentCourse
        {
            StudentId = studentAPI.Id,
            CourseId = courseId,
        };
 
        db.Insert(studentCourse);
    }
}

Вопрос вот собственно в чём.
При каждом обновлении Student, мне нужно совершать кучу этих запросов.

Как вообще опытные разработчики с этим поступают?
Я думаю должен быть способ это сделать проще.
Допустим сам ORM linq2db может что-то уже умеет делать это проще?



P.S. Сразу получить все курсы с API я не могу. (Online.API.Course)
Могу получать только пользоватлелей, и уже потом по CourseIds получать описание и всё остальное по самому курсу.

Буду благодарен за любую помощь.
Конструктивная критика, с указанием на ошибки, и способы их решений крайне приветствуется.
0
Лучшие ответы (1)
Programming
Эксперт
39485 / 9562 / 3019
Регистрация: 12.04.2006
Сообщений: 41,671
Блог
17.02.2021, 15:55
Ответы с готовыми решениями:

Как сделать Insert в таблицу, которая содержит foreign key другой таблицы
У меня такая проблемка. Есть 3 таблицы, Order, product и client. В Order хранится id-шники product и client. Я должен сделать Insert в...

SQLite - оптимальный размер транзакции, стоит ли использовать FOREIGN KEY, связь PRIMARY KEY и INDEX
1. Оптимальный размер транзакции 1.1. Есть ли какое-то ограничение на размер или содержание одной транзакции? 1.2. Может ли размер...

Insert запрос к sqlite с foreign key
Здравствуйте. есть такая тестовая база CREATE TABLE mt (id INTEGER PRIMARY KEY AUTOINCREMENT, zk INTEGER, kt TEXT); CREATE...

27
Эксперт .NET
 Аватар для Usaga
14291 / 9376 / 1352
Регистрация: 21.01.2016
Сообщений: 35,331
17.02.2021, 17:08
Цитата Сообщение от BeginnerCoderCS Посмотреть сообщение
// Чекаем существование курса
    var check = db.Course
        .Where(x => x.Id == courseId)
        .Select(x => 1).FirstOrDefault();
// Если не существует то добавить
    if (check != 1)
=>

C#
1
2
3
4
5
    var check = db.Course
        .Any(x => x.Id == courseId);
 
// Если не существует то добавить
    if (!check)
Если строго по теме, то я очень сомневаюсь, что курсов может быть столько, что их нельзя в ОЗУ вытащить. Я бы, как один из вариантов, вытащил ID всех курсов в статический Dictionary и уже по нему пробивал существование курсов. Это будет примерно в 100500 раз быстрее, чем каждый раз СУБД напрягать.

Второй вариант - оптимистичный вариант работы с курсами. Мы ничего не проверяем, а просто добавляем (на пофиг) связь студента и курса. Если получаем исключение, то добавляем курс. СУБД один же чёрт производит проверку существования записей на которые внешние ключи ссылаются. Второй раз явно это делать смысла не особо много)
1
9 / 6 / 3
Регистрация: 10.01.2020
Сообщений: 330
17.02.2021, 17:40  [ТС]
Цитата Сообщение от Usaga Посмотреть сообщение
Если строго по теме, то я очень сомневаюсь, что курсов может быть столько, что их нельзя в ОЗУ вытащить. Я бы, как один из вариантов, вытащил ID всех курсов в статический Dictionary и уже по нему пробивал существование курсов. Это будет примерно в 100500 раз быстрее, чем каждый раз СУБД напрягать.
То есть вы имеете в виду что-то вот такое?

Получить всю таблицу БД в ОЗУ и работать уже с ней?

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
namespace Online.Repo
{
    public class CourseRepo
    {
        public CourseRepo()
        {
            db = new ConnMySQL();
 
            if (AllCourses == null)
            {
                AllCourses = GetCourses();
            }
        }
 
        private ConnMySQL db;
        public static Course[] AllCourses;
 
        private Course[] GetCourses()
        {
            return db.Course.ToArray();
        }
 
        public static bool HasId(long id)
        {
            return AllCourses.Any(x => x.Id == id);
        }
 
        public static Course GetId(long id)
        {
            return AllCourses.Where(x => x.Id == id).FirstOrDefault();
        }
    }
}


Цитата Сообщение от Usaga Посмотреть сообщение
var check = db.Course
        .Any(x => x.Id == courseId);
// Если не существует то добавить
    if (!check)
Код получается красивый, а sql запрос страшный (
SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
SELECT
    CASE
        WHEN EXISTS(
            SELECT
                *
            FROM
                `Course` `x`
            WHERE
                `x`.`Id` = 1245
        )
            THEN 1
        ELSE 0
    END AS `c1`
0
 Аватар для Cupko
658 / 595 / 171
Регистрация: 17.07.2012
Сообщений: 1,682
Записей в блоге: 1
17.02.2021, 19:08
Цитата Сообщение от BeginnerCoderCS Посмотреть сообщение
Получить всю таблицу БД в ОЗУ и работать уже с ней?
Отвечу за коллегу: нет. Не всю таблицу и не на уровне всего репозитория. Айдишники существующих в HashSet на уровне вашего Update метода.
Цитата Сообщение от BeginnerCoderCS Посмотреть сообщение
Код получается красивый, а sql запрос страшный (
А что в нем страшного?
Цитата Сообщение от BeginnerCoderCS Посмотреть сообщение
При каждом обновлении Student, мне нужно совершать кучу этих запросов.
Проблема тут лишь в том, что не нужно каждый курс для каждого студента проверять отдельным запросом в базу, когда вы можете собрать все курсы всех студентов и проверить это одним запросом.
Цитата Сообщение от Usaga Посмотреть сообщение
Второй вариант - оптимистичный вариант работы с курсами. Мы ничего не проверяем, а просто добавляем (на пофиг) связь студента и курса. Если получаем исключение, то добавляем курс. СУБД один же чёрт производит проверку существования записей на которые внешние ключи ссылаются. Второй раз явно это делать смысла не особо много)
По мне так выглядит довольно костыльно, а в текущей ситуации и подавно. Это ж придется каждую запись в отдельной транзакции добавлять и исключения анализировать. Это хорошо, если там уникальное исключение для уникального случая, а что если еще связанные сущности добавятся, это ж всё потом поддерживать надо.
1
9 / 6 / 3
Регистрация: 10.01.2020
Сообщений: 330
17.02.2021, 20:17  [ТС]
Цитата Сообщение от Cupko Посмотреть сообщение
А что в нем страшного?
По скорости запросов он работает так же быстро как и обычный?
SQL
1
2
3
4
5
6
7
SELECT
    1
FROM
    `Course` `x`
WHERE
    `x`.`Id` = 1245
LIMIT 1
Цитата Сообщение от Cupko Посмотреть сообщение
Проблема тут лишь в том, что не нужно каждый курс для каждого студента проверять отдельным запросом в базу, когда вы можете собрать все курсы всех студентов и проверить это одним запросом.
Получается что так всегда и делается?
Я не про корявость своих запросов, я про сам подход, что нужно получать отдельным запросом внешние ключи и проверять их наличие в БД пусть это hashset или dictionary или ещё чем нибуть? Но никаких обходных путей ни БД ни ORM не даёт?

То есть одним запросом не получится добавить строку, и отсутствующие внешние ключи?


И ещё что скажете по поводу хранимых процедур?
Есть ли смысл их создавать для этих целей?
Или не пытаться искать автоматизацию работы в FOREIGN KEY?
0
 Аватар для Cupko
658 / 595 / 171
Регистрация: 17.07.2012
Сообщений: 1,682
Записей в блоге: 1
17.02.2021, 21:55
Цитата Сообщение от BeginnerCoderCS Посмотреть сообщение
Получается что так всегда и делается?
Я не про корявость своих запросов, я про сам подход, что нужно получать отдельным запросом внешние ключи и проверять их наличие в БД пусть это hashset или dictionary или ещё чем нибуть? Но никаких обходных путей ни БД ни ORM не даёт?
Вам для создания связи между записями нужно чтобы эти записи существовали. По-моему вполне логично. Разница лишь в том, на какой стороне это делается.
Цитата Сообщение от BeginnerCoderCS Посмотреть сообщение
То есть одним запросом не получится добавить строку, и отсутствующие внешние ключи?
Одним запросом обновить запись в одной таблице и добавить 2 записи в 2 другие таблицы? Зачем? Что вы хотите на этом сэкономить?
Цитата Сообщение от BeginnerCoderCS Посмотреть сообщение
И ещё что скажете по поводу хранимых процедур?
Есть ли смысл их создавать для этих целей?
Или не пытаться искать автоматизацию работы в FOREIGN KEY?
Я считаю, что вам вообще особо париться по всему этому поводу не нужно, если условие раз в час данные в базу занести. У вас там миллиарды студентов-курсов? БД хостится на микроволновке?
Цитата Сообщение от BeginnerCoderCS Посмотреть сообщение
По скорости запросов он работает так же быстро как и обычный?
Проверьте. Может сотню-другую наносекунд сэкономите.
1
9 / 6 / 3
Регистрация: 10.01.2020
Сообщений: 330
17.02.2021, 22:11  [ТС]
Цитата Сообщение от Cupko Посмотреть сообщение
Одним запросом обновить запись в одной таблице и добавить 2 записи в 2 другие таблицы? Зачем? Что вы хотите на этом сэкономить?
Да вот казалось, что ORM это уже все умеют делать, а я просто неправильно пользуюсь.

Цитата Сообщение от Cupko Посмотреть сообщение
Я считаю, что вам вообще особо париться по всему этому поводу не нужно, если условие раз в час данные в базу занести. У вас там миллиарды студентов-курсов? БД хостится на микроволновке?
Ну это утрированный пример, чтобы можно было всем понять, что я хочу.
На самом деле там больше и таблиц и связей и данных.
Таблицы внешних ключей FOREIGN KEY надеюсь что за 5-10к не перевалят.
0
 Аватар для Cupko
658 / 595 / 171
Регистрация: 17.07.2012
Сообщений: 1,682
Записей в блоге: 1
17.02.2021, 22:25
Лучший ответ Сообщение было отмечено BeginnerCoderCS как решение

Решение

Цитата Сообщение от BeginnerCoderCS Посмотреть сообщение
Да вот казалось, что ORM это уже все умеют делать, а я просто неправильно пользуюсь.
ORM-ки то может и могут, и запрос можно постараться написать, чисто теоретически, с каким-нибудь MERGE и промежуточными таблицами. Но более оптимально сделать не выйдет. Да и на стороне СУБД это всё равно будут разные команды/инструкции.
Нет ничего криминального даже отправить 100 EXISITS в базу данных, если у вас не высоконагруженное приложение с проблемами в производительности. Иногда простой понятный код намного важнее неоптимальных запросов к БД.

PS: Преждевременная оптимизация — корень всех зол. (c) Д. Кнут.
1
1497 / 1238 / 245
Регистрация: 04.04.2011
Сообщений: 4,363
17.02.2021, 23:29
Студенты + Курсы + Многие-ко-многим.
Классический случай "Многие-ко-многим". Решается через кросс-таблицу:

SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
CREATE TABLE Student (
   SId INT IDENTITY(1,1) PRYMARY KEY,
   SFIO VARCHAR(MAX) NOT NULL
)
CREATE TABLE Curse (
   CId INT IDENTITY(1,1) PRYMARY KEY,
   CName VARCHAR(MAX) NOT NULL
)
CREATE TABLE StudCurse (
  SCId INT IDENTITY(1,1) PRYMARY KEY,
  SCSId INT REFERENCE Student(SId) NOT NULL,
  SCCId INT REFERENCE Curse(CId) NOT NULL
  SCHours INT NOT NULL
)
Соответствующие классы модели + соответствующий контекст модели. Все примитивно как совковая лопата.
Вы пытаетесь из лопаты соорудить какой-то синхрофазатрон. Логика CRUD для такой базы также примитивна. Перед записью в StudCurse проверяется есть ли такая парочка и дальше либо Insert либо Update.
10-15k форейнкеев ? И что ? А 15 млн не хотите ? 1000 запросов к базе в минуту ? А 1 млн не хотите ?
И все никак не томозит если верно спроектировано. У Вас - не верно.

Добавлено через 12 минут
Linq2 за Вас не построит никакой логики. Совсем. Она просто избавит Вас от необходимости "ручками" писать все эти select, update, insert, delete. А всю "требуху" в виде семантической увязки таблиц в запросах, соблюдение целостности БД (транзакционность пакетов изменений), предварение исключений и т.д. надо писать "ручками" в виде класса-репозитория или как-Вы-там-его-назовете.

Судя по всему, Вы не определились с самой бизнес-логикой СУБД, но уже лихо лепите таблицы, форейнкеи, праймари.. И уже озабочены "оптимизацией" Вашего велосипеда. С квадратными колесами, тремя рулями и без единого седла
0
Эксперт .NET
 Аватар для Usaga
14291 / 9376 / 1352
Регистрация: 21.01.2016
Сообщений: 35,331
18.02.2021, 05:46
Цитата Сообщение от Cupko Посмотреть сообщение
ORM-ки то может и могут, и запрос можно постараться написать, чисто теоретически, с каким-нибудь MERGE и промежуточными таблицами. Но более оптимально сделать не выйдет. Да и на стороне СУБД это всё равно будут разные команды/инструкции.
Нет ничего криминального даже отправить 100 EXISITS в базу данных, если у вас не высоконагруженное приложение с проблемами в производительности. Иногда простой понятный код намного важнее неоптимальных запросов к БД.
Подтверждаю.
0
9 / 6 / 3
Регистрация: 10.01.2020
Сообщений: 330
18.02.2021, 08:20  [ТС]
Цитата Сообщение от MsGuns Посмотреть сообщение
Студенты + Курсы + Многие-ко-многим.
Классический случай "Многие-ко-многим". Решается через кросс-таблицу:
SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
CREATE TABLE Student (
   SId INT IDENTITY(1,1) PRYMARY KEY,
   SFIO VARCHAR(MAX) NOT NULL
)
CREATE TABLE Curse (
   CId INT IDENTITY(1,1) PRYMARY KEY,
   CName VARCHAR(MAX) NOT NULL
)
CREATE TABLE StudCurse (
  SCId INT IDENTITY(1,1) PRYMARY KEY,
  SCSId INT REFERENCE Student(SId) NOT NULL,
  SCCId INT REFERENCE Curse(CId) NOT NULL
  SCHours INT NOT NULL
)
Соответствующие классы модели + соответствующий контекст модели. Все примитивно как совковая лопата.
Вы пытаетесь из лопаты соорудить какой-то синхрофазатрон. Логика CRUD для такой базы также примитивна. Перед записью в StudCurse проверяется есть ли такая парочка и дальше либо Insert либо Update.
10-15k форейнкеев ? И что ? А 15 млн не хотите ? 1000 запросов к базе в минуту ? А 1 млн не хотите ?
И все никак не томозит если верно спроектировано. У Вас - не верно.
Добавлено через 12 минут
Linq2 за Вас не построит никакой логики. Совсем. Она просто избавит Вас от необходимости "ручками" писать все эти select, update, insert, delete. А всю "требуху" в виде семантической увязки таблиц в запросах, соблюдение целостности БД (транзакционность пакетов изменений), предварение исключений и т.д. надо писать "ручками" в виде класса-репозитория или как-Вы-там-его-назовете.
Судя по всему, Вы не определились с самой бизнес-логикой СУБД, но уже лихо лепите таблицы, форейнкеи, праймари.. И уже озабочены "оптимизацией" Вашего велосипеда. С квадратными колесами, тремя рулями и без единого седла
Из вашего сообщения я понял только что код у меня велосипед с квадратными колёсами и т.д.
Что конкретно предлагаете изменить?

Чем отличается ваш SQL код от моего?
Зачем вы добавили в таблицу многие-ко-многим Id? Какой смысл в этом?

Цитата Сообщение от MsGuns Посмотреть сообщение
10-15k форейнкеев ? И что ? А 15 млн не хотите ? 1000 запросов к базе в минуту ? А 1 млн не хотите ?
Это риторический вопрос? или конкретно вы говорите, что может быть 15млн внешних ключей и 1млн запросов в минуту, и всё работает стабильно?
0
1497 / 1238 / 245
Регистрация: 04.04.2011
Сообщений: 4,363
18.02.2021, 14:44
Цитата Сообщение от BeginnerCoderCS Посмотреть сообщение
Чем отличается ваш SQL код от моего?
Не предполагает вложенных select`ов там, где они не нужны вовсе (+exists, тоже не к месту)

Цитата Сообщение от BeginnerCoderCS Посмотреть сообщение
Зачем вы добавили в таблицу многие-ко-многим Id? Какой смысл в этом?
Смысл в общих правилах 3N-нормализации - все постоянные таблицы должны иметь уникальные ID, они же Primary Key.
Это - правило. Которое позволит избежать множества ошибок и костылей. Каждая таблица - сущность. В том числе кросс-таблица, каждая запись которой является отношением, связкой между другими сущностями. Это - азы СУБД.
Цитата Сообщение от BeginnerCoderCS Посмотреть сообщение
что может быть 15млн внешних ключей и 1млн запросов в минуту, и всё работает стабильно?
Вполне. Если, конечно, не грузить сервер несуразными запросами со связками по не ключевым полям с многоэтажными Select, в каждом из которых по 100 Exists
0
9 / 6 / 3
Регистрация: 10.01.2020
Сообщений: 330
18.02.2021, 15:02  [ТС]
Цитата Сообщение от MsGuns Посмотреть сообщение
Смысл в общих правилах 3N-нормализации - все постоянные таблицы должны иметь уникальные ID, они же Primary Key.
Это - правило. Которое позволит избежать множества ошибок и костылей. Каждая таблица - сущность. В том числе кросс-таблица, каждая запись которой является отношением, связкой между другими сущностями. Это - азы СУБД.
Но ведь с такой таблицей
SQL
1
2
3
4
5
CREATE TABLE StudCurse (
  SCId INT IDENTITY(1,1) PRYMARY KEY,
  SCSId INT REFERENCE Student(SId) NOT NULL,
  SCCId INT REFERENCE Curse(CId) NOT NULL
)
Я смогу добавлять одинаковые пары (Student(SId) + Curse(CId)), а это будут дубли. И Id в этом случае никак не будет мной использоваться.

Первичный ключ у меня есть это (Student(SId) + Curse(CId)) и как раз не даёт добавлять уже имеющиеся пары.

Добавлено через 2 минуты
Цитата Сообщение от MsGuns Посмотреть сообщение
Не предполагает вложенных select`ов там, где они не нужны вовсе (+exists, тоже не к месту)
Объясните подробнее, чем ваша модель проще?
Что вы можете сделать в этой модели, чего не могу я в своей?
0
1497 / 1238 / 245
Регистрация: 04.04.2011
Сообщений: 4,363
18.02.2021, 17:12
Цитата Сообщение от BeginnerCoderCS Посмотреть сообщение
Я смогу добавлять одинаковые пары (Student(SId) + Curse(CId)), а это будут дубли.
Дублей не будет если перед вставкой/изменением проверять наличие такой пары. Как уже говорилось выше. Но если надо блокировать на уровне БЛ сервера, то есть уникальные индексы - повесьте такой на кросс-таблицу и будет Вам счастье.
Цитата Сообщение от BeginnerCoderCS Посмотреть сообщение
Объясните подробнее, чем ваша модель проще?
Что вы можете сделать в этой модели, чего не могу я в своей?
"Моя" модель - это классика. Ваша - грабли с костылем. На 100 записях они будут работать одинаково, на 1млн Ваша будет тормозить. К тому в Вашей модели есть много лишнего, начиная от запутанной логики изменений-вставки (одно чудо в [5] чего стоит), заканчивая тем, что модель у Вас не содержит коллекций, что требует дополнительной логики в составлении пакетов изменений, например при удалении студента надо удалить все записи кросс-таблицы со ссылками на него. В Вашем случае надо "ручками" писать запрос на удаление сначала из кросс, затем из таблицы студентов.

Самое же главное в том, что при Вашем подходе если в базе не две сущности, а 50, то код работы с Вашей БД будет чрезмерно нагружен лишней логикой (вроде той, что в [5] и в [3]), которой бы не было при "классике".

В целом же выбор за Вами, конечно. Можно ездить и без руля и сиденья. Подобных костыльных "баз" существует чуть больше, чем до фига. И многие работают

Добавлено через 25 минут
Поймите одну простую вещь.
1. Есть предметная область.
2. Есть сущности в этой предметной области.
3. У каждой сущности есть набор атрибутов: простых (атомарных) и сложных (тоже сущностей).
4. На атрибуты есть требования (тип данных, ограничения, уникальность..)
5. Сущности как-то связаны между собою определенными отношениями.

На основании всего этого создается Модель СУБД. Абстрактная. В ней описывается (формализуется) система правил, которая образует бизнес-логику СУБД.

Заметьте, пока ни слова ни про тип сервера, ни про какие-либо приложения.

Сама бизнес-логика может быть реализована на SQL-сервере, где для этого есть чуть больше, чем все (роли, пользователи, хранимки, ограничения, триггеры, каскады, UDF, View, службы и т.д.).

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

А можно всю БЛ реализовать на "клиенте", в какой-нибудь ORM. На сервере лишь таблицы. Без ключей, ограничений, типов данных (все стринги или двоичные) и т.д. Тогда вся "требуха" будет препарироваться в приложениях, точнее, в ORM, подключенных к приложениям. Кстати, можно обойтись вообще без SQL-сервера, данные хранить в папках в виде excel, txt, xml и т.д.)

Приведены две крайности, которые в жизни, возможно, и не существуют. А существует лишь их "симбиозы", комбинации. И вот для создания подобных комбинаций требуются и опыт, и знания, и, если угодно, искусство

Большинство новичков не понимают или не знают вышеописанного, и мостят "базы" как блины, сразу ваяя таблицы, которые потом как-то лепят друг к другу, постоянно правя как их структуру, так и количество.
Отчего и получают в итоге уродцев. С которыми и маются, бедолаги
0
9 / 6 / 3
Регистрация: 10.01.2020
Сообщений: 330
18.02.2021, 18:54  [ТС]
Цитата Сообщение от MsGuns Посмотреть сообщение
"Моя" модель - это классика. Ваша - грабли с костылем. На 100 записях они будут работать одинаково, на 1млн Ваша будет тормозить.
Различия наших моделей только в одной таблице.
Моя модель
SQL
1
2
3
4
5
6
7
CREATE TABLE `StudentCourse` (
    `StudentId` BIGINT(20) UNSIGNED NOT NULL,
    `CourseId` BIGINT(20) UNSIGNED NOT NULL,
    PRIMARY KEY (`StudentId`, `CourseId`),
    CONSTRAINT `FK_studentcourse_student` FOREIGN KEY (`StudentId`) REFERENCES `Student` (`Id`),
    CONSTRAINT `FK_studentcourse_course` FOREIGN KEY (`CourseId`) REFERENCES `Course` (`Id`)
);
Ваша модель (MySQL)
SQL
1
2
3
4
5
6
7
8
CREATE TABLE `StudentCourse` (
    `Id` BIGINT(20) UNSIGNED NOT NULL,
    `StudentId` BIGINT(20) UNSIGNED NOT NULL,
    `CourseId` BIGINT(20) UNSIGNED NOT NULL,
    PRIMARY KEY (`Id`),
    CONSTRAINT `FK_studentcourse_student` FOREIGN KEY (`StudentId`) REFERENCES `Student` (`Id`),
    CONSTRAINT `FK_studentcourse_course` FOREIGN KEY (`CourseId`) REFERENCES `Course` (`Id`)
);
Или я чего-то не понимаю?
Или вы куда-то не туда смотрите.

Цитата Сообщение от MsGuns Посмотреть сообщение
Дублей не будет если перед вставкой/изменением проверять наличие такой пары.
А что мне мешает в своей модели проверить дубли?
Но суть в том, что если другое приложение в этот момент вставит такую же строку, то у меня будет исключение, которое обработается, и пойдёт работать дальше.
А в вашей модели, будет дублирующая запись. Если есть возможность сделать запрет дублирования в СУБД, зачем полагаться только на код? Как по мне, то это костыль.
Есть реляционная БД, но мне почему-то ненужно использовать её возможности?


Цитата Сообщение от MsGuns Посмотреть сообщение
К тому в Вашей модели есть много лишнего, начиная от запутанной логики изменений-вставки (одно чудо в [5] чего стоит), заканчивая тем, что модель у Вас не содержит коллекций
Вы говорите о модели SQL? или кода C#?
Что это такое чудо в [5] и (вроде той, что в [5] и в [3])?

Цитата Сообщение от MsGuns Посмотреть сообщение
Самое же главное в том, что при Вашем подходе если в базе не две сущности, а 50, то код работы с Вашей БД будет чрезмерно нагружен лишней логикой (вроде той, что в [5] и в [3]), которой бы не было при "классике".
А вы пишете что у меня грабли с костылем, а у вас классика.
Что значит классика?
У нас отличие таблицы только в том, что у меня PRIMARY KEY (`StudentId`, `CourseId`), а у вас PRIMARY KEY (`Id`),

Какая же это классика, если вы предлагаете проверять кодом дубли и не используете ограничения СУБД?

Или о чём вы вообще говорите? Это такой троллинг? Взагали по загалям?

p.s. Я не пытаюсь никого обидеть, но не пойму о чём вы конкретно.
В соседних ветках, мне писали вещи с которыми я не был согласен, но внятно.
Я гуглил, изучал, и делал выводы, в чём-то соглашался, а в чём-то нет.
Но здесь я не пойму что вы пытаетесь мне доказать.

Мой код грабли с костылем? ОК.
Так покажите что конкретно не так? Покажите ваше искусство.
0
785 / 616 / 273
Регистрация: 04.08.2015
Сообщений: 1,713
19.02.2021, 10:03
Цитата Сообщение от BeginnerCoderCS Посмотреть сообщение
Различия наших моделей только в одной таблице.
В двух. Столбцу `TotalHours`не место в таблице Student.
0
9 / 6 / 3
Регистрация: 10.01.2020
Сообщений: 330
19.02.2021, 10:31  [ТС]
Цитата Сообщение от Igr_ok Посмотреть сообщение
В двух. Столбцу `TotalHours`не место в таблице Student.
Да в sql пропустил что Hours должен быть в Course.
Посмотрите под спойлером "Сущности C# для linq2db" первого сообщения.

В таблице `Course` есть `Hours` то есть сколько часов длится курс. (в Сущности C# для linq2db там есть, а в sql пропустил)
А в таблице `Student` есть `TotalHours` то есть общее время уже пройденных лекций, по всем курсам.



Я думал что в StudCurse `SCHours` попал по невнимательности.
Цитата Сообщение от BeginnerCoderCS Посмотреть сообщение
SQL
1
2
3
4
5
CREATE TABLE StudCurse (
  SCId INT IDENTITY(1,1) PRYMARY KEY,
  SCSId INT REFERENCE Student(SId) NOT NULL,
  SCCId INT REFERENCE Curse(CId) NOT NULL
  SCHours INT NOT NULL
)
А оказывается нет?

Ну тогда есть курс (5214, 'Биология', 25).
И этот курс с этим номером длится 25 часов для любого студента.
Добавив `SCHours` в таблицу `StudCurse` многие ко многим мы будем дублировать это число для всех студентов.

Это же не индивидуальный курс, а для многих студентов, тогда согласно принципам СУБД это значение должно храниться в одном экземпляре. Если вдруг добавится + 2 часа занятий, то нужно будет менять по всей таблице `StudCurse`?


А `TotalHours` это значение индивидуально на каждого студента, поэтому оно и в таблице Student.
0
785 / 616 / 273
Регистрация: 04.08.2015
Сообщений: 1,713
19.02.2021, 12:45
Цитата Сообщение от BeginnerCoderCS Посмотреть сообщение
Добавив `SCHours` в таблицу `StudCurse` многие ко многим мы будем дублировать это число для всех студентов.
Я не говорил, что решение MsGuns идеально)
Цитата Сообщение от BeginnerCoderCS Посмотреть сообщение
А `TotalHours` это значение индивидуально на каждого студента, поэтому оно и в таблице Student.
Это вычисляемое поле и ему там точно не место. Т.е. сумма часов студента должна вычисляться только при выводе данных.
0
9 / 6 / 3
Регистрация: 10.01.2020
Сообщений: 330
19.02.2021, 13:52  [ТС]
Цитата Сообщение от Igr_ok Посмотреть сообщение
Это вычисляемое поле и ему там точно не место. Т.е. сумма часов студента должна вычисляться только при выводе данных.
Это не просто сумма часов курсов на которые записан студент.
Это количество часов уже по факту пройденных заданий. Эти данные есть только на сервере.
Студент мог записаться на курс, но не ходить, или отходить 10 часов вместо 23 и т.д.

Цитата Сообщение от Igr_ok Посмотреть сообщение
Я не говорил, что решение MsGuns идеально)
Ну как минимум я уже не один
0
1497 / 1238 / 245
Регистрация: 04.04.2011
Сообщений: 4,363
19.02.2021, 15:03
Цитата Сообщение от Igr_ok Посмотреть сообщение
Я не говорил, что решение MsGuns идеально)
Это вообще не решение Это простейший пример, на котором видна связка таблиц через кросс-таблицу.

Что же касается решения, то для него слишком мало информации. Прежде всего потому, что нет внятного описания Модели БД в целом. Что за сущности "Курсы" ? Возможно, есть ситуация когда один и тот же предмет читается разное количество часов для разных специальностей (факультетов). Тогда нужна еще одна таблица - таблица предметов. Возможно, потребуется и таблица преподавателей, ведь курсы по предметам читают именно они.
Задача описана слишком абстрактно, о каких "решениях" может тут идти речь ?

Добавлено через 16 минут
Что же касается "посещений" - тут явно не хватает еще 2-х таблиц:
SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
CREATE TABLE AbsReason ( -- справочник причин отсутствия
   AId INT IDENTITY(1,1) PRIMARY KEY,
   ACode INT NOT NULL,
   ADescription VARCHAR(MAX) NOT NULL,
)
CREATE TABLE Visits ( -- - таблица посещений
   VId INT IDENTITY(1,1) PRIMARY KEY,
   VSId INT REFERENCE Student(SId) NOT NULL,
   VCId INT REFERENCE Curse(CId) NOT NULL,
   VAId INT REFERENCE AbsReason(AId) NULL,
   VDate DATETIME NOT NULL,
   VHourse INT NOT NULL,
   VRoom VARCHAR(MAX) NULL
)
Все суммы (посещаемость студента, посещаемость курсов и т.д.) определяются запросами и хранить их в БД не имеет смысла.
В этом случае кросс-таблица StudCurse вовсе не нужна.

Добавлено через 16 минут
А вот примерное решение:

1. Сущность "Предметы" (ID, наименование, дисциплина, краткая характеристика и т.д.)
2. Сущность "Кафедра" (ID, наименование, адрес: корпус, комната, телефон, ФИО завкафедрой и т.д.)
3. Сущность "Факультет" (ID, наименование, вид обучения и т.д.)
4. Сущность "Преподаватель" (ID, ФИО, телефон, адрес, ссылка на кафедру и т.д.)
5. Сущность "Группа" (ID, номер, наименование, ссылка на базовую кафедру, ссылка на факультет, ссылка на препода-куратора и т.д.)
6. Сущность "Студент" (ID, ссылка на группу, ФИО, телефон, адрес и т.д.)
7. Сущность "Курс" (ID, ссылка на предмет, ссылка на преподавателя, ссылка на факультет, кол-во часов и т.д.)
8. Сущность "Лекция" (ID, ссылка на курс, ссылка на преподавателя - лекцию может читать не "основной" преподаватель, дата-время, количество часов, аудитория и т.д.
9. Сущность "Посещение" (ID, ссылка на лекцию, ссылка на студента, причина отсутствия и т.д.)

В этой модели нет кросс-таблицы за ненадобностью. При этом можно получить любую информацию о посещениях (пропусках, в т.ч. по определенной причине) как по факультету, кафедре, группе, студенту - с одной стороны, так и по предмету (курсу), предметам (курсам), преподавателям - с другой.

Добавлено через 12 минут
Интерфейс для регистрации посещений/отсутствий предельно прост и ясен:
Из расписания (таблица курсов по факультету или общая) выбирается нужная запись и из списка групп выбирается группа (группы). По всему этому строится таблица в памяти, отображаемая через грид. В гриде список студентов с указанием групп, который можно сортировать/фильтровать. В гриде единственная колонка для редактирования, куда вводится только причина отсутствия. Если студент присутствовал, его ячейка в этой колоне остается пустой.
Когда все заполнено (точнее, отмечены отсутствующие) жмется кнопка "Сохранить", по которой все записи грида инсертами заливаются в таблицы "Лекция" (одна запись) и "Посещение" (для каждого студента).
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
inter-admin
Эксперт
29715 / 6470 / 2152
Регистрация: 06.03.2009
Сообщений: 28,500
Блог
19.02.2021, 15:03
Помогаю со студенческими работами здесь

The INSERT statement conflicted with the FOREIGN KEY
В чем собственно у меня ошибка?(На 1 скрине) п. 4.11 Правил форума

Конфликт инструкции INSERT с ограничением Foreign Key
Здравствуйте! В БД есть таблица, в которой содержатся внешние ключи с разрешенным значением NULL. Для добавления данных используется...

Конфликт инструкции INSERT с ограничением FOREIGN KEY
Здравствуйте! Есть две таблицы которые связаны ключом, при создании строки с с этим ключом SQL жалуется: Сообщение 547, уровень 16,...

Конфликт инструкции INSERT с ограничением FOREIGN KEY
вот код using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; ...

Конфликт инструкции INSERT с ограничением FOREIGN KEY
Добрый день. Помогите, пожалуйста, решить проблему. Есть БД, 2 таблицы: public class Client { public string Name {...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
20
Ответ Создать тему
Новые блоги и статьи
SDL3 для Web (WebAssembly): Реализация движения на Box2D v3 - трение и коллизии с повёрнутыми стенами
8Observer8 20.02.2026
Содержание блога Box2D позволяет легко создать главного героя, который не проходит сквозь стены и перемещается с заданным трением о препятствия, которые можно располагать под углом, как верхнее. . .
Конвертировать закладки radiotray-ng в m3u-плейлист
damix 19.02.2026
Это можно сделать скриптом для PowerShell. Использование . \СonvertRadiotrayToM3U. ps1 <path_to_bookmarks. json> Рядом с файлом bookmarks. json появится файл bookmarks. m3u с результатом. # Check if. . .
Семь CDC на одном интерфейсе: 5 U[S]ARTов, 1 CAN и 1 SSI
Eddy_Em 18.02.2026
Постепенно допиливаю свою "многоинтерфейсную плату". Выглядит вот так: https:/ / www. cyberforum. ru/ blog_attachment. php?attachmentid=11617&stc=1&d=1771445347 Основана на STM32F303RBT6. На борту пять. . .
Камера Toupcam IUA500KMA
Eddy_Em 12.02.2026
Т. к. у всяких "хикроботов" слишком уж мелкий пиксель, для подсмотра в ESPriF они вообще плохо годятся: уже 14 величину можно рассмотреть еле-еле лишь на экспозициях под 3 секунды (а то и больше),. . .
И ясному Солнцу
zbw 12.02.2026
И ясному Солнцу, и светлой Луне. В мире покоя нет и люди не могут жить в тишине. А жить им немного лет.
«Знание-Сила»
zbw 12.02.2026
«Знание-Сила» «Время-Деньги» «Деньги -Пуля»
SDL3 для Web (WebAssembly): Подключение Box2D v3, физика и отрисовка коллайдеров
8Observer8 12.02.2026
Содержание блога Box2D - это библиотека для 2D физики для анимаций и игр. С её помощью можно определять были ли коллизии между конкретными объектами и вызывать обработчики событий столкновения. . . .
SDL3 для Web (WebAssembly): Загрузка PNG с прозрачным фоном с помощью SDL_LoadPNG (без SDL3_image)
8Observer8 11.02.2026
Содержание блога Библиотека SDL3 содержит встроенные инструменты для базовой работы с изображениями - без использования библиотеки SDL3_image. Пошагово создадим проект для загрузки изображения. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru