Comments 48
Раньше был продукт у QuestSoftware — FrogLight, рисовал такие зеленые Dashboard и мерял жизнь SQL в реальном времени. В том числе и запросы. Очень удобная была программа. Потом когда Dell купил их — и TOAD и остальные полезняшки как то пропали.
Вот если б там ещё б добавили к курсору fast_forward, static, read_only, forward_only в комбинациях и всё это сравнили с циклом по временным таблицам (время исполнения, чтение/запись), то да, было б интересно. Есть ещё задачи, когда одним запросом дорого по времени (сервера или разработчика), а с циклом быстрее/понятнее.
Еще добавлю, что не нужно делать фиктивных обновлений.
Часто вижу приложение, которое делает фиктивное обновление каких-либо строк. В результате чего напрягает стандартную репликацию. И еще хуже-когда при сохранении удаляются строки и добавляются новые (при этом таблица реплецируема). Видел такие разработки на C++ и C#. И еще неоптимально, когда в качестве первичного ключа берут ГУИД, которым никто пользоваться не будет. Нет чтобы взять ту же дату вставки например с каким-нибудь полем-уж явно чаще использоваться будет и в сортировках в том числе.
DECLARE @a VARCHAR(max) = NULL;
WITH a AS
(
SELECT 1 AS a
UNION ALL SELECT 2 AS a
UNION ALL SELECT 3 AS a
)
SELECT @a = COALESCE(@a + ',', '') + CAST(a AS VARCHAR)
FROM a
SELECT @a
?
DECLARE @a VARCHAR(MAX)
;WITH
E1(N) AS (
SELECT * FROM (
VALUES
('1'),('1'),('1'),('1'),('1'),
('1'),('1'),('1'),('1'),('1')
) t(N)
),
E2(N) AS (SELECT '1' FROM E1 a, E1 b),
E4(N) AS (SELECT '1' FROM E2 a, E2 b)
SELECT @a = COALESCE(@a + ',', '') + N
FROM E4
ORDER BY LEN(N)
SELECT @a
Увы хорошее репро у меня было только одно, что я привел в статье.
Большая часть статьи из разряда "а как иначе?".
Но обычно так думаешь уже постфактум :)
Так что плюсую.
Все что я описал сохраняет актуальность с 2005 версии и по 2016 (за мелкими исключениями, потому что кое где планы выполнения будут другими).
Функция ISNULL преобразует к наименьшему типу из двух операндов. COALESCE преобразует к наибольшему типу.
ISNULL преобразует к типу первого операнда.
ISNULL позволяет сбросить признак nullable с колонки (бывает нужно при создании видов или временных таблиц).
COALESCE позволяет указывать много аргументов.
Добавление в копилку полезных приемов:
Создавать временные таблицы желательно без явного указания типов, путем копирования типов нужных полей из таблиц с которыми собираемся в дальнейшем работать. Например:
SELECT e.BusinessEntityID
, p.FirstName
, p.MiddleName
, p.LastName
INTO #TmpTable
FROM HumanResources.Employee e
JOIN Person.Person p ON p.BusinessEntityID = e.BusinessEntityID
WHERE 0=1
Получаем пустую временную таблицу с колонками нужных типов. И, если в будущем размерность какого-либо поля изменится, код переписывать не придется.
Хинт1. конструкт NULLIF(column,column) as column позволяет писать в колонку временной таблицы NULL, даже если в исходной NULL не допустим.
Хинт2. конструкт column+0 AS column позволяет снять признак IDENTITY с колонки
Я бы больше сказал — только guid. Иначе когда приложение выростет до уровня Enterprise и вам придется настраивать Peer-To-Peer репликацию а то и вставлять гуиды из серверов приложения — все эти инты в дизайне встанут боком.
Да здравствует распухание кластерного индекса из-за вставок в рандомных местах? :)
Существуют приложения, которые никогда не вырастут до того уровня, где требуется merge-репликация.
Некоторые советы тут бесполезные.
Про даты — надо не искать "универсальный" формат даты, а указать нужные настройки для соединения. А еще лучше — использовать параметризованные запросы и вообще не указывать даты в строковых литералах.
Про NULL в (NOT) IN. Необходимость выполнить такой запрос по атрибуту, не являющемуся первичным ключом — говорит о том, что схему БД забыли перевести в третью нормальную форму. Не надо так делать.
- Кстати, где совет использовать третью нормальную форму?
… а лучше всего использовать ORM и не мучаться с "сырыми" запросами.
Долго ждал, но так и не увидел случаев, когда действительно стоит слезть с ORM на уровень ниже. А их два:
передача на сервер действительно больших массивов данных. Решение — делаем хранимую процедуру, которая принимает табличный параметр. На стороне ADO.NET передаем в качестве параметра в запрос DataTable.
- быстрый подсчет агрегатов через индексированные (они же материализованные) представления. Классический пример — баланс аккаунта можно считать как сумму изменений баланса по журналу операций.
Для "старших" редакций само наличие индексированного представления ускорит запросы, в которую используются подсчитанные агрегаты. Для "младших" (Express/Developer Edition) — надо делать выборку именно из индексированного представления, указав with(noexpand)
. Удобно создавать второе представление, которое делает select * from ... with (noexpand)
— его можно завести в ORM как read-only таблицу.
… а лучше всего использовать ORM и не мучаться с «сырыми» запросами.
Вот тут не согласен. Зачем так категорично? :)
Относительно индексированных представлений… OLTP или DW? Их не всегда выгодно применять. Там много приколов, особенно когда присутствует неслабая OLTP нагрузка.
В OLTP: тут вопрос в том, требуется ли агрегат в процессе обработки поступающей транзакции. Если требуется — то хочешь-не хочешь, а придется где-то его хранить и оперативно обновлять. Если не требуется — то зачем он вообще нужен?
В DW: нет никаких препятствий для создания стольких индексированных представлений, сколько хочется.
$('.post__title').after('<div class="directory"><h1>Содержание</h1></div>');$('h5').each(function(index) {$(this).attr('id', 'h5_' + index); $('.directory').append('<br/><a href="#h5_' + index + '">' + $('h5')[index].innerText + '</a>');})
DECLARE @txt VARCHAR(50) = ''
SELECT @txt = CONCAT(@txt, i)
FROM #t
SELECT @txt
Invoking CLR User-Defined Aggregate Functions
Прямо в документации — реализация конкатенирующей агрегатной функции для примера...
https://msdn.microsoft.com/en-us/library/mt790580.aspx
Msg 195, Level 15, State 10, Line 4
'STRING_AGG' is not a recognized built-in function name.
Хотя к слову, крайне советую ознакомиться с изменениями в новом SP1 для 2016-го.
В Express, Standart редакциях можно наконец-то использовать секционирование и columnstore индексы. С небольшими оговорками конечно, но все равно круто :)
SELECT p.BusinessEntityID, s.SalesQuota
FROM Person.Person p
LEFT JOIN Sales.SalesPersonQuotaHistory s
LEFT JOIN Sales.SalesPersonQuotaHistory s1 ON s1.BusinessEntityID = s.BusinessEntityID AND s1.QuotaDate > s.QuotaDate
ON s.BusinessEntityID = p.BusinessEntityID AND s1.[Первичный ключ] IS NULL
Сейчас не могу сам проверить.
TOP 24.69s
self join 13.33s
ROW_NUMBER 7.05s
Спасибо за статью!
Кстати, ссылка на доклад Сергея: https://www.youtube.com/watch?v=C1I5v1xxJv4. В том же канале есть видео со всех прошлых встреч Russian Virtual Chapter.
Это поведение описано в документации:
If the sum of start and length is greater than the number of characters in expression, the whole value expression beginning at start is returned.
Репро:
DECLARE @t1 TABLE (t1 INT, UNIQUE CLUSTERED(t1))
INSERT INTO @t1 VALUES (1), (2)
DECLARE @t2 TABLE (t2 INT, UNIQUE CLUSTERED(t2))
INSERT INTO @t2 VALUES (1), (NULL)
set statistics io on
SELECT *
FROM @t1
WHERE t1 NOT IN (
SELECT t2
FROM @t2
WHERE t2 IS NOT NULL
)
SELECT * FROM @t1
EXCEPT
SELECT * FROM @t2
SELECT *
FROM @t1
WHERE NOT EXISTS(
SELECT 1
FROM @t2
WHERE t1 = t2
)
select t_1.*
from @t1 t_1
left join @t2 t_2
on t1 = t2
where t2 is null
Результат (все варианты одинаково хороши за исключением первого):
Table '#54AF0098'. Scan count 2, logical reads 4, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table '#50DE6FB4'. Scan count 1, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
(1 row(s) affected)
(1 row(s) affected)
Table '#54AF0098'. Scan count 0, logical reads 4, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table '#50DE6FB4'. Scan count 1, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
(1 row(s) affected)
(1 row(s) affected)
Table '#54AF0098'. Scan count 0, logical reads 4, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table '#50DE6FB4'. Scan count 1, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
(1 row(s) affected)
(1 row(s) affected)
Table '#54AF0098'. Scan count 0, logical reads 4, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table '#50DE6FB4'. Scan count 1, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
(1 row(s) affected)
Жаль, что автор удалил эти сообщения. Мне кажется он очень эмоционально реагирует на ту непростую ситуацию в которую мы все сейчас разворачивается вокруг нас. Тем не мение мне кажется не правильно переносить политику в программирование, и подобные статьи должны жить дальше, тем более, что рукописи не горят...
https://web.archive.org/web/20201004042530/https://habr.com/ru/post/315142/
Ой знаете реально хотел все затереть, но Хабр не дал :) пусть все остается как есть.
Эмоционально или нет... скажем так :) у меня свое мнение. Я гамна уже насмотрелся в своей жизни оттого что делает братский народ в моей стране и городе.
Если хочется конструктива велкомс: https://www.youtube.com/watch?v=wXH3fUN0PsM
Сегодня будем с другом стримить и паралельно шутить о русском мире а Харькове, колумсторах и немного поговорим о жизни :)
Утки, Таиланд и T-SQL… или что может подстерегать программистов при работе с SQL Server?