Інформаційна система і реляційна СУБД, Інтеграція додатків і даних, Бази даних, статті

Отже. “Кожна госпоперація підлягає відображенню в одній і тій же сумі одночасно по дебету одного рахунку та кредиту іншого”. Приберемо з цього визначення погано детерміноване поняття “госпоперація” і введемо поняття “проводка”. Так всім буде зрозуміліше. Вийде: “Кожна проводка підлягає відображенню в одній і тій же сумі одночасно по дебету одного рахунку та кредиту іншого”.


Задамося питанням – що ж означає слово “відображенню”? В даному випадку це означає, що проводка повинна змінювати залишок на обох рахунках. Точніше, вона повинна зменшувати залишок на одному рахунку і збільшувати на іншому. Якщо мислити примітивно, то нам знадобиться ввести сутність (об’єкт, якщо кому так більше подобається) “рахунок” і фізично зберігати для неї залишок. Але тут виникає питання, а на яку дату треба зберігати залишок? Або це має бути залишок взагалі (за весь період життя підприємства)? Відповідь – ні, так як і бухгалтеру і управлінцю потрібно отримувати залишки на певну дату. Більше того, їм потрібні ще й обороти. Правильно? Думаю – так. І тут потрібно звернути увагу на кинуте мною по ходу справи слово: отримувати!


Це слово було сказано не спроста. Справа в тому, що зберігати залишки і обороти зовсім не потрібно. Їх і так можна порахувати в будь-який момент часу. Правильно?


Чую тихий гомін: “при деяких обсягах ці розрахунки просто несерйозно робити кожен раз перераховуючи все з самого початку. Потрібні зрізи …” Правильно, але це всього лише окреме питання продуктивності. І вирішувати його потрібно окремо від питання структури зберігання інформації. Індексовані / матеріалізовані view прекрасно вирішують проблему продуктивності сервера (та й програміста). Якщо ви ще не перейшли на SQLServer 2000 або Oracle8i (9i), то саме час зробити це. Та й на інших серверах можна грамотно створити таблиці, що містять агреговану інформацію. Головне, що це не привід для нереляційних зберігання даних в реляційної СУБД.


Існує думка, що подвійний запис погано описується засобами реляційних БД і при спробі реалізації призводить до денормалізації з обмеженням цілісності. Такий думку приводить до створення систем, складаються з величезної кількості слабо пов’язаних таблиць, наприклад, окремих таблиць для накладних приходу товару на склад (витрати зі складу) та обліку касових ордерів. Це призводить до суттєвого ускладнення узагальненого аналізу даних, що зберігаються в такому конгломераті таблиць. В принципі, будь-які облікові дані поліморфні за своєю природою. У них завжди можна виділити загальну складову, що дозволяє узагальнено розглядати дані з різних точок зору. На жаль, при вищеописаному підході цього дуже важко досягти. Так, у наведеному прикладі складські накладні і касові ордери швидше за все будуть містити абсолютно різні поля і, швидше за все, не буде враховано, що і касовий ордер, і накладна в кінцевому рахунку є відображенням переказу грошей чи товару.


Таке “потаблічное” проектування дуже спрощує життя проектувальника. Однак це та простота, яка згодом вийде боком, так як не описує життєвих процесів підприємства, а займається простою реєстрацією документів. Системи, побудовані за таким або подібним принципом можна назвати документо-орієнтованими, або системами, що будуються від документа. Зазвичай такі системи або взагалі не містять бухгалтерії, або містять її у вигляді окремого блоку. Дані з БД потрапляють до бухгалтерії з таблиць документів, зазвичай пакетно, за звітний період. Тільки після цього бухгалтер починає працювати з інформацією, перебуваючи поза зв’язком з реальним життям, тобто реальної облікової ситуацією.


Чи є нормалізованої таблиця, описана в лістингу 2?


Кожен запис у таблиці відображає інформацію тільки про одну сутності – проводці. Дана таблиця не буде мати ні одного повторюється записи, з таблиці не можна винести дані в інші (посилальні) таблиці. Начебто, все говорить про те, що таблиця відповідає найжорсткішим вимогам нормалізації. Значить, на перше запитання відповідаємо позитивно.


Чи можна в цій таблиці зберігати проводки, що відповідають принципам подвійного запису?


