Comment écririez-vous cette requête?
-
20-08-2019 - |
Question
Je cherche à reformuler la requête ci-dessous en une solution plus lisible et modifiable. La première moitié est identique à la seconde, à l'exception de la base de données interrogée (les noms des tables sont identiques).
SELECT
Column 1 AS c1,
...
Column N AS cN
FROM
database1.dbo.Table1
UNION
SELECT
'Some String' as c1,
...
NULL as cN
FROM
database1.dbo.Table2
UNION
SELECT
Column 1 AS c1,
...
Column N AS cN
FROM
database2.dbo.Table1
UNION
SELECT
'Some String' as c1,
...
NULL as cN
FROM
database2.dbo.Table2
Cette requête correspond à la définition de DRY et m'appelle pour me contacter. -écrit, mais je ne sais pas comment!
MODIFIER : nous ne pouvons pas utiliser linq et nous souhaitons des résultats distincts; Je cherche à réduire la taille de la requête à la taille du fichier physique et non aux résultats renvoyés.
MODIFIER : la base de données sur laquelle j'interroge est une base de données ERP propriétaire. Restructurer ce n'est pas une option.
La solution
Ceci est un modèle SQL assez standard. Parfois, il est facile de transférer par inadvertance des principes de code POOP / procédural tels que DRY vers SQL, mais ils ne sont pas nécessairement transférables.
Notez à quel point il est facile de gérer l'ensemble de la conception logique de la requête, par opposition à la recherche dans des sous-modules. Si l'une des sous-expressions comportait une colonne supplémentaire ou des colonnes inversées, elle resterait visible. Il s’agit d’une instruction SQL assez simple à utiliser comme unité d’exécution, où la désagréger l’embrouillerait.
Et lorsque vous déboguez, il est pratique de pouvoir utiliser l'option de mise en surbrillance du texte de l'éditeur pour exercer de manière sélective des parties de l'instruction - une technique qui n'existe pas dans le code de procédure. OTOH, il peut être compliqué d'essayer de tracer tous les éléments s'ils sont dispersés dans des vues, etc. Même les CTE peuvent rendre cela gênant.
Autres conseils
Je vais vous parler un peu ici et dire, en fonction des informations que vous nous avez fournies;
C'est aussi bon que ça va être
Un conseil en matière de performances que je vois immédiatement est d'utiliser UNION ALL
au lieu de UNION
sauf si vous souhaitez intentionnellement des enregistrements distincts. Un simple <=> élimine les doublons, ce qui prend du temps. <=> ne fait pas cela.
Vous pouvez le réécrire avec du SQL dynamique et une boucle mais je pense que le résultat serait pire. S'il y a suffisamment de doublons de code pour justifier l'approche SQL dynamique, alors je suppose que cela pourrait être justifié.
Sinon, avez-vous envisagé de déplacer la logique de la procédure stockée vers quelque chose comme LINQ? Pour beaucoup, ce n’est pas une option, je ne fais que demander.
Un dernier mot: résistez à la tentation de réparer ce qui n’est pas cassé pour le rendre plus propre. Si le nettoyage facilite la maintenance, la vérification, etc., alors allez-y.
Quel est le problème? Trop long? Trop répétitif?
Parfois, vous obtenez un mauvais SQL - vous ne pouvez pas y faire grand chose.
Je ne vois aucun moyen de le nettoyer, sauf si vous souhaitez utiliser des vues séparées, puis les unir ensemble.
Je vote pour des points de vue qui imposent un surcoût presque nul (OK, peut-être un petit coût de compilation mais ce devrait être tout). Ensuite, vos procs deviennent quelque chose de la forme
SELECT * FROM database1.view1
UNION
SELECT * FROM database1.view2
UNION
SELECT * FROM database2.view1
UNION
SELECT * FROM database2.view2
Je ne suis pas sûr de vouloir le condenser davantage, bien que la plupart des plateformes le tolèrent.
Sur le thème SQL dynamique - voici un exemple - vous ne savez pas s’il est meilleur. L'avantage est que vous n'avez qu'à écrire la liste SELECT une fois.
DECLARE @Select1 varchar(1000)
DECLARE @Select2 varchar(1000)
DECLARE @SQL varchar(4000)
SET @Select1 = 'SELECT
Column 1 AS c1,
...
Column N AS cN'
SET @Select2 = 'SELECT
''Some String'' as c1,
...
NULL as cN'
SET @SQL = @Select1 + ' FROM database1.dbo.Table1 '
SET @SQL = @SQL + ' UNION ' + @Select2 + ' FROM database1.dbo.Table2 '
SET @SQL = @SQL + ' UNION ' + @Select1 + ' FROM database2.dbo.Table1 '
SET @SQL = @SQL + ' UNION ' + @Select2 + ' FROM database2.dbo.Table2 '
EXEC @SQL
Si toutes vos procédures ressemblent à ceci, vous avez probablement un problème d'architecture.
Tous vos appels vers table2 ont-ils un seul champ utile? (et à cause de UNION, n’avons-nous qu’une rangée?)
Je souscris totalement à l'idée d'utiliser le code SQL dynamique paramétré et / ou la génération de code pour ce travail, allant même jusqu'à générer la liste de colonnes de manière dynamique à l'aide de INFORMATION_SCHEMA
. Ce n'est pas exactement ce dont vous avez besoin, mais c'est un début (vous pouvez générer à partir d'une table de bases de données et de tables):
DECLARE @template AS varchar(MAX)
SET @template = 'SELECT {@column_list} FROM {@database_name}.dbo.{@table_name}'
DECLARE @column_list AS varchar(MAX)
SELECT @column_list = COALESCE(@column_list + ',', '') + COLUMN_NAME
FROM database1.dbo.INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = @table_name
ORDER BY ORDINAL_POSITION
DECLARE @sql AS varchar(MAX)
SET @sql = @template
SET @sql = REPLACE(@sql, '{@column_list}', @column_list)
SET @sql = REPLACE(@sql, '{@database_name}', @database_name)
SET @sql = REPLACE(@sql, '{@table_name}', @table_name)
En fonction du nombre de lignes renvoyées, il vaut peut-être mieux utiliser UNION ALL sur les sélections avec une requête distincte sélectionnée autour de celle-ci. J'ai déjà vu un problème similaire auparavant et mes plans d'exécution étaient différents pour les deux styles différents
SELECT DISTINCT subquery.c1, subquery.cN FROM ( SELECT Column 1 AS c1, Column N AS cN FROM database1.dbo.Table1 UNION ALL SELECT 'Some String' as c1, NULL as cN FROM database1.dbo.Table2 UNION ALL SELECT Column 1 AS c1, Column N AS cN FROM database2.dbo.Table1 UNION ALL SELECT 'Some String' as c1, NULL as cN FROM database2.dbo.Table2 ) subquery