У меня есть SQL-запрос, точно такой, как описан в этом посте. Подводя итог, он будет читать все Carboards, указанные со смещением. Этот запрос выполняется в базе данных MariaDB.
Теперь у меня есть картоны (ID, Cardboard_number, DateTime, Production_LineNumber) в моей программе C# ASP.NET. Мне нужно прочитать все производственные процессы, в которых использовался каждый картон (в основном производство.start <= картон.датавремя <= производство.конец).
Таблица Productions в базе данных Oracle выглядит следующим образом (я не создавал эту таблицу сам и не могу ничего изменить, поскольку она также используется в производственной программе):
Столбец 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 картона. находится между началом и концом производства, И производство находилось на той же линии, на которой сканировался картон.
Обратите внимание, что одно и то же производство может быть выполнено несколько раз, но с разными временными метками (например, производство было приостановлено).
@MT0 На самом деле и то, и другое. Таблица Cardboards хранится в MariaDB, а таблица Productions — в базе данных Oracle.
Вы можете создать ссылку на базу данных Oracle на MariaDB, а затем напрямую использовать таблицу; вместо того, чтобы пытаться прочитать таблицу MariaDB на C#, а затем попытаться внедрить ее в запрос Oracle. (Драйверы базы данных Oracle C# не поддерживают передачу коллекций SQL - т.е. типов данных массива - и только по какой-то неизвестной причине поддерживают передачу ассоциативных массивов - т.е. типов данных сопоставления. Если вы хотите передать массив значений через C#, вам нужно передать ассоциативный массив и преобразовать его в коллекцию в базе данных, поэтому лучше соединить две базы данных и сделать это напрямую).
Пожалуйста, отредактируйте вопрос и включите минимально воспроизводимый пример с соответствующим кодом C#.
@MT0 Конечно, я обновлю свой вопрос. На самом деле можно было бы перенести таблицу Cardboards в БД Oracle. Прав ли я, полагая, что это решит мою проблему, потому что я могу использовать КУРСОР?
Вы не предоставили операторы CREATE TABLE
и INSERT
для вашего примера данных и не указали ожидаемый результат для этого примера данных. Если у вас есть обе таблицы в одной БД (или обе доступны из Oracle через ссылку на базу данных), я бы не использовал курсор и циклы, потому что это очень неэффективно; Я бы попробовал использовать JOIN
между двумя таблицами. Но чтобы понять, как это реализовать, потребуется больше знать о ваших данных и ожидаемом результате.
Почему вы используете две разные СУБД?
Копирование картонов в Oracle по гетерогенной ссылке на базу данных (через шлюз) позволит вам получить все необходимые данные за одно выполнение SQL. Это был бы самый эффективный метод. Если вы не можете этого сделать, по крайней мере создайте временную таблицу в Oracle и вставьте в нее массив картонных ключей из вашей программы на C#. Затем выполните этот единственный SQL, который соединит данные Oracle с временной таблицей. PL/SQL здесь не является решением, он позволяет избежать отдельного запроса и обратного соединения для каждого нужного вам картона, что также позволяет использовать другие методы соединения, кроме вложенных циклов для объемных данных.
Кроме того, убедитесь, что вы используете переменные привязки в своем запросе, а не строку, объединяющую значения в виде литералов. Вы хотите устранить время синтаксического анализа как основного виновника. По-прежнему вероятно, что при миллионах вызовов в первую очередь виновата задержка вашей сети, но просто чтобы убедиться, что анализ не усугубляет проблему. Я мало что помню о C#, но ваш код выглядит так, как будто он объединяет строки, и это может быть проблемой. Узнайте, как использовать переменные связывания.
@MT0 Я обновил свой вопрос и предоставил некоторые примеры данных, надеюсь, это поможет.
@JonasMetzler Честно говоря, это только в процессе разработки. Мы планируем перенести базу данных MariaDB на Oracle, как только мы начнем ее работу, возможно, было бы лучше напрямую разрабатывать ее с помощью СУБД Oracle, но мы не знали лучшего варианта.
@PaulW Я ценю совет с переменными привязки, обязательно рассмотрю его.
Скрипт Oracle не работает, поскольку Oracle допускает только один оператор в каждой команде (что помогает предотвратить атаки SQL-инъекций), поэтому вы не можете использовать CREATE
, INSERT
и SELECT
в одной команде. Переместите каждую команду в отдельное поле и все заработает.
@PaulW В качестве дополнительной информации к вашему совету по переменным привязки, согласно MSDC: «Хотя этот синтаксис может выглядеть как обычная интерполяция строк C#, предоставленное значение заключено в DbParameter, а сгенерированное имя параметра вставлено туда, где {0} был указан заполнитель. Это делает FromSql безопасным от атак SQL-инъекций и эффективно и правильно отправляет значение в базу данных». - Learn.microsoft.com/en-us/ef/core/querying/…
Если у вас есть две таблицы: обе в базе данных 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;
/
Выходы:
Если вы хотите искать активные строки с определенным 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
Выходы:
Извините, что спрашиваю, но почему у картона есть «границы»? На картоне имеется только одна временная метка, которая должна находиться между началом и окончанием производства.
Вы упоминаете MariaDB на протяжении всего вопроса, но вы отметили Oracle (и упомянули PLSQL в последнем абзаце). Это две разные СУБД; какой из них вы используете?