Перебор списка C# и выполнение инструкции SELECT в базе данных для каждого элемента – более эффективный способ?

У меня есть SQL-запрос, точно такой, как описан в этом посте. Подводя итог, он будет читать все Carboards, указанные со смещением. Этот запрос выполняется в базе данных MariaDB.
Теперь у меня есть картоны (ID, Cardboard_number, DateTime, Production_LineNumber) в моей программе C# ASP.NET. Мне нужно прочитать все производственные процессы, в которых использовался каждый картон (в основном производство.start <= картон.датавремя <= производство.конец).

Таблица Productions в базе данных Oracle выглядит следующим образом (я не создавал эту таблицу сам и не могу ничего изменить, поскольку она также используется в производственной программе):

  • PRODUCTION_NUMBER (NUMBER)
  • ПОСНР (НОМЕР)
  • ДАТАВРЕМЯ (МЕТКА ВРЕМЕНИ(6))
  • PROCESS_ACTIVE (VARCHAR2(1))
  • PRODUCTION_LINE (NUMBER)

Столбец PROCESS_ACTIVE используется как флаг: при запуске производственного процесса вставляется строка с DATETIME=sysdate, PROCESS_ACTIVE = 1, а остановленная указывается с sysdate, PROCESS_ACTIVE = 0.

Я создал запрос, который суммирует мои процессы, поэтому я получаю начало и конец для каждого PRODUCTION_NUMBER и POSNR:

SELECT * 
FROM
(
    SELECT PRODUCTION_NUMBER, POSNR, PROCESS_ACTIVE, PRODUCTION_LINE, DATETIME as START, LEAD(DATETIME, 1, SYSDATE) OVER (ORDER BY DATETIME ASC) AS END
    FROM ssq_lauftr
    GROUP BY PRODUCTION_NUMBER, POSNR, PROCESS_ACTIVE, PRODUCTION_LINE, DATETIME
    ORDER BY DATETIME 
)
WHERE PROCESS_ACTIVE = 1

Я перебираю все картоны, полученные из MariaDB, в моем коде C# и выполняю этот запрос для каждого картона (где картон — это внедренный объект из моего цикла C#):

SELECT * 
FROM
(
    SELECT PRODUCTION_NUMBER, POSNR, PROCESS_ACTIVE, PRODUCTION_LINE, DATETIME as START, LEAD(DATETIME, 1, SYSDATE) OVER (ORDER BY DATETIME ASC) AS END
    FROM ssq_lauftr
    WHERE PRODUCTION_LINE = cardboard.PRODUCTION_LINENUMBER and DATETIME <= cardboard.DATETIME
    GROUP BY PRODUCTION_NUMBER, POSNR, PROCESS_ACTIVE, PRODUCTION_LINE, DATETIME
    ORDER BY DATETIME 
)
WHERE PROCESS_ACTIVE = 1 AND cardboard.DATETIME <= END

который работает довольно хорошо с небольшим количеством картонов.

Проблема с этим решением в том, что если у меня много Cardboards, вся функция чтения всех Productions займет слишком много времени. Есть ли способ (например, с помощью PLSQL) сделать этот процесс более эффективным? Приведенный выше оператор SQL работает довольно быстро, но перебор списка на C# и добавление результатов в мой ProductionSet сильно замедляют работу приложения.

Редактировать:
Картоны хранятся в MariaDB, а продукция — в базе данных Oracle. Мой текущий код C# для описанной функциональности выглядит следующим образом:

