Приветствую, уважаемые хабражители!
Я занимаюсь переводом кода из MS SQL Server в PostgreSQL с начала 2019 года и сегодня продолжу сравнение этих СУБД.
В прошлой публикации мы рассматривали отличия в быстродействии MS SQL Server и PostgreSQL для «1C».
В Ozon есть решения и на MS SQL Server, и на PostgreSQL: первая используется в логистике и системах внутренних сервисов, вторая — в mission critical-подсистемах, от которых напрямую зависит бизнес компании (склад, корзина, оплата картами, платежи, информация о товарах на сайте и др.).
Периодически появляются задачи перевода решений из огромных монолитных баз из MS SQL Server в PostgreSQL. Поэтому давайте сравним основные конструкции синтаксиса этих СУБД для правильного чтения кода, а также для того, чтобы быстро изменять код из MS SQL Server для PostgreSQL и наоборот.
Начнём с сопоставления типов.
Сопоставление типов
MS SQL |
PostgreSQL |
BIGINT |
BIGINT, INT8 |
BINARY(n) |
BYTEA |
VARBINARY(n) |
BYTEA |
VARBINARY(max) |
BYTEA |
ROWVERSION |
BYTEA |
IMAGE |
BYTEA |
FIELDHIERARCHYID |
BYTEA, LTREE (расширение) |
BIT |
BOOLEAN, BOOL |
CHAR(n), n<=8000 |
TEXT |
NCHAR(n), n<=4000 |
TEXT |
VARCHAR(n), n<=8000 |
TEXT |
NVARCHAR(n), n<=4000 |
TEXT |
VARCHAR(max) |
TEXT |
NVARCHAR(max) |
TEXT |
TEXT |
TEXT |
NTEXT |
TEXT |
FLOAT(n) |
DOUBLE PRECISION, FLOAT8 |
SMALLMONEY |
MONEY |
MONEY |
MONEY |
INT, INTEGER |
INT, INTEGER, INT4 |
SMALLINT |
SMALLINT, INT2 |
NUMERIC(n,m) |
NUMERIC(n,m) |
DEC(n,m), DECIMAL(n,m) |
DEC(n,m), DECIMAL(n,m) |
TINYINT |
SMALLINT, INT2 |
REAL |
REAL, FLOAT4 |
UNIQUEIDENTIFIER |
CHAR(16), UUID |
DATE |
DATE |
TIME(n) |
TIME(n) |
DATETIME |
TIMESTAMP(3) |
DATETIME2(n) |
TIMESTAMP(m) |
DATETIMEOFFSET(n) |
TIMESTAMP(n) WITH TIME ZONE, TIMESTAMPTZ |
SMALLDATETIME |
TIMESTAMP(0) |
XML |
XML |
Примечание. Типы CHAR и VARCHAR лучше не использовать. Причины подробно описаны здесь.
Более подробно о типах данных:
Теперь перейдём к сопоставлению синтаксиса MS SQL Server и PostgreSQL.
Сопоставление синтаксиса MS SQL Server и PostgreSQL
I. Регистрозависимое обращение к схемам, таблицам (представлениям) и их полям и другим объектам базы данных
В MS SQL Server при обращениях к объектам можно использовать квадратные скобки (они обязательны, только если в названии объекта или его поля присутствуют недопустимые символы):
[schema]
[table]
[view]
[object]
[table].[field]
[view].[field]
[schema].[table]
[schema].[view]
[schema].[object]
[schema].[table].[field]
[schema].[view].[field]
В PostgreSQL для этого используются двойные кавычки (они обязательны, только если в названии объекта присутствуют заглавные буквы или есть недопустимые символы в названии объекта или его поля):
"schema"
"table"
"view"
"table"."field"
"view"."field"
"schema"."table"
"schema"."view"
"schema"."table"."field"
"schema"."view"."field"
II. Выборка заданных N данных
III. Постраничная загрузка данных (скользящее окно)
Задача: извлечь 100 строк начиная с 202-й строки включительно по возрастанию даты рождения:
в PostgreSQL: |
|
SELECT * |
select * |
Примечание. Вместо row можно использовать rows в любом месте запроса, а вместо next можно использовать first в обеих СУБД. |
IV. Выборка первого непустого значения
V. Тернарный оператор IIF
MS SQL Server |
PostgreSQL |
IIF (<условие>,<выражение_если_условие_истинно>,<выражение_если_условие_ложно>) или CASE WHEN <условие> THEN <выражение_если_условие_истинно> ELSE <выражение_если_условие_ложно> END |
case when <условие> then <выражение_если_условие_истинно> else <выражение_если_условие_ложно> end |
VI. Создание псевдонима
MS SQL Server |
PostgreSQL |
AS — рекомендуется = — не рекомендуется |
as |
VII. Выражения CASE
VIII. Работа с переменными
Объявление переменной
Присвоение переменной значения
Вывод значения на консоль
MS SQL Server |
PostgreSQL |
print 'строка'; print @переменная; RAISERROR(@переменная, 1, 1) WITH NOWAIT; |
RAISE NOTICE '%', 'строка'; RAISE NOTICE '%', <переменная>; |
IX. Управление выполнением кода
Выполнение скрипта
В MS SQL Server:
declare @_query int;
set @_query=777;
set @query=1+8;
RAISERROR(@_query, 1, 1) WITH NOWAIT; --PRINT @_query;
В PostgreSQL:
Шаблон:
do $$
<объявление переменных>
begin
<код>
end;
$$;
Пример (вывод информации):
do $$
declare _query int;
begin
_query:=777;
_query:=1+8;
RAISE NOTICE '%', _query;
end;
$$;
Пример (передача значения клиенту):
do $$
declare _query int;
begin
_query:=777;
_query:=1+8;
PERFORM
set_config('my._query',
_query::text, FALSE);
end;
$$;
SELECT current_setting
('my._query');
Для PostgreSQL:
В DBeaver (бобре) нужно нажать CTRL+SHIFT+O при отсутствии окна вывода, а в pgAdmin вывод происходит автоматически.
В psql и так всё работает.
Цикл WHILE
MS SQL Server |
PostgreSQL |
WHILE <условие_при_котором_цикл_работает> BEGIN ... END |
while <условие_при_котором_цикл_работает> loop ... end loop |
Логическое ветвление
MS SQL Server |
PostgreSQL |
IF-BEGIN-END IF-BEGIN-END-ELSE-BEGIN-END |
if-then-else-end if; if-then-elseif-then-else-end if; |
Более подробно про управление выполнением кода:
X. Функции для работы со строками
Определение длины строки (количество символов в строке)
MS SQL Server |
PostgreSQL |
LEN (<строка>) |
length (<строка>) |
Примечание. В MS SQL Server исключаются конечные пробелы. Если нужно учитывать и их, то необходимо воспользоваться функцией DATALENGTH (<строка>), которая возвращает суммарное количество байтов в символах строки. |
Возвращение символа по его коду:
MS SQL Server |
PostgreSQL |
char(n) |
chr(n) |
Конкатенация строк
MS SQL Server |
PostgreSQL |
+ |
|| |
Нахождение позиции вхождения подстроки
В MS SQL Server: |
В PostgreSQL: |
CHARINDEX(<что_ищем>, <где_ищем>,<с_какой_позиции_ищем_начиная_с_1>) |
strpos(<где_ищем>, <что_ищем>) strpos(substring(<где_ищем>, <с_какой_позиции_ищем_начиная_с_1>, length(<где_ищем>)- <с_какой_позиции_ищем_начиная_с_1>+1), <что_ищем>) |
Примечание. Точного соответствия не будет, если производить поиск не с начала строки. |
Регистронезависимое сравнение и поиск данных
В MS SQL Server: |
В PostgreSQL: |
1. LIKE 2. a = b 3. <> 4. a in (b1, ...) |
1. ilike 2. lower(a) = lower(b) или upper(a)=upper(b) 3. lower(a) <> lower(b) или upper(a)<>upper(b) 4. lower(a) in (lower(b1), ...) или upper(a) in (upper(b1), ...) |
Примечание. В PostgreSQL рекомендуется произвести оптимизацию через создание функционального индекса:
create [concurrently] index idx_lower_<field>
on <schema>.<table> (lower(<field>));
--После создания concurrently-индекса,
--его необходимо проверить на наличие битых индексов следующим запросом:
SELECT indexrelid::regclass FROM pg_index where not indisvalid;
--Далее для обновления статистики по нужной таблице
--необходимо выполнить команду ANALYZE:
ANALYZE <table>;
Более подробно про команду ANALYZE.
Слияние строк по запросу в одну строку по заданному разделителю
В MS SQL Server можно использовать функцию STUFF следующим образом:
STUFF(( SELECT DISTINCT
', ' + CONVERT(varchar, tbl.<поле>)
FROM
<схема>.<таблица> tbl
[WHERE
<условия>]
FOR XML PATH(''))
, 1
, 1
, '') AS STUFF_tbl;
Также начиная с версии 2017 доступна функция STRING_AGG.
В PostgreSQL для этого можно использовать функцию string_agg таким образом:
string_agg((SELECT
distinct ', ' ||
cast(tbl.<поле> as VARCHAR) FROM
<схема>.<таблица> tbl,
[WHERE
<условия>]
), 1, 1,
'') AS
string_agg_field;
Более подробно про функции для работы со строками:
XI. Функции для работы с датой и временем
Получение текущей даты и времени (локальное время)
MS SQL Server |
PostgreSQL |
GetDate() SysDateTime() current_timestamp |
clock_timestamp now() |
Получение текущей даты
MS SQL Server |
PostgreSQL |
CAST(GetDate() as DATE) |
current_date |
Пример преобразования формата даты и времени из строки public_date:
В MS SQL Server:
FORMAT(public_date, 'dd.MM.yyyy HH:mm:ss', 'ru-RU') — предпочтительный способ
convert(varchar(32),convert(datetime,public_date,104),120)
В PostgreSQL:
to_char(to_timestamp(public_date, 'dd.MM.yyyy hh24.mi'), 'yyyy-mm-dd hh24:mi:ss')
Приращение даты/времени
В MS SQL Server: |
В PostgreSQL: |
DateAdd(datepart, count, dt); |
dt + (count * interval '1 datepart'); |
Более подробно про функции для работы с датой и временем: 2. PostgreSQL |
XII. Получение количества строк, затронутых при выполнении последней команды
MS SQL Server |
PostgreSQL |
get diagnostics <переменная>:=row_count; |
XIII. Выполнение динамического SQL-кода
MS SQL Server |
PostgreSQL |
execute sp_executesql @sql; |
execute _sql; |
XIV. Проверка и приведение типов
Проверка строки на то, что она является числом
В MS SQL Server:
встроенная функция isnumeric(val)
В PostgreSQL:
CREATE OR REPLACE
FUNCTION
dbo.isnumeric(_input varchar(255) DEFAULT NULL::varchar(255))
RETURNS bit
LANGUAGE plpgsql
AS $function$
/*
Проверяет,
является ли входная строка
числом
*/
declare _result bit;
begin
begin
perform
_input::numeric;
_result:=1::bit;
exception
when others THEN
_result:=0::bit;
end;
return
_result;
end;
$function$
;
Безопасное приведение типа
В MS SQL Server:
try_cast(val as <type>)
Примечание. try_cast в MS SQL Server возвращает NULL, если значение невозможно привести к заданному типу, в других случаях — работает как оператор CAST.
В PostgreSQL есть два способа:
1) через обработку ошибок:
declare _result оператор CAST <type>;
...
BEGIN
_result :=
cast(val as <type>);
exception
when others then
_result :=null;
end;
2) через реализацию функции:
CREATE OR REPLACE
FUNCTION
dbo.try_cast(value character varying, typename CHARACTER varying)
returns text
LANGUAGE plpgsql
AS $function$
declare _sql_command text;
DECLARE _result text;
begin
_result=value;
_sql_command :=
'select
cast('||''''||
value||''''||' as '||
typename||');';
BEGIN
execute _sql_command;
exception
when others then
_result :=null;
end;
return
_result;
end;
$function$
;
Функция в итоге не возвращает преобразованное в заданный тип значение.
Функция на вход принимает текст и возвращает текст.
Если значение невозможно привести к заданному типу, то возвращается NULL.
Пример использования (чтобы было как в MS SQL Server):
cast(dbo.try_cast(val::text, '<type>') as <type>)
XV. DML-команды
Обновление данных
Пример в MS SQL Server:
Обновление поля Name в таблице Production.ScrapReason для тех строк, для которых есть соответствующие записи в таблице Production.WorkOrder по равенству ScrapReasonID и у которых значение ScrappedQty больше 300:
UPDATE
sr
SET
sr.Name = 'Name'
OUTPUT
deleted.*
, inserted.*
FROM Production.ScrapReas sr
JOIN Production.WorkOrder wo ON (sr.ScrapReasonID = wo.ScrapReasonID)
AND (wo.ScrappedQty > 300);
Ключевое слово OUTPUT позволяет получить данные об обновлении.
Пример в PostgreSQL:
Обновление поля Name в таблице production.scrapreason для тех строк, для которых есть соответствующие записи в таблице production.workorder по равенству scrapreasonid и у которых значение scrappedqty больше 300:
update
production.scrapreason as sr
set sr.Name = 'Name'
from production.workorder as wo
where (sr.scrapreasoid = wo.scrapreasonoid)
and (wo.scrappedqty > 300)
returning *;
Ключевое слово returning позволяет получить данные об обновлении.
Более подробно о команде UPDATE:
Удаление данных
Пример в MS SQL Server:
Удаление из таблицы Sales.SalesPersonQuotaHistory тех записей, для которых есть соответствующие записи в таблице Sales.SalesPerson по равенству BusinessEntityID и у которых значение SalesYTD больше 2500000.00:
DELETE FROM
spqh
OUTPUT deleted.*
FROM Sales.SalesPersonQuotaHistory spqh
INNER JOIN Sales.SalesPerson sp ON (spqh.BusinessEntityID = sp.BusinessEntityID)
WHERE
(sp.SalesYTD > 2500000.00);
Ключевое слово OUTPUT позволяет получить данные об удалении.
Пример в PostgreSQL:
Удаление из таблицы sales.salespersonquotahistory тех записей, для которых есть соответствующие записи в таблице sales.salesperson по равенству businessentitid и у которых значение salesytd больше 2500000.00:
delete from
sales.salespersonquotahistory AS spqh
using
sales.salesperson AS sp
where
(spqh.businessentityid = sp.businessentitid)
and (sp.salesytd > 2500000.00)
returning *;
Ключевое слово returning позволяет получить данные об удалении.
Более подробно о команде DELETE:
Получение изменённых записей
В MS SQL Server: |
В PostgreSQL: |
insert/update/delete таблица |
insert/update/delete таблица |
В update есть доступ только к inserted. Примечание. В PostgreSQL не нужна промежуточная таблица для получения изменённых записей. Более подробно: |
Удаление дубликатов (дублирующих строк):
В MS SQL Server:
with dbl_in_stage as (
select
row_number() over
(partition by
<field_1>, ...,
<field_N> order by
1) as rn
from
<схема>.<таблица>
as stg
)
delete from
dbl_in_stage where
rn > 1;
В PostgreSQL:
with x as (
select a, ctid, row_number() over(partition by a order by ctid) rn
from t
)
delete from t
using x
where t.a = x.a
and t.ctid = x.ctid
and x.rn > 1;
или более сложный вариант:
delete from <схема>.<таблица>
where ctid=any(
array(select unnest(ctids[2:])
from (
select array_agg(
ctid order by string_to_array(
regexp_replace(ctid::text, E'\\(|\\)','','g'),',')::bigint[]) ctids
FROM <схема>.<таблица> as T group by T::text) as T)::tid[]);
Примечание. Оптимальный вариант — внести в таблицу уникальный ключ, так как работа с метаданными увеличивает нагрузку на систему.
При наличии уникального ключа удалять дубликаты в PostgreSQL можно следующим образом:
delete from <схема>.<таблица> where <уникальный ключ> in
(select <уникальный ключ>
from (
select *, row_number() over (partition by <field_1>, ..., <field_N> order by 1) as rn
from <схема>.<таблица>
) as tbl where rn > 1);
XVI. DDL-команды для работы с таблицами
Удаление таблицы с предварительной проверкой
В MS SQL Server:
Для основной таблицы:
DROP TABLE IF EXISTS <schema>.<table>;
Для локальной временной таблицы:
IF EXISTS(SELECT [name] FROM tempdb.sys.tables WHERE [name] like '#<table>%')
BEGIN
DROP TABLE #<table>;
END;
Для глобальной временной таблицы:
IF EXISTS(SELECT [name] FROM tempdb.sys.tables WHERE [name] like '##<table>%')
BEGIN
DROP TABLE ##<table>;
END;
Здесь:
#<table> — локальная временная таблица, которая видна только в текущей сессии
##<table> — глобальная временная таблица, которая видна всем пока она существует
Все временные таблицы живут, либо пока активна сессия, во время которой они были созданы, либо пока их явно не удалят.
В PostgreSQL:
Для основной таблицы:
drop table if exists
<schema>.<table>;
Для временной таблицы:
drop table if exists <table>;
Более детально про удаление таблиц:
Создание таблицы через выборку
В MS SQL Server:
Для основной таблицы:
select ...
into <table>
from …
Для временной таблицы:
select ...
into #<table>
from …
В PostgreSQL:
Для основной таблицы:
create table <table> as
select ...
Для временной таблицы:
create temp table <table> as
select …
Более детально про создание таблиц через выборку:
Создание/изменение и удаление значения по умолчанию для колонки таблицы
В MS SQL Server:
Добавление:
ALTER TABLE
<схема>.<таблица>
ADD CONSTRAINT
<название_правила>
DEFAULT
<значение_по_умолчанию> FOR <поле>;
Выборка всех значений по умолчанию:
SELECT SCHEMA_NAME(t.[schema_id]) AS sch
, t.name AS tbl
, col.name AS colname
, dc.definition AS def
FROM sys.default_constraints dc
INNER JOIN sys.columns col ON dc.parent_object_id = col.[object_id]
INNER JOIN sys.tables t ON t.[object_id] = col.[object_id];
Удаление:
DROP DEFAULT IF EXISTS <название_правила>;
Изменение происходит через удаление и добавление.
В PostgreSQL:
Создание и изменение:
alter table
<схема>.<таблица>
alter column <поле>
set default
<значение_по_умолчанию>;
Выборка всех значений по умолчанию:
select
col.table_schema,
col.table_name,
col.column_name,
col.column_default
from
information_schema.columns as col;
Удаление:
alter table <схема>.<таблица>
alter column <поле> drop default;
Изменение типа колонки таблицы
В MS SQL Server: |
В PostgreSQL: |
ALTER TABLE |
alter table |
Примечание. Если у изменяемого столбца есть какие-либо констрейнты или значения по умолчанию, то сначала нужно их сохранить, а затем удалить. Вернуть их можно будет после изменения типа. |
Перенос автоинкрементных полей
В MS SQL Server делаем запрос вида:
SELECT
'do $$
declare
start_with_val
bigint;
declare
sql_statement
varchar;
begin
start_with_val :=
coalesce((select
max(' + c.[name] + ') from
' + s.[name] + '.' + o.[name] + '),0)+1;
sql_statement :=
''alter table
' + s.[name] + '.' + o.[name] + ' alter ' + c.[name] + '
add generated by
default as identity
(start with ''
||
cast(start_with_val
as varchar)||'');'';
execute
sql_statement;
end;
$$;' AS plsql_statement
--select distinct s.name
FROM
sys.all_columns c
INNER JOIN sys.all_objects o ON o.[object_id] = c.[object_id]
INNER JOIN sys.schemas s ON s.[schema_id] = o.[schema_id]
WHERE
is_identity <> 0
AND SCHEMA_NAME(o.[schema_id]) <> 'sys'
AND o.[type] = 'U';
Пример:
do $$
declare start_with_val bigint;
declare sql_statement varchar;
begin
start_with_val := coalesce((select max(ID) from dbo.ExchangeQueue),0)+1;
sql_statement := 'alter table dbo.ExchangeQueue alter ID add generated by default as identity (start with ' || cast(start_with_val as varchar)||');';
EXECUTE sql_statement;
end;
$$;
Полученные скрипты применяем на стороне PostgreSQL.
Создание автоинкрементных полей
В MS SQL Server:
ALTER TABLE [схема].[таблица]
ADD
<IDENTITY-поле> bigint IDENTITY(1, 1) NOT NULL;
В PostgreSQL:
do $$
DECLARE start_with_val bigint;
DECLARE sql_statement varchar;
BEGIN start_with_val := coalesce((select max(<IDENTITY-поле>) from <схема>.<таблица>),0)+1;
sql_statement := 'alter table <схема>.<таблица> alter <IDENTITY-поле> add generated by default as identity (start with ' || cast(start_with_val as varchar)||');';
EXECUTE sql_statement;
END;
$$;
Более детально про создание таблиц: | |
Более детально про изменение таблиц: | |
XVII. Создание и изменение представления
В MS SQL Server: |
В PostgreSQL: |
CREATE OR ALTER VIEW [схема].[название_представления] AS |
create or replace view <схема>.<название_представления> as |
Примечание. В PostgreSQL лучше сначала удалять представление, а потом заново его создавать, если набор полей меняется или меняются названия выходных полей, иначе можно получить ошибку при обращении к изменённому представлению. | |
Более подробно про создание и изменение представлений: | |
XVIII. Построчная обработка строк в наборе
В MS SQL Server:
--объявление переменных @field_1, ...@field_N
DECLARE <курсор>
CURSOR LOCAL FOR
<SELECT>;
OPEN <курсор>;
FETCH NEXT FROM
<курсор> INTO
@field_1, ...@field_N;
WHILE
(@@FETCH_STATUS = 0)
BEGIN
--оперируем значениями переменных @field_1, ...@field_N
...
FETCH NEXT
FROM <курсор> INTO @field_1, ...@field_N;
END
CLOSE <курсор>;
DEALLOCATE <курсор>;
В PostgreSQL:
do $$
declare _val
record;
begin
drop table if
exists _tmp_tbl;
create temp
table _tmp_tbl as
<select>
for _val in
(select field_1, ..., field_n from_tmp_tbl)
loop
--можно обратиться к любому выбранному ранее полю через _val.<поле>. Например, _val.<field_1>
end loop;
end
$$
XIX. Системные информационные функции безопасности
Текущий пользователь
В MS SQL Server используется функция CURRENT_USER().
В PostgreSQL:
session_user — под каким пользователем открыта сессия
current_user (или просто user) — под каким контекстом (ролью) идёт выполнение (session_user переключается для выполнения — здесь важно, под каким правом делается переключение)
Получение имени экземпляра и IP-адреса сервера СУБД
В MS SQL Server:
Получить информацию об IP-адресе сервера СУБД:
SELECT
CONNECTIONPROPERTY('
net_transport') AS net_transport
, CONNECTIONPROPERTY('
protocol_type') AS protocol_type
, CONNECTIONPROPERTY('
auth_scheme') AS auth_scheme
, CONNECTIONPROPERTY('
local_net_address') AS local_net_address
, CONNECTIONPROPERTY('
local_tcp_port') AS local_tcp_port
, CONNECTIONPROPERTY('
client_net_address') AS client_net_address;
Получить название экземпляра СУБД:
SELECT @@SERVERNAME;
В PostgreSQL:
Получить IP-адрес сервера СУБД:
do $$
declare
title varchar(100) :=host(inet_server_addr());
begin
raise notice '%',
title;
end; $$;
Получение названия экземпляра СУБД пока не реализовано.
Более подробно про системные информационные функции безопасности:
XX. Определение и вызов хранимой процедуры
Определение хранимой процедуры
CREATE OR ALTER PROCEDURE
[схема].[назание_процедуры]
<переменная_1> <тип_1>[=<значение_по_умолчанию_1>],
...
AS
BEGIN
...
END
В PostgreSQL:
CREATE OR REPLACE PROCEDURE
<схема>.<название_процедуры>
(
[INOUT] <переменная_1> <тип_1>[=<значение_по_умолчанию1>],
...
)
LANGUAGE plpgsql
AS $body$
[<Объявление переменных>]
BEGIN
...
END;
$body$
;
Вызов хранимой процедуры
В MS SQL Server:
EXEC <схема>.<процедура>
<переменная_1>=<значение_1>, ..., <переменная_OUTPUT> OUT[PUT];
В PostgreSQL:
call <схема>.<процедура> (
<переменная_1>=<значение_1>, ..., <переменная_OUTPUT>);
XXI. Создание скалярной функции
CREATE OR ALTER
FUNCTION [схема].[название_функции]
(<параметр_1> <тип_1>[=<значение_по_умолчанию_1>], ...)
RETURNS <возвращаемый_тип> AS
BEGIN
... RETURN ...
END
В PostgreSQL:
CREATE OR REPLACE FUNCTION
<схема>.<название_функции>
(<параметр_1> <тип_1>[=<значение_по_умолчанию_1>], ...)
RETURNS <возвращаемый_тип>
LANGUAGE plpgsql
AS $body$
[<Объявление переменных>]
begin
... return (
select ...
);
end;
$body$
;
XXII. Передача табличного значения (вывод таблицы)
В MS SQL Server:
CREATE OR ALTER
PROCEDURE
[схема].[название_хранимой_процедуры]
<параметр_1> <тип_1>,
...,
<параметр_N> <тип_N>
AS
BEGIN
...
SELECT ...
END
В PostgreSQL:
create or replace function
<схема>.<название_функции>
(<параметр_1> <тип_1>, ..., <параметр_N> <тип_N>)
return table (<поле_1> <тип_1>, ..., <поле_N> <тип_N>)
language 'plpgsql'
as $body$
[<Объявление переменных>]
begin
return query (select ....);
end;
$body$;
XXIII. DML-триггеры
Пример в MS SQL Server:
CREATE TRIGGER [info].[tr_isupoll_question_text_last_update_trigger]
ON [info].[isupoll_question_text]
FOR UPDATE
AS
UPDATE
info.isupoll_question_text
SET
last_update_date = GETDATE()
, last_update_user = SUSER_NAME()
FROM
info.isupoll_question_text ds
INNER JOIN INSERTED i ON
ds.isupoll_question_text_id = i.isupoll_question_text_id;
Здесь создаётся триггер tr_isupoll_question_text_last_update_trigger для таблицы info.isupoll_question_text после обновления данных, который для обновляемых строк проставляет текущие дату, время и пользователя соответственно.
DROP TRIGGER IF EXISTS [tr_isupoll_question_text_last_update_trigger]
on [info].[isupoll_question_text];
Здесь удаляется триггер tr_isupoll_question_text_last_update_trigger для таблицы info.isupoll_question_text
Пример в PostgreSQL:
CREATE OR REPLACE
FUNCTION dbo.update_mod()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
begin
new.last_update_date=now();
new.last_update_user=session_user;
return new;
end;
$function$
;
Здесь создаётся функция dbo.update_mod(), которая заполняет два поля текущими датой, временем и пользователем соответственно.
create trigger tr_isupoll_question_text_last_update_trigger before update
on info.isupoll_question_text for each row execute function dbo.update_mod();
Здесь создаётся триггер tr_isupoll_question_text_last_update_trigger для таблицы info.isupoll_question_text до обновления данных, который для каждой строки вызывает выполнение функции dbo.update_mod().
drop trigger if exists tr_isupoll_question_text_last_update_trigger
on info.isupoll_question_text;
Здесь удаляется триггер tr_isupoll_question_text_last_update_trigger для таблицы info.isupoll_question_text.
Важно! В триггере используйте ключевое слово before, когда хотите нашкодничать в той же таблице, для которой создаётся триггер, и after — для логирования в другую таблицу.
Более подробно про DML-триггеры:
И в качестве бонуса кратко рассмотрим сопоставление основных системных представлений и приведём ссылки для мониторинга.
Немного о сопоставлении системных представлений и мониторинге
Сопоставление системных представлений
MS SQL Server |
PostgreSQL |
Описание |
Предоставляет статистику по выполненным запросам. В MS SQL Server содержит только то, что в кеше, а в PostgreSQL — всю статистику. Для PostgreSQL: CREATE EXTENSION pg_stat_statements; на каждую БД. |
||
Предоставляет статистику по вызовам пользовательских функций. |
||
Предоставляет статистику по использованию всех пользовательских индексов. |
||
Предоставляет статистические данные по каждой БД. |
Системные представления PostgreSQL:
Сборщик статистики
Изображение взято с Postgres 13 Observability Updates.
Системные представления MS SQL Server:
Мониторинг работы СУБД
Заключение
Мы рассмотрели сопоставление типов и основные конструкции синтаксиса MS SQL Server и PostgreSQL, что позволит быстрее адаптировать решения из одной СУБД под другую.
Также мы пробежались по сопоставлению системных представлений и ссылкам на документацию, в том числе о мониторинге, что позволит анализировать производительность реализованного или перенесённого кода.
Благодарность
Спасибо коллегам за ценные комментарии:
Источники
Комментарии (53)
n0isy
21.01.2022 10:40+2IIF в MS SQL это новодел (с SQL 2016), а так всегда был CASE. Ещё не описаны оконные функции, хотя они указаны для удаления дубликатов.
jobgemws Автор
21.01.2022 10:49Все конструкции описать-цели такой не стояло.
Только самое часто встречаемое.
Много чего нет в материале, это так.
Однако, есть то, что встречалось часто при переводе нескольких проектов.
Т е материал создавался из опыта (не теории).
IIF тоже нужно было перенести-потому он и описан в материале.
CASE-выражение есть и в MS SQL Server, и в PostgreSQL.
Akina
21.01.2022 10:46+2В "I. Регистрозависимое обращение к схемам, таблицам (представлениям) и их полям и другим объектам базы данных" - о квотировании сказано, а вот именно о регистрозависимости - ни полслова.
Не понял "V. Тернарный оператор IIF" - написано так, словно MS SQL не поддерживает CASE...
jobgemws Автор
21.01.2022 10:55+1Благодарю за замечание. В п.5 добавил CASE, чтобы не вводить путаницу
belch84
21.01.2022 18:26+1Я занимаюсь переводом кода из MS SQL Server в PostgreSQL с начала 2019
Интересно, возможно ли в этом случае программное конвертирование исходного кода? На самой первой своей работе я когда-то заглядывал в чужой исходный текст конвертера для программ на FORTRAN из операционной системы DOS/360 в OS/360. Конвертер был написан на ассемблере DOS/360. Различия FORTRANов были незначительными, но изменение большого количества текстов вручную было бы сложной задачей (нужно помнить, что это была еще эпоха префокарт). Кстати, сама система, для которой создавался конвертер, была довольно дурацкой и состояла чуть ли не из тысяч фортрановских подпрограмм. Для описанных различий диалектов SQL программное конвертирование кажется вполне реальнымjobgemws Автор
21.01.2022 18:29Думаю вполне возможно. По крайней мере еще в ВУЗе была лаба по переносу (не всего конечно, но опять же основных конструкций) из VB.NET в C#.NET и наоборот с учетом вложенности типов. Потому здесь тоже можно причем со временем даже почти все переносить в обе стороны (ну что поддается переносу, т е если есть точное соответствие). Однако, на это уйдет очень много времени+постоянно что-то новое выходит.
edo1h
21.01.2022 18:35+1jobgemws Автор
21.01.2022 19:04Но судя по версии 1С очень старое решение, а новые фичи появляются чаще, чем раз в год. Так что в любом решении важно зафиксировать версии откуда и куда и чтобы они соовтетствовали тому, на чем будет решение применено.
edo1h
21.01.2022 19:28+1Но судя по версии 1С очень старое решение
а какие версии 1с им указывать? следующая мажорная нативно умеет постгре
jobgemws Автор
21.01.2022 19:43Про версию 7.7 имел в виду
edo1h
21.01.2022 23:45+1и я про неё. 8.x работает с postgre из коробки, транслятор запросов из ms sql в postgresql для 1c 8.x не продашь.
jobgemws Автор
22.01.2022 01:21Вы её пробовали в работе?
В целом какие недостатки или особенности отметили?
Просто не особо верится, что раз и прямо из коробки всё само встало.
lisper
21.01.2022 19:52+1В пункте "Регистронезависимое сравнение и поиск данных" наверное стоит упомянуть, что регистронезависимость сравнения будет зависеть от выбранного значения параметра Collation конкретной БД. Т.е. при значении Collation, например, SQL_Latin1_General_CP1_CS_AS, сравнение строк будет учитывать регистр (_CS - Case Sensitive), а также аксоны и диакритические знаки ('e' <> 'è', _AS - Accent Sensitive), тогда как при SQL_Latin1_General_CP1_CI_AI - нет.
Кстати, есть ли нечто подобное в PostgreSQL?
jobgemws Автор
21.01.2022 19:53Благодарю за дополнение, но не будем перегружать материал.
По последнему вопросу не могу ответить, т к так глубоко не копал.
K_Chicago
21.01.2022 20:32+1Как я понимаю, PG равняется на Oracle в первую очередь.
Поэтому настоящее сравнение во многом сходно со сравнением синтаксиса Oracle с MS SQL.
Хотелось бы увидеть именно сравнение производительности и синтаксиса PG и Oracle.
Я сейчас занимаюсь как раз бенчмаркингом PG по сравнению с Oracle на примере API большого веб приложения. Пока получается что в большинстве случаев PG сравним или в 2-3 раза медленнее Oracle, хотя в иерархических запросах и курсорах с функцией random PG отстает в 10-20 раз.
jobgemws Автор
21.01.2022 20:46+1Сравнение производительности для 1С для конкретных задач проводилась ранее между MS SQL Server и PostgreSQL и ссылка дана на результат и о том как проводились тесты вначале публикации.
С Oracle сам не сталкивался, потому не могу ничего сказать по этому поводу.
Но Вы можете описать как тестировали и что получили в своей публикации.
Думаю это будет очень интересно и полезно.
K_Chicago
22.01.2022 02:56+1я уже описал что мог, а тестирую простейшим образом - у нас есть набор хранимых процедур и идентичные таблицы с данными, запускаю в цикле 1000 раз в Oracle SQLPlus, с SET TIMING ON/OFF, и в PG с вычислением разницы timestampt в конце и в начале цикла.
Результаты вполне устойчивые. Сами процедуры/функции в основном открытие рефкурсоров на запросы от простеньких до довольно сложных. Пока главные отличия - в иерархии и в random, основное в общем сравнимо, хотя PG стабильно в 2-4 раза медленнее.
jobgemws Автор
22.01.2022 08:14А где можно почитать подробнее как настраивали обе СУБД, характеристики железа каждого сервера под СУБД (Вы же понимаете, что если обе СУБД на одном сервере, то нужно будет выключать ту СУБД, которую в данный момент не тестируете), настройки ОС (тут опять же нужно смотреть какая ОС лучше подходит под конкретную СУБД), настройки файловой системы и настройки системы ввода-вывода?
И полностью код теста для каждой СУБД тоже важно видеть.
Потому что в интернете много чего пишут, но по факту забыли что-то настроить должным образом и результаты получились ошибочными. В частности, часто забывают в Windows выключить индексирование диска, в следствие чего делают вывод, что в линуксе ФС быстрее на 40%. Если индексирование выключить, то получится примерно одинаково.
Потому важно полное и детально описание настроек и характеристик, и сам код для тестирования под каждую СУБД.
rrrad
22.01.2022 00:45+1В первую очередь, PostgreSQL равняется на стандарт SQL, хотя не отказывается от своих расширений. То, что oracle гораздо ближе по синтаксису к стандарту, чем mssql (взять хотя бы конкатенацию строк - зачем выдумывать свой оператор), это, наверное, исторически сложилась из-за типичной для microsoft тех лет стратегии захвата рынка, а oracle развивался вместе со стандартами (или даже до них: взять хотя бы особый синтаксис сравнений, заменяющих left join-ы, он появился до появления left join-а в стандарте).
Единственный компонент oracle, на который postgresql ориентирован - язык pl/psql, но всё-таки, синтаксис у них отличается в деталях, подозреваю, что pl/sql был всего лишь хорошей идеей для повторения чего-то похожего, а задача обеспечения совместимости не ставилась.
K_Chicago
22.01.2022 02:51+1PG во всю себя позиционирует именно как замена Oracle во всех отношениях. И новые релизы PG постоянно указывают на все большую совместимость с Oracle в смысле облегчения миграции оттуда сюда. Я пока с програмной точки зрения вижу один большой недостаток PG по сравнению с Oracle - отсутствие простого синтаксиса для иерархических запросов, а мои измерения показывают что PG-решение примерно в 100 раз медленнее оракловского:(
rrrad
22.01.2022 21:20+1Рекурсивные CTE - гораздо более понятный синтаксис, чем start with/connect by. Причём всё, что можно сделать через start with/connect by, можно выразить и через рекунсивный cte, а вот в обратную сторону это не выполняется, так что этот синтаксис еще и более расширенный.
Что касается скорости - про 100 раз хотелось бы пруфов с корректным измерением, но боюсь, что лицензия Oracle вам не позволит без согласования с ними опубликовать тест сравнения с другой СУБД.
jobgemws Автор
22.01.2022 21:23А как лицензия может запретить публиковать тест сравнения с другой СУБД?
rrrad
22.01.2022 23:30jobgemws Автор
23.01.2022 08:21Здесь я Вас успокою, консультировались с юристами по этому поводу в плане скуля-эти требования в лицензии ничтожны. Компания может написать что угодно в лицензии, но ограничивать права и свободы, не относящиеся к раскрытию исходного кода, который является закрытым, не может. Аналогично думаю и с ораклом.
Вы же тоже по договору понимаете какие пункты ничтожны относительно местного законодательства (или проверяете) или верите тому, что написала одна сторона даже пусть большая компания?
Также в лицензиях часто пишут, что Вы приобретаете не право на продукт, а право использовать продукт, что отсюда вытекает, что не можете в случае потерь для бизнеса или личных целей требовать возместить потери. Однако, в российском законодательстве есть две статьи: про качество услуги/товаров и ещё что-то, благодаря чему можно и нужно в том числе через суд взыскать потери с компании, которая произвела некачественное решение в виде ПО или сервиса и для которой Вы приобрели платную лицензию, т е заплатили.
Примеры судебных решений много в интернете.
Также можно через суд поставить гиганта на место в случае аннулирования лицензии по основаниям, противоречащим местному законодательству. И такие примеры есть в интернете.
jobgemws Автор
23.01.2022 08:26Или если не хотите возможных судебных тяжб, то обычно создают временно компанию-дочку и от её имени публикуют или выступают (причём можно даже бренд головной компании оставить) , а потом в случае чего, это не Ваша компания была, а вообще сторонне юрлицо и что право выступать с брендом Вашей компании не было дано.
Т е я не я, лошадь не моя.
Всё, прицепиться не к чему.
rrrad
23.01.2022 12:49+1Вам не кажется, что публикация сравнения производительности двух субд - слишком малозначительная причина для того, чтобы создавать дочернее юр.лицо или нанимать/нагружать юристов дополнительной работой (если, конечно, они не наняты для другого и временно простаивают)?
Тем более что по хорошему, тестировать надо под конкретный профиль использования СУБД, а он у каждого будет свой. Плюс, нужно учитывать способности имеющихся в наличии разработчиков (если не нанимаем новых под конкретный проект). А значит, любое тестирование (на сколько оно правильным бы не было), для другого проекта может оказаться, как минимум, малополезным, как максимум - неприменимым. При этом, в худшем случае, человек, почитая подобное сравнение, примет неправильное решение (и даже не узнает об этом).
Приведу пример: есть в одной компании биллинг, написанный на Oracle. Админов попросили оценить требуемый сайзинг для случая перевода биллинга на PostgreSQL. Они смигрировали таблицы и запустили несколько отчётов (каждый из них - SQL-запросы на несколько листов исходного кода). На выходе получили просто невозможный сайзинг (увеличение мощности RAC-а из трёх серверов в десятки раз). При этом, зная, как оптимизируются запросы у разработчика биллинга, уверен, что многие места в этих отчётах захинтованы, чтобы Oracle не выбирал заведомо неподходящий план. Естественно, в PostgreSQL это не применимо и многие вещи пришлось бы переписать.
Другой пример: у нас есть ряд SQL-запросов, генерирующих иерархический json (демонстрация того, как это делать - http://sqlfiddle.com/#!17/b0d7a/7). На Oracle такого 1:1, скорее всего, не написать, поэтому когда коллеге надо было сделать подобный функционал под Oracle, он посмотрел на наши примеры, решил, что это слишком сложно, и написал рекурсивную функцию на pl/sql. В итоге один такой оракловый запрос выполняется дольше чем у нас десяток (совсем честное сравнение не делалось, но железо было примерно похожее, а объём генерируемого json-а и уровень вложенности у нас был, в основном, больше) и им пришлось вставить кеш таких json-ов с ручным сбросом. При чём им повезло с тем, что кеш применим, у нас json-ы генерируются свои под конкретного пользователя.
jobgemws Автор
23.01.2022 13:29Абсолютно с Вами согласен.
Однако, увы, неправильные решения в том числе именно так и принимаются причём повсеместно и даже сейчас.
Всегда нужно взвешивать все за и все против, а также сравнивать именно под кокретные условия, а не абстрактно в общем. И тем более ставить под сомнения любые выводы даже авторитетов-все перепроверить под конкретные нужды, условия и задачи. И тем более ставить под сомнения те выводы, в которых нет детализации как проводилось сравнение и как делался вывод.
K_Chicago
22.01.2022 02:59+1я бы сказал что вот как раз скриптовый язык PG - это очень таксебе pl/sql, с очень большой натяжкой. Пакетов нету, одно это уже роняет ценность как минимум вполовину. При миграции пакеты традиционно меняют на схемы...ужос же.
rrrad
22.01.2022 21:26+1Схемы в PostgreSQL не прибиты к юзерам, так что этот ваш "ужос" - вкусовщина.
Зато исполнение кода в PostgreSQL не блокирует програмные объекты. В оракле на высоконагруженной системе обновить популярный пакет (да даже функцию) на нагруженной системе - целая проблема.
CrushBy
22.01.2022 11:25+1Пока получается что в большинстве случаев PG сравним или в 2-3 раза медленнее Oracle, хотя в иерархических запросах и курсорах с функцией random PG отстает в 10-20 раз.
На самом деле, там не все так просто. У Oracle, например, сильно умнее планировщик запросов, но он, соответственно, планирует гораздо медленнее, чем PostgreSQL. И если Вы делаете много разных простых запросов (у которых планы не кэшируются), то бывают ситуации, когда PostgreSQL в итоге работает быстрее.
Но у того же PostgreSQL есть одна большая проблема - он не умеет "перепланировать" запрос в ходе выполнения (что в каком-то виде умеет Oracle). То есть, если PostgreSQL построил план, а в ходе выполнения выяснилось, что записей там не 100000, а 10, то PostgreSQL продолжит выполнение по старому плану, что может привести к катастрофическим затратам на выполнение. Мы это (так как автоматически компилируем запросы) решаем таким образом : когда видим, что запрос выполняется дольше чем должен (порог определяется эвристически), то останавливаем запрос и автоматически разбиваем его на несколько запросов (с временными таблицами). Тогда уже при повторном выполнении PostgreSQL будет знать статистику во временной таблице и построит новый план.
Но стратегически просто надо сразу писать запросы с учетом определенных СУБД. И то, что оптимизировано под PostgreSQL не станет быстрее работать на Oracle (без рефакторинга). Более подробно разницу выполнения запросов на разных СУБД с примерами мы описывали в этой статье.
rrrad
22.01.2022 22:59+1Вообще, существует достаточно вариантов, когда написанный под PostgreSQL код будет медленнее работать на Oracle, если его просто перенести и подправить синтаксически.
Что касается планировщика: для простых запросов это идёт ораклу в минус, т.к. тупо теряется время на IPC, в итоге, пока Oracle планирует запрос, PostgreSQL его уже выполнил. Один из примеров - execute (immediate) с простым запросом в цикле. Да, так неправильно делать и под PostgreSQL это также будет работать хуже, чем статически написанный запрос. Но при этом потеря производительности из-за динамического запроса по сравнению с обычным в Oracle будет гораздо больше (раз в 10), чем в PostgreSQL.
jobgemws Автор
22.01.2022 23:07Приведите полностью скрипт для слона и для оракла и по времени сколько получилось?
Также важно расписать настройки каждой СУБД, настройки ОС под каждую СУБД, ФС, систему ввода-вывода и железо.
Только после этого можно объективно сравнить производительность решения и оценить качество данной проверки (всё ли было настроено верно и в равных ли условиях были обе СУБД).
rrrad
22.01.2022 23:58+1Не приведу - давно было, сейчас доступа к тем машинам нету. Да и правильный тест делается не совсем так: нужна одна машина (как вариант - полностью идентичные машины), куда накатывается сперва одна субд, затем другая. Могу лишь сказать, что опыта в настройке Oracle у админов, которые эти сервера ставили, было гораздо больше, чем в настройке PostgreSQL.
jobgemws Автор
23.01.2022 08:11+1В том то и проблема, что возможно настройки должны быть разными. Но для Oracle не буду утверждать (т к с ним не работал). Однако, слона лучше на винду не ставить равно как и скуль до 2019 версии лучше не на винду не ставить. Иначе заранее уже получите плохой результат. Если нет достаточного опыта в админстве одной из СУБД, то привлекают 2-3 экспертов, кто может провести ревью настроек и помочь в подготовке среды для тестирования. По крайней мере мы так делали. У нас тогда скуль 2017 был на линухе и был в проигрышном варианте, но всё равно оказался шустрее слона даже при таких настройках. Но там пришлось на линуксе тестить, ибо заказчик отказывался от винды.
Ссылка на результат тестирования приведён вначале публикации.
NewFirefly
21.01.2022 22:48+1У нас на проекте PG 14, и много где current_timestamp используется, но у Вас эта функция неупомянута или в MS SQL нет сравнимого типа?
vagon333
22.01.2022 06:18+1Используем SQL CLRs (функции, триггеры, хранимки на C#).
Сталкивались ли с SQL CLR и на каком языке реализовывали альтернативу?
PS: рассматриваем частичный перенос базок на PG.
BigD
MS SQL Server недостаточно хорош для business critical? Или другая причина движения в сторону постгре?
jobgemws Автор
MS SQL, конечно, достаточно хорош для бизнес-критикал систем, и многие компании его так и юзают.
Просто у нас так сложилось, что где-то MS SQL, а где-то — PostgreSQL.
Tzimie
У многих просто жаба
jobgemws Автор
Прямолинейно, но в этом есть доля правды скорее всего)
vervolk
У когото сейчас санкции и легально купить ни Windows server ни ms sql невозможно.
jobgemws Автор
Суровая правда текущей реальности
jobgemws Автор
Но стоит отметить, что есть вполне законные способы обойти эти ограничения. Причём не только в аутсорс отдать создание и эксплуатацию сред.
speshuric
В целом замечателен. Только Enterprise лицензия больше $5к за ядро. А сейчас ядер в пухлом (и относительно недорогом!) сервере под сотню. А Enterprise нужен, если памяти >128ГБ (ёлы-палы, 128 ГБ это сейчас комфортный комп разработчка, а не сервер предприятия). И если двигаться в сторону микро- или хотя бы мини- сервисов, то лицензиями придётся покрывать несколько больших серверов.
jobgemws Автор
В том числе и это.
Хотя часто Standard хватает+можно получить существенную скидку у партнёров при большой закупке.
И ещё вопрос сколько по деньгам обойдётся кастомизация открытого (не бесплатного!) решения и его сопровождение. На сколько стабильно и безопасно работают те или иные возможности. И в случае чего кому предъявлять притензию.
А если ещё использовать ORM, то при росте данных можно получить реальные проблемы в производительности.
Здесь очень хорошо эти и другие моменты описаны особенно в конце.