1с левое соединение первые 1. Соединения в запросах

01.11.2019 Безопасность

Соединение — одна из наиболее частых операций, выполняемых над базами данных. Соединения используются для того, что бы поставить в соответствие строки из одной таблицы строкам в другой. Соответствие производится по значению одного из полей, которое мы будем называть ключевым.

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

Существует четыре вида соединений таблиц, которые обрабатывает механизм запросов 1С:Прсдприятия: Внутреннее, Левое внешнее. Правое внешнее, Полное Внешнее (или. проще. Внутреннее, Левое, Правое, Полное).

Внутреннее соединение

[ВНУТРЕННЕЕ] СОЕДИНЕНИЕ означает, что из обеих исходных таблиц – источников данных в результат запроса необходимо включить только те комбинации записей, которые соответствуют указанному условию. Остальные записи в результат не попадают.Ключевое слова ВНУТРЕННЕЕ можно не указывать вообще, оно повышает наглядность и удобочитаемость текста запроса.

Самый простейший вид соединения — внутреннее. В этом случае запрос просто находит пары строк с совпадающим значением» ключа (в данном примере, как и во всех последующих, в качестве ключевого поля используется поле «Контрагент»).

Левое (Правое) соединение

ЛЕВОЕ [ВНЕШНЕЕ] СОЕДИНЕНИЕ означает, что в результат запроса надо включить комбинации записей из обеих исходных таблиц, которые соответствуют указанному условию. Но, в отличие от внутреннего соединения, в результат запроса надо включить также еще и записи из первого (указанного слева от слова СОЕДИНЕНИЕ) источника, для которых не найдено соответствующих условию записей из второго источника. Таким образом, в результат запроса будут включены все записи из первого источника; они будут соединены с записями из второго источника при выполнении указанного условия. Строки результата запроса, для которых не найдено соответствующих условию записей из второго источника, будут содержать NULL в полях, формируемых на основании записей из этого источника.

Ситуация осложняется, когда источники совпадают не полностью. ‘Го есть, в одной таблице есть запись с некоторым значением ключа, а в другой такой записи нет. На схеме показана ситуация, когда запись есть в таблице контрагентов, но нет в таблице продаж. Это означает, что некий контрагент ничего у нас не покупал, хотя в справочнике контрагентов он у нас имеется (например, попросил человек выписать ему счёт на оплату, а покупать передумал; вполне жизненная ситуация). В этом случае в выборке на месте отсутствующей записи появится значение Null.

Исходные таблицы:

Полное соединение

ПРАВОЕ [ВНЕШНЕЕ] СОЕДИНЕНИЕ означает, что в результат запроса надо включить комбинации записей из обеих исходных таблиц, которые соответствуют указанному условию. Кроме того, в результат запроса надо включить также еще и записи из второго (указанного справа от слова СОЕДИНЕНИЕ) источника, для которых не найдено соответствующих условию записей из первого источника.Таким образом, в результат запроса будут включены все записи из второго источника; они будут соединены с записями из первого источника при выполнении указанного условия. Строки результата запроса, для которых не найдено соответствующих условию записей из первого источника, будут содержать NULL в полях, формируемых на основании записей из этого источника.

Полное внешнее соединение, это, как ясно уже из названия, дальнейшее развитие Левого (или Правого) соединений. При организации полного соединения важно учитывать такое обстоятельство: при соединении данного вида в запрос попадают все записи из обеих таблиц. Иначе говоря, значение ключевого ноля нам нужно будет получать и из левой, и из правой таблиц. Обратите внимание: обязательно из обеих таблиц! Один из вариантов решения такой: создать для этого дополнительное поле, в котором делать проверку на Null. Если ключ в одной из таблиц равен Null, тогда нужно брать его значение из другой таблицы.

Поскольку полное соединение несколько сложнее других видов соединений, немного изменим наш пример. Теперь нам нужно получить не просто развёрнутый список продаж, а список всех действующих контрагентов.

Исходные таблицы:

Закупки
Контрагент Сумма
Иванов 4000
Андреев 3500
Продажи
Контрагент Сумма
Иванов 5000
Петров 7500
Сидоров 15000

Таблица после полного соединения, со всеми полями исходных таблиц:

Действующие контрагенты
КонтрагентЗакупки СуммаЗакупки КонтрагентПродажи СуммаПродажи
Иванов 4000 Иванов 5000
Null Null Петров 7500
Null Null Сидоров 15000
Андреев 3500 Null Null

Окончательный вид таблицы, в которой контрагенты сведены в одно поле:

Действующие контрагенты
Контрагент СуммаЗакупки СуммаПродажи
Иванов 4000 5000
Петров 7500
Сидоров 15000
Андреев 3500

Ниже приводится вариант соответствующего запроса. Обратите внимание, что данные по контрагентам мы получаем не просто из документов, а из подзапросов. В них мы выполняем группировку по контрагентам таким образом, что бы каждый контрагент был представлен в выборке но одному разу. И, естественно, не забываем про Null:

