Как найти документ в 1С 7.7 по значению реквизита?

Программист 1С v7.7
← На главную

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

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

1. Использование метода Документ.ВыбратьПоЗначению()

Один из наиболее простых и часто используемых способов поиска документов по реквизиту в 1С 7.7 – это метод ВыбратьПоЗначению(). Этот метод позволяет выбрать документы, удовлетворяющие заданным условиям по значению определенного реквизита. Однако, важно понимать его ограничения.

  1. Принцип работы:

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

  2. Синтаксис метода:

    Документ.ВыбратьПоЗначению(<ДатаНачала>, <ДатаКонца>, <ИмяРеквизита>, <Значение>)

    • <ДатаНачала> и <ДатаКонца>: Даты начала и конца периода поиска. Если вы хотите искать по всему периоду, можно передать минимальное и максимальное значения даты (например, Дата(1,1,1) и Дата(9999,12,31)) или пустые даты.
    • <ИмяРеквизита>: Строка, содержащая имя реквизита, по которому производится отбор. Это может быть как общий реквизит документов, так и графа отбора журнала.
    • <Значение>: Значение, которое мы ищем в указанном реквизите.
  3. Критическое ограничение: Индексированные реквизиты

    Важно понимать, что метод ВыбратьПоЗначению() эффективно работает только для тех реквизитов, которые в конфигураторе помечены как "Отбор" (то есть, для которых созданы индексы в базе данных). Если для реквизита, по которому вы пытаетесь выполнить поиск, флаг "Отбор" не установлен, то метод либо не будет работать вовсе, либо будет выполняться крайне медленно, перебирая все документы, или даже может вызвать ошибку "Неверное имя реквизита".

    Мы можем проверить это в конфигураторе: открыв свойства реквизита (для общего реквизита документов) или графы отбора (для журналов документов), убедитесь, что установлен флажок "Отбор".

  4. Пример использования:

    Предположим, нам нужно найти первый документ "РеализацияТоваровУслуг", у которого реквизит "Контрагент" равен определенному значению.

    
    // Искомый контрагент
    ЗначениеКонтрагента = Справочники.Контрагенты.НайтиПоКоду("00001");
            
    Если ЗначениеКонтрагента.Пустой() = 1 Тогда
        Сообщить("Контрагент не найден!");
        Возврат;
    КонецЕсли;
            
    // Создаем объект документа для работы с ним
    Док = СоздатьОбъект("Документ.РеализацияТоваровУслуг");
            
    // Указываем период поиска (можно указать весь период)
    НачДата = Дата(1,1,1);
    КонДата = Дата(9999,12,31);
            
    // Выполняем выборку по значению реквизита "Контрагент"
    Если Док.ВыбратьПоЗначению(НачДата, КонДата, "Контрагент", ЗначениеКонтрагента) = 1 Тогда
        // Если найден хотя бы один документ, то Док позиционируется на первом из них
        Сообщить("Найден первый документ по контрагенту " + ЗначениеКонтрагента.Наименование + ":");
        Сообщить("   Тип документа: " + Док.Вид());
        Сообщить("   Номер: " + Док.НомерДок);
        Сообщить("   Дата: " + Док.ДатаДок);
        Сообщить("   Ссылка: " + Док.ТекущийДокумент());
                
        // Если нужно обработать все найденные документы, используем цикл
        // Пока Док.ПолучитьДокумент() = 1 Цикл
        //     Сообщить("   Найден: " + Док.ТекущийДокумент());
        // КонецЦикла;
    Иначе
        Сообщить("Документы по контрагенту " + ЗначениеКонтрагента.Наименование + " не найдены.");
    КонецЕсли;
    

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

