Написання простої схеми банку: Як я можу підтримувати баланси у синхронізації з історією транзакцій?


57

Я пишу схему для простої банківської бази даних. Ось основні характеристики:

  • База даних зберігатиме транзакції проти користувача та валюти.
  • У кожного користувача є один залишок на валюту, тому кожен залишок - це просто сума всіх транзакцій проти даного користувача та валюти.
  • Баланс не може бути негативним.

Банківська програма зв’язуватиметься зі своєю базою даних виключно через збережені процедури.

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

Мої варіанти:

  1. Майте окрему balancesтаблицю і виконайте одну з наступних дій:

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

    2. Застосовуйте транзакції до transactionsтаблиці та майте тригер, який оновлює balancesтаблицю для мене на суму транзакції.

    3. Застосовуйте транзакції до balancesтаблиці та майте тригер, який додає transactionsдля мене новий запис із сумою транзакції.

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

  2. Майте balancesіндексований вигляд, який відповідним чином агрегує транзакції. Система зберігання даних гарантує баланс, щоб він синхронізувався зі своїми транзакціями, тому мені не потрібно покладатися на підходи, засновані на безпеці, щоб гарантувати це. З іншого боку, я не можу примусити залишки бути негативними, оскільки погляди - навіть індексовані - не можуть мати CHECKобмежень. (За підтримки Денні .)

  3. Майте просто transactionsтаблицю, але з додатковим стовпцем, щоб зберігати баланс, що діє після закінчення транзакції. Таким чином, останній запис транзакцій для користувача та валюти також містить їх поточний баланс. (Запропоновано Андрієм нижче ; варіант, запропонований garik .)

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

  • Ви створили та керували такою базою даних із профілем високого навантаження? Яким було ваше вирішення цієї проблеми?

  • Як ви вважаєте, я зробив правильний вибір дизайну? Чи варто щось пам’ятати?

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

  • Якщо індексований вигляд - це шлях, як я можу гарантувати відсутність балансу негативного?


Архівні транзакції:

Дозвольте трохи зупинитися на архівуванні транзакцій та "зведених транзакціях", про які я згадував вище. По-перше, регулярне архівування буде необхідним у такій системі з високим навантаженням. Я хочу зберегти узгодженість між залишками та їх історіями трансакцій, дозволяючи перенести старі транзакції кудись в інше місце. Для цього я заміню кожну партію архівованих транзакцій підсумком їх суми на користувача та валюти.

Так, наприклад, цей перелік операцій:

user_id    currency_id      amount    is_summary
------------------------------------------------
      3              1       10.60             0
      3              1      -55.00             0
      3              1      -12.12             0

архівується геть і замінюється цим:

user_id    currency_id      amount    is_summary
------------------------------------------------
      3              1      -56.52             1

Таким чином, баланс із заархівованими транзакціями підтримує повну та послідовну історію транзакцій.


1
Якщо ви вибрали варіант 2 (який, на мою думку, є більш чистим), погляньте на сторінку pgcon.org/2008/schedule/attachments/… як ефективно реалізувати "матеріалізовані погляди". У варіанті 1, розділ 11 прикладної математики Хаана та Коппелаарса для професіоналів баз даних (не хвилюйтесь за назвою) було б корисно скласти уявлення про те, як ефективно реалізувати "обмеження переходу". Перше посилання призначене для PostgreSQL, а друге - для Oracle, але методи повинні працювати для будь-якої розумної системи баз даних.
jp

Теоретично ви хочете зробити №3. Правильний спосіб зробити «поточний баланс» - присвоїти баланс кожній транзакції. Переконайтеся, що ви можете остаточно замовити транзакції за допомогою послідовного ідентифікатора (бажано) або часової позначки. Ви насправді не повинні "обчислювати" ходовий баланс.
pbreitenbach

Відповіді:


15

Я не знайомий з бухгалтерським обліком, але вирішив деякі подібні проблеми в середовищах запасів. Я зберігаю поточні підсумки в одному рядку з транзакцією. Я використовую обмеження, так що мої дані ніколи не помиляються, навіть за умови великої одночасності. Таке рішення я написав ще в 2009 році :

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