ВЫБРАТЬ ЕСТЬNULL(Приход.Контрагент. Расход.Контрагент) КАК Контрагент, ЕСТЬNULL(Приход.Сумма, 0) КАК СуммаЗакупки, ECTЬNULL(Pacxoд.Сумма, 0) КАК СуммаПродажи ИЗ (ВЫБРАТЬ Приходная.Контрагент КАК Контрагент, СУММА(Приходная.Сумма) КАК Сумма ИЗ Документ.Приходная КАК Приходная СГРУППИРОВАТЬ ПО Приходная.Контрагент) КАК Приход ПОЛНОЕ СОЕДИНЕНИЕ (ВЫБРАТЬ Расходная.Контрагент КАК Контрагент, СУММА(Расходная.Сумма) КАК Сумма ИЗ Документ.Расходная КАК Расходная СГРУППИРОВАТЬ ПО Расходная.Контрагент) КАК Расход ПО Приход.Контрагент = Расход.Контрагент

Когда мы хотим видеть данные из нескольких таблиц одновременно, т.е. собрать несколько таблиц в одну возникает понятие соединения таблиц и связей между ними. Соединения бывают четырех типов:

  • левое;
  • правое,
  • внутреннее;
  • полное.

Каждый тип мы рассмотрим на абстрактном примере. Имеется 2 таблицы, в первой храним описательную информацию о номенклатуре, во второй о ее остатках:

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

Левое соединение

Используя левое соединение мы говорим системе, что в результате хотим видеть все записи из левой таблицы и записи из правой удовлетворяющие условию связи. Допустим мы связываем таблицы по полю товар с условием равно, тогда получим таблицу вида:

Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Товар,
| Номенклатура.Цвет КАК ЦветНоменклатура,
| Остатки.Цвет КАК ЦветОстатки,
| Остатки.Количество
|ИЗ

";

Для стула не нашлось сопоставлений из таблицы остатков, поэтому поля заполнились значениями NULL, которые обязательно нужно обработать функцией ЕСТЬNULL, см. Функции языка запросов 1С 8.

Левое соединение работает примерно как цикл в цикле - берется первая запись из левой таблицы и пробегаются все записи из правой на предмет удовлетворения условию связи. Затем берется вторая запись из левой таблицы и т.д. Если вдруг условию связи удовлетворяют несколько записей из правой таблицы, то в результирующую таблицу будет добавлено несколько строк (по количеству удачных связей).Как видим, полученная таблица не информативна, данные не отражают реальную суть, поэтому лучше связать эти таблицы по двум полям: Товар и Цвет, только на этот раз обработаем NULLы:

Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Товар,
| Номенклатура.Цвет,
| ЕСТЬNULL(Остатки.Количество, 0) КАК Количество
|ИЗ
| Номенклатура КАК Номенклатура
| ЛЕВОЕ СОЕДИНЕНИЕ Остатки КАК Остатки
| ПО Номенклатура.Товар = Остатки.Товар

Правое соединение

Правое соединение по сути ничем не отличается от левого. Если поменять таблицы местами, то правое соединение превратится в левое, более того, при использовании конструктора система сама преобразует все правые соединения в левые.

Внутреннее соединение

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

Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Товар,
| Номенклатура.Цвет,
| Остатки.Количество КАК Количество
|ИЗ
| Номенклатура КАК Номенклатура
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ Остатки КАК Остатки
| ПО Номенклатура.Товар = Остатки.Товар
| И Номенклатура.Цвет = Остатки.Цвет";

Полное соединение

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

Задач на эту тему может быть много, давайте попробуем решить одну из них. Наша организация является дилером 2 мебельных фабрик: "Заря" и "Рассвет". Ассортимент со стоимостью каждой из фабрик хранится в разных таблицах. Необходимо составить единый прайс лист, причем брать в него продукцию по минимальной цене:

Применим полное соединение с выборкой всех полей, связывать будем по товару:

Запрос.Текст =
"ВЫБРАТЬ
| НоменклатураЗаря.Товар КАК ТоварЗаря,
| НоменклатураЗаря.Цена КАК ЦенаЗаря,
| НоменклатураРассвет.Товар КАК ТоварРассвет,
| НоменклатураРассвет.Цена КАК ЦенаРассвет
|ИЗ

Это не совсем то, что нам нужно, давайте соединим поле товар в одно и обработаем NULLы:

Запрос.Текст =
"ВЫБРАТЬ
//конструкция ЕСТЬNULL рассматривалась в разделе функции языка запросов
//если цена не определена, то инициализируем ее
//почему 1000000 см. пояснения ниже
| ЕСТЬNULL(НоменклатураЗаря.Цена, 1000000) КАК ЦенаЗаря,
| ЕСТЬNULL(НоменклатураРассвет.Цена, 1000000) КАК ЦенаРассвет
|ИЗ
| НоменклатураЗаря КАК НоменклатураЗаря
| ПОЛНОЕ СОЕДИНЕНИЕ НоменклатураРассвет КАК НоменклатураРассвет
| ПО НоменклатураЗаря.Товар = НоменклатураРассвет.Товар";

Осталось только выбрать минимальную цену. Итоговый тект запроса будет выглядеть следующим образом:

Запрос.Текст =
"ВЫБРАТЬ
| ЕСТЬNULL(НоменклатураЗаря.Товар, НоменклатураРассвет.Товар) КАК Товар,
| ВЫБОР
| КОГДА ЕСТЬNULL(НоменклатураЗаря.Цена, 1000000) > ЕСТЬNULL(НоменклатураРассвет.Цена, 1000000)
| ТОГДА ЕСТЬNULL(НоменклатураРассвет.Цена, 1000000)
| ИНАЧЕ ЕСТЬNULL(НоменклатураЗаря.Цена, 1000000)
| КОНЕЦ КАК Цена
|ИЗ
| НоменклатураЗаря КАК НоменклатураЗаря
| ПОЛНОЕ СОЕДИНЕНИЕ НоменклатураРассвет КАК НоменклатураРассвет
| ПО НоменклатураЗаря.Товар = НоменклатураРассвет.Товар";

