I have a table that was created by a SQL query where each of the results were combined into one cell. I would like to get each value associated correctly on separate rows.
The data is currently set up like this:
+-------+---------+-------------------------------------------+---------------+
| ID | Desc | Users | Functions |
+-------+---------+-------------------------------------------+---------------+
| a | a desc | First Last [uname3], First Last [uname45] | abc, def, xyz |
+-------+---------+-------------------------------------------+---------------+
| b | b desc | First Last [uname8], First Last [uname72] | lmn, def, xyz |
+-------+---------+-------------------------------------------+---------------+
I would like for it to be presented as:
+-------+---------+----------------------+---------------+
| ID | Desc | Users | Functions |
+-------+---------+----------------------+---------------+
| a | a desc | First Last[uname3] | abc, def, xyz |
+-------+---------+----------------------+---------------+
| a | a desc | First Last [uname45] | abc, def, xyz |
+-------+---------+----------------------+---------------+
| b | b desc | First Last[uname8] | lmn, def, xyz |
+-------+---------+----------------------+---------------+
| b | b desc | First Last [uname72] | lmn, def, xyz |
+-------+---------+----------------------+---------------+
I would just do this manually but there are ~75 rows with as many as 125 users listed in the same cell.
Thanks for any help!
Let's say your data are stored in A to D columns in the sheet named TheNameOfSheet:
Dim i As Integer, j As Integer
Dim users() As String
Dim wsh As Worksheet
Set wsh = ThisWorkbook.Worksheets("TheNameOfSheet")
i = 1 'starting row
Do
'get the list of users and split it by comma
users = Split(wsh.Range("C" & i), ", ")
'go through the list of users
For j = LBound(users()) To UBound(users())
'insert new row
If j>0 Then wsh.Range("A" & i).EntireRow.Insert(Shift:=xlShiftDown)
'copy original data
wsh.Range("A" & i) = wsh.Range("A" & i)
wsh.Range("B" & i) = wsh.Range("B" & i)
'insert single name
wsh.Range("C" & i) = users(j)
wsh.Range("D" & i) = wsh.Range("D" & i)
'increase counter
i = i + 1
Next j
i = i +1
Loop
For further information, please see:
MS Excel: How to use the SPLIT Function (VBA)
Range.Insert Method (Excel)
Note: I didn't tested this code! I've written it directly from my head ;)
if you are not interested in writing an excel macro.
then do this.
copy paste the sorted results twice in our excel.
Select the entire column of users, choose text to columns with delimiter as Coma,
Sort all the columns (inc/desc, your wish) based on users column
then insert one more row beside between functions and users.
paste the below formula
=IF(D3=D2,E3,D3) on the first cell of newly added column and drag till bottom.
the output will look like the below image.
remvoe the columns D and E. there you are with the end result as you wanted it
Related
I have a table which I have shown a simplified example of below:
ID | Item1 | Item2 | Item3 | Item4 | Item5
------------------------------------------
A | NULL | NULL | YES | YES | NULL
B | NULL | NULL | NULL | YES | NULL
C | NULL | NULL | NULL | NULL | NULL
I want to return the following data set:
ID | Count
------------
A | 2
B | 1
C | 0
I.e. I want a count of how many of the columns are NOT NULL for that ID
One potential solution would be
SELECT
ID,
SUM(
IIf(Item1 is NULL,0,1)
+
IIf(Item2 is NULL,0,1)
+
IIf(Item3 is NULL,0,1)
+
IIf(Item4 is NULL,0,1)
+
IIf(Item5 is NULL,0,1)
) 'Count'
FROM
tableName
GROUP BY
ID
However in practice the real table I am using has over a hundred columns and I would prefer to avoid having to write out the names of each column. Is there a simpler way to do this?
You can use VBA to loop through every record and field:
Function CountFields()
Set db = CurrentDb()
db.Execute ("delete * from ItemCounts")
Set RS = db.OpenRecordset("select * from [DataTable]")
RS.MoveFirst
Do While Not RS.EOF
Id = RS.Fields("ID").Value
Count = 0
For Each Item In RS.Fields
If (Item.Name <> "ID" And RS.Fields(Item.Name).Value <> "") Then Count = Count + 1
Next Item
db.Execute ("insert into ItemCounts (ID,[count]) select " & Id & "," & Count)
RS.MoveNext
Loop
MsgBox ("done")
End Function
This puts the counts in a table called ItemCounts, which needs to be set up before the VBA is executed. The fields in that table are ID and Count.
And, if you can reformat the source data, I agree with Minty - but I know that's not always feasible.
Your data is not normalised and therefore you are having to perform gymnastics in your code to work around the problem.
Your data should be stored vertically not horizontally;
ID | ItemNo | Value
---------------------
A | 2 | 1
A | 3 | 1
B | 4 | 1
This would make your query a simple total query, and allow for any number of items. You are also only storing data when you have some not for every case.
Edit: This will loop through the fields
Dim Rst As Recordset
Dim f As Field
Set Rst = CurrentDb.OpenRecordset(TableName)
For Each f In Rst.Fields
Debug.Print (f.name)
Next
Rst.Close
You can reduce it a little:
SELECT
ID,
ABS(SUM((Item1 is Not NULL)+(Item2 is Not NULL)+(Item3 is Not NULL)+(Item4 is Not NULL)+(Item5 is Not NULL))) As [Count]
FROM
tableName
GROUP BY
ID
I need to transpose rows into columns in MS Access database, VBA, SQL both the codes are welcome.
Table
| Name | Ticker | ID | Innovation | Quality | Year |
| XYZ | PQR | 11 | 1 | 1 | 2009 |
| XYZ | PQR | 11 | 0 | 1 | 2010 |
| XYZ | PQR | 11 | 1 | 0 | 2011 |
| XYZ | PQR | 11 | 1 | 1 | 2012 |
Desired Table
| Year | 2009 | 2010 | 2011 | 2012 |
| Name | XYZ | XYZ | XYZ | XYZ |
| Ticker | PQR | PQR | PQR | PQR |
| ID | 11 | 11 | 11 | 11 |
| Innovation | 1 | 0 | 1 | 1 |
| Quality | 1 | 1 | 0 | 1 |
As you can see from the desired table, I am trying to have the Year row as Column and list all the columns apart from Year as my rows.
I have tried using Tranform and Pivot function in MS Access but it only Pivots one variable. Let me know your thoughts on it.
The below code failed in transposing all the variables.
TRANSFORM Max([Quality])
SELECT Ticker
FROM Table
Where Ticker = "XYZ"
GROUP BY Ticker
PIVOT Year;
Also, if possible I want to publish it as PDF document.
Thanks in advance,
RVG
Access TRANSFORM is not really intuitive and easy to use and I do not think you can use it that way. Each result row is supposed to be an aggregate of your table. I know of no way to get the previous field names into a new column.
See a working example:
TRANSFORM and PIVOT in Access 2013 SQL
What you really seem to want is just a new presentation to the existing data.
Excel might help.
I have never exported pdf from Access but from Excel is easy. Here is an example:
Sub ExportPdf(path As String, Optional openAfter As Boolean)
''calculate best range for print area
Dim lastCol As Integer
Dim firstRow As Integer
Dim lastRow As Integer
lastCol = pt.TableRange2.Columns(pt.TableRange2.Columns.Count).Column
firstRow = pt.TableRange2.Rows(1).Row
lastRow = ms.Cells(pt.TableRange2.Rows.Count * 3, 1).End(xlUp).Row
Worksheets(ContextSheet).PageSetup.PrintArea = Range(Cells(firstRow, 1), Cells(lastRow, lastCol)).Address
Worksheets(ContextSheet).ExportAsFixedFormat Type:=xlTypePDF, Filename:= _
path & "\Area " & getPivotTablePageFilters(getPivotTable()) & ".pdf", Quality:= _
xlQualityStandard, IncludeDocProperties:=True, IgnorePrintAreas:=False, _
OpenAfterPublish:=openAfter
End Sub
This is a vb script that takes the data from TableSource and transposes it into TableTranspose. The 2nd table has to be set up with a column named FName to take the field names, and columns for each year in the source table.
Function Transpose()
Set db = CurrentDb()
db.Execute ("delete * from TableTranspose")
Set RS = db.OpenRecordset("TableSource")
Set YrList = db.OpenRecordset("select distinct [Yr] from [TableSource] Group by [Yr]")
For Each F In RS.Fields
FN = F.Name
INS = "Insert Into TableTranspose (FName"
SQL = "Select '" & FN & "'"
YrList.MoveFirst
Do While Not YrList.EOF
YR = YrList.Fields("YR").Value
INS = INS & ",[" & YR & "]"
SQL = SQL & ",max(iif(YR=" & YR & ",[" & FN & "])) AS [" & YR & "]"
YrList.MoveNext
Loop
SQL = SQL & " From TableSource"
db.Execute (INS & ") " & SQL)
Next F
MsgBox ("Done")
End Function
This works by processing one field at a time to match the layout of the desired output, and looping through each year of TableSource to find the data to make up the row in TableTranspose. It shouldn't matter how many fields there are or what they are named.
It will create a row in the output for the Year, which will be redundant - you can delete it, or add logic to skip that field if necessary.
This seems to work fine with the 4 years of data in your sample, and should extend OK to more years. It's possible that you will hit a limit on SQL command length if there are too many years in the data, but I think not.
If you are filtering the records from TableSource, you can add the WHERE clause on the line just from the db.execute near the bottom.
Is the data always the same set of field names and years? If so, you might be able to use a UNION query, something like:
Select "Name" as [FName], max(iif(year="2009",name))as [2009], max(iif(year="2010"
,name)) as [2010], max(iif(year="2011",name)) as [2011], max(iif(year="2012", name)) as [2012] from Table group by FName
Union all Select "Ticker", max(iif(year="2009",ticker)), max(iif(year="2010"
,ticker)), max(iif(year="2011",ticker)), max(iif(year=-"2012",ticker)) from Table group by FName
Union all Select "ID", max(iif(year="2009",id)), max(iif(year="2010"
,id)), max(iif(year="2011",is)), max(iif(year="2012",id)) from Table group by FName
Union all Select "Innovation", max(iif(year="2009",innovation)), max(iif(year="2010"
,innovation)), max(iif(year="2011",innovation)), max(iif(year=-"2012",innovation)) from Table group by FName
Union all Select "Quality", max(iif(year="2009",quality)), max(iif(year="2010"
,quality)), max(iif(year="2011",quality)), max(iif(year=-"2012",quality)) from Table group by FName
Hi I have a excel in which one sheet A contains a table with a column which has comma separated value and another sheet B having some data.
So what I need I need to search the data from B to the sheet A comma separated column and if it is found remove that data from the string. Here I need to work on these columns based on another column. For Example:
Sheet A
column a | comma separated column
a | 1,2,3,4
b | 1,3,5,7
c | 1,2,3,4
d | 1,3,5,7
e | 1,2,3,4
Sheet B
column a | column b
a | 1
a | 2
b | 1
b | 3
c | 1
c | 4
d | 3
d | 7
Result:
column a | comma separated column
a | 3,4
b | 5,7
c | 2,3
d | 1,5
e | 1,2,3,4
Is it possible to fetch the result? I am new in excel and macro but tried may ways still finding the exact solution. Is there any easiest way whether using macro or excel formulas.
This worked for me. Efficiency may be an issue with larger data sets.
Sub Main()
Dim shtAList As Range, rngA As Range
Dim shtBList As Range, rngB As Range
Set shtAList = Worksheets("A").Range("A1:A5")
Set shtBList = Worksheets("B").Range("A1:A8")
For Each rngA In shtAList
For Each rngB In shtBList
If rngB = rngA Then
rngA.Offset(0, 1) = StripString(rngA.Offset(0, 1), rngB.Offset(0, 1))
End If
Next rngB
Next rngA
End Sub
Function StripString(csv As Range, strip_string As String) As String
Dim strings() As String, i As Integer, temp As String, result As String
temp = vbNullString
strings = VBA.Split(csv, ",")
For i = 0 To UBound(strings)
If strings(i) <> strip_string Then
temp = temp & strings(i) & ","
End If
Next i
result = IIf(VBA.Right$(temp, 1) = ",", VBA.Left$(temp, Len(temp) - 1), temp)
StripString = result
End Function
Main is simply looping over your two lists to find matches.
StripString is where the comparison of strings and stripping takes place
I have a large dataset (around 96,000 entries) representing courses run by different education providers. In total there are around 5,500 different course providers.
Instead of each row containing all the courses offered by a single provider, each course has its own row with a column identifying the providers id (i.e. for one provider id, there are 750 rows representing various bits of information relating to each course). An example of the data is provided here:
+-----------+-------------+-----------------------+---------------------------------------+------------------------------------------------------+--------------------+-----------------------+
| COURSE_ID | PROVIDER_ID | LAD_ID | PROVIDER_COURSE_TITLE | COURSE_SUMMARY | PROVIDER_COURSE_ID | COURSE_URL |
+-----------+-------------+-----------------------+---------------------------------------+------------------------------------------------------+--------------------+-----------------------+
| 53072591 | 300015 | | Functional English 1 | English Entry 3 and Level 1 | | http://www.asfc.ac.uk |
| 53072593 | 300015 | | Functional English 2 | Literacy level 1 and 2 | | http://www.asfc.ac.uk |
| 53072595 | 300015 | | Functional Maths 1 | "Anyone who wants to improve their maths skills . | | http://www.asfc.ac.uk |
| 53728417 | 300015 | | HND in Creative Media Production | This course is aimed at those with a passion | | http://www.asfc.ac.uk |
| 53887498 | 300017 | 60133600 | Floristry NCFE Creative Craft Level 1 | This is an assessed course | AADE1215XA | http://www.esc.ac.uk/ |
| 53887499 | 300017 | 60132322 | Floristry NCFE Creative Craft Level 2 | This course follows on from the NCFE Level 1 | AADE1218XA | http://www.esc.ac.uk/ |
| 53887500 | 300017 | Z0002105 | Upholstery | This course will give you | AADE1X37XA | http://www.esc.ac.uk/ |
| 53887501 | 300017 | Z0002105 | Upholstery | The aim of this course is to give the inexperienced | AADE1X38XA | http://www.esc.ac.uk/ |
+-----------+-------------+-----------------------+---------------------------------------+------------------------------------------------------+--------------------+-----------------------+
I would like to create a new sheet formatted so that each row represents a unique provider (e.g. Provider i.d 300015 on row 2, 300017 on row 3) followed by each individual course that provider runs on the same row. It would be ideal if I could select only the fields PROVIDER_COURSE_TITLE, COURSE_SUMMARY, COURSE_URL for each course from the original sheet to copy across to the new sheet rather than the whole row.
Ultimately I'm looking for a table that looks like a bit like this
+-------------+----------------------+-----------------------------+-----------------------+----------------------+------------------------+-----------------------+
| PROVIDER_ID | COURSE_TITLE1 | COURSE_SUMMARY1 | COURSE_URL1 | COURSE_TITLE2 | COURSE_SUMMARY2 | COURSE_URL2 |
+-------------+----------------------+-----------------------------+-----------------------+----------------------+------------------------+-----------------------+
| 300015 | Functional English 1 | English Entry 3 and Level 1 | http://www.asfc.ac.uk | Functional English 2 | Literacy level 1 and 2 | http://www.asfc.ac.uk |
+-------------+----------------------+-----------------------------+-----------------------+----------------------+------------------------+-----------------------+
Essentially therefore I need a macro that searches the PROVIDER_ID column and identifies a unique provider id, and subsequently copies that across to a new sheet. Then it identifies all rows with that Provider_ID, and copies the PROVIDER_COURSE_TITLE, COURSE_SUMMARY and COURSE_URL from each of those rows, and pastes them in to the new worksheet on the singular row for that provider_id.
I have spent a whole day trying to get my head around this, and had a couple of other people look at this, and we can't figure out the loops, the find next blank cell commands, and rules to move on to the next PROVIDER_ID needed.
Untested:
Sub Test()
Dim rngSrc As Range, rw As Range
Dim dictR As Object, dictC As Object, shtDest As Worksheet
Dim pid, rL As Long, cD As Range
Set dictR = CreateObject("scripting.dictionary")
Set dictC = CreateObject("scripting.dictionary")
Set rngSrc = ActiveSheet.Range("a1").CurrentRegion
Set shtDest = ActiveWorkbook.Sheets("Reformatted")
'first empty row...
rL = shtDest.Cells(Rows.Count, 1).End(xlUp).Row + 1
For Each rw In rngSrc.Rows
pid = Trim(rw.Cells(2).Value)
If Not dictR.exists(pid) Then
dictR.Add pid, rL 'tracking row
dictC.Add pid, 2 'tracking column
shtDest.Cells(rL, 1).Value = pid
rL = rL + 1
End If
Set cD = shtDest.Cells(dictR(pid), dictC(pid))
cD.Resize(1, 3).Value = Array(Trim(rw.Cells(4).Value), _
Trim(rw.Cells(5).Value), _
Trim(rw.Cells(7).Value))
dictC(pid) = dictC(pid) + 3 'increment columns
Next rw
End Sub
Here is the routine that will do this for you. Place this routine in a standard code module:
Sub Courses()
Dim c&, i&, j&, k&, s$, v, w
v = [A1].CurrentRegion
For i = 2 To UBound(v)
If InStr(s, "|" & v(i, 2)) = 0 Then s = s & "|" & v(i, 2)
Next
ReDim w(1 To 1 + UBound(Split(s, "|")), 1 To 10000)
w(1, 1) = "PROVIDER_ID"
s = ""
k = 1
For i = 2 To UBound(v)
If s <> v(i, 2) Then
c = 1
j = 1
k = k + 1
s = v(i, 2)
w(k, 1) = v(i, 2)
End If
w(k, j + 1) = Trim$(v(i, 4)): If Len(w(1, j + 1)) = 0 Then w(1, j + 1) = "COURSE_TITLE" & c
w(k, j + 2) = Trim$(v(i, 5)): If Len(w(1, j + 2)) = 0 Then w(1, j + 2) = "COURSE_SUMMARY" & c
w(k, j + 3) = Trim$(v(i, 7)): If Len(w(1, j + 3)) = 0 Then w(1, j + 3) = "COURSE_URL" & c
j = j + 3
c = c + 1
Next
Sheets.Add After:=ActiveSheet
[A1].Resize(UBound(w, 1), UBound(w, 2)) = w
Cells.EntireColumn.AutoFit
End Sub
Then on the worksheet where you want the work done, press ALT-F8 to open the Macro Dialog.
Run the Courses macro.
That's it.
I have a two pivot tables constructed from some data (without the same structure), and I need to compare each entry using the Cartesian product of some other tables.
I'd like to have some nested for loops in VBA which grab data from the pivot tables based on some lists I've constructed.
My Lists:
+------+-----+
| Date | ID |
+------+-----+
| 9/1 | 123 |
| 9/2 | 124 |
| 9/3 | 200 |
| 9/4 | 201 |
| | 202 |
| | 300 |
| | 500 |
| | 999 |
+------+-----+
I can't figure out how to reference each entry in each column in these lists to use them in a command. I have in mind something like the following:
for each day in Date:
for each num in ID:
do_pivot_fetch(from pivot1, date=day, ID=num)
If these lists are maintained in Excel (this is how I always write these things, that way it's easy to change parameters when you need to), I usually use do...while to iterate over them. You can also try to find the last filled cell in each row and use it to calculate the range of a for loop, but I've found that to be glitchy given the things people like to do with excel spreadsheets.
So let's say you have the lists in column A (Date) and B (ID) on sheet 'Params'. Assuming you have headers in row 1, and an empty row after the last value in each list. Then your code would look something like this:
Sub useParams()
Dim Date_val, ID_val As Range
With ThisWorkbook.Worksheets("Params")
Set Date_val = Range("A2")
Do While Date_val.Value <> ""
Set ID_val = Range("B2")
Do While ID_val.Value <> ""
//do_pivot_fetch(from pivot1, date=Date_val.Value, ID=ID_val.Value)
Set ID_val = ID_val.Offset(1, 0)
Loop
Set Dateval = Date_val.Offset(1, 0)
Loop
End With
End Sub