Кожна проводка містить інформацію про дебетовому і кредитовому рахунку (у вигляді посилання), причому в ній відображені і дебетуемий, і кредитований рахунку.


Проводка містить дату її вчинення і суму. Сукупність дати проводки, суми та напрями переміщення грошей дає нам можливість записувати таким чином нормальні бухгалтерські проводки, задовольняючи всім вимогам подвійного запису. Просто замість урахування зміни рахунків ми почали враховувати зміни цих самих рахунків у часі. Ніхто не забороняє записувати проводку як два окремих зміни рахунків (Дебетового і кредитового), але тоді і з’являється та сама проблема з “нереляційних” і “ненормалізованностью”.


Щоб не бути голослівним, я створив маленьку тестову базу (див. лістинг 1), що складається з двох таблиць (я буду користуватися синтаксисом SQL Server 2000, але він досить близький до SQL 9x). Перша зберігає якісь аналітичні ознаки. Спростимо завдання і припустимо, що це просто бухгалтерські рахунки. Ось опис цих таблиць у вигляді ER-діаграми:

Лістинг 1

SET QUOTED_IDENTIFIER ON
SET ANSI_NULLS ON
GO
CREATE TABLE Z
(
idZ int NOT NULL PRIMARY KEY,
ZName varchar(100) NOT NULL
)

Друга таблиця – це таблиця, що містить проводки.

Лістинг 2

SET QUOTED_IDENTIFIER ON
SET ANSI_NULLS ON
GO
CREATE TABLE Oper
(
idOper int IDENTITY (0, 1) NOT NULL PRIMARY KEY,
Db int NOT NULL FOREIGN KEY REFERENCES Z,
Cr int NOT NULL FOREIGN KEY REFERENCES Z,
OperDate datetime NOT NULL,
OperSum float NOT NULL
)

Як видно з визначення другої таблиці, поля Db і Cr є посиланнями на аналітичну таблицю. В принципі, кожен запис аналітичної таблиці може містити необмежену кількість додаткових аналітичних ознак, але суть від цього не зміниться. Головне, що ми здійснюємо переклад грошей з однієї одиниці аналітики на іншу (рівнозначну по глибині / рівню). Порахувати залишки / обороти для такого виду таблиць досить просто. Для початку заповнимо наші таблиці даними:

Insert into Z(idZ, ZName) Values(1, “OC”) Insert into Z (idZ, ZName) Values ​​(2, “Амортизація ОС”) Insert into Z (idZ, ZName) Values ​​(10, “Склад матеріалів”) Insert into Z (idZ, ZName) Values ​​(41, “товоров”) Insert into Z (idZ, ZName) Values ​​(46, “Реалізація”) Insert into Z (idZ, ZName) Values ​​(50, “Каса”) Insert into Z (idZ, ZName) Values ​​(51, “Розрахунковий рахунок”) Insert into Z (idZ, ZName) Values ​​(60, “Постачальники”) Insert into Z (idZ, ZName) Values ​​(71, “Овансовие звіти”) Insert into Z (idZ, ZName) Values ​​(75, “Учт.кап.”)
SET DATEFORMAT dmy
Insert into Oper(Db, Cr, OperDate, OperSum) Values(51, 75, “01.01.2002”, 1000)
Insert into Oper(Db, Cr, OperDate, OperSum) Values(50, 51, “02.01.2002”, 300)
Insert into Oper(Db, Cr, OperDate, OperSum) Values(71, 50, “03.01.2002”, 100)
Insert into Oper(Db, Cr, OperDate, OperSum) Values(10, 71, “04.01.2002”, 55)
Insert into Oper(Db, Cr, OperDate, OperSum) Values(50, 71, “04.01.2002”, 40)
Insert into Oper(Db, Cr, OperDate, OperSum) Values(60, 50, “05.01.2002”, 200)
Insert into Oper(Db, Cr, OperDate, OperSum) Values(41, 60, “06.01.2002”, 180)
Insert into Oper(Db, Cr, OperDate, OperSum) Values(51, 60, “06.01.2002”, 20)
Insert into Oper(Db, Cr, OperDate, OperSum) Values(50, 71, “07.01.2002”, 5)

Тепер можна створити спрощений варіант розрахунку оборотної відомості. Для простоти викинемо розрахунок залишку на проміжну дату і обороти (їх неважко додати, але це ускладнить розуміння основного принципу).