Если цена не определена (NULL), то ее необходимо инициализировать каким либо значением, иначе операция сравнения на больше/меньше вывалится с ошибкой. инициализируем цену нереально большой суммой, чтобы она "проиграла" в операции сравнения, ведь по условию задачи мы подбираем наименьшую цену.

← Функции языка запросов 1C 8 | Объединения в запросах 1С 8 →

А сейчас рассмотрим подробнее соединение таблиц.

Напомню, что видов соединений в языке запросов 1С8 может быть несколько, а именно: ЛЕВОЕ СОЕДИНЕНИЕ , ПРАВОЕ СОЕДИНЕНИЕ , ВНУТРЕННЕЕ СОЕДИНЕНИЕ , ПОЛНОЕ СОЕДИНЕНИЕ . Рассмотрим каждое из них подробно.

  • ЛЕВОЕ СОЕДИНЕНИЕ

    В случае левого соединения данные из одной таблицы выбираются полностью, а из таблицы, которая присоединяется справа, выбираются только те записи для которых выполняется одно или несколько условий по которым эти таблицы соединяются. Из этого предложения конечно же мало, что можно понять, поэтому разберем пример.
    Есть таблица «Товары»:

    Сформируем эту таблицу в консоли запросов 1С с помощью вот такого запроса:

    ВЫБРАТЬ "001" КАК КодТовара, "Яблоки" КАК Наименование ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ "002", "Апельсины" ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ "003", "Мандарины"

    А еще есть таблица «Страны» вот такого вида:

    КодТовара Страна
    001 Россия
    002 Турция
    003 Марокко

    Также сконструируем ее запросом:

    ВЫБРАТЬ "001" КАК КодТовара, "Россия" КАК Страна ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ "002", "Турция" ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ "003", "Марокко"

    А теперь соединим эти две таблицы с помощью левого соединения, чтобы получить одну, в которой будет и код товара, и наименование, и страна. Для этого поместим наши таблицы во временные и в пакетном запросе выполним левое соединение.

    Записи будем связывать конечно же по полям КодТовара

    ВЫБРАТЬ "001" КАК КодТовара, "Яблоки" КАК Наименование ПОМЕСТИТЬ ВТ_Товар ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ "002", "Апельсины" ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ "003", "Мандарины" ; //////////////////////////////////////////////////////////////////////////////// ВЫБРАТЬ "001" КАК КодТовара, "Россия" КАК Страна ПОМЕСТИТЬ ВТ_Страна ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ "002", "Турция" ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ "003", "Марокко" ; //////////////////////////////////////////////////////////////////////////////// ВЫБРАТЬ ВТ_Товар.КодТовара, ВТ_Товар.Наименование, ВТ_Страна.Страна ИЗ ВТ_Товар КАК ВТ_Товар ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Страна КАК ВТ_Страна ПО ВТ_Товар.КодТовара = ВТ_Страна.КодТовара

    Можете скопировать этот код в консоль запросов и выполнив его получите вот такой результат:

    Здесь мы рассмотрели идеальную ситуацию, когда каждой записи в левой таблице соответствует одна запись в правой. Давайте немного усложним.

    Допустим для нашего товара у нас есть еще цены. Причем они могут меняться в зависимости от даты:

    КодТовара Дата Цена
    001 01.01.2016 120
    001 01.02.2016 150
    002 01.01.2016 200
    004 01.01.2016 350

    Здесь мы видим, что для товара с кодом 001 (Яблоки) у нас 2 записи в таблице цен. Для мандаринов цена отсутствует. И кроме того в ценах есть код товара 004, которого нет в таблице с товарами. Давайте выполним левое соединение для товаров и цен с условием связи по полю код и посмотрим, что у нас получится.

    ВЫБРАТЬ "001" КАК КодТовара, "Яблоки" КАК Наименование ПОМЕСТИТЬ ВТ_Товар ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ "002", "Апельсины" ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ "003", "Мандарины" ; //////////////////////////////////////////////////////////////////////////////// ВЫБРАТЬ "001" КАК КодТовара, "01.01.2016" КАК Дата, 120 КАК Цена ПОМЕСТИТЬ ВТ_Цена ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ "001", "01.02.2016", 150 ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ "002", "01.01.2016", 200 ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ "004", "01.01.2016", 350 ; //////////////////////////////////////////////////////////////////////////////// ВЫБРАТЬ ВТ_Товар.КодТовара, ВТ_Товар.Наименование, ВТ_Цена.Цена, ВТ_Цена.Дата ИЗ ВТ_Товар КАК ВТ_Товар ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Цена КАК ВТ_Цена ПО ВТ_Товар.КодТовара = ВТ_Цена.КодТовара

    После выполнения запроса видим вот такую картину:

    Проанализируем полученный результат. Из таблицы «Товары» были выбраны все записи, т.к. она у нас основная. Для мандаринов цена имеет значение NULL, т.к. записи с кодом 003 нет в таблице с ценами. А запись с кодом 004 из таблицы цен (а она у нас вспомогательная) была проигнорирована, т.к. для нее нет соответствия в таблице товаров. И как мы видим у нас появилось 2 записи с яблоками несмотря на то, что в таблице товаров запись с кодом 001 одна. В этом и заключается некоторое коварство соединений таблиц.

    И на эти грабли наступают как начинающие, так и опытные программисты. Когда запись в основной таблице одна, а в присоединяемой несколько записей удовлетворяют условию соединения, то в итоговой таблице количество записей будет равно количеству записей во вспомогательной таблице. В нашем случае произошло задвоение записи с яблоками. Этот момент надо обязательно учитывать при написании запросов и проверять при тестировании.

  • ПРАВОЕ СОЕДИНЕНИЕ

    Оно практически ничем не отличается от левого. В итоговую таблицу попадают все записи из правой таблицы, а из левой только те, для которых выполняется условие соединения. На практике правое соединение практически не используется и рассматривать его отдельно не будем. Есть даже небольшой забавный момент, связанный с правым соединением в конструкторе запросов 1С. Если при составлении запроса в конструкторе попытаться использовать правое соединение, то после нажатия кнопки ОК правое соединение будет автоматически преобразовано в левое.

  • ВНУТРЕННЕЕ СОЕДИНЕНИЕ

    В случае внутреннего соединения в итоговую таблицу попадут только те записи как из левой, так и из правой таблицы, для которых выполняется условие соединения. Если в нашем последнем запросе мы заменим строку

    ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Цена КАК ВТ_Цена

    ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Цена КАК ВТ_Цена

    то в итоге получим:

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

  • ПОЛНОЕ СОЕДИНЕНИЕ

    В случае полного соединения в итоговую таблицу попадут все записи из обоих таблиц.
    В нашем случае при использовании строки соединения

    ПОЛНОЕ СОЕДИНЕНИЕ ВТ_Цена КАК ВТ_Цена

    получим следующий результат

    На практике полное соединение используется достаточно редко, но иногда бывает необходимо.

