2010-02-14 35 views
4

這裏是我的查詢:如何調用VBA函數來指定SQL TRANSFORM查詢中的列?

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

這是一個跨查詢和我需要設置所有的列標題與該行:PIVOT Mytable.type In ("Other","Info")現在我已經硬編碼的標題,其他信息

但我想動態地做到這一點。所以我想要做的就是調用一個vba函數,它返回我需要的所有標題。

事情是這樣的:

PIVOT Mytable.type In (myVbaFunction()); 

所以我的問題是:如何調用SQL查詢裏面的VBA功能?

+0

可能有更簡單的方法來做你需要的。你的最終結果是什麼,一份報告?你的標題需要完全改變還是僅僅是有時候有人在那裏,而另一個則不在? – Praesagus

回答

-2

正如你所說你正在使用Access,那麼(從我的頭頂開始)是的,可以在查詢中使用VBA函數。

檢查文檔和MSDN。

+0

你知道我應該怎麼做嗎?或者,在哪裏,比MSDN更具體,我可以找到更多關於此的信息? – Johan

3

是的,這是可能的。
但是,我認爲這不可能與WHERE IN (...)

下面是一個例子正常WHERE查詢:

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

...然後:

SELECT * FROM Users WHERE Name = Test(); 

它的工作原理,只要函數只返回一個值。
但我認爲這是可能讓你的函數返回類似「史密斯,米勒」,並使用類似:

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

(至少我不知道該怎麼做)

+1

你是對的 - 一個函數不能用於IN()子句。 Access中唯一的解決方案是即時編寫SQL。 –

+0

這不是真的,我們必須即時生成SQL。如果堅持將解決方案限制在PIVOT ... IN列表中,也許David的說法是正確的,但是OP不一定限制解決方案。相反,在IN列表中放置一個函數只是一個例子來說明預期的效果。我發佈了另一個答案,它顯示了各種方式來「動態」限制哪些列出現在TRANSFORM查詢中。 –

0

爲什麼不在表中添加這些標題並將其添加到xtab查詢中?
這可能更容易維護,它在一個函數中硬編碼。

+0

由於格式必須是'PIVOT Mytable.type In(「Heading 1」,「Heading 2」)'並且'PIVOT Mytable.type In(SELECT name FROM Mytable)'我沒有得到這種格式。 – Johan

+1

好的,但如果你加入一個包含像「ColHeader」這樣只有2或3個值的字段的表,你不需要in()條件。另外,如果您正確設置了該表,則內部聯接將自動排除不相關的記錄。 –

1

如果從PIVOT子句中排除IN列表,則TRANSFORM查詢將自動創建的每列,這些值是從SELECT語句生成的每個PIVOT值。可以通過用「硬編碼」(即文字)值指定IN表達式來過濾最後的列。這當然是從其他答案中得知的。

通過限制數據從SELECT查詢開始... 之前 TRANSFORM需要過濾它可以實現完全相同的效果。通過這種方式,人們不僅限於預先定義的文字值 - 而是JOINS,子查詢和/或VBA函數的組合也可以對數據進行預過濾,從而有效地選擇哪些列出現在變換表中。請注意,TRANSFORM查詢中不允許使用HAVING子句,但可以在TRANSFORM隨後選擇的另一個查詢中使用該子句,因此在TRANSFORM之前數據的準備方式實際上沒有限制。

以下所有情況均產生同等結果。首先使用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) 

WHERE子句中使用IN操作符:

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]) 

但不同的是PIVOT ... IN子句,該列表也可以是另一個查詢:

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

最後,使用VBA功能(回答原始問題):

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]) 

使用標準模塊中定義的VBA函數:

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已經建議在評論這一解決方案,但我不認爲這是理解並需要很好的例子)

+0

這是一個很好的方法,但有一個缺點:'(x,y,z)'中的PIVOT [foo] *保證結果將有列x,y,z - 即使輸入數據不包含一些價值。例如。在生成包含列=月份的年份報告時,即使在年中生成報告時,您也總是需要1到12個月。 (但當然在這種情況下,不需要動態列。) – Andre

+0

@Andre謝謝你指出。就在幾個月前,我爲這個問題創建了自己的解決方案,但是在編寫答案時我忘了它。我添加了一個詳細說明該技術的附加答案。 –

1

保證在列轉換中包含列

Andre指出我最後的回答未能解決顯式PIVOT列列表的一個特徵,即即使數據不包含相應的值,也能列出列。在很多情況下,像David W Fenton評論的那樣,「隨時」生成完整的SQL可能也是一樣。下面是這樣一些模板代碼:

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 

就像我的其他的答案,以下查詢允許一個能夠利用在選擇和過濾的標準不同^技術。這種技術不僅可以保證列,還可以更多地控制行^^。

^儘管VBA函數仍然可以在SQL中用於其通常的程度,但Access不允許在使用VBA進行SQL執行期間動態添加新行數據......行必須基於實際的錶行。 (從技術上講,可以使用具有字面值的UNION SELECT來創建行,但這對於大量數據是禁止的,並且不利於任何類型的動態列選擇。)因此,以下技術需要使用輔助表來定義/選擇列值

第一個查詢應用選擇條件並進行初始分組。如果與我的其他答案相比,這與原始TRANSFORM查詢基本相同 - 只有沒有TRANSFORM和PIVOT。保存並命名此查詢[1初始集合]

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 

接下來創建一個查詢,在所有需要的行值組。在這個例子中,我選擇只包含初始選擇標準中相同的值。 ^^這組數值也可以與之前的選擇標準脫離,不用過濾表或其他查詢。保存並命名此查詢[2個標題]

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

創建行標題和輔助表[PivotValues]含有列標題的交叉連接。交叉連接從兩個表的每個組合中創建行 - 在Access SQL中,它通過排除所有JOIN關鍵字來完成。保存並命名此查詢[3交叉聯接]

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

最後,轉換:通過使用LEFT JOIN,這將包括存在於交叉聯接查詢中的所有列和行。 對於在聯合選擇查詢中缺少數據的列和行對,該列仍將包含(即有保證),其值爲Null。儘管我們已經將最初的查詢分組,但是轉換要求我們重新進行分組 - 可能有點多餘,但對於獲得對最終交叉表結果的理想控制並不是什麼大事。

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 

這似乎有點小題大做只是爲了交叉表列動態的,但它可以是值得定義對結果的完全控制一些額外的查詢。 VBA代碼可用於(重新)定義輔助表中的值,從而滿足使用VBA動態指定列的原始問題。