- PVSM.RU - https://www.pvsm.ru -
Одна компания даёт кандидатам на должность программиста 1С тестовое задание из шести коротких задач и предупреждает: «Задачи довольно простые, но практика показывает, что 80% кандидатов понимают их неправильно или, как минимум, учитывают не все случаи».
Разберу третью задачу из этого задания вместе с настоящим решением одного кандидата. Это классическая задача сравнения множеств, её знает любой, кто писал SQL. Интересно в ней то, что почти все решения проверяют только половину условия, а рабочее решение кандидата ломается на пустом множестве — случае, о котором никто не думает, пока он не сломает запрос.
Есть справочник «ТырымПырым» с табличной частью ТЧ, в которой есть реквизиты «Контрагент» и «Договор». Есть эталонная таблица значений такой же структуры, как ТЧ. Нужно написать запрос, который найдет элементы справочника, у которых ТЧ в точности совпадает с эталонной таблицей значений. Строки в обеих таблицах уникальны.
Пример. Эталон из двух строк: «Ромашка / Договор 1», «Лютик / Договор 2». Элементы справочника:
А — Ромашка / Договор 1, Лютик / Договор 2 — подходит.
Б — те же строки в другом порядке — подходит.
В — только Ромашка / Договор 1 — не хватает строки.
Г — обе строки эталона плюс Василёк / Договор 3 — лишняя строка.
Д — Ромашка / Договор 1, Лютик / Договор 5 — строк столько же, но одна другая.
Е — табличная часть пустая.
Запрос должен вернуть А и Б. А если эталон пустой, запрос должен вернуть Е: пустая табличная часть в точности совпадает с пустой таблицей.
На этих шести элементах видна каждая ошибка.
Для тех, кто пришёл из SQL, несколько особенностей.
Табличная часть в запросе — отдельная таблица Справочник.ТырымПырым.ТЧ с полями Ссылка (владелец), НомерСтроки и реквизитами. У элемента без строк в ней нет ни одной записи — найти его можно, только начав с Справочник.ТырымПырым и присоединив ТЧ левым соединением.
Таблицу значений можно передать параметром, но выбирать из неё можно только в запросе с ПОМЕСТИТЬ, то есть сначала положить во временную таблицу. Колонки должны быть типизированы.
Пакетный запрос — несколько запросов через ;, каждый следующий видит временные таблицы предыдущих.
Агрегатные функции пропускают NULL. КОЛИЧЕСТВО(Поле) считает только значения, отличные от NULL, а СУММА от одних NULL возвращает NULL, а не ноль. Эта деталь решит задачу.
Операций EXCEPT и INTERSECT в языке запросов 1С нет. Классический приём «A минус B пусто и B минус A пусто» приходится собирать из соединений.
Каждая строка эталона есть в ТЧ, и каждая строка ТЧ есть в эталоне. Почти все ошибки — проверка только одного из них.
ВЫБРАТЬ
ТЧ.Ссылка
ИЗ
Справочник.ТырымПырым.ТЧ КАК ТЧ
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Эталон КАК Эталон
ПО ТЧ.Контрагент = Эталон.Контрагент
И ТЧ.Договор = Эталон.Договор
СГРУППИРОВАТЬ ПО
ТЧ.Ссылка
ИМЕЮЩИЕ
КОЛИЧЕСТВО(*) = &КоличествоСтрокЭталона
Вернёт А, Б и Г. У Г есть обе строки эталона, а лишнюю строку «Василёк» внутреннее соединение просто отбросило. Г содержит всё нужное и ещё немного — как чемодан, собранный «на всякий случай».
Обратная проверка: выбрать элементы без единой строки вне эталона. Вернёт А, Б, В, а если начинать со справочника — ещё и Е. У В нет лишних строк, у него просто не хватает одной. У пустого Е лишних строк нет тем более.
... ПО ТЧ.НомерСтроки = Эталон.НомерСтроки
И ТЧ.Контрагент = Эталон.Контрагент
И ТЧ.Договор = Эталон.Договор
Не найдёт Б: строки те же, но в другом порядке. В условии про порядок ничего нет, а строки названы уникальными — значит, речь о совпадении наборов. Если вы считаете, что порядок важен, это допущение нужно записать явно.
Сравнить количества правильно, но собрать запрос от Справочник.ТырымПырым.ТЧ, а не от справочника. На эталоне из примера всё работает. При пустом эталоне правильный ответ — Е, а у него нет ни одной записи в таблице ТЧ. Запрос не увидит его в принципе.
Перебрать элементы в цикле и сравнить каждую ТЧ в коде. Работать будет, но условие просит запрос. Да и на десяти тысячах элементов цикл успеет надоесть.
Кандидат написал пакет из семи запросов (комментарии и имена его, включая опечатку):
ВЫБРАТЬ
Т.Контрагент КАК Контрагент,
Т.Договор КАК Договор
ПОМЕСТИТЬ Таблица
ИЗ
&Таблица КАК Т
;
// ссылки, у которых хотя бы одна запись ТЧ совпадает с эталонной таблицей
ВЫБРАТЬ РАЗЛИЧНЫЕ
Т1.Ссылка КАК Ссылка
ПОМЕСТИТЬ Кандидаты
ИЗ
Справочник.ТырымПырым.ТЧ КАК Т1
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Таблица КАК Т2
ПО Т1.Контрагент = Т2.Контрагент
И Т1.Договор = Т2.Договор
;
// дополняем эталонную таблицу ссылкой из таблицы кандидатов
ВЫБРАТЬ
Т1.Ссылка КАК Ссылка,
Т2.Контрагент КАК Контрагент,
Т2.Договор КАК Договор
ПОМЕСТИТЬ ДополненнаяТаблиа
ИЗ
Кандидаты КАК Т1,
Таблица КАК Т2
;
// ссылки, в ТЧ которых есть строки, которых нет в эталонной таблице
ВЫБРАТЬ РАЗЛИЧНЫЕ
Т1.Ссылка КАК Ссылка
ПОМЕСТИТЬ ОтсевСлева
ИЗ
Справочник.ТырымПырым.ТЧ КАК Т1
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Кандидаты КАК Т3
ПО Т1.Ссылка = Т3.Ссылка
ЛЕВОЕ СОЕДИНЕНИЕ ДополненнаяТаблиа КАК Т2
ПО Т1.Контрагент = Т2.Контрагент
И Т1.Договор = Т2.Договор
И Т1.Ссылка = Т2.Ссылка
ГДЕ
Т2.Ссылка ЕСТЬ NULL
;
// ссылки, в ТЧ которых нет строк, присутствующих в эталонной таблице
ВЫБРАТЬ РАЗЛИЧНЫЕ
Т2.Ссылка КАК Ссылка
ПОМЕСТИТЬ ОтсевСправа
ИЗ
Справочник.ТырымПырым.ТЧ КАК Т1
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Кандидаты КАК Т3
ПО Т1.Ссылка = Т3.Ссылка
ПРАВОЕ СОЕДИНЕНИЕ ДополненнаяТаблиа КАК Т2
ПО Т1.Контрагент = Т2.Контрагент
И Т1.Договор = Т2.Договор
И Т1.Ссылка = Т2.Ссылка
ГДЕ
Т1.Ссылка ЕСТЬ NULL
;
// удаляем из таблицы кандидатов ссылки,
// в ТЧ которых есть строки, которых нет в эталонной таблице
ВЫБРАТЬ
Т1.Ссылка КАК Ссылка
ПОМЕСТИТЬ _Кандидаты
ИЗ
Кандидаты КАК Т1
ЛЕВОЕ СОЕДИНЕНИЕ ОтсевСлева КАК Т2
ПО Т1.Ссылка = Т2.Ссылка
ГДЕ
Т2.Ссылка ЕСТЬ NULL
;
// удаляем из новой таблицы кандидатов ссылки,
// в ТЧ которых нет строк, присутствующих в эталонной таблице
ВЫБРАТЬ
Т1.Ссылка КАК Ссылка
ИЗ
_Кандидаты КАК Т1
ЛЕВОЕ СОЕДИНЕНИЕ ОтсевСправа КАК Т3
ПО Т1.Ссылка = Т3.Ссылка
ГДЕ
Т3.Ссылка ЕСТЬ NULL
По шагам:
Кандидаты — элементы, у которых совпала хотя бы одна строка с эталоном.
ДополненнаяТаблица — каждому кандидату приписан полный эталон: «вот какие строки у него должны быть».
ОтсевСлева — кандидаты с лишней строкой.
ОтсевСправа — кандидаты, которым не хватает строки.
Результат — кандидаты минус оба отсева.
По сути это и есть EXCEPT в обе стороны, собранный из левых и правых соединений. Главное кандидат сделал: проверил оба условия, ошибки 1 и 2 обошёл, порядок строк не учитывается. На эталоне из примера запрос работает.
Чего не хватает:
Пустой эталон. Кандидаты отбираются внутренним соединением с эталоном. Эталон пустой — кандидатов нет — запрос ничего не возвращает, хотя должен вернуть Е. Та же ошибка 4, только пришедшая с другой стороны.
Декартово произведение. ДополненнаяТаблица — все кандидаты, умноженные на все строки эталона. Кандидатом становится любой элемент, у которого совпала хоть одна строка. Эталон на 100 строк с популярным контрагентом, который встречается в 10 000 элементах, — и во временной таблице миллион строк.
Объём. Семь запросов и три уровня отсева. Логика верная, но проверяющему придётся потратить время, чтобы в этом убедиться.
Засчитают ли? Скорее да — с вопросом «А что вернёт ваш запрос, если эталон пустой?». Хороший ответ: «Ничего, и это ошибка — вот как исправить».
Отдельно интересно, почему кандидат не заметил ошибку. Он сделал генератор тестовых данных: в каждый элемент добавляется минимум две строки. Пустых табличных частей в его базе не было вообще, и ошибка с пустым эталоном не могла проявиться. Тестовые данные строились с теми же предположениями, что и решение.
Идея: элемент подходит, если одновременно
строк в его ТЧ столько же, сколько в эталоне, и
совпавших с эталоном строк тоже столько же.
Почему этого достаточно. Строки уникальны, поэтому совпавшие строки — это пересечение ТЧ и эталона. Если пересечение по размеру равно и ТЧ, и эталону, оно совпадает с каждым из них — значит, ТЧ и эталон одинаковы.
На примере (в эталоне 2 строки):
А: в ТЧ 2, совпало 2 — да
Б: 2 и 2 — да
В: 1 и 1 — нет
Г: 3 и 2 — нет
Д: 2 и 1 — нет
Е: 0 и 0 — нет
При пустом эталоне нужны оба нуля — подходит только Е.
// Возвращает элементы справочника ТырымПырым, у которых табличная часть ТЧ
// совпадает с эталоном как набор строк: порядок строк не учитывается.
//
// Параметры:
// Эталон - ТаблицаЗначений - типизированные колонки Контрагент и Договор, строки уникальны
//
// Возвращаемое значение:
// Массив из СправочникСсылка.ТырымПырым
//
Функция ЭлементыСТакойЖеТЧ(Эталон) Экспорт
Запрос = Новый Запрос;
Запрос.УстановитьПараметр("Эталон", Эталон);
Запрос.УстановитьПараметр("КоличествоСтрокЭталона", Эталон.Количество());
Запрос.Текст =
"ВЫБРАТЬ
| Т.Контрагент КАК Контрагент,
| Т.Договор КАК Договор
|ПОМЕСТИТЬ Эталон
|ИЗ
| &Эталон КАК Т
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| Спр.Ссылка КАК Ссылка
|ИЗ
| Справочник.ТырымПырым КАК Спр
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ТырымПырым.ТЧ КАК ТЧ
| ПО (ТЧ.Ссылка = Спр.Ссылка)
| ЛЕВОЕ СОЕДИНЕНИЕ Эталон КАК Эталон
| ПО (Эталон.Контрагент = ТЧ.Контрагент)
| И (Эталон.Договор = ТЧ.Договор)
|
|СГРУППИРОВАТЬ ПО
| Спр.Ссылка
|
|ИМЕЮЩИЕ
| КОЛИЧЕСТВО(ТЧ.НомерСтроки) = &КоличествоСтрокЭталона
| И КОЛИЧЕСТВО(Эталон.Контрагент) = &КоличествоСтрокЭталона";
Возврат Запрос.Выполнить().Выгрузить().ВыгрузитьКолонку("Ссылка");
КонецФункции
Как это работает:
Запрос начинается со справочника. Элемент без строк после левого соединения даёт одну строку, где все поля ТЧ равны NULL, — и не теряется.
КОЛИЧЕСТВО(ТЧ.НомерСтроки) — сколько строк в ТЧ. Для пустой ТЧ это 0: NULL не считается.
КОЛИЧЕСТВО(Эталон.Контрагент) — сколько строк ТЧ нашли пару в эталоне. Строки без пары дают NULL и не считаются.
Одно сравнение с &КоличествоСтрокЭталона отсекает и лишние строки, и недостающие.
Почему КОЛИЧЕСТВО, а не СУММА(1) по совпавшим строкам: у Е при пустом эталоне сумма по одним NULL даст NULL, а NULL = 0 — не «истина». Элемент потерялся бы ровно в том случае, ради которого всё затевалось.
По сравнению с решением кандидата: один запрос вместо шести после помещения эталона, каждая строка ТЧ соединяется с эталоном один раз, пустой эталон обрабатывается без особого случая.
Уникальность строк. Представьте ТЧ из двух одинаковых строк «Ромашка / Договор 1» и эталон «Ромашка / Договор 1», «Лютик / Договор 2». Строк в ТЧ — 2, совпавших — тоже 2 (обе нашли пару), и запрос скажет «совпадает», хотя Лютика в ТЧ нет. Условие гарантирует уникальность, но на собеседовании стоит сказать вслух, что решение на неё опирается. Без этой гарантии придётся сравнивать сгруппированные строки с количеством повторов.
Вопросы к заказчику, которые показывают, что вы заметили неоднозначности:
Порядок строк. Я считаю ТЧ набором. Если порядок важен — в соединение добавляется НомерСтроки, в эталон — колонка с номером.
Помеченные на удаление. Запрос их не отбрасывает. Если не нужны — ГДЕ НЕ Спр.ПометкаУдаления.
Группы справочника. В иерархическом справочнике у групп обычно нет строк ТЧ. При пустом эталоне они попадут в результат — их стоит отсечь условием НЕ Спр.ЭтоГруппа.
Эталон совпадает с ТЧ одного элемента — этот элемент.
Те же строки в другом порядке — найден.
ТЧ — подмножество эталона — не найден.
ТЧ — надмножество эталона — не найден.
Строк столько же, одна отличается — не найден.
Несколько элементов с одинаковой ТЧ — найдены все.
Пустой эталон — элементы с пустой ТЧ.
Пустой эталон, элементов с пустой ТЧ нет — пустой результат без ошибок.
Справочник пустой — пустой результат.
Задача хороша тем, что ответы раскладываются по уровням. Ошибка 1 или 2 — кандидат не сформулировал, что значит «совпадает». Решение как у кандидата — понимает суть, но не думает о пустом множестве и объёме данных. Один запрос от справочника, оговорка про уникальность и сам названный пустой эталон — то, что нужно.
Лучший вопрос к любому решению этой задачи: «А если эталон пустой?»
Автор: Maraton1185
Источник [1]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/null/461622
Ссылки в тексте:
[1] Источник: https://habr.com/ru/articles/1104156/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1104156
Нажмите здесь для печати.