Для тех кто только начинает работать с запросами 1С8 советую посмотреть как на закладке «Связи».

Привет Хабравчанам!

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

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


В качестве языка, который будет интегрироваться с 1С, я выбрал Питон. Он очень хорошо подходит для автоматизации процессов. Этому способствуют минималистичность синтаксиса (код набирается очень быстро), богатая стандартная библиотека (меньшая потребность в сторонних модулях), кроссплатформенность - с большой вероятностью, код, написанный в ОС Linix, успешно заработает в Windows.

Для начала обрисую данные, с которыми будем работать. Организация - энергосбытовая компания в дальневосточном регионе - обслуживает приблизительно 400 тыс. абонентов, база 1С на самописной конфигурации. Для каждого абонента хранятся его платежи, начисления, потребляемые услуги и схемы расчета, приборы учета, показания и множество других данных.

Когда-то в организации стояла программа, написанная на Дельфи и использующая в качестве БД MSSQL/Firebird. В те славные времена можно было подключиться к базе с помощью любого языка и совершить множество действий - выбрать абонентов-должников, разнести поступившие оплаты, зафиксировать показания приборов. Неудивительно, что коллекция скриптов, автоматизирующих рутину, постоянно росла. Программисты могли выполнять любые действия, не открывая саму программу.

Увы, с переходом на 1С халява кончилась - не стало возможности соединяться с базой напрямую. Вообще, платформа 1С сама по себе неделима и плохо идет на интеграцию с другими системами. Она, как говорится, вещь в себе. Загружая данные в 1С, следует помнить, что извлечь их оттуда будет не так просто. Но в виду того, что организации требовалось внедрять платежные системы и личный кабинет, было необходимо найти какое-то решение.

Основные задачи, стоявшие передо мной - это возможность быстрого получения данных по конкретному лицевому счету - ФИО, адрес, приборы учета, показания приборов, платежи, начисления. Плюс формирование документов - акта сверки, платежной квитанции. Итак, возможность прямого соединения с БД отсутствует - каждый, кто просматривал базу 1С на SQL-сервере, видел, что в массе таблиц вида aaa1, aaa2 разобраться трудно. А строить запросы с такими названиями таблиц и полей просто нереально. К тому же, многие таблицы 1С (особенно самые важные, вроде среза последних, остатков и оборотов) являются виртуальными и разбросаны по разным физическим таблицам, собираясь множественными джоинами. Это способ не подходит.

Платфома 1С предоставляет возможность соединяться с ней через COM-соединение. Подобно многим windows-программам, во время установки 1С в системе регистрируются два COM-объекта - Automation Server и COM Connector. С обоими объектами можно работать, используя язык, в котором предусмотрена поддержка COM-технологии.

Объект Automation Server - это приложение 1С, почти ничем не отличающееся от обычного клиентского приложения. Разница в том, что дополнительно появляется возможность программного управления экземпляром приложения. При работе с объектом COM Connector запускается облегченный вариант 1С-приложения, в котором недоступны формы, а так же функции и методы, имеющие отношение к интерфейсу и визуальным эффектам. Само приложение запускается в режиме «Внешнее соединение». Инициализация глобальных переменных (например, определение текущего пользователя и его настроек) должна выполняться в модуле внешнего соединения 1С. Если в режиме внешнего соединения в коде будет вызвана функция, не доступная в этом режиме, то будет вызвано исключение (которое будет передано в наш питон-скрипт). Вызов небезопасных функций следует обрамлять конструкциями вида

#Если НЕ ВнешнееСоединение Тогда Предупреждение("Привет!"); #КонецЕсли

