【问题标题】:Transform multiple rows into columns with unique key将多行转换为具有唯一键的列
【发布时间】:2018-02-04 12:27:12
【问题描述】:

我是新手,所以我想知道是否有人可以帮助我满足以下要求。我试图创建一个查询来提取表值,将它们组合到以唯一标识符 (ID) 分组的同一行中。

我曾尝试使用交叉表查询,但它们似乎更多地用于执行计算并使用 PIVOT 函数,但它似乎也不符合我的要求。

非常感谢任何帮助。请参阅下面的数据示例。

我的数据具有以下格式

ID    EMAIL                   COMMENTS
1     email1@email.com        <TEXT>
1     email2@email.com        <TEXT>
2     email1@email.com        <TEXT>
2     email2@email.com        <TEXT>
2     emailN@email.com        <TEXT>

期望的输出

ID COL1             COL2   COL3              COL4   COLN                 COLN+1
1  email1@email.com <TEXT> email2@email.com  <TEXT>
2  email1@email.com <TEXT> email2@email.com  <TEXT> emailN@email.com     <TEXT>

【问题讨论】:

  • 你的数据有N个限制吗? Access 的固有限制为 255 列。
  • 您的原始数据是否有任何其他可用的唯一键?我意识到 ID 不是唯一的,但电子邮件地址和 cmets 的来源是否有另一个唯一键?
  • N 不会/不应该达到 255。查看我当前的源将达到的最高值是 155。我可以使用数据创建新表并添加一个新的自动生成的密钥给我每行的唯一标识符。这会有帮助吗?
  • 是的,它会有所帮助,但请记住链接答案的最终目标(在我的其他评论中)是为每个 ID 重新启动序列号。唯一的 ID 可用于生成此类重新启动号码。但这只是另一个步骤。因为您还希望将两列包装成一行(电子邮件和 cmets),所以您最终需要执行两次转换查询,然后在最终查询中组合结果。如果您熟悉 VBA 以及如何遍历记录集,您可能会发现编写一个过程来完成这一切会更容易。

标签: ms-access vba transform pivot-table


【解决方案1】:

今天在室内等待飓风过去,所以我想我会创建这个自定义解决方案。所有这些步骤的答案都可以在其他地方找到,但要对所有人为的解决方案进行分类并不容易,所以我希望这个答案对所有方面都更有用。

将行更改为列的基本答案是here。但与这个问题的数据不同,该答案的样本数据已经正确排序。只要列值 [ID] 和 [Email] 形成唯一对并且没有 NULL 值,就可以使用聚合子查询或调用 Access 聚合函数来生成正确的排序。为了减少查询的总数,我继续在同一个查询中生成了转换后的列名。 (如果值不是唯一的或有空值,排序将被关闭,最终结果将丢失一些数据。)

这里的第二个挑战是有两列需要转换,但 Access SQL 转换语句(即 Crosstab 查询)每个查询只允许一个转换列。创建两个单独的查询然后将它们连接起来相当简单,但由于交叉表查询会生成动态(即未确定的)列数,因此如果不手动按顺序选择每一列,实际上不可能交错电子邮件和评论列。此外,显式指定查询中的列会破坏交叉表查询的动态方面,并且会留下额外的列,或者如果列的总数减少,则会产生错误。

更新:在发布原始解决方案(现在标记为解决方案 2)后,我意识到我可以通过实际向后退一步来解决列交错问题...首先生成 更多行 -- 每个电子邮件地址一个行,每个评论一个单独的行- 在最终转换之前将它们再次完全放在同一行上。

解决方案 1

保存以下查询并将其命名为 [Sequenced]。为了促进最终输出中的正确列排序,我使用了“备注”一词而不是“评论”,因为它排在“电子邮件”之后:

SELECT Data.ID, Data.Email, Data.Comments,
    1 + DCount("[ID]","Data","[ID]=" & [ID] & " and [Email]<'" & Replace([Email],"'","''") & "'") AS SeqNum,
    Format([SeqNum],"000") & ' Email' AS EmailColumn,
    Format([SeqNum],"000") & ' Remark' AS CommentsColumn
FROM Data
ORDER BY Data.ID, Data.Email;

保存以下查询并将其命名为 [Backwards]:

SELECT ID, EmailColumn AS ColumnName, Email AS [Value]
FROM Sequenced
UNION SELECT ID, CommentsColumn AS ColumnName, Comments AS [Value]
FROM Sequenced
ORDER BY [ID], [ColumnName];

保存以下查询并将其命名为 [InterleavedCrosstab]:

TRANSFORM First(Backwards.Value) AS FirstOfValue
SELECT Backwards.ID
FROM Backwards
GROUP BY Backwards.ID
ORDER BY Backwards.ID, Backwards.ColumnName
PIVOT Backwards.ColumnName;

解决方案 2

保存以下查询并将其命名为 [Sequenced2]:

SELECT Data.ID, Data.Email, Data.Comments,
    1 + DCount("[ID]","Data","[ID]=" & [ID] & " and [Email]<'" & Replace([Email],"'","''") & "'") AS SeqNum,
    'Email' & Format([SeqNum],"000") AS EmailColumn,
    'Comments' & Format([SeqNum],"000") AS CommentsColumn