Cardboards = [.. _mariaDB.Cardboards.FromSql($@"
    SET @cb_num = {request.Cardboard_Number};
    select *
    from (
        SELECT *,
        sum(Cardboard_Number LIKE CONCAT(@cb_num, '%')) over (
            partition by ProductionLine_Number
            order by timestamp, id
            rows BETWEEN {CardboardOffset} preceding AND {CardboardOffset} following
        ) matches
        from cardboards
    ) cardboards_with_matches
    where matches
")];

HashSet<Production> productionSet = [];

for(int i = 0; i < Cardboards.Count(); i++)
{
    productionSet.UnionWith(_oracleDB.Production.FromSqlRaw($@"
        SELECT * 
        FROM
        (
            SELECT PRODUCTION_NUMBER, POSNR, PROCESS_ACTIVE, PRODUCTION_LINE, DATETIME as START, LEAD(DATETIME, 1, SYSDATE) OVER (ORDER BY DATETIME ASC) AS END
            FROM Productions
            WHERE PRODUCTION_LINE = {Cardboards.ElementAt(i).ProductionLine_Number} and {Cardboards.ElementAt(i).DateTime} <= cardboard.DATETIME
            GROUP BY PRODUCTION_NUMBER, POSNR, PROCESS_ACTIVE, PRODUCTION_LINE, DATETIME
            ORDER BY DATETIME 
        )
        WHERE PROCESS_ACTIVE = 1 AND {Cardboards.ElementAt(i).DateTime} <= END
    "));
}

Скрипка MariaDB - Скрипка для картона
Скрипт OracleDB - Fiddle for Productions (не работает в скрипте, но работает в Oracle SQL Developer, я, честно говоря, не знаю, в чем проблема)

Чтобы разобрать это, в основном, когда пользователь ищет картон «WDL-005943998-1», ожидаемым результатом будут данные со всего картона (поиск в MariaDB) и производство с PRODUCTION_NUMBER = 461618, как DateTime картона. находится между началом и концом производства, И производство находилось на той же линии, на которой сканировался картон.

Обратите внимание, что одно и то же производство может быть выполнено несколько раз, но с разными временными метками (например, производство было приостановлено).

Вы упоминаете MariaDB на протяжении всего вопроса, но вы отметили Oracle (и упомянули PLSQL в последнем абзаце). Это две разные СУБД; какой из них вы используете?

MT0 28.03.2024 11:36

@MT0 На самом деле и то, и другое. Таблица Cardboards хранится в MariaDB, а таблица Productions — в базе данных Oracle.

philipp8230 28.03.2024 11:38

Вы можете создать ссылку на базу данных Oracle на MariaDB, а затем напрямую использовать таблицу; вместо того, чтобы пытаться прочитать таблицу MariaDB на C#, а затем попытаться внедрить ее в запрос Oracle. (Драйверы базы данных Oracle C# не поддерживают передачу коллекций SQL - т.е. типов данных массива - и только по какой-то неизвестной причине поддерживают передачу ассоциативных массивов - т.е. типов данных сопоставления. Если вы хотите передать массив значений через C#, вам нужно передать ассоциативный массив и преобразовать его в коллекцию в базе данных, поэтому лучше соединить две базы данных и сделать это напрямую).

MT0 28.03.2024 11:40

Пожалуйста, отредактируйте вопрос и включите минимально воспроизводимый пример с соответствующим кодом C#.

MT0 28.03.2024 11:40

@MT0 Конечно, я обновлю свой вопрос. На самом деле можно было бы перенести таблицу Cardboards в БД Oracle. Прав ли я, полагая, что это решит мою проблему, потому что я могу использовать КУРСОР?

philipp8230 28.03.2024 11:44

Вы не предоставили операторы CREATE TABLE и INSERT для вашего примера данных и не указали ожидаемый результат для этого примера данных. Если у вас есть обе таблицы в одной БД (или обе доступны из Oracle через ссылку на базу данных), я бы не использовал курсор и циклы, потому что это очень неэффективно; Я бы попробовал использовать JOIN между двумя таблицами. Но чтобы понять, как это реализовать, потребуется больше знать о ваших данных и ожидаемом результате.

MT0 28.03.2024 11:47

Почему вы используете две разные СУБД?

Jonas Metzler 28.03.2024 11:47

Копирование картонов в Oracle по гетерогенной ссылке на базу данных (через шлюз) позволит вам получить все необходимые данные за одно выполнение SQL. Это был бы самый эффективный метод. Если вы не можете этого сделать, по крайней мере создайте временную таблицу в Oracle и вставьте в нее массив картонных ключей из вашей программы на C#. Затем выполните этот единственный SQL, который соединит данные Oracle с временной таблицей. PL/SQL здесь не является решением, он позволяет избежать отдельного запроса и обратного соединения для каждого нужного вам картона, что также позволяет использовать другие методы соединения, кроме вложенных циклов для объемных данных.

Paul W 28.03.2024 12:20

Кроме того, убедитесь, что вы используете переменные привязки в своем запросе, а не строку, объединяющую значения в виде литералов. Вы хотите устранить время синтаксического анализа как основного виновника. По-прежнему вероятно, что при миллионах вызовов в первую очередь виновата задержка вашей сети, но просто чтобы убедиться, что анализ не усугубляет проблему. Я мало что помню о C#, но ваш код выглядит так, как будто он объединяет строки, и это может быть проблемой. Узнайте, как использовать переменные связывания.

Paul W 28.03.2024 12:25

@MT0 Я обновил свой вопрос и предоставил некоторые примеры данных, надеюсь, это поможет.

philipp8230 28.03.2024 12:41

@JonasMetzler Честно говоря, это только в процессе разработки. Мы планируем перенести базу данных MariaDB на Oracle, как только мы начнем ее работу, возможно, было бы лучше напрямую разрабатывать ее с помощью СУБД Oracle, но мы не знали лучшего варианта.

philipp8230 28.03.2024 12:42

@PaulW Я ценю совет с переменными привязки, обязательно рассмотрю его.

philipp8230 28.03.2024 12:48

Скрипт Oracle не работает, поскольку Oracle допускает только один оператор в каждой команде (что помогает предотвратить атаки SQL-инъекций), поэтому вы не можете использовать CREATE, INSERT и SELECT в одной команде. Переместите каждую команду в отдельное поле и все заработает.

MT0 28.03.2024 13:32

@PaulW В качестве дополнительной информации к вашему совету по переменным привязки, согласно MSDC: «Хотя этот синтаксис может выглядеть как обычная интерполяция строк C#, предоставленное значение заключено в DbParameter, а сгенерированное имя параметра вставлено туда, где {0} был указан заполнитель. Это делает FromSql безопасным от атак SQL-инъекций и эффективно и правильно отправляет значение в базу данных». - Learn.microsoft.com/en-us/ef/core/querying/…

philipp8230 29.03.2024 11:39
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
14
69
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Если у вас есть две таблицы: обе в базе данных Oracle; или иметь доступ к базе данных MariaDB из базы данных Oracle (т.е. через ссылку на базу данных), тогда вы сможете найти все связи между двумя таблицами, используя запрос:

WITH production_bounds (production_number, posnr, process_active, production_line, start_dt, end_dt) AS (
  SELECT PRODUCTION_NUMBER,
         POSNR,
         PROCESS_ACTIVE,
         PRODUCTION_LINE,
         DATETIME as start_dt,
         LEAD(DATETIME, 1, SYSTIMESTAMP) OVER (
           PARTITION BY production_line
           ORDER BY DATETIME ASC
         ) AS end_dt
  FROM   productions
)
SELECT p.production_number,
       p.posnr,
       p.process_active,
       p.production_line,
       p.start_dt,
       p.end_dt,
       c.id,
       c.cardboard_number
FROM   production_bounds p
       INNER JOIN cardboard c
       ON     p.production_line = c.productionline_number
          AND c.date_time BETWEEN p.start_dt AND p.end_dt

Что для примера данных (с обеими таблицами в Oracle):

CREATE TABLE cardboard
(
    id int,
    Cardboard_Number varchar2(100),
    date_Time TIMESTAMP(0),
    ProductionLine_Number int
);

INSERT INTO cardboard VALUES 
(2,'WDL-005943998-1', TIMESTAMP '2014-08-05 10:03:32', 1),
(4,'spL1ml82N4o',TIMESTAMP '2024-02-29 17:13:54', 1),
(5,'WDL-005943998-1',TIMESTAMP '2024-03-01 09:44:42', 1),
(6,'WDL-005943998-1',TIMESTAMP '2024-03-01 10:34:57', 1),
(7,'950024027237',TIMESTAMP '2024-03-01 10:44:57', 1),
(8,'950024027237',TIMESTAMP '2024-03-01 10:52:57', 1),
(9,'WDL-005943998-1',TIMESTAMP '2024-03-01 13:58:43', 2),
(10,'WDL-005943998-1',TIMESTAMP '2024-03-01 13:58:46', 2),
(11,'spL1ml82N4o',TIMESTAMP '2024-03-01 14:09:43', 2),
(12,'WDL-005943998-1',TIMESTAMP '2024-03-12 15:48:36', 2);

CREATE TABLE Productions
(   
  PRODUCTION_NUMBER NUMBER, 
  POSNR NUMBER, 
  DATETIME  TIMESTAMP(0), 
  PROCESS_ACTIVE VARCHAR2(1),
  PRODUCTION_LINE NUMBER
);

BEGIN
INSERT INTO Productions(PRODUCTION_NUMBER, POSNR, DATETIME, PROCESS_ACTIVE, PRODUCTION_LINE)
  VALUES (461793, 1, TO_TIMESTAMP('2014-08-04 09:01:41', 'YYYY-MM-DD HH24:MI:SS'), '1', 1);
INSERT INTO Productions(PRODUCTION_NUMBER, POSNR, DATETIME, PROCESS_ACTIVE, PRODUCTION_LINE)
  VALUES (461793, 1, TO_TIMESTAMP('2014-08-04 11:01:41', 'YYYY-MM-DD HH24:MI:SS'), '0', 1);
INSERT INTO Productions(PRODUCTION_NUMBER, POSNR, DATETIME, PROCESS_ACTIVE, PRODUCTION_LINE)
  VALUES (461618, 2, TO_TIMESTAMP('2014-08-05 10:01:41', 'YYYY-MM-DD HH24:MI:SS'), '1', 1);
INSERT INTO Productions(PRODUCTION_NUMBER, POSNR, DATETIME, PROCESS_ACTIVE, PRODUCTION_LINE)
  VALUES (461619, 2, TO_TIMESTAMP('2014-08-05 10:02:46', 'YYYY-MM-DD HH24:MI:SS'), '1', 2);
INSERT INTO Productions(PRODUCTION_NUMBER, POSNR, DATETIME, PROCESS_ACTIVE, PRODUCTION_LINE)
  VALUES (461618, 2, TO_TIMESTAMP('2014-08-05 10:05:09', 'YYYY-MM-DD HH24:MI:SS'), '0', 1);
INSERT INTO Productions(PRODUCTION_NUMBER, POSNR, DATETIME, PROCESS_ACTIVE, PRODUCTION_LINE)
  VALUES (461619, 2, TO_TIMESTAMP('2014-08-05 10:07:46', 'YYYY-MM-DD HH24:MI:SS'), '0', 2);
INSERT INTO Productions(PRODUCTION_NUMBER, POSNR, DATETIME, PROCESS_ACTIVE, PRODUCTION_LINE)
  VALUES (461818, 1, TO_TIMESTAMP('2014-08-14 22:53:12', 'YYYY-MM-DD HH24:MI:SS'), '1', 1);
INSERT INTO Productions(PRODUCTION_NUMBER, POSNR, DATETIME, PROCESS_ACTIVE, PRODUCTION_LINE)
  VALUES (461818, 1, TO_TIMESTAMP('2014-08-14 23:25:30', 'YYYY-MM-DD HH24:MI:SS'), '0', 1);
END;
/

Выходы:

PRODUCTION_NUMBER ПОСНР ПРОЦЕСС_АКТИВ КОНВЕЙЕР START_DT END_DT ИДЕНТИФИКАТОР CARDBOARD_NUMBER 461618 2 1 1 2014-08-05 10:01:41. 2014-08-05 10:05:09.000000 2 WDL-005943998-1 461818 1 0 1 2014-08-14 23:25:30. 2024-03-28 12:53:28.569815 4 spL1ml82N4o 461818 1 0 1 2014-08-14 23:25:30. 2024-03-28 12:53:28.569815 5 WDL-005943998-1 461818 1 0 1 2014-08-14 23:25:30. 2024-03-28 12:53:28.569815 6 WDL-005943998-1 461818 1 0 1 2014-08-14 23:25:30. 2024-03-28 12:53:28.569815 7 950024027237 461818 1 0 1 2014-08-14 23:25:30. 2024-03-28 12:53:28.569815 8 950024027237 461619 2 0 2 2014-08-05 10:07:46. 2024-03-28 12:53:28.569815 9 WDL-005943998-1 461619 2 0 2 2014-08-05 10:07:46. 2024-03-28 12:53:28.569815 10 WDL-005943998-1 461619 2 0 2 2014-08-05 10:07:46. 2024-03-28 12:53:28.569815 11 spL1ml82N4o 461619 2 0 2 2014-08-05 10:07:46. 2024-03-28 12:53:28.569815 12 WDL-005943998-1

Если вы хотите искать активные строки с определенным cardboard_number, добавьте следующие фильтры:

WITH production_bounds (production_number, posnr, process_active, production_line, start_dt, end_dt) AS (
  SELECT PRODUCTION_NUMBER,
         POSNR,
         PROCESS_ACTIVE,
         PRODUCTION_LINE,
         DATETIME as start_dt,
         LEAD(DATETIME, 1, SYSTIMESTAMP) OVER (
           PARTITION BY production_line
           ORDER BY DATETIME ASC
         ) AS end_dt
  FROM   productions
)
SELECT p.production_number,
       p.posnr,
       p.process_active,
       p.production_line,
       p.start_dt,
       p.end_dt,
       c.id,
       c.cardboard_number
FROM   production_bounds p
       INNER JOIN cardboard c
       ON     p.production_line = c.productionline_number
          AND c.date_time BETWEEN p.start_dt AND p.end_dt
WHERE  cardboard_number = 'WDL-005943998-1'
AND    process_active = 1

Выходы:

PRODUCTION_NUMBER ПОСНР PROCESS_ACTIVE КОНВЕЙЕР START_DT END_DT ИДЕНТИФИКАТОР CARDBOARD_NUMBER 461618 2 1 1 2014-08-05 10:01:41. 2014-08-05 10:05:09.000000 2 WDL-005943998-1

рабочий пример

Извините, что спрашиваю, но почему у картона есть «границы»? На картоне имеется только одна временная метка, которая должна находиться между началом и окончанием производства.

philipp8230 28.03.2024 13:45

Другие вопросы по теме