2009-06-18 5 views
3

Je vais avoir une requête SQL (MSSQLSERVER) où ajouter des colonnes au ResultSet en utilisant les sous-requêtes:SQL: les sous-requêtes sélectives

SELECT P.name, 
(select count(*) from cars C where C.type = 'sports') AS sportscars, 
(select count(*) from cars C where C.type = 'family') AS familycars, 
(select count(*) from cars C where C.type = 'business') AS businesscars 
FROM people P 
WHERE P.id = 1; 

La requête ci-dessus est juste à partir d'une configuration de test qui est un non-sens peu, mais sert assez bien comme exemple je pense. La requête sur laquelle je travaille actuellement couvre un certain nombre de tables complexes qui ne font que détourner l'attention du problème.

Dans l'exemple ci-dessus, chaque enregistrement de la table "people" comporte également trois colonnes supplémentaires: "wantsSportscar", "wantsFamilycar" et "wantsBusinesscar". Maintenant, ce que je veux faire est seulement de faire le sous-menu de chaque colonne supplémentaire si le champ "want ....." respectif dans la table people est mis à "true". En d'autres termes, je veux seulement faire la première sous-sélection si P.wantsSportscar est défini sur true pour cette personne spécifique. Les deuxième et troisième sous-groupes devraient fonctionner de la même manière. Par conséquent, la façon dont cette requête devrait fonctionner est qu'elle affiche le nom d'une personne en particulier et le nombre de modèles disponibles pour les types de voitures qu'elle souhaite posséder. Il peut être intéressant de noter que mon résultat final ne contiendra toujours qu'un seul enregistrement, à savoir celui d'un utilisateur spécifique.

Il est important que si une personne n'est pas intéressée par un certain type de voitures, que la colonne pour ce type ne soit pas incluse dans le résultat final. Un exemple pour être sûr que cela est clair:

Si la personne A veut une voiture de sport et une voiture familiale, le résultat inclurait les colonnes "nom", "voitures de sport" et "voitures de famille".

Si la personne B veut un businesscar, le résultat inclurait les colonnes "name" et "businesscar".

J'ai essayé d'utiliser diverses combinaisons avec les instructions IF, CASE et EXISTS, mais jusqu'à présent je n'ai pas réussi à obtenir une solution syntaxiquement correcte. Est-ce que quelqu'un sait si c'est même possible? Notez que la requête sera stockée dans une procédure stockée.

Répondre

5

Dans votre cas, il existe 8 dispositions de colonnes possibles et pour ce faire, vous aurez besoin de 8 requêtes distinctes (ou créez votre requête dynamiquement).

Il n'est pas possible de modifier la présentation du jeu de résultats au sein d'une même requête.

Au lieu de cela, vous pouvez concevoir votre requête comme suit:

SELECT P.name, 
     CASE WHEN wantssport = 1 THEN (select count(*) from cars C where C.type = 'sports') ELSE NULL END AS sportscars, 
     CASE WHEN wantsfamily = 1 THEN (select count(*) from cars C where C.type = 'family') ELSE NULL END AS familycars, 
     CASE WHEN wantsbusiness = 1 THEN (select count(*) from cars C where C.type = 'business') ELSE NULL END AS businesscars 
FROM people P 
WHERE P.id = 1 

qui sélectionnera NULL dans la colonne appropriée si une personne ne veut pas, et analyser ces NULL « s sur le côté client. Notez que le modèle relationnel répond aux requêtes en termes de relations.

Dans votre cas, la relation est la suivante: « cette personne a besoin sont satisifed avec autant de voitures de sport, ce nombre de voitures d'affaires et ce grand nombre de voitures de la famille ».

modèle Relational répond toujours à cette question spécifique avec une relation quaternaire.

Il n'omit aucun des membres de la relation: il les place simplement à NULL, ce qui est la manière de SQL de montrer que le membre d'une relation n'est pas défini, applicable ou significatif.

+0

Je suppose que cela a résolu mon problème pour moi (voir mon commentaire en réponse à la réponse de ChrisCM). Cette solution me permettra de vérifier la valeur des différentes colonnes de mon code de programmation et de prendre les mesures appropriées. C'est assez bon pour moi. Merci. – Jerry

0

Je suis surtout un gars d'Oracle mais il y a de fortes chances qu'il en soit de même. À moins d'avoir mal compris, ce que vous voulez n'est pas possible à ce niveau - vous aurez toujours un nombre fixe de colonnes. Votre requête peut contrôler si la colonne est vide mais puisque dans la partie la plus externe de la requête vous avez spécifié X nombre de colonnes, vous avez la garantie d'obtenir X colonnes dans votre resultset. Comme je l'ai dit, je ne connais pas MS SQL Server, mais je suppose qu'il y aura un moyen d'exécuter du SQL dynamique, auquel cas vous devriez faire une recherche car cela devrait vous permettre de construire une requête plus flexible.

