Добро пожаловать в форум, Guest  >>   Войти | Регистрация | Поиск | Правила | В избранное | Подписаться
Все форумы / Microsoft SQL Server Новый топик    Ответить
Топик располагается на нескольких страницах: [1] 2   вперед  Ctrl      все
 Использование временных таблиц вместо вложенных подзапросов  [new]
?????
Guest
Коллеги, возник вопрос.
Какой из вариантов с точки зрения производительности будет работать быстрее:
Вариант 1.
SELECT t1.fld1, t1.fld2, t2.fld1, t2.fld2, t2.fld3
   FROM tab1 t1 LEFT OUTER JOIN
           ( SELECT id, fld1, fld2, fld3 
                FROM tab2
              WHERE fld4 = ... ) t2 ON t1.id = t2.id

Вариант 2.
SELECT id, fld1, fld2, fld3 
   INTO #tbl2
   FROM tab2
 WHERE fld4 = ... 

-- Сама выборка
SELECT t1.fld1, t1.fld2, t2.fld1, t2.fld2, t2.fld3
   FROM tab1  t1 LEFT OUTER JOIN
           #tbl2 t2 ON t1.id = t2.id

DROP TABLE #tbl2


Заранее спасибо за ответы )
13 июл 12, 14:46    [12863403]     Ответить | Цитировать Сообщить модератору
 Re: Использование временных таблиц вместо вложенных подзапросов  [new]
step_ks
Member

Откуда:
Сообщений: 936
да любой, особенно на пустых таблицах
13 июл 12, 14:51    [12863440]     Ответить | Цитировать Сообщить модератору
 Re: Использование временных таблиц вместо вложенных подзапросов  [new]
Shakill
Member

Откуда: мск
Сообщений: 1887
?????,

а зачем здесь подзапросы и временные таблицы

SELECT t1.fld1, t1.fld2, t2.fld1, t2.fld2, t2.fld3
FROM tab1 t1 
LEFT JOIN tab2 t2 ON t1.id = t2.id AND t2.fld4 = ... 
13 июл 12, 14:52    [12863453]     Ответить | Цитировать Сообщить модератору
 Re: Использование временных таблиц вместо вложенных подзапросов  [new]
?????
Guest
Shakill, интересует вариант в разрезе использования именно вложенного запроса.
Приведен пример простого кода, но бывают ситуации, когда использование вложенных запросов просто необходимо.
Вот и хотим определиться какой из предложенных вариантов оптимальней в быстродействии и использовании памяти на весомых объемах данных (от ~2х миллионов строк).
13 июл 12, 14:59    [12863515]     Ответить | Цитировать Сообщить модератору
 Re: Использование временных таблиц вместо вложенных подзапросов  [new]
step_ks
Member

Откуда:
Сообщений: 936
?????
Shakill, интересует вариант в разрезе использования именно вложенного запроса.
Приведен пример простого кода, но бывают ситуации, когда использование вложенных запросов просто необходимо.
Вот и хотим определиться какой из предложенных вариантов оптимальней в быстродействии и использовании памяти на весомых объемах данных (от ~2х миллионов строк).

где 2 миллиона? в результате? структуры, планы запросов, не устраивающих по производительности, показывайте тогда уж.
13 июл 12, 15:03    [12863540]     Ответить | Цитировать Сообщить модератору
 Re: Использование временных таблиц вместо вложенных подзапросов  [new]
?????
Guest
Планов нет. Это спор.
Интересует мнение людей, которые действительно сталкивались с написанием данных вариантов и сравнения их планов.
13 июл 12, 15:07    [12863575]     Ответить | Цитировать Сообщить модератору
 Re: Использование временных таблиц вместо вложенных подзапросов  [new]
Shakill
Member

Откуда: мск
Сообщений: 1887
?????
Планов нет. Это спор.
Интересует мнение людей, которые действительно сталкивались с написанием данных вариантов и сравнения их планов.

то есть если лично в моей практике (на моих данных, структурах, железе и запросах) всегда побеждал вариант один, это будет считаться аргументом в вашем споре? пусть будет так :)
13 июл 12, 15:19    [12863662]     Ответить | Цитировать Сообщить модератору
 Re: Использование временных таблиц вместо вложенных подзапросов  [new]
step_ks
Member

