Vérifier la validité d’un SIRET – Algorithme de Luhn

Lors d’un chargement massif de données et/ou d’une migration, il est possible que la qualité des données soient altérées. Il faut alors tester leur qualité et valider chaque écart. Parmi ceux-ci, on peut trouver les numéro SIRET, qui permet l’identification d’un établissement ou d’une entreprise Française.

Cet identifiant numérique est constitué de 14 chiffres, et est articulé en trois parties :

  • le SIREN
  • le Numéro Interne de Classement, à quatre chiffres : c’est l’ordre de création de l’établissement par l’entreprise Française
  • une clé de contrôle, constituée d’un seul chiffre

Ce dernier est calculé à l’aide de l’algorithme de Luhn. Cet algorithme est aussi utilisé pour valider les numéros de carte bleue, et de nombreux identifiants distribués par les administrations de beaucoup de pays. Nous allons nous en servir pour vérifier la validité de SIRETs en l’implémentant spécifiquement pour ce type d’identifiant.

L’algorithme est descriptible comme suit :

  1. Pour chaque chiffre dont l’index dans la chaîne est impair, on le multiplie par 2; si le résultat est supérieur à 9, on additionne les chiffes qui constituent ce résultat. Ceci revient à multiplier par 2 le chiffre, diviser le résultat par 10, puis ajouter la multiplication par 2 de ce même chiffre modulo 10.
  2. On additionne ensuite les chiffres du résultat un à un, avec les chiffres de l’identifiant qui n’ont pas été traités, c’est à dire ceux dont l’index dans la chaîne est pair. Ceci inclut le chiffre de l’identifiant qui est clé.
  3. Si le résultat obtenu est un multiple de 10, alors l’identifiant est valide. Ceci revient à vérifier que le résultat modulo 10 vaut zéro.

La traduction en SQL donne :

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
CASE
        WHEN laColonneSIRET IS NULL THEN NULL
        WHEN LEN(laColonneSIRET) < 14 THEN CAST(0 AS bit)
        WHEN laColonneSIRET LIKE'%[^0-9]%' THEN CAST(0 AS bit)
        WHEN (
                        2 * CAST(SUBSTRING(laColonneSIRET, 1, 1) AS tinyint) / 10
                        + 2 * CAST(SUBSTRING(laColonneSIRET, 1, 1) AS tinyint) % 10
                        + 2 * CAST(SUBSTRING(laColonneSIRET, 3, 1) AS tinyint) / 10
                        + 2 * CAST(SUBSTRING(laColonneSIRET, 3, 1) AS tinyint) % 10
                        + 2 * CAST(SUBSTRING(laColonneSIRET, 5, 1) AS tinyint) / 10
                        + 2 * CAST(SUBSTRING(laColonneSIRET, 5, 1) AS tinyint) % 10
                        + 2 * CAST(SUBSTRING(laColonneSIRET, 7, 1) AS tinyint) / 10
                        + 2 * CAST(SUBSTRING(laColonneSIRET, 7, 1) AS tinyint) % 10
                        + 2 * CAST(SUBSTRING(laColonneSIRET, 9, 1) AS tinyint) / 10
                        + 2 * CAST(SUBSTRING(laColonneSIRET, 9, 1) AS tinyint) % 10
                        + 2 * CAST(SUBSTRING(laColonneSIRET, 11, 1) AS tinyint) / 10
                        + 2 * CAST(SUBSTRING(laColonneSIRET, 11, 1) AS tinyint) % 10
                        + 2 * CAST(SUBSTRING(laColonneSIRET, 13, 1) AS tinyint) / 10
                        + 2 * CAST(SUBSTRING(laColonneSIRET, 13, 1) AS tinyint) % 10
                        ---
                        + CAST(SUBSTRING(laColonneSIRET, 2, 1) AS tinyint)
                        + CAST(SUBSTRING(laColonneSIRET, 4, 1) AS tinyint)
                        + CAST(SUBSTRING(laColonneSIRET, 6, 1) AS tinyint)
                        + CAST(SUBSTRING(laColonneSIRET, 8, 1) AS tinyint)
                        + CAST(SUBSTRING(laColonneSIRET, 10, 1) AS tinyint)
                        + CAST(SUBSTRING(laColonneSIRET, 12, 1) AS tinyint)
                        + CAST(SUBSTRING(laColonneSIRET, 14, 1) AS tinyint)
                ) % 10 = 0 THEN CAST(1 AS bit) ELSE CAST(0 AS bit)
END AS is_valid_SIRET