2. Прямые SQL-запросы ("Черные запросы")

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

  1. Когда применять:

    Прямые SQL-запросы становятся незаменимыми в следующих случаях:

    • Когда искомый реквизит не индексирован, и ВыбратьПоЗначению() неэффективен или невозможен.
    • При необходимости сложной фильтрации, объединения данных из нескольких таблиц или использования агрегирующих функций.
    • Когда требуется максимальная скорость выполнения запроса, особенно на больших объемах данных.
    • При поиске "первого" документа без полного перебора, используя операторы типа TOP 1 (в MS SQL Server) или LIMIT 1 (в PostgreSQL/MySQL).
  2. Инструменты для работы с SQL в 1С 7.7:

    Для выполнения полноценных SQL-запросов с оператором WHERE и получением результатов в 1С 7.7, мы обычно используем внешние компоненты, такие как 1С++ (с объектом ODBCRecordset). Без таких компонент "черные запросы" через встроенный объект Запрос имеют гораздо более ограниченные возможности по непосредственной фильтрации на уровне SQL, особенно в файловых базах.

  3. Особенности формирования SQL-запросов:

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

    • Таблица _1SJourn: Содержит общие данные о документах (дата, номер, тип, ID документа).
    • Таблицы документов (например, _DocumentNN): Для каждого вида документа создается своя таблица (где NN – это внутренний числовой идентификатор вида документа). Эти таблицы содержат реквизиты, специфичные для данного вида документа.
    • Реквизиты: Внутренние имена реквизитов имеют вид _FldXXXXX, где XXXXX – числовой идентификатор. Для общих реквизитов, по которым установлен отбор, данные могут храниться непосредственно в _1SJourn.
    • Графы отбора и подчиненные документы: Хранятся в таблице _1SCRDOC.
  4. Пример использования SQL-зазапроса через 1С++:

    Представим, что у нас установлена компонента 1С++ и мы можем использовать объект ODBCRecordset для выполнения SQL-запросов. Нам нужно найти первый документ "РеализацияТоваровУслуг" по значению реквизита "Контрагент".

    Для примера мы предположим, что внутреннее имя таблицы для "РеализацияТоваровУслуг" – _Document72, а реквизит "Контрагент" имеет внутреннее имя _Fld12345 (эти значения нужно будет найти в вашем конкретном 1Cv7.DDS или через отладчик/конфигуратор).

    
    // Предполагаем, что компонента 1С++ успешно загружена
    // и объект ODBCRecordset создан.
    // Пример инициализации (может отличаться в зависимости от версии и настроек)
    // Попытка
    //     ЗагрузитьВнешнююКомпоненту("1cpp.dll"); // Или другую подходящую DLL
    // Исключение
    //     Сообщить("Не удалось загрузить внешнюю компоненту 1С++!");
    //     Возврат;
    // КонецПопытки;
    //
    // Попытка
    //     rs = СоздатьОбъект("ODBCRecordset");
    //     rs.УстановитьСоединение("DBQ=" + КаталогИБ() + "1Cv7.MD;DRIVER={Microsoft Access Driver (*.mdb)};"); // Пример для файловой базы
    // Исключение
    //     Сообщить("Не удалось создать объект ODBCRecordset или установить соединение!");
    //     Возврат;
    // КонецПопытки;
            
    // Предположим, что ЗначениеКонтрагента - это объект справочника
    // Его внутренний ID (IDDREF) нам потребуется для сравнения в SQL
    ЗначениеКонтрагента = Справочники.Контрагенты.НайтиПоКоду("00001");
    Если ЗначениеКонтрагента.Пустой() = 1 Тогда
        Сообщить("Контрагент не найден!");
        Возврат;
    КонецЕсли;
    ИДКонтрагента = ЗначениеКонтрагента.IDDREF;
            
    // Формируем текст SQL-запроса
    ТекстЗапроса = "
        SELECT TOP 1
            Журнал.IDD // IDD - это внутренний идентификатор документа
        FROM
            _1SJourn AS Журнал // Таблица журнала документов
        INNER JOIN
            _Document72 AS ДокРеализация // Таблица конкретного документа "РеализацияТоваровУслуг" (пример)
        ON
            Журнал.IDD = ДокРеализация.IDDOC // Связываем журнал с таблицей документа по ID
        WHERE
            Журнал.DOCTYPE = 'ДокументРеализацияТоваровУслуг' // Фильтруем по типу документа (строковое имя или числовой ID)
            AND ДокРеализация._Fld12345 = '" + ИДКонтрагента + "' // Фильтруем по внутреннему ID контрагента
        ORDER BY
            Журнал.DATE_TIME_IDDOC // Сортируем, чтобы получить "первый" по дате/времени/ID
    ";
            
    // Пример выполнения запроса (если rs - это объект ODBCRecordset)
    // Если rs.Выполнить(ТекстЗапроса) = 1 Тогда
    //     Если rs.БольшеНет() = 0 Тогда
    //         // Получаем ID найденного документа
    //         НайденныйIDD = rs.Получить("IDD");
    //
    //         // Создаем объект документа и загружаем его по IDD
    //         Док = СоздатьОбъект("Документ.РеализацияТоваровУслуг");
    //         Док.НайтиДокумент(НайденныйIDD);
    //
    //         Если Док.Выбран() = 1 Тогда
    //             Сообщить("Найден документ: " + Док.Вид() + " №" + Док.НомерДок + " от " + Док.ДатаДок);
    //         Иначе
    //             Сообщить("Документ по IDD не найден (ошибка загрузки)");
    //         КонецЕсли;
    //     Иначе
    //         Сообщить("Документы по заданным условиям не найдены.");
    //     КонецЕсли;
    // Иначе
    //     Сообщить("Ошибка выполнения SQL-запроса: " + rs.ПолучитьОписаниеОшибки());
    // КонецЕсли;
    

    Этот метод требует более глубоких знаний структуры 1С 7.7, но предоставляет несравненно большую мощь и гибкость в поиске. Для упрощения работы с такими выборками подойдёт готовый шаблон для прямых SQL-запросов в 1С 7.7.

3. Объект Запрос встроенного языка 1С 7.7

