
Në të kaluarën Kam e përshkruan konceptin dhe zbatimin e një bazë të dhënash të ndërtuar mbi funksione, në vend të tabelave dhe fushave si në bazat e të dhënave relacionale. Ajo përmban shumë shembuj që tregojnë avantazhet e këtij qasjeje përballë atyre klasike. Shumë e konsideruan atë të pamjaftueshme bindëse.
Në këtë artikull, do të tregoj se si një koncept i tillë lejon që të balancojmë shpejt dhe lehtësisht shkrimin dhe leximin në një bazë të dhënash pa ndonjë ndryshim në logjikën e funksionimit. Një funksionalitet i tillë është përpjekur të realizohet në bazat e të dhënave komerciale moderne (veçanërisht Oracle dhe Microsoft SQL Server). Në fund të artikullit, do të tregoj se çfarë arritën, për të thënë të drejtën, jo shumë mirë.
Përshkrimi
Si dhe më parë, për një kuptim më të mirë, do të filloj përshkrimin me shembuj. Supozoni se na duhet të zbatojmë logjikën që do të kthejë një listë departamentesh me numrin e punonjësve në to dhe pagat e tyre totale.
Në një bazë të dhënash funksionale, kjo do të dukej si vijon:
CLASS Departamenti ‘Departamenti’;
emri ‘Emri’ = STRING DHE TË DHËNA[100] (Departamenti);
CLASS Employee ‘Punonjës’;
department ‘Departamenti’ = DATA Department (Employee);
salary ‘Paga’ = DATA NUMERIC[10,2] (Employee);
countEmployees ‘Numri i punonjësve’ (Departamenti d) =
GROUP SUM 1 IF department(Employee e) = d;
salarySum ‘Paga e Totale’ (Departamenti d) =
GROUP SUM salary(Employee e) IF department(e) = d;
SELECT name(Departamenti d), countEmployees(d), salarySum(d);
Kompleksiteti i realizimit të këtij kërkese në çdo sistem mund të jetë ekuivalent me O(numri i punonjësve), pasi për këtë llogaritje duhet të skanohet tabela e punonjësve dhe më pas të grupohen ato sipas departamentit. Po ashtu, do të ketë një shtesë të vogël (supozoni se punonjësit janë shumë më të shumtë se departamentet) në varësi të planit të zgjedhur O(log numri i punonjësve) ose O(numri i departamenteve) për grupimin dhe të tjera.
Është e qartë se shpenzimet për ekzekutimin mund të jenë të ndryshme në sisteme të ndryshme, por komplekstiteti nuk do të ndryshojë në asnjë rast.
Në zbatimin e propozuar, baza e të dhënave funksionale do të formojë një nënkërkesë që do të llogarisë vlerat e nevojshme për departamentin dhe pastaj do të bëjë JOIN me tabelën e departamenteve për të marrë emrin. Megjithatë, për secilën funksion, kur shpallet, ka mundësinë të caktohet një tregues të veçantë MATERIALIZED. Sistemi do të krijojë automatikisht një fushë për çdo një nga ato funksione. Kur vlera e funksionit ndryshon, vlera e fushës do të ndryshohet në të njëjtën transaksion. Kur i qaset këtij funksioni, do të bëhet referencë direkt tek fusha e llogaritur më parë.
Në veçanti, nëse caktohet MATERIALIZED për funksionet countEmployees dhe salarySum, atëherë do të shtohen dy fusha në tabelën me listën e departamenteve, ku do të ruhet numri i punonjësve dhe paga e tyre totale. Në çdo ndryshim në punonjës, pagat e tyre ose përkatësinë e departamenteve, sistemi do të ndryshojë automatikisht vlerat e këtyre fushave. Kërkesa e sipërme do të bëhet referencë direkt në këto fusha dhe do të realizohet për O(numri i departamenteve).
Cilat janë kufizimet? Vetëm një: një funksion i tillë duhet të ketë një numër të përfunduar hyrjesh, për të cilat është e përcaktuar vlera e tij. Përndryshe do të jetë e pamundur të ndërtohet një tabelë që ruan të gjitha vlerat e tij, pasi nuk mund të ketë një tabelë me numër të pafund rreshtash.
Shembuj:
employeesCount 'Numri i punonjësve me pagë > N' (Departamenti d, NUMERIC[10,2] N) =
GRUPI SUM pagën(Punonjësi e) NËSE departamenti(e) = d DHE paga(e) > N;
Ky funksion është i përcaktuar për një numër të pafund vlerash të numrit N (për shembull, çdo vlerë negative e përshtatet). Prandaj, MATERIALIZED nuk mund të vendoset mbi të. Kështu, është një kufizim logjik, jo teknologjik (pra, jo sepse nuk arritëm ta realizonim). Ndryshe — asnjë kufizim. Mund të përdoren grupime, renditje, AND dhe OR, PARTITION, rikursione, etj.
Për shembull, në detyrën 2.2 të artikullit të mëparshëm, mund të vendosim MATERIALIZED në të dy funksionet:
bleu 'Blerë' (Kundër c, Produkt p, INTEGER y) =
GRUPI SHUMË SUM (Detaj d) NËSE
klienti(order(d)) = c DHE
produkti(d) = p DHE
nxjerrVit(year(date(order(d))) = y MATERIALIZUAR;
vlerësimi 'Vlerësimi' (Kundër c, Produkt p, INTEGER y) =
PARTITION SHUMË 1 REND DESC bleer(c, p, y), p NGA c, y MATERIALIZUAR;
ZGJEDH emrinKontaktil(Customer c), emrin(Produkt p) KU vlerësimi(c, p, 1997) < 3;
Sistemi do të krijojë automatikisht një tabelë me çelësa të tipeve Klienti, Produkti dhe INTEGER, do të shtojë dy fusha në të dhe do të përmirësojë vlerat në to çdo ndryshim. Gjatë qasjeve të mëtejshme në këto funksione, nuk do të ndodhë llogaritja e tyre, por do të lexohen vlerat nga fushat përkatëse.
Me këtë mekanizëm, është e mundur të eliminohen rikursioni (CTE) në kërkesa. Në veçanti, le të shqyrtojmë grupet që formojnë një pemë përmes marrëdhënies child/parent (çdo grup ka një referencë në prindin e tij):
prind = DATA Group (Grupi);
Në një bazë të dhënash funksionale, logjika e rikursioneve mund të vendoset si vijon:
niveli (Grup fëmijë, Grup prind) = REKURSION 1l NËSE fëmija ËSHTË Grup DHE prindi == fëmija
HAPI 2l NËSE prindi == prindi($prindi);
ështëPrind (Grup fëmijë, Grup prind) = E VERTETË NËSE niveli(fëmija, prindi) MATERIALIZUAR;
Pasi për funksionin isParent është caktuar MATERIALIZED, një tabelë do të krijohet me dy çelësa (grupe), ku fusha isParent do të jetë e vërtetë vetëm nëse çelësi i parë është pasardhës i çelësit të dytë. Numri i regjistrimeve në këtë tabelë do të jetë i barabartë me numrin e grupeve të shumëzuar me thellësinë mesatare të pemës. Nëse është e nevojshme, për shembull, të llogaritet numri i pasardhësve të një grupi të caktuar, atëherë mund të merren me këtë funksion:
numriFëmijëve (Grupi g) = GRUPI SOMA 1 NËSE ështëPrind(Grup fëmijë, g);
Никакого CTE в SQL запросе при этом не будет. Вместо этого будет простой GROUP BY.
При помощи этого механизма можно также легко делать денормализацию базы данных при необходимости:
CLASS Order 'Porosi';
date 'Data' = DATA DATE (Porosi);
CLASS OrderDetail 'Строка заказа';
order 'Заказ' = DATA Order (OrderDetail);
date 'Дата' (OrderDetail d) = date(order(d)) MATERIALIZED INDEXED;
При обращении к функции date для строки заказа будет идти чтение из таблицы со строками заказов поля, по которому есть индекс. При изменении даты заказа система будет сама автоматически пересчитывать денормализованую дату в строке.
Përfitimet
Для чего весь этот механизм нужен? В классических СУБД, без переписывания запросов, разработчик или DBA могут лишь изменять индексы, определять статистику и подсказывать планировщику запросов, как их выполнять (причем HINT’ы есть только в коммерческих СУБД). Как бы они не старались, они не смогут первый запрос в статье выполнить за О (кол-во отделов) без изменения запросов и дописывания триггеров. В предложенной же схеме, на этапе разработки можно не задумываться о структуре хранения данных и о том, какие агрегации использовать. Это все можно спокойно менять на лету уже непосредственно в эксплуатации.
На практике это выглядит следующим образом. Некоторые люди разрабатывают непосредственно логику на основе поставленной задачи. Они не разбираются ни в алгоритмах и их сложности, ни в планах выполнения, ни в типах join’ов, ни в любой другой технической составляющей. Эти люди — скорее бизнес-аналитики, чем разработчики. Затем, все это идет в тестирование или эксплуатацию. Включается логирование длительных запросов. Когда обнаруживается долгий запрос, то уже другими людьми (более техническими — по сути DBA) принимается решение о включении MATERIALIZED на некоторой промежуточной функции. Тем самым немного замедляется запись (так как требуется обновление дополнительного поля в транзакции). Однако, значительно ускоряется не только этот запрос, но и все другие, которые используют эту функцию. При этом принятие решения о том, какую именно функцию материализовать принимается относительно несложно. Два основных параметра: кол-во возможных входных значений (именно столько записей будет в соответствующей таблице), и насколько часто она используется в других функциях.
Analoge
В современных коммерческих СУБД есть схожие механизмы: MATERIALIZED VIEW с FAST REFRESH (Oracle) и INDEXED VIEW (Microsoft SQL Server). В PostgreSQL MATERIALIZED VIEW не умеет обновляться в транзакции, а только по запросу (да еще с совсем жесткими ограничениями), так что его не рассматриваем. Но у них есть несколько проблем, что значительно ограничивает их использование.
Во-первых, можно включить материализацию только, если у вас уже был создан обычный VIEW. Иначе придется переписывать остальные запросы на обращение к вновь созданному представлению, чтобы использовать эту материализацию. Или оставить все как есть, но будет как минимум неэффективно, если есть определенные уже преподсчитанные данные, но многие запросы их не всегда используют, а высчитывают заново.
Во-вторых, у них есть огромное количество ограничений:
Oracle
5.3.8.4 General Restrictions on Fast Refresh
The defining query of the materialized view is restricted as follows:
- The materialized view must not contain references to non-repeating expressions like
SYSDATEandROWNUM.- The materialized view must not contain references to
RAWorLONGRAWdata types.- It cannot contain a
SELECTlist subquery.- It cannot contain analytic functions (for example,
RANK) in theSELECTclause.- It cannot reference a table on which an
XMLIndexindex is defined.- It cannot contain a
MODELclause.- It cannot contain a
HAVINGclause with a subquery.- It cannot contain nested queries that have
ANY,TË GJITHA, orJOEXISTS.- It cannot contain a
[START WITH …] CONNECT BYclause.- It cannot contain multiple detail tables at different sites.
ONCOMMITmaterialized views cannot have remote detail tables.- Nested materialized views must have a join or aggregate.
- Materialized join views and materialized aggregate views with a
GROUPBYclause cannot select from an index-organized table.5.3.8.5 Restrictions on Fast Refresh on Materialized Views with Joins Only
Defining queries for materialized views with joins only and no aggregates have the following restrictions on fast refresh:
- All restrictions from ««.
- They cannot have
GROUPBYclauses or aggregates.- Rowids of all the tables in the
FROMlist must appear in theSELECTlist of the query.- Materialized view logs must exist with rowids for all the base tables in the
FROMlist of the query.- You cannot create a fast refreshable materialized view from multiple tables with simple joins that include an object type column in the
SELECTstatement.Also, the refresh method you choose will not be optimally efficient if:
- The defining query uses an outer join that behaves like an inner join. If the defining query contains such a join, consider rewriting the defining query to contain an inner join.
- The
SELECTlist of the materialized view contains expressions on columns from multiple tables.5.3.8.6 Restrictions on Fast Refresh on Materialized Views with Aggregates
Defining queries for materialized views with aggregates or joins have the following restrictions on fast refresh:
- All restrictions from ««.
Fast refresh is supported for both
ONCOMMITandONDEMANDmaterialized views, however the following restrictions apply:
- All tables in the materialized view must have materialized view logs, and the materialized view logs must:
- Contain all columns from the table referenced in the materialized view.
- Specify with
ROWIDandINCLUDINGNEWVALUES.- Specify the
SEQUENCEclause if the table is expected to have a mix of inserts/direct-loads, deletes, and updates.- Only
SUM,COUNT,AVG,STDDEV,VARIANCE,MINandMAXare supported for fast refresh.COUNT(*)must be specified.- Aggregate functions must occur only as the outermost part of the expression. That is, aggregates such as
AVG(AVG(x))orAVG(x)+AVG(x)are not allowed.- For each aggregate such as
AVG(expr), the correspondingCOUNT(expr)must be present. Oracle recommends thatSUM(expr)be specified.- If
VARIANCE(expr)orSTDDEV(expr) is specified,COUNT(expr)andSUM(expr)must be specified. Oracle recommends thatSUM(expr *expr)be specified.- The
SELECTcolumn in the defining query cannot be a complex expression with columns from multiple base tables. A possible workaround to this is to use a nested materialized view.- The
SELECTlist must contain allGROUPBYcolumns.- The materialized view is not based on one or more remote tables.
- If you use a
CHARdata type in the filter columns of a materialized view log, the character sets of the master site and the materialized view must be the same.- If the materialized view has one of the following, then fast refresh is supported only on conventional DML inserts and direct loads.
- Materialized views with
MINorMAXaggregates- Materialized views which have
SUM(expr)but noCOUNT(expr)- Materialized views without
COUNT(*)Such a materialized view is called an insert-only materialized view.
- A materialized view with
MAXorMINis fast refreshable after delete or mixed DML statements if it does not have aWHEREclause.
The max/min fast refresh after delete or mixed DML does not have the same behavior as the insert-only case. It deletes and recomputes the max/min values for the affected groups. You need to be aware of its performance impact.- Materialized views with named views or subqueries in the
FROMclause can be fast refreshed provided the views can be completely merged. For information on which views will merge, see .- If there are no outer joins, you may have arbitrary selections and joins in the
WHEREclause.- Materialized aggregate views with outer joins are fast refreshable after conventional DML and direct loads, provided only the outer table has been modified. Also, unique constraints must exist on the join columns of the inner join table. If there are outer joins, all the joins must be connected by
DHEs and must use the equality (=) operator.- For materialized views with
CUBE,ROLLUP, grouping sets, or concatenation of them, the following restrictions apply:
- The
SELECTlist should contain grouping distinguisher that can either be aGROUPING_IDfunction on allGROUPBYexpressions orGROUPINGfunctions one for eachGROUPBYexpression. For example, if theGROUPBYclause of the materialized view is «GROUPBYCUBE(a, b)«, then theSELECTlist should contain either «GROUPING_ID(a, b)» or «GROUPING(a)DHEGROUPING(b)» for the materialized view to be fast refreshable.GROUPBYshould not result in any duplicate groupings. For example, «GROUP BY a, ROLLUP(a, b)» is not fast refreshable because it results in duplicate groupings «(a), (a, b), AND (a)«.5.3.8.7 Restrictions on Fast Refresh on Materialized Views with UNION ALL
Materialized views with the
UNIONTË GJITHAset operator support theREFRESHFASToption if the following conditions are satisfied:
- The defining query must have the
UNIONTË GJITHAoperator at the top level.The
UNIONTË GJITHAoperator cannot be embedded inside a subquery, with one exception: TheUNIONTË GJITHAcan be in a subquery in theFROMclause provided the defining query is of the formSELECT * FROM(view or subquery withUNIONTË GJITHA) as in the following example:CREATE VIEW view_with_unionall AS (SELECT c.rowid crid, c.cust_id, 2 umarker FROM customers c WHERE c.cust_last_name = 'Smith' UNION ALL SELECT c.rowid crid, c.cust_id, 3 umarker FROM customers c WHERE c.cust_last_name = 'Jones'); CREATE MATERIALIZED VIEW unionall_inside_view_mv REFRESH FAST ON DEMAND AS SELECT * FROM view_with_unionall;Note that the view
view_with_unionallsatisfies the requirements for fast refresh.- Each query block in the
UNIONTË GJITHAquery must satisfy the requirements of a fast refreshable materialized view with aggregates or a fast refreshable materialized view with joins.The appropriate materialized view logs must be created on the tables as required for the corresponding type of fast refreshable materialized view.
Note that the Oracle Database also allows the special case of a single table materialized view with joins only provided theROWIDcolumn has been included in theSELECTlist and in the materialized view log. This is shown in the defining query of the viewview_with_unionall.- The
SELECTlist of each query must include aUNIONTË GJITHAmarker, and theUNIONTË GJITHAcolumn must have a distinct constant numeric or string value in eachUNIONTË GJITHAbranch. Further, the marker column must appear in the same ordinal position in theSELECTlist of each query block. See «» for more information regardingUNIONTË GJITHAmarkers.- Some features such as outer joins, insert-only aggregate materialized view queries and remote tables are not supported for materialized views with
UNIONTË GJITHA. Note, however, that materialized views used in replication, which do not contain joins or aggregates, can be fast refreshed whenUNIONTË GJITHAor remote tables are used.- The compatibility initialization parameter must be set to 9.2.0 or higher to create a fast refreshable materialized view with
UNIONTË GJITHA.
Не хочу обидеть поклонников Oracle, но судя по их списку ограничений, создается впечатление, что этот механизм писали не в общем случае, используя какую-то модель, а тысячи индусов, где каждому дали писать свою ветку, и каждый из них что смог, то и сделал. Использование этого механизма для реальной логики — это как хождение по минному полю. В любой момент можно получить мину, попав на одно из не очевидных ограничений. Как это работает — тоже отдельный вопрос, но он находится вне рамок данной статьи.
Microsoft SQL Server
Additional Requirements
In addition to the SET options and deterministic function requirements, the following requirements must be met:
- The user that executes
CREATE INDEXmust be the owner of the view.- When you create the index, the
IGNORE_DUP_KEYoption must be set to OFF (the default setting).- Tables must be referenced by two-part names, schema.tablename in the view definition.
- User-defined functions referenced in the view must be created by using the
WITH SCHEMABINDINGoption.- Any user-defined functions referenced in the view must be referenced by two-part names, <schema>.<function>.
- The data access property of a user-defined function must be
NO SQL, and external access property must beNO.- Common language runtime (CLR) functions can appear in the select list of the view, but cannot be part of the definition of the clustered index key. CLR functions cannot appear in the WHERE clause of the view or the ON clause of a JOIN operation in the view.
- CLR functions and methods of CLR user-defined types used in the view definition must have the properties set as shown in the following table.
Pronë
NoteDETERMINISTIC = TRUE
Must be declared explicitly as an attribute of the Microsoft .NET Framework method.PRECISE = TRUE
Must be declared explicitly as an attribute of the .NET Framework method.DATA ACCESS = NO SQL
Determined by setting DataAccess attribute to DataAccessKind.None and SystemDataAccess attribute to SystemDataAccessKind.None.EXTERNAL ACCESS = NO
This property defaults to NO for CLR routines.- The view must be created by using the
WITH SCHEMABINDINGoption.- The view must reference only base tables that are in the same database as the view. The view cannot reference other views.
- The SELECT statement in the view definition must not contain the following Transact-SQL elements:
COUNT
ROWSET functions (OPENDATASOURCE,OPENQUERY,OPENROWSET, ANDOPENXML)
OUTERjoins (LEFT,RIGHT, orFULL)Derived table (defined by specifying a
SELECTstatement in theFROMclause)
Self-joins
Specifying columns by usingSELECT *orSELECT <table_name>.*
DISTINCT
STDEV,STDEVP,VAR,VARP, orAVG
Common table expression (CTE)float1, text, ntext, image, XML, or filestream columns
Subquery
OVERklauzola, e cila përfshin funksione të renditjes ose funksione agregatePredikate të tekstit të plotë (
PËRMBAN,FREETEXT)
SUMfunksioni që referon një shprehje nullable
RENDIT MEfunksioni agregat të përcaktuar nga përdoruesi CLR
MË TË LARTË
CUBE,ROLLUP, orGRUPIM NË GRUPEToperatorë
MIN,MAX
UNION,Përjashtim, orINTERSECToperatorë
SAMPLETABLEVariablat e tabelës
APLIKIM I JASHTËMorCROSS APPLY
PIVOT,UNPIVOTgrupet e kolonave të hollë
Funksione të drejtpërdrejta (TVF) ose funksione të tabelës me shumë deklarata (MSTVF)
OFFSET
CHECKSUM_AGG1 Pamja e indekseve mund të përmbajë float kolona; megjithatë, këto kolona nuk mund të përfshihen në çelësin e indeksit të rreshtuar.
- If
GROUP BYështë e pranishme, përkufizimi i VIEW duhet të përmbajëCOUNT_BIG(*)dhe nuk duhet të përmbajëHAVING. KëtoGROUP BYkufizime janë të aplikueshme vetëm për përkufizimin e pamjes me indekse. Një pyetje mund të përdorë një pamje me indekse në planin e ekzekutimit të saj edhe nëse nuk i përmbush këtoGROUP BYkufizime.- Nëse përkufizimi i pamjes përmban një
GROUP BYklauzolë, çelësi i indeksit unik të rreshtuar mund të referojë vetëm kolonat e përcaktuara nëGROUP BYclause.
Këtu shihet se indianët nuk u tërhoqën, pasi vendosën të veprojnë sipas skemës "do bëjmë pak, por mirë". Kështu, ata kanë më shumë të minuar në fushë, por vendosja e tyre është më e qartë. Më shumë se gjithçka, ky kufizim më shqetëson:
The view must reference only base tables that are in the same database as the view. The view cannot reference other views.
Në terminologjinë tonë, kjo do të thotë se funksioni nuk mund të aksesojë një funksion tjetër të materializuar. Kjo e prish tërë ideologjinë.
Po ashtu, ky kufizim (dhe më tej në tekst) zvogëlon ndjeshëm opsionet e përdorimit:
The SELECT statement in the view definition must not contain the following Transact-SQL elements:
COUNT
ROWSET functions (OPENDATASOURCE,OPENQUERY,OPENROWSET, ANDOPENXML)
OUTERjoins (LEFT,RIGHT, orFULL)Derived table (defined by specifying a
SELECTstatement in theFROMclause)
Self-joins
Specifying columns by usingSELECT *orSELECT <table_name>.*
DISTINCT
STDEV,STDEVP,VAR,VARP, orAVG
Common table expression (CTE)float1, text, ntext, image, XML, or filestream columns
Subquery
OVERklauzola, e cila përfshin funksione të renditjes ose funksione agregatePredikate të tekstit të plotë (
PËRMBAN,FREETEXT)
SUMfunksioni që referon një shprehje nullable
RENDIT MEfunksioni agregat të përcaktuar nga përdoruesi CLR
MË TË LARTË
CUBE,ROLLUP, orGRUPIM NË GRUPEToperatorë
MIN,MAX
UNION,Përjashtim, orINTERSECToperatorë
SAMPLETABLEVariablat e tabelës
APLIKIM I JASHTËMorCROSS APPLY
PIVOT,UNPIVOTgrupet e kolonave të hollë
Funksione të drejtpërdrejta (TVF) ose funksione të tabelës me shumë deklarata (MSTVF)
OFFSET
CHECKSUM_AGG
OUTER JOINS, UNION, RENDIT ME dhe të tjera janë të ndaluara. Ndoshta do të ishte më e lehtë të përcaktohej se çfarë mund të përdoret, sesa çfarë nuk mund të përdoret. Lista ndoshta do të ishte shumë më e vogël.
Duke përmbledhur: një set i madh kufizimesh në çdo (vërejtë komerciale) DBMS vs asgjë (përveç një logjik, e jo teknike) në teknologjinë LGPL. Megjithatë, duhet të theksohet se implementimi i këtij mekanizmi në logjikën relacionale është disi më i komplikuar se sa në logjikën e përshkruar.
Realizimi
Si funksionon kjo? Si "makinë virtuale" përdoret PostgreSQL. Brenda saj ka një algoritëm të komplikuar që merret me ndërtimin e pyetjeve. Ja . Dhe aty nuk ka thjesht një set të madh heuristikësh me shumë if’ë. Pra, nëse keni disa muaj për të studiuar, mund të provoni të kuptoni arkitekturën.
A funksionon kjo efektivisht? Mjaft efektivisht. Fatkeqësisht, është e vështirë të provohet kjo. Mund të them vetëm se nëse shqyrtoni mijëra pyetje që ekzistojnë në aplikacione të mëdha, atëherë mesatarisht ato janë më efektive se ato të një zhvilluesi të mirë. Një programues SQL i shkëlqyer mund të shkruajë çdo pyetje më efektivisht, por mbi një mijë pyetje ai thjesht nuk do të ketë as motivim, as kohë për ta bërë këtë. E vetmja gjë që mund të jap tani si provë efektiviteti është se në bazën e platformës që është ndërtuar mbi këtë DBMS operojnë disa projekte , që përmbajnë mijëra funksione të ndryshme MATERIALIZED, me mijëra përdorues dhe baza të dhënash terabajt me qindra miliona regjistrime, që funksionojnë në një server të zakonshëm me dy procesorë. Megjithatë, çdo person i interesuar mund të verifikojë/përgënjeshtrojë efektivitetin, duke shkarkuar dhe PostgreSQL, logimin e pyetjeve SQL dhe duke provuar të ndryshojë logjikën dhe të dhënat atje.
Në artikujt e ardhshëm, do të flas gjithashtu për atë se si mund të vendosni kufizime mbi funksionet, punën me seancat e ndryshimeve dhe shumë më tepër.
Burimi: habr.com