Отже, розрахувати оборотку для деякого рівня аналітики можна в два прийоми. Спочатку потрібно виділити рівень аналітики, для якого необхідно провести розрахунок, а потім зробити агрегацію, з’єднавши таблицю проводок і аналітики. У нашому, простому, випадку є тільки один рівень аналітики, а значить, додатковий запит на вибірку та агрегацію аналітики не потрібно. Тобто ми можемо розраховувати обороти і залишки на підставі даних з таблиці проводок.


Загальний алгоритм зовсім простий – потрібно вибрати необхідні записи (за деякий період) з таблиці проводок і порахувати їх суму для дебету і кредиту кожного з рахунків. На жаль, записати це одним запитом (Без підзапитів) неможливо (якщо хто викрутиться, то покажіть, як). Я знаю два обходу даної проблеми. Перший відомий мені спосіб – ми можемо створити основний запит, який буде повертати список аналітичних ознак, а обороти і залишки підраховувати в простих підзапит. Другий – можна зробити агрегацію окремо для дебетових і кредитових ознак і з’єднати отримані запити за допомогою FULL OUTER JOIN. Я продемонструю другий підхід:

SELECT IsNull(DbS.Db, CrS.Cr) AS Z,
IsNull(CrS.CrSum, 0) AS Db,
IsNull(DbS.DbSum, 0) AS Cr
FROM
(SELECT Cr, SUM(OperSum) AS CrSum
FROM Oper
GROUP BY Cr) as CrS

FULL OUTER JOIN
(SELECT Db, SUM(OperSum) AS DbSum
FROM Oper
GROUP BY Db) as DbS

ON CrS.Cr = DbS.Db

Даний запит містить два підзапиту (виділених жирним), які розраховують обороти по дебету і кредиту аналітичних ознак.


Якщо потрібно обчислювати оборот і залишок за певний період, потрібно додати в підзапити фільтрацію необхідного періоду. Наведений вище запит повинен буде сам стати підзапитом. Справа в тому, що для повноцінної оборотки потрібно розрахувати залишок на початок періоду і оборот за період. Техніка тут буде приблизно така ж, так що навряд чи її варто торкатися. Той же FULL OUTER JOIN за аналітичними ознаками … Залишок на кінець розраховується шляхом додавання залишку на початок періоду і обігу за період.


Якщо потрібно додати назву аналітичного ознаки, то можна зробити ще одну вкладеність і JOIN з довідковою таблицею. Ось варіант для нашого прикладу:

SELECT  sb.Db, Z.ZName, sb.Cr, sb.Z
FROM Z [b]INNER JOIN[/b]
([b]SELECT IsNull(DbS.Db, CrS.Cr) AS Z,
IsNull(CrS.CrSum, 0) AS Db,
IsNull(DbS.DbSum, 0) AS Cr
FROM
(SELECT Cr, SUM(OperSum) AS CrSum
FROM Oper
GROUP BY Cr) as CrS FULL
OUTER JOIN
(SELECT Db, SUM(OperSum) AS DbSum
FROM Oper
GROUP BY Db) as DbS
ON CrS.Cr = DbS.Db[/b]
) as sb ON Z.idZ = sb.Z

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


Варто також звернути увагу на те, що при з’єднанні результату з записами з таблиці аналітики застосовується INNER JOIN. INNER JOIN більш ефективний, до того ж, якщо застосувати OUTER JOIN, в запит потраплять “Зайві записи” (аналітики, для якої не було оборотів).


Однак може з’явитися (і з’являється) питання:


Все це звичайно дуже здорово. Але ось простий приклад. Я хочу отримати баланс, причому не просто за рахунками, але і з угрупованням по аналітикам. Щось на кшталт:

Товари на складі 1000 Продукти харчування 200 Вобла 50 Пиво “Lowenbrau” 150 Одяг 400 Джинси 250 Майки 150 Взуття 400 Кросівки 200 Ласти на каблуках 200

Тут “Товари на складі” – це рахунок, “Продукти харчування”, “Одяг”, “Взуття” – аналітики типу “Вид товару”, а все інше – аналітики типу “Товар”. Якщо дані зберігаються у вигляді:

tblEntries
———-
entry_id
entry_volume
account_id
analytic_0_id
analytic_1_id

