Call function for specifying columns in SQL TRANSFORM query

后端 未结 5 1545
情歌与酒
情歌与酒 2020-12-21 05:33

Here is my query:

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

This is a cross query and I need

相关标签:
5条回答
  • 2020-12-21 05:54

    Why not adding those headings in a table and join that table in your xtab query ?
    That's probably easier to maintain that hard coding it in a function.

    0 讨论(0)
  • 2020-12-21 05:59

    If the IN list is excluded from the PIVOT clause, the TRANSFORM query automatically creates columns for every PIVOT value generated from the SELECT statement. The final columns can be filtered by specifying the IN expression with "hard-coded" (i.e. literal) values. That is of course known from the other answers.

    The exact same effect can be achieved by limiting the data from the SELECT query to start with... before the TRANSFORM needs to filter it. In this way, one is not limited to only pre-defined literal values--rather combinations of JOINS, sub-queries and/or VBA functions can also pre-filter the data, effectively choosing which columns appear in the transform table. Note that the HAVING clause is not allowed in a TRANSFORM query, but it could be used in another query which the TRANSFORM then selects on, so there are effectively no limits to how the data is prepared before the TRANSFORM.

    All of the following produce equivalent results. First using 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)
    

    Using IN operator in WHERE clause:

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

    but unlike the PIVOT...IN clause, the list can also be another query:

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

    Finally, using a VBA function (which answers the original question):

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

    With the VBA function defined in a standard module:

    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 already suggested this solution in a comment, but I don't think it was understood and good examples are required.)

    0 讨论(0)
  • 2020-12-21 06:05

    Yes, it is possible.
    However, I don't think it's possible with WHERE IN (...).

    Here is an example for a normal WHERE query:

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

    ...and then:

    SELECT * FROM Users WHERE Name = Test();
    

    It works, as long as the function only returns one value.
    But I think it's not possible to let your function return something like "Smith, Miller" and use that like:

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

    (at least I don't know how to do it)

    0 讨论(0)
  • 2020-12-21 06:07

    As you have stated that you're using Access, then (out of the top of my head) yes, it's possible to use VBA functions in queries.

    Check on the documentation and MSDN.

    0 讨论(0)
  • 2020-12-21 06:10

    Guaranteed Column Inclusion in Transform

    Andre pointed out that my last answer failed to address one feature of the explicit PIVOT column list, namely that it gaurantees columns even when the data does not include corresponding values. In many cases it might be just as well to generate the full SQL "on-the-fly" as David W Fenton commented. Here's some template code for doing so:

    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
    

    Like my other answer, the following queries allow one to utilize various^ techniques in selecting and filtering the criteria. Not only can this technique guarantee columns, this also allows more control of the rows^^.

    ^ Even though VBA functions can still be used to their usual extent in the SQL, Access does not allow new row data to be dynamically added during SQL execution using VBA... rows must be based on actual table rows. (Technically one can use a UNION SELECT with literal values to create rows, but this is prohibitive for a lot of data and does not facilitate any sort of dynamic column selection.) Hence, the following technique requires use of an auxillary table for defining/selecting column values.

    The first query applies selection criteria and does initial grouping. If you compare to my other answer, this is essentially the same as the original TRANSFORM query--only without the TRANSFORM and PIVOT. Save and name this query [1 Initial Aggregate]:

    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
    

    Next create a query that groups on all desired row values. In this example, I choose to include only the same values from the initial selection criteria. ^^ This set of values could also be decoupled from the previous selection criteria by basing it off the unfiltered table or another query. Save and name this query [2 Row Headings]:

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

    Create a cross join of the row headings and the auxiliary table [PivotValues] containing the column headings. A cross join creates rows from every combination of the two tables--in Access SQL it is accomplished by excluding all JOIN keywords. Save and name this query [3 Cross Join]:

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

    Finally, the transform: By using a LEFT JOIN, this will include all columns and rows that exist in the cross join query. For column and row pairs that are missing data in the joined select query, the column will still be included (i.e. guaranteed) with Null as the value. Even though we have already grouped on the initial query, the transform requires that we re-group anyway--perhaps a bit redundant but not a big a deal to obtain the desired control over the final crosstab results.

    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
    

    This might seem like overkill just to make the crosstab columns dynamic, but it can be worth defining a few extra queries for complete control over the results. VBA code can be used to (re)define the values in the auxiliary table, thus satisfying the original question of using VBA to dynamically specify the columns.

    0 讨论(0)
提交回复
热议问题