Откуда:
Сообщений: 936
да у людей один и от же запрос сегодня так работает, а завтра по-другому, а вы хотите сравнить два абстрактных.
13 июл 12, 15:19    [12863663]     Ответить | Цитировать Сообщить модератору
 Re: Использование временных таблиц вместо вложенных подзапросов  [new]
мимо
Guest
?????
Планов нет. Это спор.
Интересует мнение людей, которые действительно сталкивались с написанием данных вариантов и сравнения их планов.

Если спор, то, на больших данных, второй вариант.
13 июл 12, 15:46    [12863878]     Ответить | Цитировать Сообщить модератору
 Re: Использование временных таблиц вместо вложенных подзапросов  [new]
Mnior
Member

Откуда: Кишинёв
Сообщений: 6724
мимо
Если спор, то, на больших данных, второй вариант.
Херасе.
А если в "основной выборке" стоит такой фильтр, что оптимизатор уберёт 100500 обращений, которые гарантированно произойдут при запись во времяку. Это первое.
Второе, при времянке, вы не только читаете данные (для времянки), но записываете (во времянку), а потом еще раз читаете (из времянки). А если общий запрос, то только читаете раз.
Всё дело в плане.

На моей практике я очень много переделывал запросы, убивая времянки, и не помню, что когда-то их писал. Я чаще исправляю проблемы, а не пишу такие костыли.

автор
Приведен пример простого кода, но бывают ситуации, когда использование вложенных запросов просто необходимо.
Практически никогда. Это означает ошибка в проектировании. Надо знать все шаблоны проектирования и использовать их, по назначению. В данном случае представления (вьюшки).
13 июл 12, 20:02    [12865127]     Ответить | Цитировать Сообщить модератору
 Re: Использование временных таблиц вместо вложенных подзапросов  [new]
Mnior
Member

Откуда: Кишинёв
Сообщений: 6724
Mnior
В данном случае представления (вьюшки).
Или параметризованные представления (инлайн функции).

Остальные под-запросы очень простые, чтобы на них кто-то обращал внимание.
13 июл 12, 20:07    [12865136]     Ответить | Цитировать Сообщить модератору
 Re: Использование временных таблиц вместо вложенных подзапросов  [new]
Mind
Member

Откуда: Лучший город на Земле
Сообщений: 2322
Mnior
мимо
Если спор, то, на больших данных, второй вариант.
Херасе.
А если в "основной выборке" стоит такой фильтр, что оптимизатор уберёт 100500 обращений, которые гарантированно произойдут при запись во времяку. Это первое.
Второе, при времянке, вы не только читаете данные (для времянки), но записываете (во времянку), а потом еще раз читаете (из времянки). А если общий запрос, то только читаете раз.
Всё дело в плане.

На моей практике я очень много переделывал запросы, убивая времянки, и не помню, что когда-то их писал. Я чаще исправляю проблемы, а не пишу такие костыли.

автор
Приведен пример простого кода, но бывают ситуации, когда использование вложенных запросов просто необходимо.
Практически никогда. Это означает ошибка в проектировании. Надо знать все шаблоны проектирования и использовать их, по назначению. В данном случае представления (вьюшки).
Времянки стоит рассматривать только в 2-х случаях:
1. Для уменьшения сложности запроса, когда у оптимизатора крышу срывает от количества таблиц в запросе и не хвататет ресурсов построить оптимальный план.
2. При кривой статистике/распределении данных, когда оптимизатор не может правильно рассчитать кардиналити в результате наложения фильтра и/или соединения таблиц. В этих случаях иногда может помочь создание временных таблиц.

И естественно нужно помнить, что использование времянок нехило увеличивает вероятность рекомпиляции запроса.
14 июл 12, 00:34    [12865938]     Ответить | Цитировать Сообщить модератору
 Re: Использование временных таблиц вместо вложенных подзапросов  [new]
Mind
Member

Откуда: Лучший город на Земле
Сообщений: 2322
Вспомнил еще один сценарий. Если создание промежуточного результата весьма затратно по времени/ресурсам, а использоваться этот результат затем будет многократно в хранимой процедуре, тогда тоже имеет смысл рассмотреть использование времянки.
14 июл 12, 00:38    [12865945]     Ответить | Цитировать Сообщить модератору
 Re: Использование временных таблиц вместо вложенных подзапросов  [new]
