How-to: Импорт данных
Пример 1
Условие
Есть книги, для которых заданы наименование и цена. Также определена логика заказов.
REQUIRE Utils;
CLASS Book 'Книга';
name 'Наименование' = DATA ISTRING[100] (Book) IN id;
id 'Код' = DATA STRING[20] (Book) IN id;
book 'Книга' (STRING[20] id) = GROUP AGGR Book b BY id(b);
CLASS Order 'Заказ';
date 'Дата' = DATA DATE (Order);
number 'Номер' = DATA STRING[10] (Order);
CLASS OrderDetail 'Строка заказа';
order 'Заказ' = DATA Order (OrderDetail) NONULL DELETE;
book 'Книга' = DATA Book (OrderDetail) NONULL;
nameBook 'Книга' (OrderDetail d) = name(book(d));
quantity 'Количество' = DATA INTEGER (OrderDetail);
price 'Цена' = DATA NUMERIC[14,2] (OrderDetail);
FORM order 'Заказ'
OBJECTS o = Order PANEL
PROPERTIES(o) date, number
OBJECTS d = OrderDetail
PROPERTIES(d) nameBook, quantity, price, NEW, DELETE
FILTERS order(d) == o
EDIT Order OBJECT o
;
FORM orders 'Заказы'
OBJECTS o = Order
PROPERTIES(o) READONLY date, number
PROPERTIES(o) NEWSESSION NEW, EDIT, DELETE
;
NAVIGATOR {
NEW orders;
}
Нужно сделать кнопку, которая загрузит содержимое заказа из Excel-файла, выбранного пользователем на своем компьютере.
Решение
importXlsx 'Импортировать из XLS' (Order o) {
INPUT f = EXCELFILE DO {
LOCAL bookId = STRING[20] (INTEGER);
LOCAL quantity = INTEGER (INTEGER);
LOCAL price = NUMERIC[14,2] (INTEGER);
IMPORT XLS FROM f TO bookId = A, quantity = B, price = C;
FOR imported(INTEGER i) NEW d = OrderDetail DO {
order(d) <- o;
book(d) <- book(bookId(i));
quantity(d) <- quantity(i);
price(d) <- price(i);
}
}
}
EXTEND FORM order
PROPERTIES(o) importXlsx
;
Оператор INPUT
, который запрашивает файл, вызовет пользователю диалог с выбором файлов с расширениями xls и xlsx. При успешном выборе будет вызвано действие, которое следует после слова DO
.
Предполагается, что в файле будет три колонки. В первой A
будет содержаться код книги, во второй B
- количество, а в третьей C
- цена.
Оператор IMPORT
считывает содержимое выбранного файла в локальные свойства, у которых единственным параметром является номер строки. Эти номера начинаются с нуля. В свойстве imported
будет TRUE
, если в файле есть строка с соответствующим номером. Затем для каждой такой строки создается соответствующая строка в заказе.
Пример 2
Условие
Аналогично Примеру 1. Кроме того, задан директорий, в который некоторая внешняя система складывает заказы. Для каждого заказа формируется отдельный файл в формате CSV, в котором хранятся дата и номер заказа (в денормализованном виде), а также код книги, количество и цена.
serverDirectory 'Директорий на сервере, из которого импортировать заказы' = DATA STRING[100] ();
EXTEND FORM orders PROPERTIES() serverDirectory;
Необходимо реализовать действие, которое будет импортировать из этой папки заказы в систему.
Решение
importOrders 'Импортировать заказы из директория' () {
listFiles('file://' + serverDirectory());
FOR ISTRING[255] f = fileName(INTEGER j) AND NOT fileIsDirectory(j) DO NEWSESSION {
LOCAL file = FILE ();
READ 'file://' + serverDirectory() + '/' + f TO file;
LOCAL date = DATE (INTEGER);
LOCAL number = STRING[10] (INTEGER);
LOCAL bookId = STRING[20] (INTEGER);
LOCAL quantity = INTEGER (INTEGER);
LOCAL price = NUMERIC[14,2] (INTEGER);
IMPORT CSV '|' NOHEADER CHARSET 'CP1251' FROM file() TO date, number, bookId, quantity, price;
NEW o = Order {
date(o) <- date(0);
number(o) <- number(0);
FOR imported(INTEGER i) NEW d = OrderDetail DO {
order(d) <- o;
book(d) <- book(bookId(i));
quantity(d) <- quantity(i);
price(d) <- price(i);
}
}
APPLY;
move('file://' + serverDirectory() + '/' + f, 'file://' + serverDirectory() + '/' + (IF canceled() THEN 'error/' ELSE 'success/') + f);
}
}
EXTEND FORM orders PROPERTIES() importOrders;
Действие listFiles
объявлено в системном модуле Utils
. Оно сканирует указанную в параметре папку и считывает все файлы из нее в свойства fileName
(имя файла) и fileIsDirectory
(логическое свойство - является ли файл директорием).
Оператор READ
читает указанный файл в локальное свойство с типом FILE
, которое затем обрабатывает оператор IMPORT
. В его параметрах указывается, что форматом файла является CSV без заголовка в первой строке, с ве ртикальной чертой в качестве разделителем и кодировкой CP1251.
Предполагается, что дата и номер в каждой из строк будут содержать одинаковое значение. Поэтому их значения читаются из первой строки с номером 0.
Каждый файл обрабатывается в отдельной новой сессии изменений с последующим сохранением путем вызова оператора APPLY
. Этот оператор записывает в свойство canceled
TRUE
, если при сохранении было нарушено некоторое ограничение. Дальше при помощи конструкции MOVE
оператора READ
файл перемещается либо в папку success
, либо в папку error
. Это нужно, чтобы действие можно было вызывать повторно, не обрабатывая при этом одни и те же заказы повторно.
Так как полученное действие не имеет параметров, то его можно включать в планировщик для автоматического запуска через определенные промежутки времени.
Пример 3
Условие
Аналогично Примеру 1.
Во внешней базе данных хранится справочник книг с полями код и наименование.
Нужно сделать действие, которое будет синхронизировать справочник книг со внешней базой данных.
Решение
importBooks 'Импортировать книги' () {
LOCAL file = FILE ();
READ 'jdbc:sqlserver://localhost;databaseName=books;User=import;Password=password@SELECT id, name FROM books' TO file;
LOCAL id = STRING[20] (INTEGER);
LOCAL name = ISTRING[100] (INTEGER);
IMPORT TABLE FROM file() TO id, name;
//создаем новые книги
FOR id(INTEGER i) AND NOT book(id(i)) NEW b = Book DO {
id(b) <- id(i);
}
// меняем значения
FOR id(Book b) == id(INTEGER i) DO {
name(b) <- name(i);
}
// удаляем книги
DELETE Book b WHERE b IS Book AND NOT [ GROUP SUM 1 BY id(INTEGER i)](id(b));
}