+0

D'accord, un nombre statique de colonnes semble raisonnable. Cela m'a fait penser cependant, parce que la colonne peut exister, mais si la colonne "veut ...." est fausse, je pourrais mettre une valeur spécifique dans la colonne du resultset. Est-il possible de laisser la colonne dedans, mais de mettre sa valeur à -1 ou quelque chose comme ça, basé sur la valeur dans le champ "veut ...."? – Jerry

0

Vous pourriez être en mesure de faire ce que vous voulez en sélectionnant d'abord les valeurs sous forme de lignes séparées dans une table temporaire, puis en faisant un PIVOT sur cette table (tourner les lignes en colonnes).

0

Il est important que si une personne n'est pas intéressé par un certain type de voitures, que la colonne de ce type ne sera pas inclus dans le resultset final. Un exemple pour être sûr que ce soit clair:

Vous ne pourrez le faire en SQL. Je vous suggère de faire cette colonne NULL ou ZERO.

Si vous souhaitez que la requête soit développer dynamiquement lorsque de nouvelles voitures sont ajoutées, puis en faisant pivoter pourrait vous aider un peu.

0

Il y a trois principes fondamentaux que vous voulez apprendre pour faciliter ce travail. Le premier est la normalisation des données, le second est GROUP BY, et le troisième est PIVOT.

D'abord, la normalisation des données. Votre conception de la table des personnes n'est pas en première forme normale. Les colonnes "wantports", "wantfamily", "wantbusiness" sont vraiment un groupe qui se répète, même si elles ne ressemblent pas à un. Si vous pouvez modifier la conception de la table, vous trouverez qu'il est avantageux de créer une troisième table, appelons-la "peoplewant", avec deux colonnes clés, personid et cartype. Je peux détailler les raisons pour lesquelles cette conception sera plus flexible et puissante si vous le souhaitez, mais je vais passer cette étape pour l'instant.

Passage à GROUP BY. Cela vous permet de produire un résultat qui résume chaque groupe dans une ligne du résultat.

SELECT 
    p.name, 
    c.type, 
    c.count(*) as carcount 
FROM people p, 
    INNER JOIN peoplewant pw ON p.id = pw.personid 
    INNER JOIN cars c on pw.cartype = c.type 
WHERE 
    p.id = 1 
GROUP BY 
    p.name, 
    c.type 

Ce (non testé) requête vous donne le résultat que vous voulez, sauf que le résultat a une ligne distincte pour chaque type de voiture que la personne veut.

Enfin, PIVOT. L'outil PIVOT dans votre SGBD vous permet de transformer ce résultat en un formulaire où il n'y a qu'une seule ligne pour la personne, et il y a une colonne distincte pour chacun des cartypes voulus par cette personne. Je n'ai pas utilisé PIVOT moi-même, donc je vais laisser quelqu'un d'autre éditer cette réponse pour donner un exemple en utilisant PIVOT. Si vous utilisez la même technique pour récupérer des données pour plusieurs personnes dans un balayage, gardez à l'esprit qu'une colonne apparaîtra pour chaque type voulu et que des zéros apparaîtront dans le résultat PIVOT pour les personnes qui ne le font pas. vouloir un type de voiture qui est dans les colonnes de résultat.

0

Juste venu à travers ce poste grâce à une recherche google, donc je me rends compte que je suis en retard à cette fête par un peu, mais .. c'est vraiment possible possible de faire ... Cependant, je ne suggérerais pas en fait de cette façon parce que c'est généralement considéré comme une très mauvaise chose (tm).Dynamic SQL est votre réponse.

Avant de dire comment le faire, je veux faire une préface avec, SQL dynamique est une chose très dangereuse, si vous ne désinfectez pas votre entrée de l'application.

Ainsi donc procéder avec prudence:

declare @sqlToExecute nvarchar(max); 
declare @includeSportsCars bit; 
declare @includeFamilyCars bit; 
declare @includeBusinessCars bit; 

set @includeBusinessCars = 1 
set @includeFamilyCars = 1 
set @includeSportsCars = 1 

set @sqlToExecute = 'SELECT P.name ' 

if @includeSportsCars = 1 
    set @sqlToExecute = @sqlToExecute + '(select count(*) from cars C where C.type = ''sports'') AS sportscars, '; 
if @includeFamilyCars = 1 
    set @sqlToExecute = @sqlToExecute + '(select count(*) from cars C where C.type = ''family'') AS familycars, '; 
if @includeBusinessCars = 1 
    set @sqlToExecute = @sqlToExecute + '(select count(*) from cars C where C.type = ''business'') AS businesscars ' 

set @sqlToExecute = @sqlToExecute + ' FROM people P WHERE P.id = 1;'; 

exec(@sqlToExecute)