On peut l'utiliser directement dans une vue, comme dans le pseudo code suivant :

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
CREATE VIEW maVue
AS
SELECT  desColonnes
        , CASE
                WHEN laColonneSIRET IS NULL THEN NULL
                WHEN LEN(laColonneSIRET) < 14 THEN CAST(0 AS bit)
                WHEN laColonneSIRET LIKE'%[^0-9]%' THEN CAST(0 AS bit)
                WHEN (
                                2 * CAST(SUBSTRING(laColonneSIRET, 1, 1) AS tinyint) / 10
                                + 2 * CAST(SUBSTRING(laColonneSIRET, 1, 1) AS tinyint) % 10
                                + 2 * CAST(SUBSTRING(laColonneSIRET, 3, 1) AS tinyint) / 10
                                + 2 * CAST(SUBSTRING(laColonneSIRET, 3, 1) AS tinyint) % 10
                                + 2 * CAST(SUBSTRING(laColonneSIRET, 5, 1) AS tinyint) / 10
                                + 2 * CAST(SUBSTRING(laColonneSIRET, 5, 1) AS tinyint) % 10
                                + 2 * CAST(SUBSTRING(laColonneSIRET, 7, 1) AS tinyint) / 10
                                + 2 * CAST(SUBSTRING(laColonneSIRET, 7, 1) AS tinyint) % 10
                                + 2 * CAST(SUBSTRING(laColonneSIRET, 9, 1) AS tinyint) / 10
                                + 2 * CAST(SUBSTRING(laColonneSIRET, 9, 1) AS tinyint) % 10
                                + 2 * CAST(SUBSTRING(laColonneSIRET, 11, 1) AS tinyint) / 10
                                + 2 * CAST(SUBSTRING(laColonneSIRET, 11, 1) AS tinyint) % 10
                                + 2 * CAST(SUBSTRING(laColonneSIRET, 13, 1) AS tinyint) / 10
                                + 2 * CAST(SUBSTRING(laColonneSIRET, 13, 1) AS tinyint) % 10
                                ---
                                + CAST(SUBSTRING(laColonneSIRET, 2, 1) AS tinyint)
                                + CAST(SUBSTRING(laColonneSIRET, 4, 1) AS tinyint)
                                + CAST(SUBSTRING(laColonneSIRET, 6, 1) AS tinyint)
                                + CAST(SUBSTRING(laColonneSIRET, 8, 1) AS tinyint)
                                + CAST(SUBSTRING(laColonneSIRET, 10, 1) AS tinyint)
                                + CAST(SUBSTRING(laColonneSIRET, 12, 1) AS tinyint)
                                + CAST(SUBSTRING(laColonneSIRET, 14, 1) AS tinyint)
                        ) % 10 = 0 THEN CAST(1 AS bit) ELSE CAST(0 AS bit)
        END AS is_valid_SIRET
FROM    dbo.maTable

Si l'on souhaite l'utiliser en colonne calculée, on peut utiliser ce CASE directement dans la définition de ladite colonne, ou bien définir une fonction scalaire comme suit :

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
CREATE FUNCTION fn_scl_is_valid_SIRET
        (
                 @_siret char(14)
        )
RETURNS bit
WITH SCHEMABINDING
AS
BEGIN
        DECLARE @ok bit

        SELECT  @ok = CASE
                WHEN @_siret IS NULL THEN NULL
                WHEN LEN(@_siret) < 14 THEN CAST(0 AS bit)
                WHEN @_siret LIKE'%[^0-9]%' THEN CAST(0 AS bit)
                WHEN (
                                2 * CAST(SUBSTRING(@_siret, 1, 1) AS tinyint) / 10
                                + 2 * CAST(SUBSTRING(@_siret, 1, 1) AS tinyint) % 10
                                + 2 * CAST(SUBSTRING(@_siret, 3, 1) AS tinyint) / 10
                                + 2 * CAST(SUBSTRING(@_siret, 3, 1) AS tinyint) % 10
                                + 2 * CAST(SUBSTRING(@_siret, 5, 1) AS tinyint) / 10
                                + 2 * CAST(SUBSTRING(@_siret, 5, 1) AS tinyint) % 10
                                + 2 * CAST(SUBSTRING(@_siret, 7, 1) AS tinyint) / 10
                                + 2 * CAST(SUBSTRING(@_siret, 7, 1) AS tinyint) % 10
                                + 2 * CAST(SUBSTRING(@_siret, 9, 1) AS tinyint) / 10
                                + 2 * CAST(SUBSTRING(@_siret, 9, 1) AS tinyint) % 10
                                + 2 * CAST(SUBSTRING(@_siret, 11, 1) AS tinyint) / 10
                                + 2 * CAST(SUBSTRING(@_siret, 11, 1) AS tinyint) % 10
                                + 2 * CAST(SUBSTRING(@_siret, 13, 1) AS tinyint) / 10
                                + 2 * CAST(SUBSTRING(@_siret, 13, 1) AS tinyint) % 10
                                ---
                                + CAST(SUBSTRING(@_siret, 2, 1) AS tinyint)
                                + CAST(SUBSTRING(@_siret, 4, 1) AS tinyint)
                                + CAST(SUBSTRING(@_siret, 6, 1) AS tinyint)
                                + CAST(SUBSTRING(@_siret, 8, 1) AS tinyint)
                                + CAST(SUBSTRING(@_siret, 10, 1) AS tinyint)
                                + CAST(SUBSTRING(@_siret, 12, 1) AS tinyint)
                                + CAST(SUBSTRING(@_siret, 14, 1) AS tinyint)
                        ) % 10 = 0 THEN CAST(1 AS bit) ELSE CAST(0 AS bit)
        END

        RETURN @ok
