Les nouvelles fonctions T-SQL de SQL Server 2012 – Episode 2/8 – FORMAT()

A l’épisode précédent, nous avions abordé les possibilités offertes par la nouvelle fonction PARSE().

Poursuivons avec FORMAT(), qui est une extension de CONVERT(), puisqu’elle est plus ouverte aux différentes cultures … et beaucoup plus !

Tout d’abord, la syntaxe :

FORMAT (valeur, format [, culture ])

Comme avec PARSE() :

– la nouveauté c’est qu’on peut formater une valeur suivant une culture (33 cultures disponibles !)
– les types supportés par cette fonction : bigint, int, smallint, tinyint, decimal, numeric, float, real, smallmoney, money, date, time, datetime, smalldatetime, datetime2 et datetimeoffset.
– on ne peut pas utiliser PARSE() pour exécuter une requête sur un serveur distant, car celle-ci dépend du .NET Framework 3.5 SP1 , et que rien ne garantit qu’il est présent sur la machine distante.

On remarquera que les types de données de chaînes de caractère, comme pour PARSE(), ne sont pas supportés.
Dès lors, on ne peut pas s’attendre à pouvoir passer une date au format ISO dans une chaîne de caractères, et que cela fonctionne :

=> Formatage des dates

Commençons avec quelques exemples sur les dates, qui sont généralement assez recherchés.
Voyons sans la culture d’abord :

1
2
3
4
5
6
7
DECLARE @dt date = '20120102'
 
SELECT  FORMAT(@dt, N'dddd, MMM dd, yyyy') AS anglo_saxon_date_format_0
  , FORMAT(@dt, N'dddd dd MMMM yyyy') AS anglo_saxon_date_format_1
  , FORMAT(@dt, N'dd-MM-yyyy') AS French_numeric_format
  , FORMAT(@dt, N'yyyyMMdd') AS ISO_format
  , FORMAT(@dt, N'yyyyMMdd HH:mm:ss.000') AS ISO_format_with_time

Et avec la culture :

On peut maintenant apprendre les bases d’une langue tout en codant :)

En revanche le formatage composite n’est pas directement supporté :

1
2
3
DECLARE @dt date = '20120102'
 
SELECT  FORMAT(@dt, N'Nous sommes le dddd dd MMMM yyyy', 'fr-FR')

Attention donc, car les lettres qui correspondent à des parties de date sont automatiquement remplacées, sans qu’une erreur ne soit levée.

Essayons d’éviter ces lettres :

1
2
3
DECLARE @dt date = '20120102'
 
SELECT  FORMAT(@dt, N'Now : dddd dd MMMM yyyy', 'en-US')

Mais il s’agit là d’un jeu dangereux.
Voyons si on peut les échapper. Est-ce que le classique anti-slash va fonctionner ?

1
2
3
DECLARE @dt date = '20120102'
 
SELECT  FORMAT(@dt, N'Nou\s \so\m\me\s le dddd dd MMMM yyyy', 'fr-FR')

Il faut reconnaître que c’est assez tortueux pour ne pas dire spaghetti.
Par extension, on pourra créer une fonction en T-SQL qui effectue automatiquement l’échappement de ces caractères …

=> Examinons maintenant les formats raccourcis :

1
2
3
4
5
6
7
8
9
10
DECLARE @dt date = '20120102'
 
SELECT  FORMAT(@dt, 'D', 'fr-FR') AS weekday_litteral_FR
  , FORMAT(@dt, 'D', 'en-US') AS weekday_litteral_US
  , FORMAT(@dt, 'd', 'fr-FR') AS decimal_date_FR
  , FORMAT(@dt, 'd', 'en-US') AS decimal_date_US
  , FORMAT(@dt, 'm', 'fr-FR') AS month_date_litteral_FR
  , FORMAT(@dt, 'm', 'en-US') AS month_date_litteral_US
  , FORMAT(@dt, 'M', 'fr-FR') AS month_date_litteral_FR_case
  , FORMAT(@dt, 'M', 'en-US') AS month_date_litteral_US_case