Mnior
Member

Откуда: Кишинёв
Сообщений: 6724
Mind
Вспомнил еще один сценарий. Если создание промежуточного результата весьма затратно по времени/ресурсам, а использоваться этот результат затем будет многократно в хранимой процедуре, тогда тоже имеет смысл рассмотреть использование времянки.
Имеет смысл рассмотреть постоянку, включая вариант с индексированным представлением.
Но обычно тут пахнет (а) сопляком, который размазал "код" по многим запросам, хотя можно и нужно одним или (б) вывернутой архитектурой системы - обычно ERмизм ГМ.

Mind, часто бывает что совет профессионала, нубом совершенно вычурно понимается и делается ещё хуже. ;)

Нужно понимать причины, почему именно так делается, тогда все выше описанные следствия будут очевидны.
14 июл 12, 03:32    [12866250]     Ответить | Цитировать Сообщить модератору
 Re: Использование временных таблиц вместо вложенных подзапросов  [new]
мимо
Guest
Mnior
мимо
Если спор, то, на больших данных, второй вариант.
Херасе.
А если в "основной выборке" стоит такой фильтр, что оптимизатор уберёт 100500 обращений, которые гарантированно произойдут при запись во времяку. Это первое.
Второе, при времянке, вы не только читаете данные (для времянки), но записываете (во времянку), а потом еще раз читаете (из времянки). А если общий запрос, то только читаете раз.
Всё дело в плане.

На моей практике я очень много переделывал запросы, убивая времянки, и не помню, что когда-то их писал. Я чаще исправляю проблемы, а не пишу такие костыли.

автор
Приведен пример простого кода, но бывают ситуации, когда использование вложенных запросов просто необходимо.
Практически никогда. Это означает ошибка в проектировании. Надо знать все шаблоны проектирования и использовать их, по назначению. В данном случае представления (вьюшки).

Вот об фильтре, точнее о порядке выполнения операций при селекте, и идёт речь. Согласно букваря, вначале исполняется from с джойнами и онами, а уж потом where и иже с ним всякие группенги, хевинги и т.д. Представим , что имеется несколько таблиц с очень большими объёмами, которые надо заджойнить и применить фильтр при которой в выборке будет только одна запись.
Как-то так:
select *
from t1
inner join t2 on (t2.t1f1=t1.t1f1)
...
inner join tN on (tN.t1f1=t1.t1f1)

where t1.t1f2 = что-то

Как показывает практика, в таких случая часто выгоднее такое выполнение запроса:
select *
into #t1
from t1
where t1.t1f2 = что-то

select *
from #t1 as t1
inner join t2 on (t2.t1f1=t1.t1f1)
...
inner join tN on (tN.t1f1=t1.t1f1)


PS Вместо временной таблицы можно(нужно) использовать табличную переменную.
16 июл 12, 10:01    [12870981]     Ответить | Цитировать Сообщить модератору
 Re: Использование временных таблиц вместо вложенных подзапросов  [new]
Shakill
Member

Откуда: мск
Сообщений: 1887
мимо
Вот об фильтре, точнее о порядке выполнения операций при селекте, и идёт речь. Согласно букваря, вначале исполняется from с джойнами и онами, а уж потом where и иже с ним всякие группенги, хевинги и т.д. Представим , что имеется несколько таблиц с очень большими объёмами, которые надо заджойнить и применить фильтр при которой в выборке будет только одна запись.

если вы сейчас ссылаетесь на статью про select, то там указан логический порядок выполнения и есть фраза
http://msdn.microsoft.com/ru-ru/library/ms189499.aspx
Обратите внимание, что фактическое физическое выполнение оператора определяется обработчиком запросов и порядок из этого списка может значительно отличаться.
то есть фильтр может быть применен раньше, если сервер определит что это выгоднее и не исказит логику вычислений, т.е. сам результат

мимо
Как-то так:
select *
from t1
inner join t2 on (t2.t1f1=t1.t1f1)
...
inner join tN on (tN.t1f1=t1.t1f1)

where t1.t1f2 = что-то

Как показывает практика, в таких случая часто выгоднее такое выполнение запроса:
...
PS Вместо временной таблицы можно(нужно) использовать табличную переменную.
это в случаях, когда сервер не может найти оптимальный запрос. но это не связано с логическим порядком обработки селекта
16 июл 12, 10:44    [12871241]     Ответить | Цитировать Сообщить модератору
 Re: Использование временных таблиц вместо вложенных подзапросов  [new]