END

Et en ce qui concerne la création de la colonne calculée, on peut l'ajouter à l'aide du pseudo-T-SQL suivant :

1
2
ALTER TABLE maTable
ADD is_valid_SIRET AS(dbo.fn_scl_is_valid_SIRET(laColonneSIRET))

Enfin, si l'on souhaite exécuter cette vérification au sein d'une requête, on peut s'en remettre à une fonction de table en ligne. Ce type de fonction n'étant appelé qu'une seule fois, il s'avérera plus performant à l'exécution sur un grand nombre de lignes que l'appel à une fonction scalaire : ces dernières sont appelées autant de fois qu'il y a de lignes de résultat. Ce qui donne :

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
CREATE FUNCTION fn_it_is_valid_SIRET
        (
                @siret char(14)
        )
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN 
(
        SELECT  CASE
                        WHEN LEN(COALESCE(@siret,'')) < 14 THEN CAST(0 AS bit)
                        WHEN @siret LIKE'%[^0-9]%' THEN CAST(0 AS bit)
                        WHEN @siret IS NULL THEN CAST(0 AS bit)
                        WHEN (
                                        2 * CAST(SUBSTRING(@siret, 1, 1) AS tinyint) / 10
                                        + 2 * CAST(SUBSTRING(@siret, 1, 1) AS tinyint) % 10
                                        + 2 * CAST(SUBSTRING(@siret, 3, 1) AS tinyint) / 10
                                        + 2 * CAST(SUBSTRING(@siret, 3, 1) AS tinyint) % 10
                                        + 2 * CAST(SUBSTRING(@siret, 5, 1) AS tinyint) / 10
                                        + 2 * CAST(SUBSTRING(@siret, 5, 1) AS tinyint) % 10
                                        + 2 * CAST(SUBSTRING(@siret, 7, 1) AS tinyint) / 10
                                        + 2 * CAST(SUBSTRING(@siret, 7, 1) AS tinyint) % 10
                                        + 2 * CAST(SUBSTRING(@siret, 9, 1) AS tinyint) / 10
                                        + 2 * CAST(SUBSTRING(@siret, 9, 1) AS tinyint) % 10
                                        + 2 * CAST(SUBSTRING(@siret, 11, 1) AS tinyint) / 10
                                        + 2 * CAST(SUBSTRING(@siret, 11, 1) AS tinyint) % 10
                                        + 2 * CAST(SUBSTRING(@siret, 13, 1) AS tinyint) / 10
                                        + 2 * CAST(SUBSTRING(@siret, 13, 1) AS tinyint) % 10
                                        ---
                                        + CAST(SUBSTRING(@siret, 2, 1) AS tinyint)
                                        + CAST(SUBSTRING(@siret, 4, 1) AS tinyint)
                                        + CAST(SUBSTRING(@siret, 6, 1) AS tinyint)
                                        + CAST(SUBSTRING(@siret, 8, 1) AS tinyint)
                                        + CAST(SUBSTRING(@siret, 10, 1) AS tinyint)
                                        + CAST(SUBSTRING(@siret, 12, 1) AS tinyint)
                                        + CAST(SUBSTRING(@siret, 14, 1) AS tinyint)
                                ) % 10 = 0 THEN CAST(1 AS bit) ELSE CAST(0 AS bit)
                END AS is_valid_SIRET
)

On peut alors l'utiliser à l'aide de l'opérateur APPLY, comme dans l'exemple suivant :

1
2
3
4
SELECT          S.*
                , F.is_valid_SIRET
FROM            dbo.maTableAvecUneColonneSIRET AS S
CROSS APPLY     dbo.fn_it_is_valid_SIRET(S.laColonneSIRET) AS F

Bonne validation de SIRETs !

Laisser un commentaire