SQL-проектирование в PostgreSQL. Над плоским миром MS SQL.
У MS SQL столько раздражающих особенностей, что о них можно написать тысячи заметок. Одно только, что каждый отдельный функционал MS SQL требует отдельных денег - чего стоит! Например, если у вас база в влазит в 10GB - можно пользовться SQL Express, но если вдруг вы хотите иметь более ли менее актуальную копию своей базы (а это обычно поднимается на LogShiping) или иметь SQL-задания - тогда будьте любезны доплатить Биллу Гейтсу по $3500 на процессор, а если вдруг вам понадобился Профайлер - будьте любезны заплатить по $3700 на процессор, а если нужна интеграция с почтовиком или Export/Import Wizard - заплатите по $7100 на процессор, а если вдруг понадобилось сжатие бекапов или аудит - заплатите по $25000 за процессор. А микрософтовский клиент для программирования в MS SQL (MS SMS) у меня вызывает настолько рвотный рефлекс, что я даже написал свой собственный альтернативный клиент для программирования в MS SQL - ScriptManager - Менеджер MS SQL сервера - дополнение MS SMS для работы с большими скриптами. Это оказалось проще чем травмировать свою психику ежедневно сталкиваясь с тупостью микрософтовского MS SMS. Мне бы хотелось проанализировать некоторые возможности PostgreSQL, предоставляемые SQL-разработчику - о которых и не подозревает разработчик в плоском мире MS SQL. Я уже написал о некоторых взглядах на SQL-проектирования в PostgreSQL несколько заметок, например Выполняем разворот строк в столбцы в MS SQL и PostgreSQL, Пример обьектно-реляционного проектирования структуры данных в PostgreSQL. Но на этой страничке мне бы хотелось остановиться лишь на одной-единственной особенности проектирования в MS SQL - благодаря которой MS SQL проектировщики обречены на вечный копипаст и бесконечные повторы своего кода - вместо вызова уже написанного кода из своих новых процедур. |
Основная идея плоского мира MS SQL (только держите крепче PostgreSQL-проектировщиков - когда они узнают об этом они со стула упадут) - то что в новых Transact-SQL процедурах, Transact-SQL вьюшках и Transact-SQL функциях НЕЛЬЗЯ использовать результат, полученный в ранее спроектированных и отлаженных Transact-SQL процедурах.
Это кажется совершенно невероятным, но в MS SQL действительно практически невозможно построить процедуру, функцию или вьюшку, использующую некий ранее сформированный результат. Собственно говоря, в MS SQL существует три лазейки:
- Можно применить аналог PostgreSQL функции dblink - MS SQL функцию OpenRowSet - и получить отбор сделанной в некоторой Transact-SQL процедуре внутри некоторой другой Transact-SQL процедуры для дальнейшей обработки. Но, во-первых, OpenRowSet равно это нельзя применить в SQL-функциях и вьюшках MS SQL - только в Transact-SQL процедурах, во-вторых, это полностью убъет админов. В этой функции OpenRowSet надо явным текстом прописать все - имя базы, имя инстанции, логин, пароль, имя кампутера, IP-адрес его и так далее. Откуда админ системы знает, что это все нельзя менять? Или откуда он знает, где вы наковыряли в коде фиксированные айпишники, имена кампов, логины, пароли? Понятно, что функция OpenRowSet пригодна только развве что для эсперимента какого-нибудь студента на лабораторной работе или для самого админа, но не для проектировщика.
- Если Transact-SQL код ИСКЛЮЧИТЕЛЬНО простой, его можно утопить не в процедуру, а именно в "MS SQL-функцию". Так в MS SQL называется именно такой код, который можно использовать повторно и вызывать где угодно. Таких фрагментов элементарного кода в MS SQL существует три штатных разновидности:
- Пользовательские возвращающие табличное значение функции возвращают значение типа table. Встроенная возвращающая табличное значение функция не имеет тела, таблица является результирующим набором одной инструкции.
- Пользовательские скалярные функции возвращают одно значение типа данных, заданного в предложении RETURNS. Встроенная скалярная функция не имеет тела, скалярное значение является результатом одной инструкции.
- Скалярная функция из нескольких инструкций имеет тело, ограниченное блоком BEGIN...END, и содержит последовательность инструкций Transact-SQL, возвращающих одно значение. Такие функции могут возвращать любые типы данных, кроме text, ntext, image, cursor и timestamp.
- В принципе, есть и четвертый вариант, позволяющий обойти эти ограничения - сборка. Например вот эта моя сборка возвращает строку практически неограниченной длины - SQL-Client_for_remote_XML-WebService - клиент meteonova.ru - только работают сборки минимум на Enterprise версии.
- Третья лазейка, позволяющая вызвать из одной Transact-SQL процедуры код другой Transact-SQL процедур заключается в вызове через посредничество SQL-сборки. Текст этой сборки я опубликовал в 2007-м году- это последняя третья сборка на страничке Вариации на тему Notification-сервера.
Итак, обычный код Transact-SQL в самом же Transact-SQL вызвать нельзя. А в обычной MS SQL-системе 99% кода находится к теле Transact-SQL процедур. MS SQL-функции (собственно то что можно вызывать) имеет адские ограничения. Либо табличная функция должны быть в один оператор, либо если в несколько - то тогда можно вернуть только один простой скалярный тип. Либо что-то можно выкрутить на сборках, что работает только в самой дорогой версии MS SQL и требует совершенно иных навыков программирования и администрирования, чем обычное SQL-программирование.
Поэтому MS SQL проектировщик обречен на вечный копипаст ранее отлаженных фрагментов своего кода из одной Transact-SQL процедуры в другую.
В отличие от плоского мира MS SQL - PostgreSQL позволяет вести классическое поэтапное иерархическое проектирование с использованием в новых слоях кода ранее наработанных функций. Например, в этом месяце делаем функции самого нижнего уровня, в следующем месяце - вызывая готовый отлаженный код - создаем следующий SQL-уровень. Ведь в PostgreSQL ЛЮБОЙ созданный ранее plpgsql и SQL-код можно вызвать в ЛЮБОМ PostgreSQL-коде.
В PostgreSQL функции могут возвращать:
- Простые типы, GUID, даты, числа, строки и так далее. В MS SQL это поддерживается скалярными функциями. Со всеми ограничениями, присущими функциям MS SQL. В PostgreSQL нет никаких ограничений на код этих функций - хоть тысячу операторов можно напихать в код plpgsql, исключения можно обрабатывать, транзакции - да что угодно.
- Таблицы (RETURNS TABLE). Так что имя функции может быть использовано в предложении FROM. Это похоже на табличные функции MS SQL.Табличные функции MS SQL (в отличие от обычного TSQL-кода процедур) - как раз можно вызывать в своем коде. Только там так много ограничений (один SQL-оператор и детерминированность), что существование табличных фукнций конечно полезно, но этого слишком мало для выхода из плоского мира MS SQL. В PostgreSQL тоже нет никаких ограничений на код этих функций.
- Рекордсеты (RETURNS SETOF). Такие функции можно вызвать в Select, пройтись по ним курсором, сделать на них вьюшку, использовать внутри другой функции. В PostgreSQL тоже нет никаких ограничений на код этих функций. Пример такой функции я опубликовал в разделе Экспорт данных из PostgeSQL в XML. Если удается впихнуть создание этого рекордсета в один детерминированный оператор - то эту операцию вохможно реализовать в табличной функции MS SQL.
- Enum-типы. Это некий аналог того, что в MS SQL обычно делают просто целыми числами. Но когда состояний становится много - легко запутаться. Символьные ENUM-имена в PostgreSQL не дадут запутаться. Такого нет в MS SQL - ну разве что сделать CLR-тип на сборках.
- Массивы и обьекты - это невозможно в MS SQL. Разве что реализовав CLR-тип. Но в PostgreSQL это естественный подход к проектированию - позволяющий радикальнейшим образом сокращать объемы кода. Пример такой функции я опубликовал - Пример обьектно-реляционного проектирования структуры данных в PostgreSQL
Попробую проиллюстрировать сказанное каким нибудь доступным примером. На неком среднем уровне софта у меня есть вьюшка GET_СкладскиеОстаткиСоСвойствами, которая основана на трех других вьюшках:
1: CREATE OR REPLACE VIEW "_Delmar"."GET_СкладскиеОстаткиСоСвойствами" AS
2: SELECT "GET_СкладскиеОстатки".i,
3: "GET_СкладскиеОстатки".toimportsclad,
4: "GET_СкладскиеОстатки"."ПредложениеИд",
5: "GET_СкладскиеОстатки"."ПредложениеАртикул",
6: "GET_СкладскиеОстатки"."ПредложениеНаименование",
7: "GET_СкладскиеОстатки"."ПредложениеБазоваяЕдиница",
8: "GET_СкладскиеОстатки"."ПредложениеБазоваяЕдиницаКод",
9: "GET_СкладскиеОстатки"."ПредложениеБазоваяЕдиницаНаимен",
10: "GET_СкладскиеОстатки"."ПредложениеБазоваяМеждународное",
11: "GET_СкладскиеОстатки"."ПредложениеЦена",
12: "GET_СкладскиеОстатки"."ПредложениеЦенаИдТипаЦены",
13: "GET_СкладскиеОстатки"."ПредложениеЦенаЗаЕдиницу",
14: "GET_СкладскиеОстатки"."ПредложениеЦенаВалюта",
15: "GET_СкладскиеОстатки"."ПредложениеЦенаЕдиница",
16: "GET_СкладскиеОстатки"."ПредложениеЦенаКоэффициент",
17: "GET_СкладскиеОстатки"."Остаток",
18: "GET_СкладскиеОстатки"."ОстатокИдСклада",
19: "GET_СкладскиеОстатки"."ОстатокKol",
20: CASE
21: WHEN "substring"("GET_СкладскиеОстатки"."ОстатокKol"::text, '^[[:digit:]]*'::text) = ''::text THEN 0::numeric
22: ELSE "substring"("GET_СкладскиеОстатки"."ОстатокKol"::text, '^[[:digit:]]*'::text)::numeric
23: END AS "ОстатокЧисловой", "GET_СкладскиеОстатки"."ОстатокЕдиница", "GET_СкладскиеОстатки"."ОстатокКоэффициент", "GET_СкладскиеОстатки"."ИмпортСкладскихОстатков_i", "GET_СкладскиеОстатки"."ИмпортСкладскихОстатков_ДатаИмпо", "GET_ВсеСвойстваТовара".totovar, "GET_ВсеСвойстваТовара"."Вылет - ET (мм)", "GET_ВсеСвойстваТовара"."Индекс нагрузки шины", "GET_ВсеСвойстваТовара"."Индекс скорости шины", "GET_ВсеСвойстваТовара"."Посадочный диаметр шины (дюйм)", "GET_ВсеСвойстваТовара"."Размер диска",
24: CASE
25: WHEN "substring"("GET_ВсеСвойстваТовара"."Размер диска"::text, '^[[:digit:]]*'::text) = ''::text THEN '0'::character varying
26: ELSE "substring"("GET_ВсеСвойстваТовара"."Размер диска"::text, '^[[:digit:]]*'::text)::character varying(10)
27: END AS "ДиаметрДиска",
28: CASE
29: WHEN "GET_ВсеСвойстваТовара"."Размер диска"::text = ''::text THEN '0'::character varying
30: ELSE "substring"("GET_ВсеСвойстваТовара"."Размер диска"::text, "position"("GET_ВсеСвойстваТовара"."Размер диска"::text, '*'::text) + 1)::character varying(10)
31: END AS "ШиринаДиска", "GET_ВсеСвойстваТовара"."Размер шины", "GET_ВсеСвойстваТовара"."Сверловка",
32: CASE
33: WHEN "substring"("GET_ВсеСвойстваТовара"."Сверловка"::text, '^[[:digit:]]*'::text) = ''::text THEN '0'::character varying
34: ELSE "substring"("GET_ВсеСвойстваТовара"."Сверловка"::text, '^[[:digit:]]*'::text)::character varying(10)
35: END AS "Сверловка-HOLE",
36: CASE
37: WHEN "substring"("GET_ВсеСвойстваТовара"."Сверловка"::text, '^[[:digit:]]*'::text) = ''::text THEN '0'::character varying
38: ELSE "substring"("GET_ВсеСвойстваТовара"."Сверловка"::text, "position"("GET_ВсеСвойстваТовара"."Сверловка"::text, '/'::text) + 1)::character varying(10)
39: END AS "Сверловка-PCD", "GET_ВсеСвойстваТовара"."Сезонность шины", "GET_ВсеСвойстваТовара"."Тип шины", "GET_ВсеСвойстваТовара"."Цвет",
40: CASE
41: WHEN "substring"("GET_ВсеСвойстваТовара"."Центрально отверстие - DIA (мм)"::text, '^[[:digit:]]*'::text) = ''::text THEN '0'::character varying
42: ELSE "substring"("GET_ВсеСвойстваТовара"."Центрально отверстие - DIA (мм)"::text, '^[[:digit:]]*'::text)::character varying(10)
43: END AS "Центрально отверстие - DIA (мм)", "GET_ВсеСвойстваТовара"."Шина повышенной проходимости (M+S)", "GET_ВсеСвойстваТовара"."Шина усиленная (C)", "GET_ВсеСвойстваТовара"."Шипованная шина", "GET_ТоварныйКлассификатор"."ТоварИд", "GET_ТоварныйКлассификатор"."ТоварКартинка"
44: FROM "_Delmar"."GET_СкладскиеОстатки"
45: JOIN "_Delmar"."GET_ВсеСвойстваТовара" ON "GET_СкладскиеОстатки".i = "GET_ВсеСвойстваТовара".totovar
46: JOIN "_Delmar"."GET_ТоварныйКлассификатор" ON "GET_ТоварныйКлассификатор"."ТоварАртикул"::text = "GET_СкладскиеОстатки"."ПредложениеАртикул"::text;
47:
48: ALTER TABLE "_Delmar"."GET_СкладскиеОстаткиСоСвойствами" OWNER TO postgres;
Она искользуется много раз в разных местах. И в том числе некоторой перегруженной функцией DiskCount, которая позволяет отобрать из вьюшки GET_СкладскиеОстаткиСоСвойствами диски по более ли менее точно заданным параметрам. Либо вообще все, либо только по размеру 18", либо по размеру и ширине - и так далее до точного отбора по шести параметрам:
1: CREATE OR REPLACE FUNCTION "DiskCount"
2: (
3: "ДиаметрДиска" character varying,
4: "ШиринаДиска" character varying,
5: "Вылет_ET" character varying,
6: "Сверловка-HOLE" character varying,
7: "Сверловка-PCD" character varying,
8: "ЦентральноеОтверстие_DIA" character varying
9: ) RETURNS integer
10: LANGUAGE plpgsql AS $_$
11: DECLARE res int;
12: BEGIN
13: SELECT count(*) INTO res FROM "GET_СкладскиеОстаткиСоСвойствами"
14: WHERE "ОстатокЧисловой">4
15: and $1="GET_СкладскиеОстаткиСоСвойствами"."ДиаметрДиска"
16: and REPLACE($2,',','.')::real+0.5>= REPLACE("GET_СкладскиеОстаткиСоСвойствами"."ШиринаДиска",',','.')::real
17: and REPLACE($2,',','.')::real-0.5<= REPLACE("GET_СкладскиеОстаткиСоСвойствами"."ШиринаДиска",',','.')::real
18: and REPLACE($3,',','.')::real>= REPLACE("GET_СкладскиеОстаткиСоСвойствами"."Вылет - ET (мм)",',','.')::real-10
19: and REPLACE($3,',','.')::real<= REPLACE("GET_СкладскиеОстаткиСоСвойствами"."Вылет - ET (мм)",',','.')::real+2
20: and REPLACE($4,',','.')= "GET_СкладскиеОстаткиСоСвойствами"."Сверловка-HOLE"
21: and REPLACE($5,',','.')= "GET_СкладскиеОстаткиСоСвойствами"."Сверловка-PCD"
22: and REPLACE($6,',','.')<= "GET_СкладскиеОстаткиСоСвойствами"."Центрально отверстие - DIA (мм)" ;
23: RETURN res;
24:
25: EXCEPTION
26: when others then
27: insert into "TerminalError"(crdate,txt) values (now(), 'DiskCount : ' || SQLSTATE || ' : ' || SQLERRM) ;
28: return -1;
29: END;
30: $_$;
31:
32:
33: CREATE OR REPLACE FUNCTION "DiskCount"
34: (
35: "ДиаметрДиска" character varying,
36: "ШиринаДиска" character varying,
37: "Вылет_ET" character varying,
38: "Сверловка-HOLE" character varying,
39: "Сверловка-PCD" character varying
40: ) RETURNS integer
41: LANGUAGE plpgsql AS $_$
42: DECLARE res int;
43: BEGIN
44: SELECT count(*) INTO res FROM "GET_СкладскиеОстаткиСоСвойствами"
45: WHERE "ОстатокЧисловой">4
46: and $1="GET_СкладскиеОстаткиСоСвойствами"."ДиаметрДиска"
47: and REPLACE($2,',','.')::real+0.5>= REPLACE("GET_СкладскиеОстаткиСоСвойствами"."ШиринаДиска",',','.')::real
48: and REPLACE($2,',','.')::real-0.5<= REPLACE("GET_СкладскиеОстаткиСоСвойствами"."ШиринаДиска",',','.')::real
49: and REPLACE($3,',','.')::real>= REPLACE("GET_СкладскиеОстаткиСоСвойствами"."Вылет - ET (мм)",',','.')::real-10
50: and REPLACE($3,',','.')::real<= REPLACE("GET_СкладскиеОстаткиСоСвойствами"."Вылет - ET (мм)",',','.')::real+2
51: and REPLACE($4,',','.')= "GET_СкладскиеОстаткиСоСвойствами"."Сверловка-HOLE"
52: and REPLACE($5,',','.')= "GET_СкладскиеОстаткиСоСвойствами"."Сверловка-PCD"
53: ;
54: RETURN res;
55: EXCEPTION
56: when others then
57: insert into "TerminalError"(crdate,txt) values (now(), 'DiskCount : ' || SQLSTATE || ' : ' || SQLERRM) ;
58: return -1;
59: END;
60: $_$;
61:
62:
63: CREATE OR REPLACE FUNCTION "DiskCount"
64: (
65: "ДиаметрДиска" character varying,
66: "ШиринаДиска" character varying,
67: "Вылет_ET" character varying,
68: "Сверловка-HOLE" character varying
69: ) RETURNS integer
70: LANGUAGE plpgsql AS $_$
71: DECLARE res int;
72: BEGIN
73: SELECT count(*) INTO res FROM "GET_СкладскиеОстаткиСоСвойствами"
74: WHERE "ОстатокЧисловой">4
75: and $1="GET_СкладскиеОстаткиСоСвойствами"."ДиаметрДиска"
76: and REPLACE($2,',','.')::real+0.5>= REPLACE("GET_СкладскиеОстаткиСоСвойствами"."ШиринаДиска",',','.')::real
77: and REPLACE($2,',','.')::real-0.5<= REPLACE("GET_СкладскиеОстаткиСоСвойствами"."ШиринаДиска",',','.')::real
78: and REPLACE($3,',','.')::real>= REPLACE("GET_СкладскиеОстаткиСоСвойствами"."Вылет - ET (мм)",',','.')::real-10
79: and REPLACE($3,',','.')::real<= REPLACE("GET_СкладскиеОстаткиСоСвойствами"."Вылет - ET (мм)",',','.')::real+2
80: and REPLACE($4,',','.')= "GET_СкладскиеОстаткиСоСвойствами"."Сверловка-HOLE"
81: ;
82: RETURN res;
83: EXCEPTION
84: when others then
85: insert into "TerminalError"(crdate,txt) values (now(), 'DiskCount : ' || SQLSTATE || ' : ' || SQLERRM) ;
86: return -1;
87: END;
88: $_$;
89:
90:
91: CREATE OR REPLACE FUNCTION "DiskCount"
92: (
93: "ДиаметрДиска" character varying,
94: "ШиринаДиска" character varying,
95: "Вылет_ET" character varying
96: ) RETURNS integer
97: LANGUAGE plpgsql AS $_$
98: DECLARE res int;
99: BEGIN
100: SELECT count(*) INTO res FROM "GET_СкладскиеОстаткиСоСвойствами"
101: WHERE "ОстатокЧисловой">4
102: and $1="GET_СкладскиеОстаткиСоСвойствами"."ДиаметрДиска"
103: and REPLACE($2,',','.')::real+0.5>= REPLACE("GET_СкладскиеОстаткиСоСвойствами"."ШиринаДиска",',','.')::real
104: and REPLACE($2,',','.')::real-0.5<= REPLACE("GET_СкладскиеОстаткиСоСвойствами"."ШиринаДиска",',','.')::real
105: and REPLACE($3,',','.')::real>= REPLACE("GET_СкладскиеОстаткиСоСвойствами"."Вылет - ET (мм)",',','.')::real-10
106: and REPLACE($3,',','.')::real<= REPLACE("GET_СкладскиеОстаткиСоСвойствами"."Вылет - ET (мм)",',','.')::real+2
107: ;
108: RETURN res;
109: EXCEPTION
110: when others then
111: Insert into "TerminalError"(crdate,txt) values (now(), 'DiskCount : ' || SQLSTATE || ' : ' || SQLERRM) ;
112: return -1;
113: END;
114: $_$;
115:
116:
117: CREATE OR REPLACE FUNCTION "DiskCount"
118: (
119: "ДиаметрДиска" character varying,
120: "ШиринаДиска" character varying
121: ) RETURNS integer
122: LANGUAGE plpgsql AS $_$
123: DECLARE res int;
124: BEGIN
125: SELECT count(*) INTO res FROM "GET_СкладскиеОстаткиСоСвойствами"
126: WHERE "ОстатокЧисловой">4
127: and REPLACE($1,',','.')="GET_СкладскиеОстаткиСоСвойствами"."ДиаметрДиска"
128: and REPLACE($2,',','.')::real+0.5>= REPLACE("GET_СкладскиеОстаткиСоСвойствами"."ШиринаДиска",',','.')::real
129: and REPLACE($2,',','.')::real-0.5<= REPLACE("GET_СкладскиеОстаткиСоСвойствами"."ШиринаДиска",',','.')::real
130: ;
131: RETURN res;
132: EXCEPTION
133: when others then
134: Insert into "TerminalError"(crdate,txt) values (now(), 'DiskCount : ' || SQLSTATE || ' : ' || SQLERRM) ;
135: return -1;
136: END;
137: $_$;
138:
139:
140: CREATE OR REPLACE FUNCTION "DiskCount"
141: (
142: "ДиаметрДиска" character varying
143: ) RETURNS integer
144: LANGUAGE plpgsql AS $_$
145: DECLARE res int;
146: BEGIN
147: SELECT count(*) INTO res FROM "GET_СкладскиеОстаткиСоСвойствами"
148: WHERE "ОстатокЧисловой">4
149: and REPLACE($1,',','.')="GET_СкладскиеОстаткиСоСвойствами"."ДиаметрДиска"
150: ;
151: RETURN res;
152: EXCEPTION
153: when others then
154: Insert into "TerminalError"(crdate,txt) values (now(), 'DiskCount : ' || SQLSTATE || ' : ' || SQLERRM) ;
155: return -1;
156: END;
157: $_$;
158:
159:
160: CREATE OR REPLACE FUNCTION "DiskCount"() RETURNS integer
161: LANGUAGE plpgsql
162: AS $_$
163: DECLARE res int;
164: BEGIN
165: SELECT count(*) INTO res FROM "GET_СкладскиеОстаткиСоСвойствами"
166: WHERE "ОстатокЧисловой">4 ;
167: RETURN res;
168: EXCEPTION
169: when others then
170: Insert into "TerminalError"(crdate,txt) values (now(), 'DiskCount : ' || SQLSTATE || ' : ' || SQLERRM) ;
171: return -1;
172: END;
173: $_$;
174:
В следующем сое софта над этой функцией есть вьюшка AllDiskCount, которая даже не знает о том, что эта функция перегружена. Она просто использует код функции DiskCount.
1: CREATE OR REPLACE VIEW "AllDiskCount" AS
2: SELECT "DiskCount"('12'::character varying) AS "12",
3: "DiskCount"('13'::character varying) AS "13",
4: "DiskCount"('14'::character varying) AS "14",
5: "DiskCount"('15'::character varying) AS "15",
6: "DiskCount"('16'::character varying) AS "16",
7: "DiskCount"('17'::character varying) AS "17",
8: "DiskCount"('18'::character varying) AS "18";
В следующем слое софта есть таблица, которая построена на основе типа AllDiskCount и еще шести типах:
1: CREATE TABLE "TerminalMonitor" (
2: i integer NOT NULL,
3: crdate timestamp without time zone,
4: "TerminalID" uuid NOT NULL,
5: "Data" timestamp without time zone,
6: "ИмпортПрименяемости" "ИмпортПрименяемостиCount",
7: "ИмпортСкладскихОстатков" "ИмпортСкладскихОстатковCount",
8: "ИмпортТоваров" "ИмпортТоваровCount",
9: "TableCount" "TableCount",
10: "AllDiskCount" "AllDiskCount",
11: "ЗаказыCount" "ЗаказыCount",
12: "ErrorCount" "ErrorCount"
13: );
В следующем слое софта есть функция LocalMonitoring, которая использует таблицу TerminalMonitor, которая построена на основе типа AllDiskCount (и шести других сложных типах), которая в свою очередь построена на функции GetDisk, построенной на отборах из вьюшки GET_СкладскиеОстаткиСоСвойствами, которая в свою очередь построена еще на трех других вьюшках... И это всего лишь некий промежуточный слой из огромного многослойного пирога.
1:
2: CREATE OR REPLACE FUNCTION "LocalMonitoring"() RETURNS bigint
3: LANGUAGE sql AS $$
4: insert into "TerminalMonitor"
5: (crdate,
6: "TerminalID",
7: "Data",
8: "ИмпортПрименяемости",
9: "ИмпортСкладскихОстатков",
10: "ИмпортТоваров",
11: "TableCount",
12: "AllDiskCount",
13: "ЗаказыCount",
14: "ErrorCount")
15: Select now(),
16: "TerminalID",
17: now(),
18: "ИмпортПрименяемости",
19: "ИмпортСкладскихОстатков",
20: "ИмпортТоваров",
21: "TableCount",
22: "AllDiskCount",
23: "ЗаказыCount",
24: "ErrorCount"
25: from "CurrentState";
26: select currval('"TerminalMonitor_i_seq"'::regclass)
27: $$;
Вы где нибудь видели такое в MS SQL? Где 99,99% кода утоплено в тело Transact-SQL процедур, которые вообще никак нельзя вызвать из другой процедуры.
Узнать о PostgreSQL больше - вы можете здесь - Используем PostgreSQL вместо MS SQL в проектах на NET и ASP.NET
|