CREATE TABLE Data.Inventory(InventoryID INT NOT NULL IDENTITY,
  ItemID INT NOT NULL,
  ChangeDate DATETIME NOT NULL,
  ChangeQty INT NOT NULL,
  TotalQty INT NOT NULL,
  PreviousChangeDate DATETIME NULL,
  PreviousTotalQty INT NULL,
  CONSTRAINT PK_Inventory PRIMARY KEY(ItemID, ChangeDate),
  CONSTRAINT UNQ_Inventory UNIQUE(ItemID, ChangeDate, TotalQty),
  CONSTRAINT UNQ_Inventory_Previous_Columns 
     UNIQUE(ItemID, PreviousChangeDate, PreviousTotalQty),
  CONSTRAINT FK_Inventory_Self FOREIGN KEY(ItemID, PreviousChangeDate, PreviousTotalQty)
    REFERENCES Data.Inventory(ItemID, ChangeDate, TotalQty),
  CONSTRAINT CHK_Inventory_Valid_TotalQty CHECK(
         TotalQty >= 0 
     AND (TotalQty = COALESCE(PreviousTotalQty, 0) + ChangeQty)
  ),
  CONSTRAINT CHK_Inventory_Valid_Dates_Sequence CHECK(PreviousChangeDate < ChangeDate),
  CONSTRAINT CHK_Inventory_Valid_Previous_Columns CHECK(
        (PreviousChangeDate IS NULL AND PreviousTotalQty IS NULL)
     OR (PreviousChangeDate IS NOT NULL AND PreviousTotalQty IS NOT NULL)
  )
);

-- beginning of inventory for item 1
INSERT INTO Data.Inventory(ItemID,
  ChangeDate,
  ChangeQty,
  TotalQty,
  PreviousChangeDate,
  PreviousTotalQty)
VALUES(1, '20090101', 10, 10, NULL, NULL);

-- cannot begin the inventory for the second time for the same item 1
INSERT INTO Data.Inventory(ItemID,
  ChangeDate,
  ChangeQty,
  TotalQty,
  PreviousChangeDate,
  PreviousTotalQty)
VALUES(1, '20090102', 10, 10, NULL, NULL);


Msg 2627, Level 14, State 1, Line 10

Violation of UNIQUE KEY constraint 'UNQ_Inventory_Previous_Columns'. 
Cannot insert duplicate key in object 'Data.Inventory'.

The statement has been terminated.


-- add more
DECLARE @ChangeQty INT;
SET @ChangeQty = 5;

INSERT INTO Data.Inventory(ItemID,
  ChangeDate,
  ChangeQty,
  TotalQty,
  PreviousChangeDate,
  PreviousTotalQty)

SELECT TOP 1 ItemID, '20090103', @ChangeQty, TotalQty + @ChangeQty, ChangeDate, TotalQty
  FROM Data.Inventory
  WHERE ItemID = 1
  ORDER BY ChangeDate DESC;

SET @ChangeQty = 3;

INSERT INTO Data.Inventory(ItemID,
  ChangeDate,
  ChangeQty,
  TotalQty,
  PreviousChangeDate,
  PreviousTotalQty)

SELECT TOP 1 ItemID, '20090104', @ChangeQty, TotalQty + @ChangeQty, ChangeDate, TotalQty
  FROM Data.Inventory
  WHERE ItemID = 1
  ORDER BY ChangeDate DESC;

SET @ChangeQty = -4;

INSERT INTO Data.Inventory(ItemID,
  ChangeDate,
  ChangeQty,
  TotalQty,
  PreviousChangeDate,
  PreviousTotalQty)

SELECT TOP 1 ItemID, '20090105', @ChangeQty, TotalQty + @ChangeQty, ChangeDate, TotalQty
  FROM Data.Inventory
  WHERE ItemID = 1
  ORDER BY ChangeDate DESC;

-- try to violate chronological order
SET @ChangeQty = 5;

INSERT INTO Data.Inventory(ItemID,
  ChangeDate,
  ChangeQty,
  TotalQty,
  PreviousChangeDate,
  PreviousTotalQty)

SELECT TOP 1 ItemID, '20081231', @ChangeQty, TotalQty + @ChangeQty, ChangeDate, TotalQty
  FROM Data.Inventory
  WHERE ItemID = 1
  ORDER BY ChangeDate DESC;

Msg 547, Level 16, State 0, Line 4

The INSERT statement conflicted with the CHECK constraint 
"CHK_Inventory_Valid_Dates_Sequence". 
The conflict occurred in database "Test", table "Data.Inventory".

The statement has been terminated.