Встроенный объект Запрос в 1С 7.7 также позволяет формировать выборки данных, включая документы. Однако его синтаксис отличается от SQL, и для задачи поиска первого документа по неиндексированному реквизиту он может быть менее эффективен, чем прямой SQL-запрос, и менее удобен, чем ВыбратьПоЗначению() для индексированных полей. Тем не менее, это стандартный инструмент, который мы можем рассмотреть.

  1. Принцип работы:

    Объект Запрос позволяет описать, какие данные мы хотим получить и по каким условиям. После выполнения запроса мы получаем выборку, которую затем можем перебрать для поиска нужного элемента.

  2. Ограничения:

    В отличие от SQL, встроенный объект Запрос в 1С 7.7 не всегда способен полностью переложить всю логику фильтрации на СУБД, особенно для неиндексированных полей в файловых базах. Часто он может выполнять перебор на стороне 1С, что замедляет работу. Для быстрого поиска первого элемента по неиндексированному реквизиту он может оказаться не лучшим выбором.

  3. Пример использования:

    Найдем первый документ "РеализацияТоваровУслуг" по контрагенту, используя объект Запрос.

    
    // Искомый контрагент
    НужныйКонтрагент = Справочники.Контрагенты.НайтиПоКоду("00001");
            
    Если НужныйКонтрагент.Пустой() = 1 Тогда
        Сообщить("Контрагент не найден!");
        Возврат;
    КонецЕсли;
            
    // Создаем объект Запрос
    Запрос = СоздатьОбъект("Запрос");
            
    // Формируем текст запроса 1С
    // Мы указываем, какой реквизит нам нужен и какое условие на него накладываем.
    // Для документов, чтобы получить объект документа, мы указываем его в группировке
    // без дополнительных полей, чтобы получить ссылки на уникальные документы.
    Запрос.Текст = "
        |Период с Дата(1,1,1) по Дата(9999,12,31);
        |Документ.РеализацияТоваровУслуг.Контрагент;
        |Функция КонтрагентДокумента = Документ.РеализацияТоваровУслуг.Контрагент;
        |Условие (КонтрагентДокумента = ЗначениеКонтрагента);
        |Группировка Документ.РеализацияТоваровУслуг Без Группировок;
        |Порядок Документ.РеализацияТоваровУслуг.ДатаДок; // Сортируем, чтобы найти "первый"
    ";
            
    // Устанавливаем значение параметра для запроса
    Запрос.УстановитьПараметр("ЗначениеКонтрагента", НужныйКонтрагент);
            
    // Выполняем запрос
    Если Запрос.Выполнить() = 1 Тогда
        // Начинаем перебор результатов запроса
        Если Запрос.ВыбратьЗначения() = 1 Тогда
            // Перебираем документы. Так как нам нужен только первый, мы можем прервать цикл.
            Пока Запрос.ГрупповойИтог() = 1 Цикл
                Сообщить("Найден первый документ по контрагенту " + НужныйКонтрагент.Наименование + ":");
                Сообщить("   Тип документа: " + Запрос.РеализацияТоваровУслуг.Вид());
                Сообщить("   Номер: " + Запрос.РеализацияТоваровУслуг.НомерДок);
                Сообщить("   Дата: " + Запрос.РеализацияТоваровУслуг.ДатаДок);
                Сообщить("   Ссылка: " + Запрос.РеализацияТоваровУслуг.ТекущийДокумент());
                Прервать; // Нашли первый документ, выходим из цикла
            КонецЦикла;
        Иначе
            Сообщить("Документы по контрагенту " + НужныйКонтрагент.Наименование + " не найдены.");
        КонецЕсли;
    Иначе
        Сообщить("Ошибка выполнения запроса.");
    КонецЕсли;
    

    Хотя этот метод и является стандартным, для задачи быстрого поиска первого документа по значению реквизита он часто оказывается менее производительным, чем ВыбратьПоЗначению() (для индексированных полей) или прямой SQL-запрос (для неиндексированных или сложных условий). Для визуального составления и отладки подобных выборок можно использовать конструктор запросов для 1С 7.7.

Заключение

Мы рассмотрели три основных подхода к поиску документов по значению реквизита в 1С 7.7. Выбор метода зависит от нескольких факторов:

  1. Если реквизит, по которому ведется поиск, индексирован (флаг "Отбор" установлен), то наиболее простым и эффективным способом будет использование метода Документ.ВыбратьПоЗначению().
  2. Если реквизит не индексирован, или вам требуется выполнить сложный запрос, включающий объединение таблиц, агрегацию или поиск по нескольким условиям, то оптимальным решением будут прямые SQL-запросы ("черные запросы") с использованием внешних компонент типа 1С++ (ODBCRecordset). Этот метод требует глубоких знаний структуры базы данных 1С 7.7, но предоставляет максимальную гибкость и производительность.
  3. Встроенный объект Запрос также можно использовать, но для данной конкретной задачи (поиск первого документа по значению реквизита) он часто менее эффективен по скорости выполнения, особенно для неиндексированных полей.

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

← На главную