Глобалы MUMPS: Экстремальное программирование баз данных. Часть 2

в 16:45, , рубрики: cache, MUMPS, nosql, sql, бд

Начало см. часть 1.

Глава 2. SQL/реляционные БД против MUMPS

В этой главе будут изложены основные различия между обычными SQL реляционными базами данных и БД на основе MUMPS.

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

Определим структуры данных

Давайте начнём с основ — определим данные. Для примера мы будем использовать простую БД состоящую из 3-х таблиц:

1. Таблица клиентов (CUSTOMER)
2. Таблица заказов (ORDER)
3. Таблица с перечнем вещей составляющих индивидуальный заказ (ITEM)

Глобалы MUMPS: Экстремальное программирование баз данных. Часть 2

Имена таблиц указаны жирным, первичные ключи подчёркнуты.

CUSTOMER
custNo Уникальный номер клиента
name Имя клиента
address Адрес клиента
totalOrders Общее число заказов клиента

ORDER
orderNo Номер заказа
custNo Номер клиента (внешний ключ из таблицы CUSTOMER)
orderDate Дата заказа
invoiceDate Дата счёта-фактуры
totalValue Стоимость заказа

ITEM
orderNo Номер заказа (соотвествующий ключу из таблицы ORDER).
itemNo Артикул вещи
price Цена вещи для клиента (учитывая все скидки).

Отношение один-ко-многим показано на диаграмме. Каждый клиент может иметь много заказов и каждый заказ может состоять из многих вещей.

Число заказов конкретного клиента (CUSTOMER.totalOrders) это суммарное число размещённых клиентом заказов в таблице ORDERS, которые идентифицируются по его номеру.

Цена заказа (ORDER.totalValue) это сумма стоимости всех вещей в заказе, каждая конкретная стоимость определяется полем ITEM.price.

Поля CUSTOMER.totalOrders и ORDER.totalValue не вводятся напрямую пользователем — это вычисляемые поля.

Для SQL/реляционных БД эти определения таблиц должны быть загружены в БД (с помощью CREATE TABLE) прежде чем с помощью SQL можно будет добавлять, изменять и получать записи.

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

Тем не менее важно заметить, что реляционная схема может быть прозрачно наложена на MUMPS-хранилище с целью доступа к данным через SQL-инструменты. Реляционная схема может быть добавлена к существующему MUMPS-хранилищу, когда это станет нужным, предоставляя доступ к записям в нормализованной форме (если структуры поддаются нормализации).

Данные три таблицы будут представлены в MUMPS используя следующие глобалы:

Таблица CUSTOMER

^CUSTOMER(custNo)=name|address|totalOrders

Таблица ORDER

^ORDER(orderNo)=custNo|orderDate|invoiceDate|totalValue

Таблица ITEM

^ITEM(orderNo,itemNo)=price

Связь между CUSTOMER и ORDER будет представлена с помощью глобала:

^ORDERX1(custNo,orderNo)=””

Он будет предоставлять номера заказов по номеру клиента.

В MUMPS вы можете использовать любые имена глобалов. Также вы имеете выбор: использовать по одному глобалу для каждой таблицы (как мы и сделали) или один и тот же глобал для нескольких или всех таблиц.

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

^OrderDatabase(“customer”,custNo)= name_”~”_address_”~”_totalOrders
^OrderDatabase(“order”,orderNo)= custNo_”~”_orderDate_”~”_invoiceDate_”~”_totalValue
^OrderDatabase(“item”,orderNo,itemNo)=price
^OrderDatabase(“index1”,custNo,orderNo)=””

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

Также мы выбрали использовать символ тильды (~) как разделитель полей в глобалах (можно выбрать любой другой символ).

Добавление записи в БД

Давайте начнём с очень простого примера. Добавим нового клиента в таблицу CUSTOMER.

SQL

INSERT INTO CUSTOMER (CustNo, Name, Address) VALUES (100, ‘Chris Munt’, ‘Oxford’)

MUMPS

Set ^CUSTOMER(100)= “Chris Munt”_"~"_"Oxford"

"_" — это символ для склеивания (конкатенации) строк.

В правой части мы ввели 2 поля, разделённых символом тильды. К слову говоря, в качестве разделяющего можно использовать действительно любой символ, в том числе служебные (non-printable) символы.

Мы могли бы написать:

Set ^CUSTOMER(100)= “Chris Munt”_$c(1)_"Oxford"

Функция $c(1) означает «символ ASCII-значение которого 1».
$c — это сокращённое название функции $char.

И в этом случае для разделения поле использовался бы символ ASCII 1.

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

SQL

INSERT INTO CUSTOMER (custNo, name, address) VALUES (:custNo, :name, :address)

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

MUMPS

Set ^CUSTOMER(custNo)=name_"~"_address

Выборка записей из БД

SQL

SELECT A.name, A.address FROM CUSTOMER A
WHERE A.custNo = :custNo

MUMPS

Set record=$get(^CUSTOMER(custNo))
Set name=$piece(record,"~",1)
Set address=$piece(record,"~",2)