SELECT ChangeDate,
  ChangeQty,
  TotalQty,
  PreviousChangeDate,
  PreviousTotalQty
FROM Data.Inventory ORDER BY ChangeDate;

ChangeDate              ChangeQty   TotalQty    PreviousChangeDate      PreviousTotalQty
----------------------- ----------- ----------- ----------------------- -----
2009-01-01 00:00:00.000 10          10          NULL                    NULL
2009-01-03 00:00:00.000 5           15          2009-01-01 00:00:00.000 10
2009-01-04 00:00:00.000 3           18          2009-01-03 00:00:00.000 15
2009-01-05 00:00:00.000 -4          14          2009-01-04 00:00:00.000 18


-- try to change a single row, all updates must fail
UPDATE Data.Inventory SET ChangeQty = ChangeQty + 2 WHERE InventoryID = 3;
UPDATE Data.Inventory SET TotalQty = TotalQty + 2 WHERE InventoryID = 3;

-- try to delete not the last row, all deletes must fail
DELETE FROM Data.Inventory WHERE InventoryID = 1;
DELETE FROM Data.Inventory WHERE InventoryID = 3;

-- the right way to update
DECLARE @IncreaseQty INT;

SET @IncreaseQty = 2;

UPDATE Data.Inventory 
SET 
     ChangeQty = ChangeQty 
   + CASE 
        WHEN ItemID = 1 AND ChangeDate = '20090103' 
        THEN @IncreaseQty 
        ELSE 0 
     END,
  TotalQty = TotalQty + @IncreaseQty,
  PreviousTotalQty = PreviousTotalQty + 
     CASE 
        WHEN ItemID = 1 AND ChangeDate = '20090103' 
        THEN 0 
        ELSE @IncreaseQty 
     END
WHERE ItemID = 1 AND ChangeDate >= '20090103';

SELECT ChangeDate,
  ChangeQty,
  TotalQty,
  PreviousChangeDate,
  PreviousTotalQty
FROM Data.Inventory ORDER BY ChangeDate;

ChangeDate              ChangeQty   TotalQty    PreviousChangeDate      PreviousTotalQty
----------------------- ----------- ----------- ----------------------- ----------------
2009-01-01 00:00:00.000 10          10          NULL                    NULL
2009-01-03 00:00:00.000 7           17          2009-01-01 00:00:00.000 10
2009-01-04 00:00:00.000 3           20          2009-01-03 00:00:00.000 17
2009-01-05 00:00:00.000 -4          16          2009-01-04 00:00:00.000 20

14

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

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


1
Я бачу, чому обмеження балансу насправді має бути діловим правилом. База даних просто надає послугу транзакцій, і користувач вирішує, що з нею робити.
Нік Чаммас

Що ви думаєте про коментарі Джека, що використання двох таблиць надає розробникам більше гнучкості в зміні або впровадженні бізнес-логіки? Крім того, чи є у вас прямий досвід індексованих поглядів, який підтверджує або оскаржує ці проблеми ?
Nick Chammas

1
Я б не сказав, що наявність двох таблиць дає можливість гнучкості рухатись - це реалізація бізнес-логіки. Це дає вам більше гнучкості в архівуванні даних. Однак як банк (принаймні в США) у вас є закони, які говорять, скільки даних потрібно зберігати. Ви хочете перевірити, як виглядає ефективність із видом зверху, а також врахувати, що якщо у вас індексований вид, ви не можете змінити схему базових таблиць. Просто інша річ, яку слід подумати.
mrdenny

Усі пункти, згадані у статті, є дійсними питаннями щодо використання індексованого виду.
mrdenny

1
Для уточнення: IMO транзакційний API надає більшу гнучкість, реалізуючи логіку бізнесу (не маючи двох таблиць). У цьому випадку я також буду за дві таблиці (принаймні з огляду на інформацію, яку ми маємо дотепер) через компроміси, запропоновані з підходом до індексованого перегляду (наприклад, тоді не можна використовувати DRI для забезпечення балансу> 0 бізнес правило)
Джек Дуглас

13

Дещо інший підхід (аналогічний вашому 2-му варіанту), який слід розглянути, - це мати лише таблицю транзакцій із визначенням:

CREATE TABLE Transaction (
      UserID              INT
    , CurrencyID          INT 
    , TransactionDate     DATETIME  
    , OpeningBalance      MONEY
    , TransactionAmount   MONEY
);

