C'est une question sur laquelle j'ai passé des heures à faire des recherches dans le passé. Cela me semble être quelque chose qui aurait dû être abordé dans les solutions modernes RDBMS , mais je n'ai encore rien trouvé qui réponde réellement à ce que je considère être un besoin incroyablement commun dans les applications Web ou Windows. avec une base de données.
Je parle de tri dynamique. Dans mon monde imaginaire, cela devrait être aussi simple que quelque chose comme:
ORDER BY @sortCol1, @sortCol2
Il s’agit de l’exemple canonique donné par les novices SQL et les développeurs procédure stockée sur l’ensemble des forums sur Internet. "Pourquoi ce n'est pas possible?" ils demandent. Invariablement, quelqu'un finit par leur expliquer la nature compilée des procédures stockées, des plans d'exécution en général, et toutes sortes d'autres raisons pour lesquelles il est impossible d'insérer un paramètre directement dans une clause ORDER BY
.
Je sais ce que certains d'entre vous pensent déjà: "Laissez le client faire le tri, alors." Naturellement, cela décharge le travail de votre base de données. Dans notre cas cependant, nos serveurs de base de données ne sont même pas en sueur 99% du temps et ils ne sont même pas encore multi-core ni aucune des innombrables améliorations apportées à l'architecture système tous les 6 mois. Pour cette seule raison, le fait que nos bases de données gèrent le tri ne serait pas un problème. De plus, les bases de données sont very bonnes pour le tri. Ils sont optimisés pour cela et ont eu des années pour bien faire les choses, le langage utilisé est incroyablement flexible, intuitif et simple, et tout écrivain débutant en langage SQL sait le faire et, plus important encore, il sait le modifier, apporter des modifications, faire de la maintenance, etc. Lorsque vos bases de données sont loin d'être taxées et que vous souhaitez simplement simplifier (et raccourcir!) le temps de développement, cela semble être un choix évident.
Ensuite, il y a le problème du Web. J'ai joué avec JavaScript qui effectuera un tri côté client des tableaux HTML, mais ceux-ci ne sont inévitablement pas assez souples pour répondre à mes besoins et, encore une fois, mes bases de données ne sont pas excessivement taxées et peuvent vraiment trier vraiment facilement, j’ai du mal à justifier le temps qu’il faudrait pour réécrire ou trier JavaScript par ma propre. Il en va généralement de même pour le tri côté serveur, même s'il est probablement déjà préférable à JavaScript. Je ne suis pas du genre à aimer les frais généraux liés aux ensembles de données, alors poursuivez-moi en justice.
Mais cela ramène le fait que ce n'est pas possible - ou plutôt, pas facilement. J'ai utilisé, avec des systèmes antérieurs, un moyen incroyablement astucieux d'obtenir un tri dynamique. Ce n'était pas joli, ni intuitif, simple ou flexible et un rédacteur SQL débutant serait perdu en quelques secondes. Déjà, il ne s’agit pas tant d’une "solution" que d’une "complication".
Les exemples suivants ne sont pas destinés à exposer les meilleures pratiques, un bon style de codage ou quoi que ce soit, et ils ne sont pas révélateurs de mes capacités en tant que programmeur T-SQL. Ils sont ce qu’ils sont et j’admets pleinement qu’ils sont déroutants, de mauvaise forme et tout simplement du hack.
Nous passons une valeur entière en tant que paramètre à une procédure stockée (appelons simplement le paramètre "sort") et à partir de là, nous déterminons un tas d'autres variables. Par exemple ... Disons que le tri est 1 (ou la valeur par défaut):
DECLARE @sortCol1 AS varchar(20)
DECLARE @sortCol2 AS varchar(20)
DECLARE @dir1 AS varchar(20)
DECLARE @dir2 AS varchar(20)
DECLARE @col1 AS varchar(20)
DECLARE @col2 AS varchar(20)
SET @col1 = 'storagedatetime';
SET @col2 = 'vehicleid';
IF @sort = 1 -- Default sort.
BEGIN
SET @sortCol1 = @col1;
SET @dir1 = 'asc';
SET @sortCol2 = @col2;
SET @dir2 = 'asc';
END
ELSE IF @sort = 2 -- Reversed order default sort.
BEGIN
SET @sortCol1 = @col1;
SET @dir1 = 'desc';
SET @sortCol2 = @col2;
SET @dir2 = 'desc';
END
Vous pouvez déjà voir comment, si je déclarais plus de variables @colX pour définir d'autres colonnes, je pourrais vraiment être créatif avec les colonnes à trier en fonction de la valeur de "sort" ... pour l'utiliser, cela finit généralement par ressembler à ce qui suit clause incroyablement salissante:
ORDER BY
CASE @dir1
WHEN 'desc' THEN
CASE @sortCol1
WHEN @col1 THEN [storagedatetime]
WHEN @col2 THEN [vehicleid]
END
END DESC,
CASE @dir1
WHEN 'asc' THEN
CASE @sortCol1
WHEN @col1 THEN [storagedatetime]
WHEN @col2 THEN [vehicleid]
END
END,
CASE @dir2
WHEN 'desc' THEN
CASE @sortCol2
WHEN @col1 THEN [storagedatetime]
WHEN @col2 THEN [vehicleid]
END
END DESC,
CASE @dir2
WHEN 'asc' THEN
CASE @sortCol2
WHEN @col1 THEN [storagedatetime]
WHEN @col2 THEN [vehicleid]
END
END
Évidemment, ceci est un exemple très dépouillé. Le vrai matériel, puisque nous avons généralement quatre ou cinq colonnes sur lesquelles appuyer le tri, chacune avec éventuellement une deuxième ou même une troisième colonne (par exemple date décroissante puis triée secondairement par nom croissant) et tri directionnel qui double effectivement le nombre de cas. Ouais ... ça devient poilu très vite.
L'idée est que l'on pourrait "facilement" changer les cas de tri de telle sorte que vehicleid soit trié avant l'heure de stockage stockée ... mais la pseudo-flexibilité, du moins dans cet exemple simple, s'arrête là. Essentiellement, chaque cas qui échoue à un test (car notre méthode de tri ne lui est pas applicable cette fois-ci) génère une valeur NULL. Et ainsi vous vous retrouvez avec une clause qui fonctionne comme suit:
ORDER BY NULL DESC, NULL, [storagedatetime] DESC, blah blah
Vous avez eu l'idée. Cela fonctionne car SQL Server ignore effectivement les valeurs null dans les clauses order by. C’est incroyablement difficile à maintenir, comme le savent probablement tous ceux qui ont une connaissance de base de SQL. Si j'ai perdu l'un de vous, ne vous sentez pas mal. Il nous a fallu beaucoup de temps pour le faire fonctionner et nous sommes toujours perplexes lorsque nous essayons de le modifier ou d'en créer de nouveaux. Heureusement, il n'est pas nécessaire de changer souvent, sinon cela deviendrait vite "ne vaut pas la peine".
Pourtant, il a fait a fonctionné.
Ma question est alors:existe-t-il un meilleur moyen?
Les solutions autres que les procédures stockées me conviennent, car je me rends compte que ce n’est peut-être pas la solution. De préférence, j'aimerais savoir si quelqu'un peut mieux le faire avec la procédure stockée, mais sinon, comment pouvez-vous tous laisser l'utilisateur trier de manière dynamique les tables de données (également dans les deux sens) avec ASP.NET?
Et merci d'avoir lu (ou au moins écumé) une si longue question!
PS: soyez heureux de ne pas avoir illustré mon exemple de procédure stockée prenant en charge le tri dynamique, le filtrage dynamique/la recherche de texte dans les colonnes, la pagination via ROWNUMBER () OVER,ETtry ... catch avec l'annulation de transaction sur les erreurs ... "taille gigantesque" ne commence même pas à les décrire.
Mettre à jour:
Oui, c'est pénible, et ta façon de faire ressemble à ce que je fais:
order by
case when @SortExpr = 'CustomerName' and @SortDir = 'ASC'
then CustomerName end asc,
case when @SortExpr = 'CustomerName' and @SortDir = 'DESC'
then CustomerName end desc,
...
Pour moi, cela reste bien meilleur que la création de code SQL dynamique à partir de code, qui se transforme en un cauchemar d’évolutivité et de maintenance pour les administrateurs de base de données.
Ce que je fais du code est de refactoriser la pagination et le tri afin que je n’ai au moins pas beaucoup de répétition avec des valeurs remplies pour @SortExpr
et @SortDir
.
En ce qui concerne le code SQL, conservez la même conception et le même formatage entre différentes procédures stockées afin qu’il soit au moins propre et reconnaissable lorsque vous apportez des modifications.
Cette approche empêche les colonnes triables d'être dupliquées deux fois dans l'ordre et est un peu plus lisible:
SELECT
s.*
FROM
(SELECT
CASE @SortCol1
WHEN 'Foo' THEN t.Foo
WHEN 'Bar' THEN t.Bar
ELSE null
END as SortCol1,
CASE @SortCol2
WHEN 'Foo' THEN t.Foo
WHEN 'Bar' THEN t.Bar
ELSE null
END as SortCol2,
t.*
FROM
MyTable t) as s
ORDER BY
CASE WHEN @dir1 = 'ASC' THEN SortCol1 END ASC,
CASE WHEN @dir1 = 'DESC' THEN SortCol1 END DESC,
CASE WHEN @dir2 = 'ASC' THEN SortCol2 END ASC,
CASE WHEN @dir2 = 'DESC' THEN SortCol2 END DESC
Mes applications le font souvent mais elles construisent toutes dynamiquement le code SQL. Cependant, lorsque je traite des procédures stockées, je le fais:
select * from dbo.fn_myData() where ... order by ...
pour pouvoir y spécifier dynamiquement l’ordre de tri.La partie dynamique se trouve alors au moins dans votre application, mais la base de données continue de s’acquitter de tâches lourdes.
Le SQL dynamique est toujours une option. Vous devez simplement décider si cette option est plus acceptable que ce que vous avez actuellement.
Voici un article qui montre que: http://www.4guysfromrolla.com/webtech/010704-1.shtml .
Il existe peut-être une troisième option, car votre serveur dispose de nombreux cycles de secours: utilisez une procédure d'assistance pour effectuer le tri via une table temporaire. Quelque chose comme
create procedure uspCallAndSort
(
@sql varchar(2048), --exec dbo.uspSomeProcedure arg1,'arg2',etc.
@sortClause varchar(512) --comma-delimited field list
)
AS
insert into #tmp EXEC(@sql)
declare @msql varchar(3000)
set @msql = 'select * from #tmp order by ' + @sortClause
EXEC(@msql)
drop table #tmp
GO
Mise en garde: je n'ai pas testé cela, mais cela "devrait" fonctionner dans SQL Server 2005 (ce qui créera une table temporaire à partir d'un jeu de résultats sans spécifier les colonnes à l'avance.)
Une technique de procédure stockée (hack?) Utilisée pour éviter le SQL dynamique pour certains travaux consiste à avoir une colonne de tri unique. C'est à dire.,
SELECT
name_last,
name_first,
CASE @sortCol WHEN 'name_last' THEN [name_last] ELSE 0 END as mySort
FROM
table
ORDER BY
mySort
Celui-ci est facile à battre en soumission - vous pouvez concaténer des champs dans votre colonne mySort, inverser l'ordre avec des fonctions mathématiques ou de date, etc.
De préférence cependant, j'utilise mes vues de grille asp.net ou d'autres objets avec un tri intégré pour effectuer le tri pour moi APRÈS la récupération des données de Sql-Server. Ou même si cela n’est pas intégré - par exemple, des tables de données, etc. dans asp.net.
Il y a différentes façons de pirater ceci.
Conditions préalables:
Puis insérez dans une table temporaire:
create table #temp ( your columns )
insert #temp
exec foobar
select * from #temp order by whatever
Méthode n ° 2: configurez un serveur lié sur lui-même, puis sélectionnez-le à l'aide de openquery: http://www.sommarskog.se/share_data.html#OPENQUERY
Un argument contre le tri côté client concerne les données volumineuses et la pagination. Une fois que votre nombre de lignes dépasse ce que vous pouvez facilement afficher, vous effectuez souvent un tri dans le cadre d'un saut/reprise que vous souhaitez probablement exécuter en SQL.
Pour Entity Framework, vous pouvez utiliser une procédure stockée pour gérer votre recherche de texte. Si vous rencontrez le même problème de tri, la solution que j'ai vue consiste à utiliser un proc stocké pour la recherche, en renvoyant uniquement une clé id définie pour la correspondance. Ensuite, interrogez à nouveau la base de données (avec le tri) en utilisant les identifiants d'une liste (contient). EF le gère très bien, même lorsque le jeu d’ID est assez volumineux. Oui, il s'agit de deux allers-retours, mais cela vous permet de toujours conserver votre tri dans la base de données, ce qui peut être important dans certaines situations, et vous empêche d'écrire une charge de logique dans la procédure stockée.
Je suis d'accord, utilisez le côté client. Mais il semble que ce ne soit pas la réponse que vous voulez entendre.
Donc, c'est parfait comme ça. Je ne sais pas pourquoi vous voudriez le changer, ou même demander "Y a-t-il une meilleure façon." Vraiment, ça devrait s'appeler "The Way". En outre, il semble bien fonctionner et répondre parfaitement aux besoins du projet et sera probablement suffisamment extensible pour les années à venir. Puisque vos bases de données ne sont pas taxées et que le tri est vraiment très facile il devrait le rester pendant des années.
Je ne voudrais pas transpirer.
Lorsque vous paginez des résultats triés, le SQL dynamique est une bonne option. Si vous êtes paranoïaque à propos de l'injection SQL, vous pouvez utiliser les numéros de colonne au lieu du nom de la colonne. J'ai déjà fait cela avant d'utiliser des valeurs négatives pour décroissant. Quelque chose comme ça...
declare @o int;
set @o = -1;
declare @sql nvarchar(2000);
set @sql = N'select * from table order by ' +
cast(abs(@o) as varchar) + case when @o < 0 then ' desc' else ' asc' end + ';'
exec sp_executesql @sql
Ensuite, vous devez simplement vous assurer que le nombre est compris entre 1 et # des colonnes. Vous pouvez même développer ceci en une liste de numéros de colonnes et l’analyser dans un tableau d’entités à l’aide d’une fonction comme this . Ensuite, vous construirez l'ordre par clause comme si ...
declare @cols varchar(100);
set @cols = '1 -2 3 6';
declare @order_by varchar(200)
select @order_by = isnull(@order_by + ', ', '') +
cast(abs(number) as varchar) +
case when number < 0 then ' desc' else '' end
from dbo.iter_intlist_to_tbl(@cols) order by listpos
print @order_by
Un inconvénient est que vous devez vous rappeler l'ordre de chaque colonne du côté client. En particulier lorsque vous n’affichez pas toutes les colonnes ou si vous les affichez dans un ordre différent. Lorsque le client souhaite effectuer un tri, vous mappez les noms de colonne sur leur ordre et générez la liste des entiers.
À un moment donné, ne vaut-il pas la peine de s’éloigner des procédures stockées et d’utiliser uniquement des requêtes paramétrées pour éviter ce type de piratage?
Désolé, je suis en retard pour la fête, mais voici une autre option pour ceux qui veulent vraiment éviter le SQL dynamique, mais veulent la flexibilité qu'il offre:
Au lieu de générer dynamiquement le code SQL à la volée, écrivez du code afin de générer un proc unique pour chaque variante possible. Ensuite, vous pouvez écrire une méthode dans le code pour examiner les options de recherche et lui demander de choisir le proc approprié à appeler.
Si vous ne disposez que de quelques variantes, vous pouvez simplement créer les procs à la main. Mais si vous avez beaucoup de variations, alors au lieu de toutes les conserver, maintenez simplement votre générateur de proc pour qu'il soit recréé.
De plus, vous obtiendrez de meilleurs plans SQL pour de meilleures performances en procédant de la même manière.
Pourquoi ne pas gérer le tri sur les éléments affichant les résultats - grilles, rapports, etc. plutôt que sur SQL?
MODIFIER:
Pour clarifier les choses depuis que cette réponse a été votée plus tôt, je vais élaborer un peu ...
Vous avez déclaré que vous connaissiez le tri côté client, mais que vous souhaitiez l'éviter. C'est votre appel, bien sûr.
Ce que je veux souligner, cependant, c’est qu’en le faisant côté client, vous pouvez extraire les données UNE FOIS et ensuite les utiliser comme vous le souhaitez - au lieu de faire plusieurs allers et retours vers le serveur à chaque fois. le genre est changé.
Votre serveur SQL n'est pas taxé pour le moment et c'est génial. Cela ne devrait pas être. Mais ce n'est pas encore surchargé que cela ne veut pas dire que ça va rester comme ça pour toujours.
Si vous utilisez l'un des éléments ASP.NET les plus récents pour l'affichage sur le Web, une grande partie de ces éléments est déjà intégrée.
Vaut-il la peine d’ajouter autant de code à chaque procédure stockée pour gérer le tri? Encore une fois, votre appel.
Je ne suis pas celui qui sera finalement chargé de le soutenir. Mais réfléchissez à ce qui va être impliqué lorsque des colonnes sont ajoutées/supprimées dans les divers jeux de données utilisés par les procédures stockées (nécessitant des modifications des instructions CASE) ou lorsque, soudainement, au lieu de trier sur deux colonnes, l’utilisateur en décide trois - vous obligeant à mettre à jour maintenant chacune de vos procédures stockées utilisant cette méthode.
Pour moi, cela vaut la peine d’obtenir une solution côté client qui fonctionne et de l’appliquer à la poignée d’affichages d’affichage de données destinés à l’utilisateur et d’en finir. Si une nouvelle colonne est ajoutée, elle est déjà gérée. Si l'utilisateur souhaite effectuer un tri sur plusieurs colonnes, il peut en effectuer deux ou vingt.