Замечание об использовании функции $get(). Это удобный способ извлекать значения из глобалов. Если запрошенного элемента не существует, то $get() вернёт null ("").

Если бы мы не использовали $get(), то нужно было бы делать так:

Set record=^CUSTOMER(custNo)

Если запрашиваемого элемента глобала не существует, то MUMPS вернёт ошибку периода исполнения (т.е. данные не определены).

Подобно большинству команд и функций в MUMPS вместо $get() можно использовать сокращение $g():

Set record=$g(^CUSTOMER(custNo))

Удаление записи из БД

SQL

DELETE FROM CUSTOMER A WHERE A.custNo = :custNo

MUMPS

kill ^CUSTOMER(custNo)

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

Выборка нескольких записей

SQL

SELECT A.custNo, A.name, A.address FROM CUSTOMER A

MUMPS

s custNo=”” f s custNo=$order(^CUSTOMER(custNo)) Quit:custNo= “” do
. Set record=$get(^CUSTOMER(custNo))
. Set name=$piece(record,"~",1)
. Set address=$piece(record,"~",2)
. Set totalOrders=$piece(record,"~",3)
. ; добавьте свой код для обработки текущей строки

Заметьте, что мы используем синтаксис с точками (dot-syntax). Строки начинающиеся с точек представляют собой подпрограмму, вызываемую командой do (см. окончание первой строки)

Вы можете сделать всё что вам нужно с каждой строкой внутри подпрограммы, как показывает комментарий (последня строка начинающаяся с ";")

Функция $order в MUMPS один из столпов силы и гибкости глобалов. Суть её работы обычно как правило непонятна для тех кто знаком только с SQL и реляционными БД, поэтому подробнее о ней читайте в главе 1.

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

Использование MUMPS-функций для высокоуровнего доступа к данным

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

Добавление новой записи в БД

setCustomer(custNo,data) ; Определение заголовка функции
If custNo="" Quit 0
Set ^CUSTOMER(custNo)=data("name")_"~"_data("address")
Quit 1

Эта функция может быть вызвана следующим путём::

kill data ; удалим локальный массив data (он существует только в RAM)
set data("name")="Rob Tweed"
set data("address")="London"
set custNo=101
set ok=$$setCustomer(custNo,.data)  ; $$ означает, что функция пользовательская

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

Функция setCustomer может содержаться в другой программе (например в myFunctions). А поскольку программы в MUMPS содержатся в глобалах, то для вызова функции из глобала нужно написать $$setCustomer^myFunctions(custNo,.data)

Пример:

kill data ; clear down data local array
set data("name")="Rob Tweed"
set data("address")="London"
set custNo=101
set ok=$$setCustomer^myFunctions(custNo,.data)

Функцию $$setCustomer() можно назвать внешней (extrinsic). В терминах ООП это соответствует public. Мы можем обратиться к ней, даже, если она содержится в другой программе. Внешние функции это своего рода методы доступа к данным.

Функция $$setCustomer() возвращает ноль (т.е. false), если в качестве номера клиента мы передадим null. В других случаях запись будет сохранена и $$setCustomer() вернёт 1 (т.е. true). Вы можете проверять переменную ok, чтобы проверить выполнено сохранение или нет.

Поскольку у нас в таблице CUSTOMER есть вычисляемое поле totalOrders, сделаем его поддержку в коде:

setCustomer(custNo,data) ;
if custNo="" Quit 0
; Посчитаем число заказов
Set data(“totalOrders”)=0
Set orderNo=””
for set orderNo=$order(^ORDERX1(custNo,orderNo)) Quit:orderNo=”” do
. set data(“totalOrders”)=data(“totalOrders”)+1
set ^CUSTOMER(custNo)=data("name")_"~"_data("address")_”~”_data(“totalOrders”)
Quit 1

Мы продолжим обсуждение вычисляемых полей позже в секции «Триггеры».

Получение записи из БД

Следующая внешняя функция вернёт строку из таблицы CUSTOMER.

getCustomer(custNo,data) ;
new record
kill data ; clear down data array
if custNo="" Quit 0
set record=$get(^CUSTOMER(custNo))
set data("name")=$piece(record,"~",1)
set data("address")=$piece(record,"~",2)
set data("totalOrders")=$piece(record,"~",3)
Quit 1

Эта функция может быть использована следующим образом:

S custNo=101
set ok=$$getCustomer(custNo,.data)

Она вернёт локалный массив (а точнее говоря изменит его, т.к. он передаётся по ссылке), содержащий 3 поля из строки заданного клиента:

data(“name)
data(“address”)
data(“totalOrders”)

Удаление записи из БД

Следующая внешняя функция удалит строку из таблицы CUSTOMER:

deleteCustomer(custNo) ;
if custNo="" Quit 0
kill ^CUSTOMER(custNo)
Quit 1

Эта функция может быть использована следующим образом:

S custNo=101
S ok=$$deleteCustomer(custNo)

Прим. Переводчика: В 3 части мы поговорим о вторичных индексах, триггерах и транзакциях.

Продолжение следует.

Автор: inetstar

Источник

Поделиться

* - обязательные к заполнению поля