Database
 sql >> Base de données >  >> RDS >> Database

FORMAT() est agréable et tout, mais…

À l'époque où SQL Server 2012 était encore en version bêta, j'ai blogué sur le nouveau FORMAT() fonction :SQL Server v.Next (Denali) :Améliorations CTP3 T-SQL :FORMAT().

À ce moment-là, j'étais tellement enthousiasmé par la nouvelle fonctionnalité que je n'ai même pas pensé à faire des tests de performance. J'en ai parlé dans un article de blog plus récent, mais uniquement dans le contexte de la suppression de l'heure d'une date/heure :Couper l'heure d'une date/heure - un suivi.

La semaine dernière, mon bon ami Jason Horner (blog | @jasonhorner) m'a trollé avec ces tweets :

Mon problème avec ceci est juste que FORMAT() semble pratique, mais il est extrêmement inefficace par rapport à d'autres approches (oh et que AS VARCHAR ça va mal aussi). Si vous faites ce onesy-twosy et pour de petits ensembles de résultats, je ne m'en soucierais pas trop; mais à grande échelle, cela peut devenir assez coûteux. Permettez-moi d'illustrer par un exemple. Commençons par créer un petit tableau avec 1000 dates pseudo-aléatoires :

SELECT TOP (1000) d = DATEADD(DAY, CHECKSUM(NEWID())%1000, o.create_date)
  INTO dbo.dtTest
  FROM sys.all_objects AS o
  ORDER BY NEWID();
GO
CREATE CLUSTERED INDEX d ON dbo.dtTest(d);

Maintenant, alimentons le cache avec les données de ce tableau et illustrons trois des façons courantes dont les gens ont tendance à présenter juste l'heure :

SELECT d, 
  CONVERT(DATE, d), 
  CONVERT(CHAR(10), d, 120),
  FORMAT(d, 'yyyy-MM-dd')
FROM dbo.dtTest;