Поскольку работа с COM-объектами - технология исключительно windows-only, то не удивительно, что в стандартной поставке Питона она отсутствует. Потребуется установить расширение - набор модулей, предоставляющих весь нужный функционал для программирования под Windows на Питоне. Его можно скачать в виде уже собранного exe-установщика. Само расширение предоставляет доступ к реестру, службам, ODBC, COM-объектам и т.д. В качестве альтернативы можно сразу поставить дистрибутив ActiveState Python , в котором расширение Win32 поставляется из коробки.

Некоторое время я экспериментировал с COM-соединением в разработке веб-приложений, в частности, личного кабинета. Были выявлены следующие минусы:

COM-соединение работает медленно. Низкая производительность - известный минус COM-технологии.
- Процесс установки соединения с 1С в зависимости от конфигурации может занять от 1 до 8 секунд (в моем случае - 6 секунд). Стоит ли говорить, что установка соединения на каждый запрос приведет к тому, то каждая страница будет загружаться 8 секунд.
- Поскольку веб-приложения на питоне работают как самостоятельные сервера, то предыдущий пункт можно компенсировать хранением соединения в некоторой глобальной переменной и в случае ошибки восстанавливать его. Как поддерживать соединение на PHP, я, честно говоря, еще не думал.
- Теряется кроссплатформенность веб-приложения.

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

Стратегия действий состоит в следующем: питон-скрипт соединяется с 1С, выполняет нужные запросы и выгружает данные в SQLite базу. К этой базе можно подключиться из Питона, PHP, Джавы. Большинство наших проектов работает на питоне, и так как я не выношу писать сырые SQL-запросы руками, то вся работа с базой SQLite выполняется через ORM SQLAlchemy. Потребовалось лишь описать структуру данных базы декларативном стиле:

From sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, Numeric, DateTime, Unicode, Boolean, LargeBinary, ForeignKey Base = declarative_base() class Abonent(Base): __tablename__ = "abonents" id = Column(Integer, primary_key=True) account = Column(Unicode(32), index=True) code = Column(Unicode(32)) address = Column(Unicode(512)) fio = Column(Unicode(256)) source = Column(Unicode(16)) psu = Column(Unicode(256)) tso = Column(Unicode(256)) np = Column(Unicode(256)) street = Column(Unicode(256)) house = Column(Integer) flat = Column(Integer) mro = Column(Unicode(256)) class Payment(Base): __tablename__ = "payments" # и так далее...

Теперь достаточно импортировать этот модуль в любой питон-проект, и можно работать с данными.

Предвижу ваш вопрос - «а почему SQLite»? Главная причина - база нужна только для чтения, поэтому проблемы с записью в SQLite нас волновать не должны. Во-вторых, формат этой этой СУБД удобен - ее удобнее просматривать (существуем множество бесплатных утилит, в том числе супер-расширение для FireFox). В-третьих, в некоторых случаях требовалось получить доступ к абонентам с тех машин, на которых нет соединения с MySQL-сервером. В таком случае достаточно скопировать файл SQLite-базы, и на этой машине будет доступ ко всей информации.

Выгрузка происходит раз в сутки ночью. Занесение данных в 1С можно автоматизировать таким же образом. Например, требуется фиксировать показания, оставленные абонентами на сайте личного кабинета. В этом случае опять соединяемся с 1С и программным методом создаем и проводим документ «Акт снятия показаний». Код я приведу чуть ниже.

Работа с COM-объектами в Питоне немного необычна. Во-первых, утрачивается «питоничность» кода - правила именования переменных и функций в 1С, мягко говоря, не соответствуют Дзену Питона. Во-вторых, всем известно, что объекты 1С зачастую именуются кириллическими символами, что вызовет проблемы при разработке на Питоне… но они решаемы. Предлагаю ознакомиться с кодом:

Import pythoncom import win32com.client V82_CONN_STRING = "Srvr=v8_server;Ref=v8_db;Usr=username;Pwd=megapass;" pythoncom.CoInitialize() V82 = win32com.client.Dispatch("V82.COMConnector").Connect(V82_CONN_STRING)

Как видно из кода, инициализируется клиент для работы с 1С. Определение COM-объекта происходит по имени «V82.COMConnector». Обратите внимание, что это название справедливо для платформы V8.2, если у вас версия 8.1, то имя будет «V81.COMConnector».

У инициализированного клиента мы вызываем метод Сonnect(), передавая ему строку подключения. Строка складывается из имени сервера, базы, пользователя и пароля. Полученный объект V82 хранит в себе соединение с приложением 1С. У него нет метода Disconnect() или чего-то в этом роде. Чтобы отключиться о базы, достаточно удалить объект из памяти функцией del() или присвоить переменной None.

Имея объект, можно обращаться к любым полям и методам глобального контекста 1С, оперировать универсальными объеками типа ТабличныйДокумент, ТаблицаЗначений и тд. Важно учесть, что при работе через COM-соединение 1С работает в режиме «Внешнее соединение». В нем недоступны любые функции для интерактивной работы, например, всплывающие диалоги, уведомления, и, что самое главное, формы. Уверен, что вы не раз проклянете разработчиков конфигурации, которые заключают самый важный функционал в процедуру Кнопка1Нажатие() в модуле формы документа.

Давайте поговорим о такой важдной вещи, как киррилические атрибуты. Не смотря на то, что 1С - двуязычная среда и для каждого русского метода есть англоязычный аналог, рано или поздно потребуется обратиться к киррилическому атрибуту. Если на языках PHP или VBSCript это не вызовет никаких проблем,