WellSlava
Member

Откуда:
Сообщений: 207
Я повспоминал, что у меня в базе твориться, так вот большие данные я во временные таблицы никогда записываю. Пару раз случались ситуации, где "либо-либо", но в итоге получалось, что большой запрос работал быстрее.
16 июл 12, 11:00    [12871355]     Ответить | Цитировать Сообщить модератору
 Re: Использование временных таблиц вместо вложенных подзапросов  [new]
мимо
Guest
Shakill
это в случаях, когда сервер не может найти оптимальный запрос. но это не связано с логическим порядком обработки селекта

Об том и речь, что серверу при сложных запросах надо подсказывать. А ссылка к букварю - это предположение, почему он(сервер) может не "по-человечески" строить план. Т.е. он может вначале соединить, а потом фильтрануть, хотя всем понятно, что надо наоборот.
16 июл 12, 11:47    [12871710]     Ответить | Цитировать Сообщить модератору
 Re: Использование временных таблиц вместо вложенных подзапросов  [new]
Mnior
Member

Откуда: Кишинёв
Сообщений: 6724
мимо
Об том и речь, что серверу при сложных запросах надо подсказывать
1. Если у него проблемы с построением плана (нескольков десятков таблиц в запросе), то всё хамба, приплыли. Все эти лишние телодвижения с времянкой не сильно отдалит смерть проекта.
2. И в добавок ваше предположение не верно, он всё равно сделает как ему вздумается.
3. Времянка фильтр не учитывает, следовательно вы "возможно хреновый план" замените на "гарантированный хреновый план".

Уже писал выше как надо лечить пробелему, а не делать вид и усугублять её тоннами ненужного кода.
16 июл 12, 13:11    [12872230]     Ответить | Цитировать Сообщить модератору
 Re: Использование временных таблиц вместо вложенных подзапросов  [new]
NoLock
Member

Откуда: левый верний угол
Сообщений: 90
SELECT ... INTO #tbl2 FROM tab2 WHERE fld4 = ...

<а вот если здесь вставить создание индексов, то может получиться совсем другая история...>
SELECT ... FROM tab1 t1 LEFT OUTER JOIN ...
DROP TABLE #tbl2
16 июл 12, 19:59    [12874484]     Ответить | Цитировать Сообщить модератору
 Re: Использование временных таблиц вместо вложенных подзапросов  [new]
Mind
Member

Откуда: Лучший город на Земле
Сообщений: 2322
Mnior
3. Времянка фильтр не учитывает, следовательно вы "возможно хреновый план" замените на "гарантированный хреновый план".
Что вы имеете ввиду под не учитывает фильтр? После создания времянки по ней посчитается статистика, которая будет использована для соединения с другими таблицами, этого обычно бывает достаточно. Фильтр же, примененный для построения времянки, на последующих этапах уже никого не волнует.

Хотите реальный пример где применение времянки может быть весьма выгодно, даже на не очень сложных запросах?
+
Есть 2 таблицы:
TableName RowCount
Staging.PartyRelationship 1 835 307
Staging.BusinessRole 1 572 925

И простой казалось бы запрос:
SELECT COUNT(*)
  FROM Staging.PartyRelationship AS branchcustrel
       INNER JOIN Staging.BusinessRole AS branch ON branch.BusinessRoleID = branchcustrel.PrimaryPartyID
   WHERE branch.CategoryID = 1

Но проблема в том, что таблица BusinessRole содержит множество сущностей различаемых по признаку CategoryID. Да согласен, плохой дизайн, но 
а) система существует много лет и для OLTP с "легкими" запросами такой дизайн не сильно влияет на производительность. 
б) На переписывание нет времени и бюджета
в) Это база стороннего подразделения, и мы менять дизайн не имеем никакой возможности, хотя как раз таки страдаем от него больше всех, ввиду массивных запросов по извлечению данных для построения DWH.

Распределение данных между этими таблицами в разрезе по категории выглядит примерно так:
CategoryIDBusinessRoleCountPartyRelationshipCount
1103226806
22208501175576
3907722908030
4432410432410
51183611836
633
991503

