#sql #sql-server #performance #tsql #isnull
#sql #sql-сервер #Производительность #tsql #isnull
Вопрос:
У меня есть финансовое приложение. У меня есть ViewHistoricInstrumentValue
, в котором есть такие строки
instrument1, date1, price, grossValue, netValue
instrument2, date1, price, grossValue, netValue
...
instrument1, date2, price, grossValue, netValue
...
Мои представления сложны, но сама база данных невелика (4000 транзакций). ViewHistoricInstrumentValue
был выполнен менее чем за 1 секунду до того, как я добавил следующий CTE в представление. После этого требуется 26 секунд. ActualEvaluationPrice
это цена для instrumentX на сегодняшний день. Если это значение отсутствует в HistoricPrice
таблице, я нахожу предыдущую цену для instrumentX.
, UsedEvaluationPriceCte AS (
SELECT *
, isnull(ActualEvaluationPrice,
(select top 1 HistoricPrice.Price -- PreviousPrice
from HistoricPrice JOIN ValidDate
on HistoricPrice.DateId = ValidDate.Id
and HistoricPrice.InstrumentId = StartingCte.InstrumentId
and ValidDate.[Date] < StartingCte.DateValue
order by ValidDate.[Date]))
as UsedEvaluationPrice
FROM StartingCte
)
Моя проблема в том, что время выполнения увеличилось без необходимости. Прямо сейчас таблица HistoricPrice не имеет пропущенного значения, поэтому ActualEvaluationPrice никогда не имеет значения null, поэтому предыдущая цена никогда не должна определяться.
ViewHistoricInstrumentValue возвращает 1815 строк. Еще одна загадка заключается в том, что первый запрос занимает 26 секунд, а второй — только 2 секунды.
SELECT * FROM [ViewHistoricInstrumentValue]
SELECT top(2000) * FROM [ViewHistoricInstrumentValue]
Приложение
План выполнения: https://www.dropbox.com/s/5st69uhjkpd3b5y/IsNull.sqlplan?dl=0
Тот же план: https://www.brentozar.com/pastetheplan/?id=rk9bK1Wiv
Представление:
ALTER VIEW [dbo].[ViewHistoricInstrumentValue] AS
WITH StartingCte AS (
SELECT
HistoricInstrumentValue.DateId
, ValidDate.Date as DateValue
, TransactionId
, TransactionId AS [Row]
, AccountId
, AccountName
, ViewTransaction.InstrumentId
, ViewTransaction.InstrumentName
, OpeningDate
, OpeningPrice
, Price AS ActualEvaluationPrice
, ClosingDate
, Amount
, isnull(ViewTransaction.FeeValue, 0) as FeeValue
, HistoricInstrumentValue.Id AS Id
FROM ViewBriefHistoricInstrumentValue as HistoricInstrumentValue
JOIN ValidDate on HistoricInstrumentValue.DateId = ValidDate.Id
JOIN ViewTransaction ON ViewTransaction.Id = HistoricInstrumentValue.TransactionId
left JOIN ViewHistoricPrice ON ViewHistoricPrice.DateId = HistoricInstrumentValue.DateId AND
ViewHistoricPrice.InstrumentId = ViewTransaction.InstrumentId
)
, UsedEvaluationPriceCte AS (
SELECT *
, isnull(ActualEvaluationPrice,
(select top 1 HistoricPrice.Price -- PreviousPrice
from HistoricPrice JOIN ValidDate
on HistoricPrice.DateId = ValidDate.Id
and HistoricPrice.InstrumentId = StartingCte.InstrumentId
and ValidDate.[Date] < StartingCte.DateValue
order by ValidDate.[Date]))
as UsedEvaluationPrice
FROM StartingCte
)
, GrossEvaluationValueCte AS (
SELECT *
, Amount * UsedEvaluationPrice AS GrossEvaluationValue
, (UsedEvaluationPrice - OpeningPrice) * Amount AS GrossCapitalGains
FROM UsedEvaluationPriceCte
)
, CapitalGainsTaxCte AS (
SELECT *
, dbo.MyMax(GrossCapitalGains * 0.15, 0) AS CapitalGainsTax
FROM GrossEvaluationValueCte
)
, IsOpenCte AS (
SELECT
DateId
, DateValue
, TransactionId
, [Row]
, AccountId
, AccountName
, InstrumentId
, InstrumentName
, OpeningDate
, OpeningPrice
, ActualEvaluationPrice
, UsedEvaluationPrice
, ClosingDate
, Amount
, GrossEvaluationValue
, GrossCapitalGains
, CapitalGainsTax
, FeeValue
, GrossEvaluationValue - CapitalGainsTax - FeeValue AS NetEvaluationValue
, GrossCapitalGains - CapitalGainsTax - FeeValue AS NetUnrealizedGains
, CASE WHEN ClosingDate IS NULL OR DateValue < ClosingDate
THEN CAST(1 AS BIT)
ELSE CAST(0 AS BIT)
END
AS IsOpen
, convert(NVARCHAR, DateValue, 20) cast([Id] AS NVARCHAR(MAX)) AS Temp
, Id
FROM CapitalGainsTaxCte
)
Select * from IsOpenCte
Комментарии:
1. Пожалуйста, покажите свой план выполнения.
2. Дорогой @DaleK Я добавил план выполнения, но я не знаю, помогает ли это, потому что это слишком сложно (по крайней мере, для меня).
3. Дорогой @DaleK Я также поделился этим планом с брентозаром.
4. Обязательно ли использовать представление? Я бы предложил вместо использования CTE материализовать промежуточные результаты во временной таблице, а затем выбрать из нее. Разделив ваш запрос таким образом, вы получите два более простых плана запросов вместо одного сложного плана.
5. @DaleK Если ViewTransaction помещается во временную таблицу, то время составляет менее 1 секунды. В этом случае план выполнения ViewHistoricInstrumentValue более понятен: brentozar.com/pastetheplan/?id=S1F-gebsw Мне кажется, что если не используется временная таблица, то выполняется много ненужных вычислений.
Ответ №1:
Я понятия не имею, что должен делать ваш запрос. Но этот процесс:
ActualEvaluationPrice — это цена для instrumentX на сегодняшний день. Если это значение отсутствует в таблице исторических цен, я нахожу предыдущую цену для instrumentX.
легко обрабатывается с помощью lag()
:
select vhiv.*
coalesce(vhiv.ActualEvaluationPrice,
lag(vhiv.ActualEvaluationPrice) over (partition by vhiv.InstrumentId order by DateValue)
) as UsedEvaluationPrice
from ViewHistoricInstrumentValue vhiv;
Примечание: Если вам нужно отфильтровать определенные даты путем присоединения к ValidDates
, вы можете включить JOIN
их в запрос. Однако это не является частью инструкции о проблеме.
Комментарии:
1. Привет @Gordon Linoff ! Мне потребовалось некоторое время, но я понял ваше предложение. Это помогло мне сделать еще один шаг вперед. Оказалось, что проблема не в определении предыдущей цены. Если я пропущу эту часть и перечислю только результат первого cte (
StartingCte
), то он будет завершен сразу. Если я сделаю то же самое, но теперь,order by DateId
то потребуется 26 секунд, еслиorder by Id
используется, то это снова 0 секунд. Это очень странно. Я знаю, что это другой вопрос, но у вас есть идеи?