analytic_N_id то написавши
SELECT
SUM( entry_volume )

GROUP BY
account_id,
analytic_0_id
analytic_1_id

analytic_N_id

ми відразу одержимо потрібний нам результат. А як бути у випадку з більш універсальним зберіганням аналітичних атрибутів?


Перше що хочеться тут зауважити – це те, що приклад не дуже вдалий. Справа в тому, що в даному прикладі визначаються не атрибути проводки, а атрибути номенклатури. Кількісний облік – у певному сенсі облік в іншому вимірі. Не в грошовому, а в кількісному. Точніше в кількісно-грошовому.


Тут є сенс зупиниться і детальніше розібрати загальну структуру проводки. Подивіться на наступний малюнок:










Дебет


Кредит


Дата


Сума


Якщо оперувати не бухгалтерськими термінами, а загальнолюдськими (або навіть, можна сказати, об’єктно-орієнтованими), можна сказати, що Дебет – це «місце», куди прийшли гроші, Кредит – звідки ці гроші пішли, Дата – момент часу, коли ці гроші перейшли з однієї «точки» в іншу, а сума – і є Переміщена сума. Але ж гроші – це всього лише абстрактна одиниця виміру (загальний еквівалент). По суті, це універсальна одиниця виміру для будь-яких предметів і справ. По суті, сума, з точки зору проведення, є не що інше, як універсальний вимір деякого ресурсу, який піддається переміщенню з одного «місця» на інше. Наприклад, переміщенню можуть підлягати кросівки у кількості 10 пар, але в бухгалтерській проводці буде записана тільки їх сумарна вартість в певній валюті (одиниці виміру), тобто замість 10 пар кросівок буде записано 15 000 рублів. З алгоритмічної точки зору це – всього лише запис у більш полиморфном вигляді, тобто в грошовому еквіваленті, який можна обробляти більше поліморфно (наприклад, можна скласти обороти з продажу всіх товарів). Погляд на суму в проводці як на кількісний опис якогось ресурсу дозволяє довести цю абстракцію до логічної завершеності. Отже, і сума проводки, і кількість товару є всього лише виміром переміщуваного ресурсу. Ця концепція прекрасно лягає на будь-яку сферу діяльності підприємства. Наприклад, в якості ресурсу може виступати час, витрачений працівником на виконання певної роботи, що має певну вартість (за одиницю часу), тобто з’являється можливість вважати відрядну зарплату універсальними обліковими засобами. Шляхом нехитрих роздумів можна прийти до думки, що ресурс може вимірюватися в різних одиницях і мати власні атрибути (як z-об’єкти). Ось нове подання проводки:


Стало бути, говорячи про кількісно-сумовому обліку (товар і групі, до якої він належить), ми отримуємо не атрибути дебетового або кредитового z-об’єкта, а атрибути одиниці ресурсу (тип і групу). При цьому кількість товару є ні що інше, як інший вимір (відмінне від грошового). Зауважте, тип товару – це аналітика саме одиниці виміру! І не слід зараховувати її до дебету або кредиту. Більш того, я б навіть порадив не зберігати її в проводці. У проводці треба зберігати тільки суму в грошах (поліморфну ​​подання). Кількісний облік краще винести в окрему таблицю, яка містить посилання на проводку (зв’язок “одна проводка до багатьох деталізують записів”). При цьому можна буде додавати кілька кількісно-сумових записів для однієї проводки, а проводка буде містити загальну суму по всіх цих записів. Таким чином, проводка буде схожа на просту накладну, що дуже добре відображає реальне життя. Щоб пояснити думку, створимо просту реалізацію вищесказаного. Назвемо таблицю, в якій будуть лежати деталі проводки, OperDet (деталі операції). Номенклатура в цьому випадку враховується як посилання (поле в таблиці кількісно-сумового обліку) на таблицю (и) довідника номенклатури. Ось її опис та опис супутніх таблиць:

SET QUOTED_IDENTIFIER ON
SET ANSI_NULLS ON
GO
create table “Product” (
“idProd” INT IDENTITY NOT NULL PRIMARY KEY,
“ProdName” varchar(100) NOT NULL,
“ProdGroup” varchar(20) NOT NULL)
go
create table “OperDet” (
“idOper” INT NOT NULL,
“idProd” Int Not Null,
“DetCount” FLOAT NOT NULL,
“DetPrice” FLOAT NOT NULL)
go
alter table “OperDet”
add constraint “OperDet_PK”
primary key (“idOper”, “idProd”)
alter table “OperDet”
add constraint “Oper_OperDet_FK1”
foreign key(“idOper”)
references “Oper” (“idOper”)