Где видно, что для категории = 1 всего на 103 строки в BR приходится 226 тысяч в PR. Статистическая модель сиквела не имеет механизмов чтобы это учесть, в итоге получем вот такие кривые эстимэйты:

RowsEstimateRowsStmtText
11SELECT COUNT(*) FROM Staging.PartyRelationship AS branchcustrel...
01 |--Compute Scalar(DEFINE:([Expr1005]=CONVERT_IMPLICIT(int [Expr1009] 0)))
11 |--Stream Aggregate(DEFINE:([Expr1009]=Count(*)))
22677014867.05 |--Nested Loops(Inner Join OUTER REFERENCES:([bran...
103103 |--Index Seek(OBJECT:([UAT_CDH].[Staging].[BusinessRol...
226770144.3403 |--Index Seek(OBJECT:([UAT_CDH].[Staging].[Par...

Для данного запроса это не критично, но если добавить еще пару-тройку таблиц, то из-за такой ошибки статистики в 15 раз производительность может очень хорошо просесть.

Если же использовать времянку, то картина изменяется в лучшую сторону.
SELECT BusinessRoleID INTO #Branch FROM Staging.BusinessRole AS branch WHERE branch.CategoryID = 1

SELECT COUNT(*) 
  FROM Staging.PartyRelationship AS branchcustrel
       INNER JOIN #Branch AS branch ON branch.BusinessRoleID = branchcustrel.PrimaryPartyID

RowsEstimateRowsStmtText
11SELECT COUNT(*) FROM Staging.PartyRelationship AS branchcustrel...
01 |--Compute Scalar(DEFINE:([Expr1006]=CONVERT_IMPLICIT(int [Expr1010] 0)))
11 |--Stream Aggregate(DEFINE:([Expr1010]=Count(*)))
226770204750.6 |--Nested Loops(Inner Join OUTER REFERENCES:([bra...
103103 |--Table Scan(OBJECT:([tempdb].[dbo].[#Branch] AS [br...
2267701987.87 |--Index Seek(OBJECT:([UAT_CDH].[Staging].[Par...

Есть другие варианты как это исправить без применения времянок? Редизайн не предлагать.
16 июл 12, 22:07    [12874866]     Ответить | Цитировать Сообщить модератору
 Re: Использование временных таблиц вместо вложенных подзапросов  [new]
Mind
Member

Откуда: Лучший город на Земле
Сообщений: 2322
NoLock
SELECT ... INTO #tbl2 FROM tab2 WHERE fld4 = ...

<а вот если здесь вставить создание индексов, то может получиться совсем другая история...>
SELECT ... FROM tab1 t1 LEFT OUTER JOIN ...
DROP TABLE #tbl2
Если временная таблица маленькая, то она будет захэширована для джойна и все эти индексы будут не нужны. Если же времянка большая, то тогда расходы на её создание и построение индексов могут перекрыть все бенефиты. Может оказаться проще создать индексы сразу на tab2.
16 июл 12, 22:16    [12874888]     Ответить | Цитировать Сообщить модератору
 Re: Использование временных таблиц вместо вложенных подзапросов  [new]
Mnior
Member

Откуда: Кишинёв
Сообщений: 6724
Mind
Что вы имеете ввиду под не учитывает фильтр?
Фильтр же, примененный для построения времянки, на последующих этапах уже никого не волнует.
Обыкновенный фильтр/соединение которой кардинально повлияет на данный под-запрос, и вместо 100500 строк во времянке, в под-запросе отберётся 100. Никакой другой случай я не рассматривал в том контексте.

Mind
Хотите реальный пример где применение времянки может быть весьма выгодно, даже на не очень сложных запросах?
Нет, не надо, я видел их достаточно, разве что сейчас они все уже в анналах истории, как и всякие вымершие динозавры.

Mind, не очень понятно кому вы это писали. Как подтверждение моих слов?
Особенно мне нравятся выражения "бла-бла-бла не предлагать". Странный научный метод.
Хотя я выше писал про постоянку (фильтрованный индекс в данном случае), но вы посмотрите внимательно на эту команду, а потом отпишитесь о результатах. Ок?

Да, я знаю, что большинство проггеров болеют тем, что не хотят исправлять, а только писать с нуля, допуская те же ошибки. Людей которые любят думать и не ленятся крайне мало. И что вы от меня хотите? ИИ?
17 июл 12, 03:48    [12875481]     Ответить | Цитировать Сообщить модератору
 Re: Использование временных таблиц вместо вложенных подзапросов  [new]
Mind
Member

Откуда: Лучший город на Земле
Сообщений: 2322
Mnior
Mind
Что вы имеете ввиду под не учитывает фильтр?
Фильтр же, примененный для построения времянки, на последующих этапах уже никого не волнует.
Обыкновенный фильтр/соединение которой кардинально повлияет на данный под-запрос, и вместо 100500 строк во времянке, в под-запросе отберётся 100. Никакой другой случай я не рассматривал в том контексте.
Все равно не понял, ну да ладно.
Mnior
Особенно мне нравятся выражения "бла-бла-бла не предлагать". Странный научный метод.
Да не научно, но практично. Не всегда можно переписать все с нуля. Когда система уже написана, её никто так просто не будет переписывать, нужны очень весомые доводы и значительное количество ресурсов. Поэтому приходится пихать костыли, иногда это единственный выход. По крайней мере использование времянок, по моей шкала всемирного зла, идет ниже чем использование хинтов в запросах.
Mnior
Хотя я выше писал про постоянку (фильтрованный индекс в данном случае), но вы посмотрите внимательно на эту команду, а потом отпишитесь о результатах. Ок?
Да, фильтрованная статистика тоже поможет в данном случае, есть правда несколько минусов:
1) Нужно создавать отдельную статистику под каждую категорию, в случае же со времянкой все будет работать и так, через переменную, и даже parameter sniffing не будет проблемой.
2) Эту статистику нужно поддерживать в актуальном состоянии, всегда пересчитывая с FULLSCAN. Если же кто запустит sp_updatestats, то планы слетят. Это из-за того что SAMPLE для фильтрованной статистики применяется на всю таблицу, а не на фильтрованную её часть, что очень глупо, но так решили в MS.
3) Не на всех версиях сервера можно создавать фильтрованные индексы/статистику.
И хотя я согласен, что фильтрованная статистика это самое красивое решение(собственно его уже и заимплементил), но тем не менее времянка, это такое тупое решение, которое будет работать всегда и везде, пусть и выглядит как костыль :)
17 июл 12, 21:15    [12880470]     Ответить | Цитировать Сообщить модератору
 Re: Использование временных таблиц вместо вложенных подзапросов  [new]
Mnior
Member

Откуда: Кишинёв
Сообщений: 6724
Mind
Все равно не понял
А)
SELECT ...
FROM (
-- Подзапрос, из данных таблиц отобрано 100 строк, благодаря фильтру ниже
)
... -- очень замысловатое
WHERE -- Фильтр, как применить к подзапросу напрямую неизвестно, сложный
Б)
INSERT #Temp
-- Подзапрос, вернул 100500 строк, фильтра то нет

SELECT ...
FROM #Temp -- из 100500 строк понадобилось 100, остальное мусор
... -- очень замысловатое
WHERE -- Фильтр, тот же
Ничего более. Такого кода я видел очень-очень-очень много.

Mind
это такое тупое решение, которое будет работать всегда и везде, пусть и выглядит как костыль
1. Отличительная проблема в том, что вы один из не многих, кто понимает что делает. Уверяю вас, 9 из 10 применят ваш совет не к месту (в 99 случаев из 100) усугубив проблему.
2. Это костыль. Всё что непосредственно не описывает бизнес задачу есть или костыль или говнокод.
3. Не стоит заострять внимание на костыли, это отвлекает от главного и наводит мешанину неокрепшему уму, а придумать как вывернуться в той или иной исключительной ситуации, на это всяк горазд - хомо сапиенс же.

Мой учитель говаривал:
Умный выйдет из любой трудной ситуации, гений в неё не попадёт.


На счёт переписывания. Да, по тем же причинам, что людям лень думать во время разработки, по тем же причинам им лень что-то переписывать, по тем же причинам им лень не то что разбираться, смотреть чужой код. Но необычайный энтузиазм всё переписывать. Мы всё те же варвары.
18 июл 12, 02:04    [12881249]     Ответить | Цитировать Сообщить модератору
Топик располагается на нескольких страницах: [1] 2   вперед  Ctrl      все
Все форумы / Microsoft SQL Server Ответить