2010-02-14 13 views
4

Voici ma question:Comment appeler la fonction VBA pour spécifier des colonnes dans une requête SQL TRANSFORM?

PARAMETERS ... 
TRANSFORM ... 
SELECT ... 
... 
PIVOT Mytable.type In ("Other","Info"); 

Ceci est une requête croix et je dois mettre toutes les têtes de colonne avec cette ligne: PIVOT Mytable.type In ("Other","Info") et maintenant j'ai codé en dur les rubriques, Autres et Infos .

Mais je veux le faire dynamiquement. Donc ce que je veux faire est d'appeler une fonction vba qui retourne toutes les rubriques dont j'ai besoin.

Quelque chose comme ceci:

PIVOT Mytable.type In (myVbaFunction()); 

Ma question est la suivante: comment appeler une fonction vba dans la requête SQL?

+0

Il peut être plus facile de faire ce dont vous avez besoin. Quel est votre résultat final, un rapport? Est-ce que vos rubriques doivent changer complètement ou est-ce juste que parfois l'un est là tandis que l'autre ne l'est pas? – Praesagus

Répondre

-2

Comme vous l'avez indiqué que vous utilisez Access, alors (du plus haut de ma tête) oui, il est possible d'utiliser les fonctions VBA dans les requêtes.

Vérifiez dans la documentation et dans MSDN.

+0

Savez-vous comment je devrais faire l'appel ou où, plus spécifique que MSDN, je peux trouver plus d'informations à ce sujet? – Johan

3

Oui, c'est possible. Mais je ne pense pas que ce soit possible avec WHERE IN (...).

Voici un exemple pour une requête WHERE normale:

Public Function Test() As String 
    Test = "Smith" 
End Function 

... puis:

SELECT * FROM Users WHERE Name = Test(); 

Il fonctionne, aussi longtemps que la fonction retourne une seule valeur.
Mais je pense qu'il est pas possible de laisser votre retour fonction quelque chose comme « Smith, Miller » et l'utiliser comme:

SELECT * FROM Users WHERE Name In (Test()); 

(au moins je ne sais pas comment le faire)

+1

Vous avez raison - une fonction ne peut pas être utilisée pour les clauses IN(). La seule solution dans Access est d'écrire le SQL à la volée. –

+0

Il n'est pas vrai que l'on doit générer le SQL à la volée. Peut-être que la déclaration de David est correcte si l'on insiste sur la limitation de la solution à la liste PIVOT ... IN, mais que l'OP ne limitait pas nécessairement la solution. Plutôt, placer une fonction dans la liste IN était simplement un exemple pour illustrer l'effet désiré. J'ai posté une autre réponse qui montre diverses façons de limiter "dynamiquement" les colonnes qui apparaissent dans la requête TRANSFORM. –

0

Pourquoi ne pas ajouter ces en-têtes dans une table et joindre cette table dans votre requête xtab?
Il est probablement plus facile de maintenir ce codage en dur dans une fonction.

+0

Parce que le format doit être 'PIVOT Mytable.type In (" Titre 1 "," Titre 2 ")' et avec "PIVOT Mytable.type In (SELECT nom FROM Mytable)' Je ne reçois pas ce format. – Johan

+1

Ok, mais si vous joignez une table qui inclut un champ tel que "ColHeader" qui n'a que les 2 ou 3 valeurs que vous voulez autoriser, vous n'aurez pas besoin du critère in(). De plus, si vous configurez correctement cette table, la jointure interne exclura automatiquement les enregistrements non pertinents. –

1