Ви також можете ідентифікувати транзакцію / замовлення, щоб ви могли обробляти дві транзакції з однією датою та покращувати запит на пошук.

Щоб отримати поточний баланс, все, що вам потрібно, - це останній запис.

Методи отримання останнього запису :

/* For a single User/Currency */
Select TOP 1 *
FROM dbo.Transaction
WHERE UserID = 3 and CurrencyID = 1
ORDER By TransactionDate desc

/* For multiple records ie: to put into a view (which you might want to index) */
SELECT
    C.*
FROM
    (SELECT 
        *, 
        ROW_NUMBER() OVER (
           PARTITION BY UserID, CurrencyID 
           ORDER BY TransactionDate DESC
        ) AS rnBalance 
    FROM Transaction) C
WHERE
    C.rnBalance = 1
ORDER BY
    C.UserID, C.CurrencyID

Мінуси:

  • Якщо вставляти транзакцію не впорядковано (тобто для виправлення проблеми / неправильного стартового балансу), можливо, вам знадобиться каскад оновлень для всіх наступних транзакцій.
  • Операції для Користувача / Валюти потрібно буде серіалізувати, щоб підтримувати точний баланс.

    -- Example of getting the current balance and locking the 
    -- last record for that User/Currency.
    -- This lock will be freed after the Stored Procedure completes.
    SELECT TOP 1 @OldBalance = OpeningBalance + TransactionAmount  
    FROM dbo.Transaction with (rowlock, xlock)   
    WHERE UserID = 3 and CurrencyID = 1  
    ORDER By TransactionDate DESC;

Плюси:

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

Редагувати: Деякі зразки запитів щодо отримання поточного балансу та виділення кон (спасибі @Jack Douglas)


3
Це SELECT TOP (1) ... ORDER BY TransactionDate DESCбуде дуже складно реалізувати таким чином, щоб SQL Server не постійно сканував таблицю транзакцій. Олексій Кузнєцов розмістив тут рішення подібної дизайнерської проблеми, що чудово доповнює цю відповідь.
Нік Чаммас

2
+1 Я використовую подібний підхід. До речі, нам потрібно бути дуже обережними і переконатися, що наш код працює правильно при одночасному навантаженні.
АК

12

Прочитавши ці дві дискусії, я зважився на варіант 2

Ознайомившись із цими дискусіями, я не впевнений, чому ви зважилися на рішення DRI над найбільш розумним з інших варіантів, які ви накреслили:

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

Таке рішення мають величезні практичні переваги , якщо у вас є розкіш обмежувати весь доступ до даних через транзакційний API. Ви втрачаєте дуже важливу перевагу DRI, яка полягає в тому, що цілісність гарантується базою даних, але в будь-якій моделі достатньої складності існують деякі ділові правила, які DRI не можуть застосувати .

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

Навіть якщо я архівую транзакції (наприклад, переміщуючи їх кудись і замінюючи їх підсумковими транзакціями)

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

Ось підсумок переваг транзакційного підходу, як я їх бачу:

  • Ми повинні робити це все одно, якщо це можливо. Яке б рішення ви не вибрали для цієї конкретної проблеми, воно надає більшу гнучкість дизайну та контроль над вашими даними. Весь доступ тоді стає "транзакційним" з точки зору ділової логіки, а не просто з точки зору логіки бази даних.
  • Ви можете тримати свою модель акуратною
  • Ви можете "примусити" набагато ширший спектр і складність бізнес-правил (зауваживши, що поняття "примусово виконувати" є більш розгубленим, ніж для DRI)
  • Ви все ще можете використовувати DRI там, де це практично, щоб надати моделі більш міцну базову цілісність - і це може послужити перевіркою вашої транзакційної логіки
  • Більшість проблем із виконанням, які вас хвилюють, розтануть
  • Введення нових вимог може бути набагато простішим - наприклад: складні правила для спірних транзакцій можуть відштовхнути вас від чистого підходу ДРІ далі вниз, що означає багато марних зусиль
  • Розбиття чи архівування історичних даних стає набагато менш ризикованим та болючим

--edit

Щоб дозволити архівування, не додаючи складності чи ризику, ви можете зберігати зведені рядки в окремій підсумковій таблиці, що генерується постійно (запозичення у @Andrew та @Garik)

