Предполагаемое количество строк не соответствует плану выполнения

#sql #sql-server #tsql #query-performance

#sql #sql-сервер #tsql #запрос-производительность

Вопрос:

У меня ситуация, когда предполагаемое количество строк в плане выполнения отклонено

введите описание изображения здесь

Мои столбцы в соединении varchar(50) . Я пробовал разные индексы, но это не уменьшает эту проблему. Я даже пробовал использовать индекс в таблице temp. Что еще я могу сделать?

PS это первое место, где расчетное число начинает смещаться… Также таблицы невелики (48000 строк).

Код:

 SELECT DISTINCT householdnumber, householdid, primaryCustomerID
INTO #Households
FROM TableA

    
SELECT 
A.*,
MIN(B.[ProfileCreatedDate]) PROFILECREATEDDATE 
INTO #Profile
from #Households AS a 
LEFT JOIN TableA AS B
    
ON A.[HouseholdNumber]= B.[HouseholdNumber] and A.[HouseholdId]=B.[HouseholdId]
GROUP BY a.householdnumber, a.householdid, a.primaryCustomerID;
  

Я знаю, кажется, что это можно переписать как:

 SELECT householdnumber, householdid, primaryCustomerID, MIN([ProfileCreatedDate]) AS PROFILECREATEDDATE 
INTO #Profile2
from TableA
GROUP BY householdnumber, householdid, primaryCustomerID;
  

Но результаты не идентичны, и я не хочу изменять результаты, поскольку я не уверен, знал ли создатель этого кода, что они делали.

Некоторая статистика по столбцам: householdnumber всегда равно householdid . householdid есть nvarchar(50) , но householdnumber есть varchar(40) . В таблице 48877 строк. Отдельная комбинация householdnumber, householdid, primaryCustomerID имеет 48029 строк. И отдельное число primaryCustomerID равно 47152.

Комментарии:

1. Не понимая всего, что происходит, я не уверен, что это полезно, но: знаете ли вы, что можете запускаться UPDATE STATISTICS #Households как часть вашего скрипта? Или, возможно, лучше, добавить OPTION (RECOMPILE) в свой запрос (после GROUP BY строки).

2. @EdmCoff: Спасибо, я постараюсь UPDATE STATISTICS . На самом деле я пытался перекомпилировать, но это тоже не помогло. Я дам вам знать, если это сработает. Но таблица только что создана? Мне нужно больше узнать о статистике…

3. @EdmCoff: обновление статистики не помогло…

4. Пожалуйста, смотрите раздел Вставка плана , чтобы узнать, как включить план выполнения в ваш вопрос.

5. @HABO: Я бы с удовольствием, но я немного параноик, чтобы публиковать что-то конфиденциальное…

Ответ №1:

Что касается кода — похоже, что разница между более крупной (исходной) версией и вашей более простой группой ПО версии заключается в том, что оригинал находит минимум profilecreateddate для любого в этом домохозяйстве, тогда как ваша более простая версия находит profilecreateddate для конкретного primarycustomerid .

Например (с использованием более простых данных)

 CREATE TABLE #TableA (householdnumber int, householdid int, primaryCustomerID int, ProfileCreatedDate datetime);
INSERT INTO #TableA (householdnumber, householdid, primaryCustomerID, ProfileCreatedDate) VALUES
(1, 1, 1, '20201001'),
(1, 1, 1, '20201002'),
(1, 1, 2, '20201003');

SELECT DISTINCT householdnumber, householdid, primaryCustomerID
INTO #Households
FROM #TableA;

SELECT 
    A.*,
    MIN(B.[ProfileCreatedDate]) PROFILECREATEDDATE 
INTO #Profile
from #Households AS a 
    LEFT JOIN #TableA AS B  
      ON A.[HouseholdNumber]= B.[HouseholdNumber] and A.[HouseholdId]=B.[HouseholdId]
GROUP BY a.householdnumber, a.householdid, a.primaryCustomerID;

SELECT * FROM #Profile;
/* -- Results
householdnumber  householdid  primaryCustomerID  PROFILECREATEDDATE
1                1            1                  2020-10-01 00:00:00.000
1                1            2                  2020-10-01 00:00:00.000
*/

SELECT householdnumber, householdid, primaryCustomerID, MIN([ProfileCreatedDate]) AS PROFILECREATEDDATE 
INTO #Profile2
from #TableA
GROUP BY householdnumber, householdid, primaryCustomerID;

SELECT * FROM #Profile2;
/* -- Results
householdnumber  householdid  primaryCustomerID  PROFILECREATEDDATE
1                1            1                  2020-10-01 00:00:00.000
1                1            2                  2020-10-03 00:00:00.000
*/
  

Если вы заметили выше, PROFILECREATEDATE для строки 2 отличается.

Поэтому вы можете попробовать следующий код, который должен давать те же результаты, что и исходный набор — посмотрите, как это работает со временем (и подтвердите, что оно соответствует исходным результатам).

 SELECT DISTINCT t1.householdnumber, t1.householdid, primaryCustomerID, 
        MIN([ProfileCreatedDate]) OVER (PARTITION BY t1.householdnumber, t1.householdid) AS PROFILECREATEDDATE 
INTO #Profile3
FROM #TableA t1;

SELECT * FROM #Profile3;
/* -- Results
householdnumber  householdid  primaryCustomerID  PROFILECREATEDDATE
1                1            1                  2020-10-01 00:00:00.000
1                1            2                  2020-10-01 00:00:00.000
*/
  

Комментарии:

1. Вы правы. Спасибо! результаты идентичны с использованием (SELECT * FROM #Profile EXCEPT SELECT * FROM #Profile2) UNION all (SELECT * FROM #Profile2 EXCEPT SELECT * FROM #Profile)

2. Теперь это очень быстро. Иногда это занимало около 10 минут, прежде чем оно заканчивалось за 2 секунды. Но почему оценки до сих пор в исходном коде?

3. 1 за добавление опции функции window, которая должна решить исходную проблему OPs, а также помогает обучить тому, как использовать SQL Server в соответствии с его сильными сторонами

4. Что касается оценок — часто бывает нелегко понять, что происходит, но помните, что оценки основаны только на небольшом количестве метаданных , без фактического просмотра данных. Часто группировка сильно искажает оценки, потому что нужно просто угадать, сколько получится (например, если группировка, давайте предположим, что результат будет составлять 20% от размера). Я не знаю на 100% (не глядя на план выполнения в целом), почему это так далеко. Решение, которое у меня было там: а) минимизирует чтение (только один раз считывает таблицу), а затем просто группирует в конце, чтобы минимизировать группировку, вызывающую многократные ошибки.

5. Хорошо, спасибо. На самом деле метод оконной функции также делает более понятным содержание кода, в то время как исходный код трудно понять. В любом случае спасибо.