Attention donc à la casse dans les formats !
On notera que le zéro non-significatif disparaît au format Français, mais demeure au format Anglo-saxon.

J’ai trouvé ceux-ci à tâtons. Voyons les dans leur ensemble :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
DECLARE @now datetime = GETDATE()
  , @amount decimal(6,2) = 1234.56
  , @integer int = 123456
  , @percent decimal(3,2) = 0.76
  , @fr char(5) = 'fr-FR'
  , @us char(5) = 'en-US'
 
SELECT    'Currency 1' AS label, @fr AS culture, 'FORMAT(@amount, ''c'', @fr)' AS sql, FORMAT(@amount, 'c', @fr) AS exemple
UNION ALL  SELECT 'Currency 2' AS label, @us AS culture, 'FORMAT(@amount, ''c'', @us)', FORMAT(@amount, 'c', @us) AS exemple
--
UNION ALL  SELECT 'Currency 3', @fr, 'FORMAT(@amount, ''C'', @fr)', FORMAT(@amount, 'C', @fr)
UNION ALL  SELECT 'Currency 4', @us, 'FORMAT(@amount, ''C'', @us)', FORMAT(@amount, 'C', @us)
--
UNION ALL  SELECT 'Decimal 1', @fr, 'FORMAT(@integer, ''d'', @fr)', FORMAT(@integer, 'd', @fr)
UNION ALL  SELECT 'Decimal 2', @us, 'FORMAT(@integer, ''d'', @us)', FORMAT(@integer, 'd', @us)
--
UNION ALL  SELECT 'Decimal 3', @fr, 'FORMAT(@integer, ''D'', @fr)', FORMAT(@integer, 'D', @fr)
UNION ALL  SELECT 'Decimal 4', @us, 'FORMAT(@integer, ''D'', @us)', FORMAT(@integer, 'D', @us)
--
UNION ALL  SELECT 'Decimal 5', @fr, 'FORMAT(@integer, ''D8'', @fr)', FORMAT(@integer, 'D8', @fr)
UNION ALL  SELECT 'Decimal 6', @us, 'FORMAT(@integer, ''D8'', @us)', FORMAT(@integer, 'D8', @us)
--
UNION ALL  SELECT 'Full date time, short time 1', @fr, 'FORMAT(@now, ''f'', @fr)', FORMAT(@now, 'f', @fr)
UNION ALL  SELECT 'Full date time, short time 2', @us, 'FORMAT(@now, ''f'', @us)', FORMAT(@now, 'f', @us)
--
UNION ALL  SELECT 'Full date time, long time 1', @fr, 'FORMAT(@now, ''F'', @fr)', FORMAT(@now, 'F', @fr)
UNION ALL  SELECT 'Full date time, long time 2', @us, 'FORMAT(@now, ''F'', @us)', FORMAT(@now, 'F', @us)
--
UNION ALL  SELECT 'General date time, short time 1', @fr, 'FORMAT(@now, ''g'', @fr)', FORMAT(@now, 'g', @fr)
UNION ALL  SELECT 'General date time, short time 2', @us, 'FORMAT(@now, ''g'', @us)', FORMAT(@now, 'g', @us)
--
UNION ALL  SELECT 'General date time, long time 1', @fr, 'FORMAT(@now, ''G'', @fr)', FORMAT(@now, 'G', @fr)
UNION ALL  SELECT 'General date time, long time 2', @us, 'FORMAT(@now, ''G'', @us)', FORMAT(@now, 'G', @us)
--
UNION ALL  SELECT 'Short time 1', @fr, 'FORMAT(@now, ''t'', @fr)', FORMAT(@now, 't', @fr)
UNION ALL  SELECT 'Short time 2', @us, 'FORMAT(@now, ''t'', @us)', FORMAT(@now, 't', @us)
--
UNION ALL  SELECT 'Long time 1', @fr, 'FORMAT(@now, ''T'', @fr)', FORMAT(@now, 'T', @fr)
UNION ALL  SELECT 'Long time 2', @us, 'FORMAT(@now, ''T'', @us)', FORMAT(@now, 'T', @us)
--
UNION ALL  SELECT 'Numeric 1', @fr, 'FORMAT(@amount, ''n'', @fr)', FORMAT(@amount, 'n', @fr)
UNION ALL  SELECT 'Numeric 2', @us, 'FORMAT(@amount, ''n'', @us)', FORMAT(@amount, 'n', @us)
--
UNION ALL  SELECT 'Numeric 3', @fr, 'FORMAT(@amount, ''N'', @fr)', FORMAT(@amount, 'N', @fr)
UNION ALL  SELECT 'Numeric 4', @us, 'FORMAT(@amount, ''N'', @us)', FORMAT(@amount, 'N', @us)
--
UNION ALL  SELECT 'Numeric 5', @fr, 'FORMAT(@amount, ''N0'', @fr)', FORMAT(@amount, 'N0', @fr)
UNION ALL  SELECT 'Numeric 6', @us, 'FORMAT(@amount, ''N0'', @us)', FORMAT(@amount, 'N0', @us)
--
UNION ALL  SELECT 'Numeric 7', @fr, 'FORMAT(@amount, ''N1'', @fr)', FORMAT(@amount, 'N1', @fr)
UNION ALL  SELECT 'Numeric 8', @us, 'FORMAT(@amount, ''N1'', @us)', FORMAT(@amount, 'N1', @us)
--
UNION ALL  SELECT 'Percentage 1', @fr, 'FORMAT(@percent, ''p'', @fr)', FORMAT(@percent, 'p', @fr)
UNION ALL  SELECT 'Percentage 2', @us, 'FORMAT(@percent, ''p'', @us)', FORMAT(@percent, 'p', @us)
--
UNION ALL  SELECT 'Percentage 3', @fr, 'FORMAT(@percent, ''P0'', @fr)', FORMAT(@percent, 'P0', @fr)
UNION ALL  SELECT 'Percentage 4', @us, 'FORMAT(@percent, ''P0'', @us)', FORMAT(@percent, 'P0', @us)
--
UNION ALL  SELECT 'Percentage 5', @fr, 'FORMAT(@percent, ''P1'', @fr)', FORMAT(@percent, 'P1', @fr)
UNION ALL  SELECT 'Percentage 6', @us, 'FORMAT(@percent, ''P1'', @us)', FORMAT(@percent, 'P1', @us)
--
UNION ALL  SELECT 'Hexadecimal 1', @fr, 'FORMAT(@integer, ''x'', @fr)', FORMAT(@integer, 'x', @fr)
UNION ALL  SELECT 'Hexadecimal 2', @us, 'FORMAT(@integer, ''x'', @us)', FORMAT(@integer, 'x', @us)
--
UNION ALL  SELECT 'Hexadecimal 3', @fr, 'FORMAT(@integer, ''X'', @fr)', FORMAT(@integer, 'X', @fr)
UNION ALL  SELECT 'Hexadecimal 4', @us, 'FORMAT(@integer, ''X'', @us)', FORMAT(@integer, 'X', @us)
--
UNION ALL  SELECT 'Hexadecimal 5', @fr, 'FORMAT(@integer, ''X10'', @fr)', FORMAT(@integer, 'X10', @fr)
UNION ALL  SELECT 'Hexadecimal 6', @us, 'FORMAT(@integer, ''X10'', @us)', FORMAT(@integer, 'X10', @us)