Set Con = CreateObject("v81.COMConnector") Set v8 =Con.Connect("строкаПодключения") Set СчетаМенеджер = v8.Документы.Счета.... Set СчетаЗапись= СчетаМенеджер.СоздатьЭлемент() СчетаЗапись.Контрагент = .... .... СчетаЗапись.Записать()

То код на питоне просто вылетит с ошибкой Syntax Error. Что же делать? Править конфигурацию? Нет, достаточно воспользоваться методами getattr и setattr. Передавая в эти функции COM-объект и кириллическое имя аттрибута, можно соответственно получать и устанавливать значения:

#coding=cp1251 catalog = getattr(V82.Catalogs, "ЛицевыеСчета")

Важно следующее: имена реквизитов, а так же параметры функций и методов должны передаваться в кодировке cp1251. Поэтому, чтобы заранее избежать путиницы с кодировками, имеет смысл объявить ее в начале файла: #coding=cp1251. После этого можно передавать строки, не волнуясь об их кодировке. Но! Все строки, полученные из 1С (результаты вызова функций, запросов), будут в кодировке UTF-8.

Пример кода, который выполняет в среде 1С запрос, перебирает результат и сохраняет в SQLite базу:

#coding=cp1251 q = """ ВЫБРАТЬ ЛицевыеСчета.Код КАК code, ЛицевыеСчета.Строение.НаселенныйПункт.Наименование + ", " + ЛицевыеСчета.КраткийАдрес КАК address, ЛицевыеСчета.Абонент.Наименование КАК fio, ЛицевыеСчета.Дивизион.Наименование КАК psu, ВЫРАЗИТЬ(ХарактеристикиЛицевыеСчетаСрезПоследних.Значение КАК Справочник.ТерриториальноСетевыеОрганизации).Наименование КАК tso, ЛицевыеСчета.Строение.НаселенныйПункт.Наименование КАК np, ЛицевыеСчета.Строение.Улица.Наименование КАК street, ЛицевыеСчета.Строение.Дом КАК house, ЛицевыеСчета.ОсновноеПомещение.НомерПомещения КАК flat, ЛицевыеСчета.Дивизион.Родитель.Наименование КАК mro ИЗ Справочник.ЛицевыеСчета КАК ЛицевыеСчета ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ХарактеристикиЛицевыеСчета.СрезПоследних(, ВидХарактеристики = ЗНАЧЕНИЕ(Справочник.ВидыХарактеристик.ТерриториальноСетеваяОрганизация)) КАК ХарактеристикиЛицевыеСчетаСрезПоследних ПО ЛицевыеСчета.Ссылка = ХарактеристикиЛицевыеСчетаСрезПоследних.Объект """ query = V82.NewObject("Query", q) selection = query.Execute().Choose() CONN = db.connect() CONN.query(models.Abonent).delete() while selection.Next(): abonent = models.Abonent() abonent.account = selection.code.strip() abonent.code = selection.code abonent.fio = selection.fio abonent.address = selection.address abonent.psu = selection.psu abonent.tso = selection.tso abonent.source = u"ASRN" abonent.np = selection.np abonent.street = selection.street abonent.house = selection.house abonent.flat = selection.flat abonent.mro = selection.mro CONN.add(abonent) CONN.commit()

Здесь CONN - это сессия соединения с SQLite-базой. Создается объект запроса query, заполняется его текст. Как было замечено выше, текст запроса должен быть в cp1251, для чего вначале объявлена кодировка. После выполнения запроса в базе удаляются все абоненты, чтобы не добавить дубли, затем добавляются в цикле и следует финальный комит.

При работе с запросами я выявил следующие правила.

Выбирая поля, назначайте им названия латиницей, будет гораздо удобнее обращаться к ним через селектор (точку), вместо getattr().
- Выбирайте только примитиные типы данных: строки, числа, дату и булево. Никогда не выбирайте ссылки на объект (документ, справочник)! В данном контексте ссылки вам абсолютно не нужны и даже вредны, потому что любое обращение к реквизиту или методу ссылки приведет к запросу через COM-соединение. Если обращаться к атрибутам ссылки в цикле, то это будет крайне медленно.
- Если вы выбираете поле типа Дата, то оно буде возвращено как объект PyTime. Это специальный тип данных для передачи даты-времени в COM-соединении. С ним не так удобно работать, как с привычным datetime. Если передать этот объект в int(), то вернется timestamp, из которого потом можно получить datetime методом fromtimestamp().

Теперь рассмотрим, как формируются печатные документы. Дело в том, что потребителю нужно предоставлять возможность скачивать заранее подготовленные документы, например, платежную квитанцию или акт сверки. Эти документы формируются в 1С в соответствии установленным требованиям, их реализация на Питоне займет много времени. Поэтому лучше сгенерировать документы в 1С и сохранять их в формат Excel.

Так, документ акта сверки генерируется специальной внешней обработкой. Для тех, кто не знаком с терминологией 1С: обработка - это автономная программа, имеющая свой модуль, формы, шаблоны, предназначенная для запуска в среде 1С. Необходимо инициализировать обработку, заполнить ее реквизиты и вызвать функцию, которая вернет нам табличный документ, предназначенный для просмотра в 1С. Этот документ нужно сохранить в формат Excel и скопировать на сервер или записать в базу.