А ось ER-діаграма:


При цьому, правда, з’являється необхідність синхронізувати суму операції (проводки) з сумою за відповідними деталям операції. Це можна зробити в тригерах або в middleware.


Як не дивно, чудове структурування, продемонстроване в прикладі вище, не породжує безлічі аналітичних ознак. По суті, ознака один – номенклатура. А намальоване деревце – всього лише структурування самих ознак, і є простим (одиночним) атрибутом номенклатури, що посилається на окремий довідник груп. Можна зберігати такий довідник як дерево (у вигляді спеціальної реляційної структури) і перед агрегацією вибирати необхідні листя цього дерева (які і є номенклатурою), а потім робити з’єднання з таблицею деталей проводки і таблицею проводок, отримуючи тим самим структуру, аналогічну наведеній вище. Агрегації за ієрархією груп можна добитися, «розгортаючи» її у вигляді простої плоскої (віртуальної) таблиці, яка містить по одній колонці для кожного рівня вкладеності. Наприклад, дерево:

Назва гілки # гілки в БД Товари на складі 1 Продукти харчування 2 Вобла 3 Пиво “Lowenbrau” 4 Одяг 5 Джинси 6 Майки 7 Взуття 8 Кросівки 9 Ласти на каблуках 10

можна розгорнути в наступну тимчасову таблицю:









































1


NULL


NULL


1


2


NULL


1


2


3


1


2


4


1


5


NULL


1


5


6


1


5


7


1


8


9


1


8


10


Для агрегації з цієї таблиці потрібно виділити всі значення останньої колонки не містять NULL. При формуванні остаточного звіту потрібно об’єднати (з допомогу OUTER JOIN) результат агрегації з цією таблицею (За останнім полю) і підрахувати проміжні результати. Цей спосіб не вимагає зміни структури БД для зміни рівнів ієрархії. Більш того, можна створювати віртуальні ієрархії (тільки на час побудови запиту).


Але питання може бути пов’язаний не обов’язково з кількісно-сумовим обліком. Ресурс, і тим більше, z-об’єкт (адже атрибути застосовні і до них) може мати різні структуру і кількість атрибутів (аналітичних ознак). Як бути в цій ситуації? Я бачу два виходи – створити універсальну структуру для зберігання необмеженої кількості аналітичних ознак, або створювати (краще всього динамічно) окремі таблиці для зберігання аналітичної інформації для окремих типів z-об’єктів. Чесно кажучи, до сих пір я не можу віддати перевагу одному з варіантів. Динамічні таблиці складніше в реалізації, зате дають можливість зберігати атрибути не тільки посилального типу, а й звичайні атрибути (текстові, числові, булеві, дати). З іншого боку, кілька ускладнюються поліморфні запити, які повертають інформацію відразу за декількома типами z-об’єктів або різних типів ресурсів. У будь-якому випадку, найбільш часто використовувані атрибути є сенс зберігати або безпосередньо в таблиці, що зберігає z-об’екти/ресурси, або в таблиці, що описує їх типи (наприклад, безпосередньо в z-об’єкті можна зберігати бухгалтерський рахунок або тип z-об’єкта, а в описі типу – уже номер бухгалтерського рахунку). При цьому для z-об’екта/ресурса буде матися поліморфна запис в універсальній таблиці, що зберігає z-об’єкти (деталі операції для ресурсів), і в спеціалізованій таблиці для конкретного типу z-об’ектов/ресурса. Зв’язок між цими таблицями повинна бути один до одного. Власне, це дуже схоже на те, що пропонує документо-орієнтований підхід (зберігання інформації про дебеті і кредиті кожного рахунку в окремій таблиці) тільки в цьому випадку є єднальна таблиця зберігає інформацію про проводці. По суті – це те ж саме, але наведене в нормальну (нормалізовану і несуперечливу) реляційну форму.