On voit donc que pour le formatage de données :

– numériques, la casse du format court n’a pas d’importance
– numériques, on peut ajouter un chiffre au format court qui indique soit le nombre de décimales, soit la largeur de la mantisse
– numériques de devise, la culture change bien évidemment le résultat
– de date, la casse est importante, de même que la culture

=> Étudions les devises plus en détails :

1
2
3
4
5
6
7
SELECT  FORMAT(1234.56, 'C', 'en-US') AS US_format
  , FORMAT(1234.56, 'C', 'fr-FR') AS French_format
  , FORMAT(1234.56, 'C', 'de-DE') AS German_format
  , FORMAT(1234.56, 'C', 'en-GB') AS UK_format
  , FORMAT(1234.56, 'C', 'sv-SE') AS Swedish_format
  , FORMAT(1234.56, 'C', 'th-TH') AS Thai_format
  , FORMAT(1234.56, 'C', 'ar-SA') AS Arabic_format

On voit que les formats de devises sont rigoureusement respectés, notamment en ce qui concerne les points et les virgules : les anglo-saxons utilisent la virgule comme séparateur des puissances de 10 multiples de 3, alors que les Latins utilisent le point; et inversement pour les parties décimales.
De même la prise en compte des pays membres de la Zone Euro ont tous leur devise préfixée ou suffixée par le symbole €, alors que le Royaume-Uni et la Suède, qui n’en sont pas membres, ont bien évidemment conservé respectivement les symboles de la Livre Sterling et de la Couronne Suédoise.