Link = getattr(V82.Catalogs, "ОтчетыСистемы").FindByDescription("Акт Сверки Элэн") nav_url = V82.GetURL(link, "Отчет") name = V82.ExternalReports.Connect(nav_url) ExternalReport = V82.ExternalReports.Create(name) setattr(ExternalReport, "ЛицевойСчет", reference) table_doc = ExternalReport.GetDoc() path = V82.GetTempFileName("xls") table_doc.Write(path, V82 .SpreadsheetDocumentFileType.XLS) report = models.Report() report.account = reference.Code.strip() report.type = u"act" report.document = open(path, "rb").read() CONN.add(report)

В приведенном фрагменте выполняется следующее. Подключается обработка, формирующая документ. Обработка может быть встроена в конфигурацию, храниться на диске или в базе данных 1С (в каком-то справочнике). Поскольку обработки часто меняются, то, чтобы каждый раз не обновлять конфигурацию, самые часто меняющиеся обработки хранятся в справочнике «ОтчетыСистемы», в реквизите типа «хранилище значения» с именем Отчет. Обработку можно инициализировать, выгрузив ее из базы на диск и подгрузив, либо методом GetURL(), в который нужно передать ссылку на элемент справочника и имя реквизита. Полученному объекту обработки мы назначаем значения реквизитов, вызываем экспортируемую функцию GetDoc(), получаем табличный документ, который сохраняется во временный Excel-файл. Содержимое этого файла записывается в SQlite-базу.

Последнее, что остается рассмотреть - это программное занесение данных в 1С. Предположим, что требуется занести показания от абонентов. Для этого достаточно создать и провести документ «Акт снятия показаний»:

#coding=cp1251 acts = getattr(V82.Documents, "АктСнятияПоказаний") act = acts.CreateDocument() setattr(act, "Показание", 1024.23) setattr(act, "Абонент", "Иванов") # Заполнение прочих реквизитов... act.Write()
Теперь занесение данных автоматизированно.

Итак, я изложил способ, который основан на программной выгрузке и загрузке даных с использованием COM-соединния. Этот метод успешно функционирует в моей организации почти год. База, формируемая из 1С, обслуживает 3 платежные системы, интернет-эквайринг (оплата картами через интернет), а так же личный кабинет. Помимо этого, к базе подключаются различные скрипты для автоматизации рутины.

Несмотря на недостатки метода (медленная скорость COM-соединения), в целом он функционирует стабильно. У нас есть данные в платформонезависимом виде (SQLite), с которыми можно работать из любого языка. И основная часть кода написана на Питоне, а значит, доступны множество средств и приемов, о которых даже нельзя мечтать в 1С.

Это один из возможных способов взаимодейстия с 1С. Я уверен, что он не нов и наверняка уже был кем-то опробован, оптимизирован. Однако, я постарался изложить максимум деталей процесса, чтобы уберечь вас от подводных камней, на которые сам наступал.

Желаю всем удачи, и помните, что не так страшен 1С, как его малюют!

Внимание! Перед вами ознакомительная версия урока, материалы которого могут быть неполными.

Войдите на сайт как ученик

Войдите как ученик, чтобы получить доступ к материалам школы

Язык запросов 1С 8.3 для начинающих программистов: соединения

Соединения в запросах

Соединение - одна из самых важных и нужных операций, выполняемых реляционными системами управления базами данных.

Соединения используются для того, чтобы сопоставить строки одной таблицы строкам другой таблицы.

Для того, чтобы осознать необходимость соединений давайте решим следующую задачу.

У нас в базе есть справочник Клиенты :

Справочник Цвета:

И справочник Ассоциации :

Наша задача вывести любимые ассоциации клиентов, основываясь на цвете.

Таким образом для Наташи любимой ассоциацией будет трава, так как её любимый цвет зелёный. А для Петра - солнце. Вы читаете ознакомительную версию урока, полноценные уроки находятся .

Для Андрея вообще нет подходящей ассоциации, так его любимый цвет красный, а ассоциаций красного цвета в базе нет.

Будем решать задачу постепенно.

Сначала запросим всех клиентов и их любимые цвета :

Затем запросим все ассоциации и их цвета :

Если мы попробуем выполнить этот запрос, то получим ошибку:

Причина ошибки в том, что поле Наименование присутствует сразу в обеих таблицах (Клиенты и Ассоциации ) и система просто не знает поле из какой именно таблицы имеется в виду.

Чтобы устранять подобные неоднозначности при выборке из более чем одной таблицы принято указывать полные названия полей. Полное название поля включает в себя полное имя таблицы (например, Справочник.Клиенты ) и имя самого поля (например, Наименование ).

Таким образом полное название поля Наименование из таблицы Клиенты будет Справочник.Клиенты.Наименование .

А полное названия поля Наименование из таблицы Ассоциации будет Справочник.Ассоциации.Наименование .

Перекрёстное соединение

Перепишем предыдущий запрос с полными именами полей:

Только что мы произвели перекрёстное соединение двух таблиц. Обратите внимание на то, каким образом сформировался результат:

Внутреннее соединение

Очевидно, что результат перекрестного соединения двух таблиц не есть решение нашей задачи. Нам нужны не все записи из перекрёстного соединения, а только те у которых поля ЛюбимыйЦвет и Цвет имеют одинаковое значение:

Чтобы получить эти записи добавим к предыдущему запросу секцию ГДЕ :

Это то, что нужно - мы решили, поставленную задачу!

В последнем запросе мы использовали перекрёстное соединение с дополнительным условием (в секции ГДЕ ). Вы читаете ознакомительную версию урока, полноценные уроки находятся . Такое соединение называется внутренним .

