|
21.11.2007, 21:08 | #1 |
Moderator
|
Задачка о задвоении серийников
Думаю, что то, о чем сейчас поведаю, тоже можно рассмотреть как вариант. Будет и группировка, и сортировка - и практически "на месте", т.е. без утомительного создания новых структур.
Подход иллюстрируется на небольшой практической задачке, которую мне пришлось выполнить вчера. В нашей компании складской учет в Аксапте ведется по партиям. Если какая-то номенклатура учитывается по серийным номерам, то комбинация "серийный номер + номер партии" должна быть уникальна по каждому из номеров в отдельности, т.е. один и тот же серийный номер не может встречаться (комбинироваться) с разными номерами партий - только с одной. Так должно быть. И в подавляющем большинстве случаев так оно и есть. Но, однако, не абсолютно во всех случаях. По каким-то причинам образовались дублирования: одному серийнику соответствуют более, чем одна партия. Причины сейчас выясняются. Для обнаружения дубликатов был написан следующий запрос на классическом SQL (для Oracle): Код: SELECT Tmp01.InventSerialId AS "Серийный номер", Tmp02.Cnt AS "Кол-во повторений", --// ...этого серийника для разных номеров партий Tmp01.InventBatchId AS "Номер партии" FROM (SELECT InventSerialId, InventBatchId FROM InventDim INNER JOIN InventTrans ON InventDim.InventDimId = InventTrans.InventDimId WHERE InventSerialId <> CHR(2) --// CHR(2) - аксаптовский null для Oracle GROUP BY InventSerialId, InventBatchId ) Tmp01 --// уникальные сочетания серийника и партии, встречающиеся в InventTrans INNER JOIN (SELECT InventSerialId, COUNT(*) AS Cnt FROM (SELECT InventSerialId, InventBatchId FROM InventDim INNER JOIN InventTrans ON InventDim.InventDimId = InventTrans.InventDimId WHERE InventSerialId <> CHR(2) GROUP BY InventSerialId, InventBatchId) --// ЕЩЕ РАЗ увы! :( : уникальные сочетания серийника и партии, встречающиеся в InventTrans GROUP BY InventSerialId HAVING COUNT(*) <> 1 ) Tmp02 --// уникальные серийники из уникальных сочетаний серийник+партия ON Tmp01.InventSerialId = Tmp02.InventSerialId ORDER BY Tmp01.InventSerialId, Tmp01.InventBatchId 1. Из таблицы InventDim (Складская аналитика), связанной по полю InventDimId с таблицей InventTrans (Складские проводки), группировкой выбираются уникальные комбинации серийных номеров и номеров партий (поля InventSerialId, InventBatchId). 2. Далее из получившейся выборки выбираются (извиняюсь за тавтологию!) уникальные серийные номера (группировкой уже только по InventSerialId) и для каждого серийного номера подсчитывается кол-во различающихся номеров партий (count); при этом отбрасываются строки с count = 1, так как в данном случае они нам не интересны. 3. Наконец, из выборки п.1 выбираются записи, в которых встречаются серийные номера, обнаруженные выборкой п.2. Записи сортируются, чтобы две различные партии, соответствующие одному серийнику, соседствовали в этом окончательном списке. В принципе задачу обнаружения задвоений можно было бы считать законченной и приступать к поиску причин их возникновения, но захотелось попробовать реализовать этот запрос средствами Аксапты. И как-нибудь эдак..."прикольненько", чтобы не только результат, но и удовольствие от решения получить . После перебора возможных способов я остановился на варианте с "превращением" постоянной таблицы во временную (и не один раз!) при помощи setTmp (справка: kerndoc://Classes/xRecord/setTmp) и с использованием свободных в данный момент полей практически без усилий созданных временных таблиц для хранения промежуточных вычислений. Умудрился даже похранить целое (count) в текстовом поле, потому что в таблице InventDim не оказалось подходящих целочисленных полей, кроме как несвободного святого RecId. В общем, на мой взгляд, получилось действительно "прикольненько". Спешу поделиться джобом-примером, выводящим в окно infolog список проблемных серийных номеров: X++: static void KKu_FindDupleInventSerialId(Args _args) { InventDim inventDim; // промежуточные вычисления будут выполняться на временных клонах таблицы inventDim InventDim inventDimTmp01; // уникальные InventSerialId, InventBatchId InventDim inventDimTmp02; // уникальные InventSerialId InventTrans inventTrans; int rowCounter; ; // --- Шаг 1. Уникальные InventSerialId, InventBatchId inventDimTmp01.setTmp(); rowCounter = 0; ttsbegin; while select InventSerialId, InventBatchId from inventDim group by InventSerialId, InventBatchId exists join inventTrans where inventDim.InventDimId == inventTrans.InventDimId { if (inventDim.inventSerialId) { rowCounter++; inventDimTmp01.inventDimId = strfmt('%1', rowCounter); // фиктивное заполнение обязательного поля inventDimTmp01.inventSerialId = inventDim.inventSerialId; inventDimTmp01.inventBatchId = inventDim.inventBatchId; inventDimTmp01.doInsert(); // для обхода метода insert основной таблицы, который может быть перекрыт } } ttscommit; // --- Шаг 2. Искомые уникальные InventSerialId c Count(*) > 1 inventDimTmp02.setTmp(); rowCounter = 0; ttsbegin; while select InventSerialId, count(RecId) from inventDimTmp01 group by InventSerialId { if( inventDimTmp01.RecId != 1 ) { rowCounter++; inventDimTmp02.inventDimId = strfmt('%1', rowCounter); // фиктивное заполнение обязательного поля inventDimTmp02.inventSerialId = inventDimTmp01.inventSerialId; inventDimTmp02.inventBatchId = strfmt('%1', inventDimTmp01.RecId); // используем свободное строковое поле для хранения Count inventDimTmp02.doInsert(); } } ttscommit; // --- Шаг 3. Искомые проблемные InventSerialId, повторяющиеся c разными номерами партий info('Серийный номер --- Кол-во повторений -- Номер партии'); info('===================================================='); while select inventDimTmp02 order by inventSerialId join inventDimTmp01 order by inventBatchId where inventDimTmp02.inventSerialId == inventDimTmp01.inventSerialId { info( strfmt('%1 --- %2 --- %3', inventDimTmp01.inventSerialId, inventDimTmp02.inventBatchId, // в этом поле хранится Сount inventDimTmp01.inventBatchId ) ); } } * Возможность НЕ создавать в АОТ новые временные таблицы. * Возможность многократного временного клонирования исходной основной таблицы и последующая связь клонов в операторах select (while select) как между собой, так и с другими таблицами. ПРИМЕЧАНИЕ: если в вашей системе нет проблемы задвоения серийников или не ведется учёт по партиям, а работу джоба проверить хочется, то просто замените в нем условие if( inventDimTmp01.RecId != 1 ) на условие if( rowCounter < 100 ) |
|
|
Опции темы | Поиск в этой теме |
Опции просмотра | |
|