Maintenant, effectuons des requêtes individuelles qui utilisent ces différentes techniques. Nous les exécuterons 5 fois chacun et nous exécuterons les variantes suivantes :

  1. Sélection des 1 000 lignes
  2. Sélectionner TOP (1) classé par la clé d'index cluster
  3. Affectation à une variable (ce qui force une analyse complète, mais empêche le rendu SSMS d'interférer avec les performances)

Voici le script :

-- select all 1,000 rows
GO
SELECT d FROM dbo.dtTest;
GO 5
SELECT d = CONVERT(DATE, d) FROM dbo.dtTest;
GO 5
SELECT d = CONVERT(CHAR(10), d, 120) FROM dbo.dtTest;
GO 5
SELECT d = FORMAT(d, 'yyyy-MM-dd') FROM dbo.dtTest;
GO 5
 
-- select top 1
GO
SELECT TOP (1) d FROM dbo.dtTest ORDER BY d;
GO 5
SELECT TOP (1) CONVERT(DATE, d) FROM dbo.dtTest ORDER BY d;
GO 5
SELECT TOP (1) CONVERT(CHAR(10), d, 120) FROM dbo.dtTest ORDER BY d;
GO 5
SELECT TOP (1) FORMAT(d, 'yyyy-MM-dd') FROM dbo.dtTest ORDER BY d;
GO 5
 
-- force scan but leave SSMS mostly out of it
GO
DECLARE @d DATE;
SELECT @d = d FROM dbo.dtTest;
GO 5
DECLARE @d DATE;
SELECT @d = CONVERT(DATE, d) FROM dbo.dtTest;
GO 5
DECLARE @d CHAR(10);
SELECT @d = CONVERT(CHAR(10), d, 120) FROM dbo.dtTest;
GO 5
DECLARE @d CHAR(10);
SELECT @d = FORMAT(d, 'yyyy-MM-dd') FROM dbo.dtTest;
GO 5

Maintenant, nous pouvons mesurer les performances avec la requête suivante (mon système est assez silencieux ; sur le vôtre, vous devrez peut-être effectuer un filtrage plus avancé que simplement execution_count ):

SELECT 
  [t] = CONVERT(CHAR(255), t.[text]), 
  s.total_elapsed_time, 
  avg_elapsed_time = CONVERT(DECIMAL(12,2),s.total_elapsed_time / 5.0),
  s.total_worker_time, 
  avg_worker_time = CONVERT(DECIMAL(12,2),s.total_worker_time / 5.0),
  s.total_clr_time
FROM sys.dm_exec_query_stats AS s 
CROSS APPLY sys.dm_exec_sql_text(s.[sql_handle]) AS t
WHERE s.execution_count = 5
  AND t.[text] LIKE N'%dbo.dtTest%'
ORDER BY s.last_execution_time;

Les résultats dans mon cas étaient assez cohérents :

Requête (tronquée) Durée (microsecondes)
total_elapsed avg_elapsed total_clr
SÉLECTIONNER 1 000 lignes SELECT d FROM dbo.dtTest ORDER BY d; 1,170 234.00 0
SELECT d = CONVERT(DATE, d) FROM dbo.dtTest ORDER BY d; 2,437 487.40 0
SELECT d = CONVERT(CHAR(10), d, 120) FROM dbo.dtTest ORD ... 151,521 30,304.20 0
SELECT d = FORMAT(d, 'yyyy-MM-dd') FROM dbo.dtTest ORDER ... 240,152 48,030.40 107,258
SELECT TOP (1) SELECT TOP (1) d FROM dbo.dtTest ORDER BY d; 251 50.20 0
SELECT TOP (1) CONVERT(DATE, d) FROM dbo.dtTest ORDER BY ... 440 88.00 0
SELECT TOP (1) CONVERT(CHAR(10), d, 120) FROM dbo.dtTest ... 301 60.20 0
SELECT TOP (1) FORMAT(d, 'yyyy-MM-dd') FROM dbo.dtTest O ... 1,094 218.80 589
Assign variable DECLARE @d DATE; SELECT @d = d FROM dbo.dtTest; 639 127.80 0
DECLARE @d DATE; SELECT @d = CONVERT(DATE, d) FROM dbo.d ... 644 128.80 0
DECLARE @d CHAR(10); SELECT @d = CONVERT(CHAR(10), d, 12 ... 1,972 394.40 0
DECLARE @d CHAR(10); SELECT @d = FORMAT(d, 'yyyy-MM-dd') ... 118,062 23,612.40 98,556

 

And to visualize the avg_elapsed_time résultat (cliquez pour agrandir) :

FORMAT() est clairement le perdant :résultats avg_elapsed_time (microsecondes)

Ce que nous pouvons apprendre de ces résultats (encore):

  1. Tout d'abord, FORMAT() est cher .
  2. FORMAT() peut, certes, offrir plus de flexibilité et donner des méthodes plus intuitives qui sont cohérentes avec celles d'autres langages comme C#. Cependant, en plus de sa surcharge, et tant que CONVERT() les numéros de style sont cryptés et moins exhaustifs, vous devrez peut-être utiliser l'ancienne approche de toute façon, car FORMAT() n'est valide que dans SQL Server 2012 et versions ultérieures.
  3. Même le mode veille CONVERT() La méthode peut être extrêmement coûteuse (mais seulement dans le cas où SSMS devait rendre les résultats - elle gère clairement les chaînes différemment des valeurs de date).
  4. Le simple fait d'extraire la valeur datetime directement de la base de données était toujours plus efficace. Vous devez profiler le temps supplémentaire nécessaire à votre application pour formater la date comme vous le souhaitez au niveau de la présentation - il est fort probable que vous ne vouliez pas du tout que SQL Server s'implique dans le formatage (et en fait, beaucoup diraient que c'est là que cette logique appartient toujours).

Nous ne parlons ici que de microsecondes, mais nous ne parlons également que de 1 000 lignes. Adaptez cela à vos tailles de table réelles, et l'impact du choix de la mauvaise approche de formatage pourrait être dévastateur.

Si vous voulez essayer cette expérience sur votre propre machine, j'ai téléchargé un exemple de script :FormatIsNiceAndAllBut.sql_.zip