FROM Data
ORDER BY Data.ID, Data.Email;

保存以下查询并将其命名为 [EmailCrosstab]:

TRANSFORM First(Sequenced2.Email) AS FirstOfEmail
SELECT Sequenced2.ID
FROM Sequenced2
GROUP BY Sequenced2.ID
ORDER BY Sequenced2.ID
PIVOT Sequenced2.EmailColumn;

保存以下查询并将其命名为 [CommentsCrosstab]:

TRANSFORM First(Sequenced2.Comments) AS FirstOfComments
SELECT Sequenced2.ID
FROM Sequenced2
GROUP BY Sequenced2.ID
ORDER BY Sequenced2.ID
PIVOT Sequenced2.CommentsColumn;

最后,最一般的结果查询将返回所有列,但它们不会被交错,并且会出现重复的 [ID] 列:

SELECT EmailCrosstab.*,
       CommentsCrosstab.*
FROM CommentsCrosstab INNER JOIN EmailCrosstab
    ON CommentsCrosstab.ID = EmailCrosstab.ID;

这是一个美化版本,但最多只有 3 个电子邮件和评论栏:

SELECT EmailCrosstab.ID,
    EmailCrosstab.Email001,CommentsCrosstab.Comments001,
    EmailCrosstab.Email002,CommentsCrosstab.Comments002,
    EmailCrosstab.Email003,CommentsCrosstab.Comments003
FROM CommentsCrosstab INNER JOIN EmailCrosstab
    ON CommentsCrosstab.ID = EmailCrosstab.ID;

解决方案 3

当我意识到仅查询的解决方案相当简单时,我已经键入了以下 VBA 过程,所以这里有一个额外的选择。

Public Sub CustomTransform()
  '* This code assumes that the field values
  '* [ID] and [Email] constitute a unique pair
  '* and that there are NO NULL values.

  Dim i As Integer, MaxIDRows As Integer
  Dim IDSeq As Integer
  Dim lastID As Long
  Dim IDstring As String
  Dim tbl As TableDef
  Dim idx As Index
  Dim db As Database
  Dim rsSrc As Recordset2, rsResult As Recordset2
  Const resultTable As String = "Customer Crosstab"

  Set db = CurrentDb

  MaxIDRows = db.OpenRecordset( _
      "SELECT Max(Counter.Rows) AS MaxRows" & _
      " FROM ( SELECT Count(Data.[ID]) AS [Rows]" & _
        " FROM Data GROUP BY Data.[ID]) AS Counter" _
      ).Fields(0).Value

  '* Column count <= 254 : ID + N * (Email + Comment columns)
  If MaxIDRows = 0 Then
    MsgBox "No data.", vbOKOnly Or vbExclamation, "No Data"
    Exit Sub
  ElseIf MaxIDRows >= 252 / 2 Then
    MsgBox "Maximum number of columns exceeded.", _
        vbOKOnly Or vbExclamation, "Maximum Columns Exceeded"
    Exit Sub
  End If

  On Error Resume Next
  db.TableDefs.Delete resultTable
  Err.Clear
  On Error GoTo 0

  Set tbl = db.CreateTableDef(resultTable)
  With tbl
    ' Create fields and append them to the new TableDef
    ' object. This must be done before appending the
    ' TableDef object to the TableDefs collection of the
    ' Northwind database.
    .Fields.Append .CreateField("ID", dbLong)

    For i = 1 To MaxIDRows
      IDstring = Format(i, "000")
      .Fields.Append .CreateField("Email" & IDstring, dbText, 255)
      .Fields.Append .CreateField("Comments" & IDstring, dbText, 255)
    Next

    Set idx = .CreateIndex("Primary Key")
    idx.Fields.Append idx.CreateField("ID")
    idx.Primary = True
    .Indexes.Append idx
  End With
  db.TableDefs.Append tbl

  Set rsResult = db.OpenRecordset(resultTable, dbOpenTable)
  Set rsSrc = db.OpenRecordset( _
      "SELECT ID, Email, Comments" & _
      " FROM Data" & _
      " ORDER BY ID, Email")

  lastID = -1
  Do Until rsSrc.EOF
    If rsSrc!id <> lastID Then
      If lastID <> -1 Then
        rsResult.Update
      End If

      IDSeq = 0
      rsResult.AddNew
      rsResult!id = rsSrc!id
    End If
    lastID = rsSrc!id

    IDSeq = IDSeq + 1
    IDstring = Format(IDSeq, "000")

    rsResult.Fields("Email" & IDstring) = rsSrc!email
    rsResult.Fields("Comments" & IDstring) = rsSrc!Comments

    rsSrc.MoveNext
  Loop
  rsSrc.Close

  If rsResult.EditMode <> dbEditNone Then
    rsResult.Update
  End If
  rsResult.Close

  Debug.Print "CustomTransform Done"
End Sub

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-05
    • 1970-01-01
    相关资源
    最近更新 更多