Есть ещё один вариант написания того же самого внутреннего соединения :

Сравните этот и предыдущий запрос. Они совершенно одинаковы с точки зрения платформы, просто имеют разный синтаксис. И этот и предыдущий запросы содержат внутреннее соединение таблицы Клиенты с таблицей Ассоциации по полям ЛюбимыйЦвет и Цвет соответственно.

Левое соединение

Обратите внимание на то, что в результат внутреннего соединения не попал Андрей. А всё потому, что его любимый цвет красный, а красных ассоциаций у нас в базе нет вовсе.

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

Перепишем запрос так, чтобы в результат попадали в том числе те записи из первой таблицы, для которых не нашлось ни одной пары из второй таблицы (в данном случае Андрей):

Такое соединение называется левым соединением .

Р езультат левого соединения представляет из себя: все записи из внутреннего соединения ПЛЮС все записи из первой таблицы , не попавшие во внутреннее

Правое соединение

Но давайте снова вернёмся к внутреннему соединению:

Обратите внимание на то, что результат внутреннего соединения не содержит ассоциацию белый снег, так как не нашлось ни одного клиента, у которого любимым цветом был бы белый.

Перепишем запрос так, чтобы в результат попадали в том числе те записи из второй таблицы, для которых не нашлась ни одной пары из первой таблицы (в данном случае белый снег):

Такое соединение называется правым соединением .

Результат правого соединения представляет из себя: ПЛЮС все записи из второй таблицы , не попавшие во внутреннее соединение (для которых не нашлось пары).

Полное соединение

А что если нам нужно, чтобы в результат запроса попадали помимо внутреннего соединения Андрей и Снег одновременно?

Для этого потребуется совместить результаты левого и правого соединений. Такой вид соединения уже придуман и называется полным соединением:

Результат полного соединения представляет из себя: все записи из внутреннего соединения ПЛЮС все записи из первой таблицы, не попавшие во внутреннее соединение (для которых не нашлось пары) ПЛЮС все записи из второй таблицы, не попавшие во внутреннее соединение (для которых не нашлось пары).

Псевдонимы таблиц

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

Чтобы сократить полное написание имени таблицы (например, Справочник.Клиенты ) допустимо (как и для самих полей) использовать псевдонимы .

Давайте перепишем последний запрос так, чтобы при формировании полных имён полей вместо Справочник.Клиенты можно было использовать псевдоним К , а вместо Справочник.Ассоциации - псевдоним А :

А чтобы результат запроса был ещё нагляднее добавим псевдонимы полей, которые мы уже рассматривали на одном из прошлых уроков:

Обработка NULL

Присмотритесь к результатам последнего запроса (как впрочем и многих предыдущих на этом уроке).

Чему равны значения полей Ассоциация и ЕёЦвет для первой строчки? А что вы скажете насчет полей Клиент и ЕгоЦвет для последней строки?

Они равны NULL, которое как мы уже знаем означает отсутствие какого либо значения:

А так как NULL означает отсутствие значения, то любая попытка выполнить с ним какую-либо операцию (сравнение, сложение...) вызовет неопределенное поведение базы данных, непредсказуемую ошибку.

Поэтому обязательной считается обработка значений NULL всегда, когда они могут возникнуть.

Под обработкой подразумевается то, что мы должны сказать в нашем запросе, что если одно из полей будет равно NULL , то в это поле следует подставить какое-то другое значение.

В данном случае для полей Клиент и Ассоциация в случае обнаружения NULL мы будем подставлять пустую строку "".

А вот поля ЕгоЦвет и ЕёЦвет являются ссылками на элементы справочника Цвета , поэтому в них можно подставлять только значения являющиеся ссылками указанных типов. Каждый ссылочный тип (например, Справочник или Документ) имеет предопределенный элемент ПустаяСсылка . Чтобы указать его значение в запросе воспользуемся функцией ЗНАЧЕНИЕ .

Для определения того, что в поле попало NULL будем использовать уже знакомую нам по прошлым урокам функцию ЕСТЬNULL :

ВЫБРАТЬ ЕСТЬNULL( К. Наименование, "" ) КАК Клиент, ЕСТЬNULL( К. ЛюбимыйЦвет, ЗНАЧЕНИЕ(Справочник. Цвета. ПустаяСсылка) ) КАК ЕгоЦвет, ЕСТЬNULL( А. Наименование, "" ) КАК Ассоциация, ЕСТЬNULL( А. Цвет, ЗНАЧЕНИЕ(Справочник. Цвета. ПустаяСсылка) ) КАК ЕёЦвет ИЗ Справочник. Клиенты КАК К ПОЛНОЕ СОЕДИНЕНИЕ Справочник. Ассоциации КАК А ПО К. ЛюбимыйЦвет = А. Цвет

С виду (из консоли запросов) результат не изменился. Мы по-прежнему видим пустые поля. Но это только потому, что строковые представления у NULL и у пустых полей всех типов совпадают и равны пустой строке.

На самом же деле эти пустые поля уже не есть NULL (отсутствие значения), теперь в них появились значения (пустые), с которыми уже можно работать (совершать операции).

Запомните пустое значение и отсутствие значение - это две большие разницы.

Соединение более двух таблиц

Можно последовательно соединять сколько угодно таблиц.

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

Для этого последовательно соединим по цвету таблицу Клиенты с таблицей Ассоциации , а затем (получившийся результат) с таблицей Еда :

Пройдите тест

Начать тест

1. Соединения используются для того, чтобы