Si la liste IN est exclue de la clause PIVOT, la requête TRANSFORM crée automatiquement des colonnes pour chaque valeur PIVOT générée à partir de l'instruction SELECT. Les colonnes finales peuvent être filtrées en spécifiant l'expression IN avec des valeurs "codées en dur" (c'est-à-dire littérales). C'est bien connu des autres réponses.

Le même effet peut être obtenu en limitant les données de la requête SELECT pour commencer par ... avant le TRANSFORM doit le filtrer. De cette manière, on ne se limite pas à des valeurs littérales prédéfinies - plutôt des combinaisons de JOINS, de sous-requêtes et/ou de fonctions VBA peuvent également préfiltrer les données, en choisissant effectivement les colonnes qui apparaissent dans la table de transformation. Notez que la clause HAVING n'est pas autorisée dans une requête TRANSFORM, mais qu'elle pourrait être utilisée dans une autre requête sur laquelle la TRANSFORM est ensuite sélectionnée, de sorte qu'il n'y a effectivement aucune limite à la façon dont les données sont préparées avant la TRANSFORM.

Tous les résultats suivants donnent des résultats équivalents. Première utilisation de PIVOT ...IN:

TRANSFORM Count(Services.ID) AS [Schedules] 
SELECT Agreement.City FROM Agreement INNER JOIN Services ON Agreement.Account = Services.Account 
WHERE (Services.Code = "IS") 
GROUP BY Agreement.City ORDER BY Agreement.City 
PIVOT Month([ServiceDate]) In (1,4,12) 

Utiliser opérateur IN dans la clause WHERE:

TRANSFORM Count(Services.ID) AS [Schedules] 
SELECT Agreement.City FROM Agreement INNER JOIN Services ON Agreement.Account = Services.Account 
WHERE ((Month([ServiceDate]) In (1,4,12)) AND Services.Code = "IS") 
GROUP BY Agreement.City ORDER BY Agreement.City 
PIVOT Month([ServiceDate]) 

mais contrairement à la PIVOT ... clause IN, la liste peut aussi être une autre requête:

WHERE ((Month([ServiceDate]) In (SELECT Values FROM PivotValues)) AND Services.Code = "IS") 

Enfin, en utilisant une fonction VBA (qui répond à la question initiale):

TRANSFORM Count(Services.ID) AS [Schedules] 
SELECT Agreement.City FROM Agreement INNER JOIN Services ON Agreement.Account = Services.Account 
WHERE (ReportMonth([ServiceDate]) AND Services.Code = "IS") 
GROUP BY Agreement.City ORDER BY Agreement.City 
PIVOT Month([ServiceDate]) 

Avec la fonction VBA définie dans un module standard:

Public Function ReportMonth(dt As Date) As Boolean 
    Select Case Month(dt) 
    Case 1, 4, 12: ReportMonth= True 
    Case Else:  ReportMonth= False 
    End Select 
End Function 

(. IDevlop déjà proposé cette solution dans un commentaire, mais je ne pense pas qu'il a été compris et les bons exemples sont nécessaires)

+0

Ceci est une bonne approche, mais a un inconvénient: 'PIVOT [foo] dans (x, y, z)' * garantit * que le résultat aura les colonnes x, y, z - même si les données d'entrée ne contiennent pas certaines des valeurs. Par exemple. Lors de la génération d'un rapport d'année avec colonnes = mois, vous voulez toujours les mois 1..12, même lors de la génération du rapport en milieu d'année. (Mais bien sûr, dans ce cas, il n'y a pas besoin de colonnes dynamiques.) – Andre

+0

@Andre Merci d'avoir signalé cela. Il y a quelques mois à peine, j'ai créé ma propre solution à ce problème, mais je l'ai oublié en écrivant ma réponse. J'ai ajouté une réponse supplémentaire qui détaille la technique. –

1

garanti Inclusion de colonne dans la transformation

Andre a souligné que ma dernière réponse n'a pas réussi à traiter une caractéristique de la liste de colonnes PIVOT explicite, à savoir qu'elle garantit des colonnes même lorsque les données n'incluent pas les valeurs correspondantes. Dans de nombreux cas, il est peut-être tout aussi bien de générer le SQL complet "à la volée" comme l'a commenté David W Fenton. Voici un code de modèle pour le faire:

Public Function GenerateTransform(valueArray As Variant) As String 
    Dim sIN As String 
    Dim i As Integer, delimit As Boolean 

    If (VarType(valueArray) And vbArray) = vbArray Then 
    For i = LBound(valueArray) To UBound(valueArray) 
     sIN = sIN & IIf(delimit, ",", "") & valueArray(i) 
     delimit = True 
    Next i 
    If Len(sIN) > 0 Then sIN = "IN (" & sIN & ")" 
    End If 

    GenerateTransform = "TRANSFORM ... SELECT ... PIVOT ... " & sIN 

End Function 

Public Sub TestGenerateTransform() 
    Dim values(0 To 2) As Integer 
    values(0) = 1 
    values(1) = 4 
    values(2) = 12 

    Debug.Print GenerateTransform(values) 
    Debug.Print GenerateTransform(vbEmpty) 'No column list 
End Sub 

Comme mon autre réponse, les requêtes suivantes permettent d'utiliser diverses techniques^dans la sélection et le filtrage des critères. Non seulement cette technique peut garantir des colonnes, mais cela permet aussi un meilleur contrôle des lignes ^^.^Bien que les fonctions VBA puissent toujours être utilisées dans leur étendue habituelle dans SQL, Access n'autorise pas l'ajout dynamique de nouvelles données de ligne lors de l'exécution SQL à l'aide de VBA ... Les lignes doivent être basées sur des lignes de table réelles. (Techniquement, on peut utiliser un UNION SELECT avec des valeurs littérales pour créer des lignes, mais cela est prohibitif pour beaucoup de données et ne facilite aucune sorte de sélection de colonne dynamique.) Par conséquent, la technique suivante nécessite l'utilisation d'une table auxilliaire/sélection des valeurs de colonne.

La première requête applique des critères de sélection et effectue un regroupement initial. Si vous comparez à mon autre réponse, c'est essentiellement la même chose que la requête TRANSFORM originale - seulement sans TRANSFORM et PIVOT. Enregistrer et nommez cette requête [1 agrégat initial]:

SELECT Agreement.City, Month([ServiceDate]) AS [Month], Count(Services.ID) AS Schedules 
FROM Agreement INNER JOIN Services ON Agreement.Account = Services.Account 
WHERE (Services.Code = "IS") 
GROUP BY Agreement.City, Month([ServiceDate]) 
ORDER BY Agreement.City 

Suivant créer une requête que les groupes sur toutes les valeurs de ligne de votre choix. Dans cet exemple, je choisis d'inclure uniquement les mêmes valeurs que les critères de sélection initiaux. ^^ Cet ensemble de valeurs pourrait également être découplé des critères de sélection précédents en le basant sur la table non filtrée ou sur une autre requête. Enregistrer et nommez cette requête [2 têtes Row]:

SELECT RowSource.City AS City 
FROM [1 Initial Aggregate] AS RowSource 
GROUP BY RowSource.City 
ORDER BY RowSource.City 

Créer une jointure croisée des têtes de ligne et la table auxiliaire [PivotValues] contenant les têtes de colonne. Une jointure croisée crée des lignes à partir de chaque combinaison des deux tables. Dans Access SQL, elle est effectuée en excluant tous les mots-clés JOIN.Enregistrer et nommez cette requête [3 Cross Rejoignez]:

SELECT [2 Row Headings].City AS City, PivotValues.Values AS Months 
FROM [2 Row Headings], PivotValues 
ORDER BY [2 Row Headings].City, PivotValues.Values; 

Enfin, la transformation: En utilisant LEFT JOIN, cela comprendra toutes les colonnes et les lignes qui existent dans la croix requête de jointure. Pour les paires de colonnes et de lignes qui manquent des données dans la requête de sélection jointe, la colonne sera toujours incluse (c'est-à-dire garantie) avec Null comme valeur. Même si nous avons déjà groupé sur la requête initiale, la transformation nécessite que nous nous regroupions de toute façon - peut-être un peu redondant mais pas un gros problème pour obtenir le contrôle souhaité sur les résultats du tableau croisé final.

TRANSFORM Sum([1 Initial Aggregate].Schedules) AS SumOfSchedules 
SELECT [3 Cross Join].City AS City 
FROM [3 Cross Join] LEFT JOIN [1 Initial Aggregate] ON ([3 Cross Join].Months = [1 Initial Aggregate].Month) AND ([3 Cross Join].City = [1 Initial Aggregate].City) 
GROUP BY [3 Cross Join].City 
PIVOT [3 Cross Join].Months 

Cela peut sembler exagéré juste pour rendre les colonnes du tableau croisé dynamique, mais il peut être intéressant de définir quelques requêtes supplémentaires pour un contrôle complet sur les résultats. Le code VBA peut être utilisé pour (re) définir les valeurs dans la table auxiliaire, répondant ainsi à la question initiale d'utilisation de VBA pour spécifier dynamiquement les colonnes.