Наприклад, якщо підсумки щомісяця:

  • щоразу, коли відбувається транзакція (через ваш API), є відповідне оновлення або вставка в підсумкову таблицю
  • зведена таблиця ніколи не архівується, але операції з архівуванням стають настільки ж простими, як видалити (або скинути розділ?)
  • кожен рядок у підсумковій таблиці містить "початковий баланс" та "суму"
  • Перевірити обмеження, такі як "залишок на відкритті" + "сума"> 0 та "залишок на відкритті"> 0, можна застосувати до підсумкової таблиці
  • рядки підсумків можна вставити в щомісячну партію, щоб полегшити блокування останнього підсумкового рядка (завжди буде рядок для поточного місяця)

Щодо редагування: Отже, ви пропонуєте мати цю підсумкову таблицю уздовж основної таблиці балансів? Чи ефективно тоді таблиця залишків стає підсумковою таблицею, яка має лише записи за поточний місяць (оскільки обидва будуть зберігати однакові дані)? Якщо я правильно зрозумів, то чому б просто не замінити таблицю балансів відповідним розділом на підсумковій таблиці?
Нік Чамма

Вибачте, ви праві, що це незрозуміло - я мав на увазі розповсюдження таблиці залишків, оскільки це завжди було б ключовим пошуком у підсумковій таблиці, щоб отримати поточний баланс (не вірно з пропозицією Ендрюса AFAIK). Перевага полягає в тому, що обчислення залишків за попередні часи стає простішим і є чіткіший аудиторський слід для залишків, якщо вони йдуть не так.
Джек Дуглас

6

Нік.

Основна ідея - зберігання балансу та записів транзакцій в одній таблиці. Це сталося історично, я думав. Тож у цьому випадку ми можемо отримати баланс, лише розмістивши останній підсумковий запис.

 id   user_id    currency_id      amount    is_summary (or record_type)
----------------------------------------------------
  1       3              1       10.60             0
  2       3              1       10.60             1    -- summary after transaction 1
  3       3              1      -55.00             0
  4       3              1      -44.40             1    -- summary after transactions 1 and 3
  5       3              1      -12.12             0
  6       3              1      -56.52             1    -- summary after transactions 1, 3 and 5 

Кращий варіант - зменшення кількості підсумкових записів. Ми можемо мати один балансовий запис у кінці (та / або початку) дня. Як ви знаєте, кожен банк operational dayповинен відкрити і, ніж закрити, зробити деякі зведені операції на цей день. Це дозволяє нам легко обчислювати відсотки , використовуючи щоденний балансовий запис, наприклад:

user_id    currency_id      amount    is_summary    oper_date
--------------------------------------------------------------
      3              1       10.60             0    01/01/2011 
      3              1      -55.00             0    01/01/2011
      3              1      -44.40             1    01/01/2011 -- summary at the end of day (01/01/2011)
      3              1      -12.12             0    01/02/2011
      3              1      -56.52             1    01/02/2011 -- summary at the end of day (01/02/2011)

Удача.


4

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

При такому підході гарантується, що баланс у реальному часі синхронізується з таблицею транзакцій. І не має значення, чи використовуються збережені процедури або psql або jdbc. Ви можете перевірити негативний баланс, якщо це потрібно. Продуктивність не буде проблемою. Щоб отримати баланс у реальному часі, це запит на одиночку.

Архівація не вплине на такий підхід. Ви можете мати щотижневу, щомісячну та щорічну підсумкову таблицю, якщо це потрібно для речей, таких як звіти.


3

В Oracle ви можете це зробити, використовуючи лише таблицю транзакцій зі швидким оновленням Materialized View на ній, що робить агрегацію для формування балансу. Ви визначаєте тригер у Матеріалізованому представленні. Якщо Матеріалізований вигляд визначено з "ON COMMIT", він фактично не дозволяє додавати / змінювати дані в базові таблиці. Тригер виявляє [in] дійсні дані та створює виняток, коли він відновлює транзакцію. Приємний приклад тут http://www.sqlsnippets.com/en/topic-12896.html

Я не знаю sqlserver, але, можливо, він має подібний варіант?


2
Матеріалізовані представлення в Oracle схожі на "індексований вигляд" SQL Server, але вони оновлюються автоматично, а не явно керованим способом, таким як поведінка Oracle "ON COMMIT". Дивіться social.msdn.microsoft.com/Forums/fi-FI/transactsql/thread/… та techembassy.blogspot.com/2007/01/…
GregW
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.