Данный пост рассказывает о языках программирования и запросах среды разработки учетных приложений tmaplatform.
У всех вкусы разные...
При работе с платформой Вы можете использовать несколько языков программирования: Паскаль (Similar Pascal), Бейсик (Similar Basic). В стадии разработки Java-подобный язык, который появится в ближайшее время. Все примеры поста написаны на Similar Pascal.
В платформу интегрирован язык запросов, названный нами «Внутренний SQL», большая часть поста будет именно о нем.
Независимость от СУБД.
У каждой СУБД есть свои плюсы и минусы, разные СУБД предназначены для разных задач и объемов хранимых данных. И просто разные разработчики привыкли к разным серверам баз данных. Это аксиома.
Среда разработки tmaplatform позволяет легко (легко – значит без изменения кода) переносить программы между различными серверами баз данных: MySQL (работает), PgSQL (тестирование), MSSQL (тестирование), SQLite(тестирование), в дальнейшем данный перечень будет расширяться. И все мы знаем, что при всей универсальности языка SQL просто так перевести программу для работы с БД с одного сервера на другой не получится. Еще сильнее осложнит переход активное использование триггеров, хранимых процедур и функций.
Поэтому мы добавили в платформу внутренний язык запросов, максимально похожий на SQL92 (как базовый стандарт SQL). При выполнении Вашей программы платформа транслирует запросы с внутреннего языка на язык используемой БД.
Этот язык запросов не позволяет использовать 100% возможностей Вашего любимого сервера баз данных. Поэтому мы оставили возможность совместно с внутренним языком запросов использовать язык запросов базы данных (прямые запросы). При использовании прямых запросов переносимость теряется. Но если Вы используете внутренний язык запросов в 98% программы, то это облегчит Вам перенос программы и отладку на несколько порядков.
Непосредственные запросы
Запросы к базе данных можно писать непосредственно в коде программы. Язык запросов является частью языка программирования. Поэтому сразу во время ввода запроса платформа проверяет наличие таблиц, полей и их типы. Еще до запуска программы платформа проверит корректность всех запросов.
ClientId := 3;
D := (Select Sum(Summa) From Documents Where Client = :ClientId);
Например, если тип переменной ClientId отличается от типа поля Client, то платформа оповестит об этом (подчеркнет место ошибки красной линией).
Так же в редакторе платформы доступна подсказка по синтаксису, именам полей, функциям базы данных и автозавершение.
При рефакторинге базы данных платформа автоматически скорректирует все запросы. То есть, когда вы разделяете таблицу на две части, запрос "Select A, B From Table" будет заменен на "Select A, LinkField.B From Table1".
Параметры запроса указываются после символа ":", при этом можно указать не только переменную, но и любое выражение языка программирования.
Технология AutoJoin
Ничего так кратко и лаконично не описывает что-либо в программировании, как пример:
Select Account.Client.City.Country.Name From Document
В этом запросе мы получаем наименование страны, в которой находится город, в котором зарегистрирован клиент, которому принадлежит счет, на который оформлен документ.
В стандартном SQL этот запрос выглядит так:
Select Countries.Name From Document
Left Join Accounts On Accounts.Id = Document.Account
Left Join Clients On Clients.Id = Accounts.Client
Left Join Cities On Cities.Id = Clients.City
Left Join Countries On Countries.Id = Cities.Country
Правда, проще? Объем запроса уменьшился в 5 раз. Эти 4 блока Join приходятся только на одно поле. А если у нас 20 полей? Ну, в общем, Вы поняли.
Single, новая команда SQL
Оператор Select возвращает набор данных (далее датасет), а если Вам требуется получить единственное значение, то приходится добавлять в программу команды извлечения данных.
D := (Select Sum(S) From Documents Where Client=:Client);
If D.Count=0 Then S := 0 Else S := D[0].S;
Оператор Single работает аналогично Select, но возвращает единственное значение (первое поле первой строки).
S := (Single Sum(S) From Documents Where Client=:Client);
Обращение к внешним запросам
При использовании подзапросов к любому внешнему запросу можно обратиться, указав перед именем переменной несколько точек (количество зависит от уровня вложенности подзапроса).
Select Name, (Select Sum(Summa) From Money Where Client=.Id) From Clients
Select Name, (Select (Select Sum(Summa) From Money Where Client=..Id)) From Clients
Оптимизация запросов
Запросы в программе должны быть как можно проще и нагляднее (ведь, возможно, не только Вам, но и кому-то еще придется разбираться в этой программе). Но часто такие запросы не являются самыми быстрыми. И, к тому же, внутренний язык запросов не позволяет использовать все возможности СУБД, чтобы написать оптимальный запрос.
Платформа содержит инструмент «Оптимизатор запросов». С помощью него вы можете задавать пары запросов на внутреннем и внешнем языке. То есть указать самый быстрый запрос:
Заменить с
Select Name, (Select Sum(Summa) From Documents Where Client=.Id) From Clients
Заменить на
Select Client.Name, Sum(Document.Summa) From Clients, Documents Where Clients.Id=Documents.Client
Также этот инструмент позволяет опытным путем проверить идентичность результатов работы запросов и сравнить время выполнения. Оптимизируйте программу, не усложняя её!
Типизированные датасеты.
Любой Select-запрос возвращает датасет (объект типа Dataset). Мы сделали работу с ним такой же, как и с массивом.
Dataset := DirectQuery('Select * From mysql.user');
For y:=0 To Dataset.Count-1 Do
For x:=0 To Dataset.Columns.Count-1 Do
Warning(Dataset[x,y]);
Более интересная возможность — типизированные датасеты. В прошлом примере Dataset[x,y] возвращает тип данных Variant и контроль типов на этапе ввода текста программы не осуществлялся. Типизированные датасеты позволяют обращаться к определенным полям определенного типа данных — Dataset[x].ИмяПоля. То есть работает контроль типов во время ввода, автозавершение и т.п.
Procedure OnCreate;
Var
D : Dataset Of Record
Id : Integer;
CreateDate : DateTime;
ClientId : Integer;
CountryName : String;
End;
Begin
D := (Select Id, CreateDate, Client, Client.City.Country.Name From Document);
Warning(D[2].Name); // Обращение к произвольному полю
Foreach E in D Do // Перебор всех элементов
Begin
Warning(E.Id); // Обращение к полю
Warning(E.CountryName); // Обращение к полю
End;
End;
Следует отметить, что типы полей, возвращаемые Select-запросом, результат которого присваивается датасету, должны быть такого же типа, как и поля самого датасета.
Транзакции
Язык программирования поддерживает блок Transaction. Куда же без них? Все помнят первый пример в любом учебнике с переводом денег со счета на счет? :)
Transaction
Begin
(Insert Table1 ...)
(Insert Table2 ...)
End;
Этот блок значит, что другие клиенты не смогут увидеть изменения в базе данных, пока не завершится блок Transaction. И наоборот, Вы не сможете увидеть изменения, вносимые другими клиентами. Если в этом блоке происходит исключение, то все изменения, внесенные в базу данных, откатываются.
Транзакции могут быть вложенными.
Можно указать уровень изоляции транзакции.
Transaction Continous Read
Begin
End;
Автоматическое обновление
При изменении базы данных платформа анализирует запрос и автоматически обновляет интерфейс. Причем, учитываются таблицы, используемые во вьюхах (view) и вычисляемых полях. А обновляются только измененные записи.
Этот же механизм используется в Select-триггерах и материализованных вьюхах (view), которые реализует платформа, если СУБД их не поддерживает. (Например, MySQL).
SQL к локальным данным
SQL-запросы можно писать не только к таблицам базы данных, но и к локальным данным.
Procedure OnCreate;
Var
Data : Dataset Of Record ... End;
Data1 : Dataset Of Record ... End;
Begin
...
Data1 := (Select FirstName+' '+LastName, Age From :Data Where Age>20 Order By -Age, Name);
...
End;
При этом в запросах можно использовать любые функции программы.
Хранимые процедуры.
Хранимые процедуры, функции и триггеры – программы на процедурном языке СУБД (T-SQL, PL/SQL и т.д.), которые хранятся и выполняются на сервере БД. Платформа предоставляет возможность писать эти программы на своем внутреннем языке для независимости от какой-либо конкретной СУБД (текст процедуры/триггера при этом транслируется).
Использование одного и того же языка и API во всей программе — это несомненный плюс. К тому же, Similar Pascal с интегрированным языком запросов более удобен, предсказуем и нагляден, чем языки хранимых процедур (например, у MySQL).
Вы можете просто перенести код из программы в хранимую процедуру и обратно.
А главный плюс — это то, что один раз написанная хранимая процедура будет работать на всех поддерживаемых серверах баз данных.
Пример хранимой процедуры:
Procedure Recalc(Curr, Contragent : Integer);
Var
S, CurrRate, NewCurrRate : Currency;
begin
Foreach C In (Select Id, DocDate, Sum, Curr, NewSum, NewCurr From Documents Where Curr=:Curr And Contragent=:Contragent) Do
Begin
CurrRate := GetCurrRate(C.DocDate, C.Curr);
NewCurrRate := GetCurrRate(C.DocDate, C.NewCurr);
S := RoundDiv(RoundMul(C.Sum, CurrRate, 4), NewCurrRate, 2);
If C.Summa <> S Then
(Update Documents Set Summa = :S Where Id = :C.Id);
End;
End;
Заключение.
Все вышеперечисленное повышает скорость разработки, понимания и отладки программы. Эти факторы, естественно, являются основополагающими при выборе среды разработки. Хотите попробовать tmaplatform – записывайтесь на бета-тестирование (кидайте ваш email мне(smirnovss) в личку)
Полное описание платформы со справкой смотрите тут tmaplatform.ru