Другий варіант теж має на увазі наявність поліморфної таблиці z-об’ектов/ресурсов, але замість динамічного створення таблиць для кожного їх типу використовує одну універсальну таблицю. Схема, що дозволяє задавати необмежену кількість атрибутів для будь-якого z-об’єкта (на малюнку єднальна таблиця має назву Z2Ref), показана на малюнку 1.


Ref – це спрощена довідкова таблиця. У реальному житті її місце повинні зайняти таблиці, що реалізують універсальну (необхідної складності) модель довідників (куди можна динамічно додавати довідники та / або рівні ієрархії). По суті між таблицею Ref і Z з’являється зв’язок “багато до багатьох”, тобто з будь-яким z-об’єктом можна асоціювати будь-який елемент довідника і навпаки. Це дозволяє домогтися максимальної гнучкості і при цьому не створювати зайві таблиці (як у попередньому випадку).

Рисунок 1


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


Для початку нам потрібно заповнити таблиці Ref і Z2Ref початковими значеннями. Так як до реального життя цього всього далеко, я заповнив ці таблиці тим, що прийшло до голову:

SET QUOTED_IDENTIFIER ON
SET ANSI_NULLS ON
GO
create table “Ref”
(
“idRef” INT IDENTITY NOT NULL PRIMARY KEY,
“RefName” varchar(100) NOT NULL
)
create table “Z2Ref”
(
“idRef” INT NOT NULL,
“idZ” INT NOT NULL
)
alter table “Z2Ref”
add constraint “Z2Ref_PK”
primary key clustered (“idZ”, “idRef”)
Insert into Ref (RefName) Values ​​(“Васек”) Insert into Ref (RefName) Values ​​(“Склад 1”) Insert into Ref (RefName) Values ​​(“Петек”) Insert into Ref (RefName) Values ​​(“Склад 2”) Insert into Ref (RefName) Values ​​(“Каса 1”) Insert into Ref (RefName) Values ​​(“Каса 1”) Insert into Ref (RefName) Values ​​(“Нюрка”)
Insert into Z2Ref(idRef, idZ) Values(3, 10)
Insert into Z2Ref(idRef, idZ) Values(3, 41)
Insert into Z2Ref(idRef, idZ) Values(1, 50)
Insert into Z2Ref(idRef, idZ) Values(3, 60)
Insert into Z2Ref(idRef, idZ) Values(7, 60)
Insert into Z2Ref(idRef, idZ) Values(1, 71)
Insert into Z2Ref(idRef, idZ) Values(3, 71)
Insert into Z2Ref(idRef, idZ) Values(1, 75)

Припустимо тепер, що нам потрібно отримати обороти для Васька і Петько, тобто для записів 1 і 3 з таблиці Ref (в реальному житті варто було б додати тип z-об’єкта, так як не варто змішувати неоднорідні дані, але тут цього не робиться – через брак такого в спрощеній схемі). Щоб створити такий запит, досить кілька модернізувати попередні запити:

SELECT Agr.Z, Ref.RefName AS Name, Agr.Db, Agr.Cr
FROM (SELECT IsNull(DbS.idRef, CrS.idRef) AS Z,
IsNull(CrS.CrSum, 0) AS Db,
IsNull(DbS.DbSum, 0) AS Cr
FROM (SELECT Z2Ref.idRef,
SUM(Oper.OperSum) AS DbSum
FROM Oper INNER JOIN
Z ON Oper.Db = Z.idZ
INNER JOIN
Z2Ref ON Z.idZ = Z2Ref.idZ
WHERE (Z2Ref.idRef IN (1, 3))
GROUP BY Z2Ref.idRef

) DbS
FULL OUTER JOIN
(SELECT Z2Ref.idRef,
SUM(Oper.OperSum) AS CrSum
FROM Oper INNER JOIN
Z ON Oper.Cr = Z.idZ
INNER JOIN
Z2Ref ON Z.idZ = Z2Ref.idZ
WHERE (Z2Ref.idRef IN (1, 3))
GROUP BY Z2Ref.idRef

) CrS
ON DbS.idRef = CrS.idRef
) Agr
INNER JOIN Ref ON Agr.Z = Ref.idRef

Їх основу, як і раніше, складають два агрегують підзапиту, що обчислюють обороти по дебету і кредиту обраних аналітичних ознак. Завдання ознак в цьому прикладі зроблено через оператор in (), в реальних запитах може бути і результатом підзапиту.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*