Une petite remarque : en Arabe, la notation retournée par SQL Server n’est strictement identique à ce que l’on observe rien qu’en collant cette valeur de fenêtre de résultats dans la fenêtre de requêtes de SQL Server Management Studio :

J’ai essayé de faire traduire automatiquement par Google Translate, mais le résultat retourné est R.. Q. 1,234.56.
Si vous pouvez expliquer ce qui s’est passé, n’hésitez pas à poster un commentaire !

=> Les masques pour les valeurs numériques

Les masques permettent de spécifier le nombre de décimales pour une valeur numérique, et si l’on en a besoin, d’ajouter des symboles dans la valeur formatée à l’aide de la position :

1
2
3
4
5
6
7
8
9
10
11
12
13
DECLARE @uneValeur decimal(6,2) = 1234.56
 
SELECT  FORMAT(@uneValeur, '##,##0.00') AS exemple_1
  , FORMAT(@uneValeur, '#,##0.00') AS exemple_2
  , FORMAT(@uneValeur, '#,#0.00') AS exemple_3
  , FORMAT(@uneValeur, '#,#0.0') AS exemple_4
  , FORMAT(@uneValeur, '#,#0') AS exemple_5
  , FORMAT(@uneValeur, '#0') AS exemple_6
  , FORMAT(@uneValeur, '#0.0') AS exemple_7
  , FORMAT(@uneValeur, '#0.00') AS exemple_8
  , FORMAT(@uneValeur, '#0.000') AS exemple_9
  , FORMAT(@uneValeur, '#|#0.000') AS exemple_10
  , FORMAT(@uneValeur, '#\#0.000') AS exemple_11

Dans les colonnes exemple_2 et exemple_10, on a respectivement placé :

– une virgule avant les trois derniers chiffres de la partie entière de la valeur décimale
– un pipe avant les deux derniers chiffres de la partie entière de la valeur décimale

Dans la colonne exemple_11, l’antislash étant un caractère d’échappement, il y a donc un symbole dièse qui sera intercalé entre le dernier et l’avant-dernier chiffre de la partie entière de la valeur décimale.

De la même façon que ce que nous avons vu avec les formats courts, on peut aussi préciser le nombre de chiffres significatifs de la partie décimale d’une telle valeur.
Si le nombre de chiffres significatifs de la partie décimale :

– est plus petit que celui de la valeur passée en paramètre, alors la valeur formatée est arrondie
– est plus grand que celui de la valeur passée en paramètre, alors la valeur formatée est suivie d’autant de zéros que la différence entre le nombre de chiffres significatifs de la partie décimale et le nombre de chiffres de la partie décimale du masque de formatage.

Bon FORMATage de données à tous !

Laisser un commentaire