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
Related
Looking for assistance/direction in setting up a loop? function to find related records in a table.
The table (tblTransactions) holds information about various transactions we are tracking. I am also using this table to reference a predecessor transaction. Now I am seeking a way to loop through the table to find related records.
The table has the following fields:
TransID - primary key
Grantor - name field
Grantee - name field
PTrans - number field that references TransID
Some sample data:
+---------+---------+---------+--------+
| TransID | Grantor | Grantee | PTrans |
+---------+---------+---------+--------+
| 1 | Bob | Sally | 0 |
| 2 | Jane | Emily | 0 |
| 3 | Sally | Beth | 1 |
| 4 | Beth | Sam | 3 |
+---------+---------+---------+--------+
Ideally I'd like to be able to start with TransID 4 and show all the transaction data, on separate rows, for the selected transaction (4) and it's predecessors.
Results would be:
+---+-------+-------+
| 4 | Beth | Sam |
| 3 | Sally | Beth |
| 1 | Bob | Sally |
+---+-------+-------+
Your question concerning querying self-referential data is very similar to this question in which the user has a table of employees, each of which may have a supervisor whose employeee record is also present in the same table, thus forming a hierarchy.
A relatively easy way to solve this would be using a DLookup expression within a loop or within a recursive call until the expression returned Null. For example, here is a recursive variant:
Function TransLookup(lngtrn As Long)
Dim lngptr
lngptr = DLookup("ptrans", "tbltransactions", "transid = " & lngtrn)
If Not IsNull(lngptr) Then
Debug.Print lngtrn ' Do something with the data
TransLookup (lngptr)
End If
End Function
Evaluated with your data this would yield:
?TransLookup(4)
4
3
1
This is of course only printing the transaction ID, but the function could alternatively populate a separate table with the data for each transaction if required.
However, returning the results record-by-record or populating a temporary table seems inelegant if we can construct a single SQL query to return all of the results in one go.
However,since MS Access does not support recursive SQL queries, the difficulty when querying such hierarchical data is not knowing how many levels to code ahead of time.
As such, you could use a VBA function to construct the SQL query itself, and thus always incorporating as many levels as is necessary to return the full dataset.
Indeed, this is the approach I put forward in my answer to the related question linked above - the function provided in that answer could equally be adapted to suit this situation, for example:
Function BuildQuerySQL(lngtrn As Long) As String
Dim intlvl As Integer
Dim strsel As String: strsel = selsql(intlvl)
Dim strfrm As String: strfrm = "tbltransactions as t0 "
Dim strwhr As String: strwhr = "where t0.transid = " & lngtrn
While HasRecordsP(strsel & strfrm & strwhr)
intlvl = intlvl + 1
BuildQuerySQL = BuildQuerySQL & " union " & strsel & strfrm & strwhr
strsel = selsql(intlvl)
If intlvl > 1 Then
strfrm = "(" & strfrm & ")" & frmsql(intlvl)
Else
strfrm = strfrm & frmsql(intlvl)
End If
Wend
BuildQuerySQL = Mid(BuildQuerySQL, 8)
End Function
Function HasRecordsP(strSQL As String) As Boolean
Dim dbs As DAO.Database
Set dbs = CurrentDb
With dbs.OpenRecordset(strSQL)
HasRecordsP = Not .EOF
.Close
End With
Set dbs = Nothing
End Function
Function selsql(intlvl As Integer) As String
selsql = "select t" & intlvl & ".* from "
End Function
Function frmsql(intlvl As Integer) As String
frmsql = " inner join tbltransactions as t" & intlvl & " on t" & intlvl - 1 & ".ptrans = t" & intlvl & ".transid "
End Function
Now, evaluating the BuildQuerySQL function with Transaction ID 4 yields the following SQL UNION query, with each level of nesting unioned with the previous query:
select
t0.*
from
tbltransactions as t0
where
t0.transid = 4
union
select
t1.*
from
tbltransactions as t0 inner join tbltransactions as t1
on t0.ptrans = t1.transid
where
t0.transid = 4
union
select
t2.*
from
(
tbltransactions as t0 inner join tbltransactions as t1
on t0.ptrans = t1.transid
)
inner join tbltransactions as t2
on t1.ptrans = t2.transid
where
t0.transid = 4
Such function may therefore be evaluated to construct a saved query, e.g. for Transaction ID = 4, the following would create a query called TransactionList:
Sub test()
CurrentDb.CreateQueryDef "TransactionList", BuildQuerySQL(4)
End Sub
Or alternatively, the SQL may be evaluated to open a RecordSet of the results, depending on the requirements of your application.
When evaluated with your sample data, the above SQL query will yield the following results:
+---------+---------+---------+--------+
| TransID | Grantor | Grantee | PTrans |
+---------+---------+---------+--------+
| 1 | Bob | Sally | 0 |
| 3 | Sally | Beth | 1 |
| 4 | Beth | Sam | 3 |
+---------+---------+---------+--------+
I have a Datatable containing data like:
Id | Val1 | Val2
0 | 0 | 1
1 | 3 | 0
2 | 0 | 3
3 | 2 | 1
I need to know the MAX value of the SUM of Val1+Val2 (in the example table is 3) and then extract rows having these values.
I know how I can extract rows with:
MyTable.Select("(Val1 + Val2) = 3")
But I'm not able to get the max value of the sum so to put it into the "Select"
I think you will have to loop through the table to find the maximum sum, which could be done using LINQ:
Dim maxSum = (
From r In MyTable.AsEnumerable()
Select r.Field(Of Integer)("Val1") + r.Field(Of Integer)("Val2")
).Max()
And then just use that in your select:
MyTable.Select("(Val1 + Val2) = " & maxSum)
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
I have a database of users who pay monthly payment. I need to check if there is continuity in these payments.
For example in the table below:
+---------+------------+
| user_id | date |
+---------+------------+
| 1 | 2015-02-01 |
| 2 | 2015-02-01 |
| 3 | 2015-02-01 |
| 1 | 2015-03-01 |
| 2 | 2015-03-01 |
| 3 | 2015-03-01 |
| 4 | 2015-03-01 |
| 1 | 2015-04-01 |
| 2 | 2015-04-01 |
| 3 | 2015-04-01 |
| 4 | 2015-04-01 |
| 5 | 2015-04-01 |
| 1 | 2015-05-01 |
| 2 | 2015-05-01 |
| 3 | 2015-05-01 |
| 4 | 2015-05-01 |
| 5 | 2015-05-01 |
| 1 | 2015-06-01 |
| 2 | 2015-06-01 |
| 3 | 2015-06-01 |
| 5 | 2015-06-01 |
| 3 | 2015-07-01 |
| 4 | 2015-07-01 |
| 5 | 2015-07-01 |
+---------+------------+
Until May everything was ok, but in June user 4 didn't pay although he paid in the next month (July).
In July users 1 and 2 didn't pay, but this is ok, because they could resign from the service.
So in this case I need to have information "User 4 didn't pay in June".
Is it possible to do that using SQL?
I use MS Access if it's necessary information.
From my experience, you cannot just work with paid in table to fill the gaps. If in case all of your user does not pay a specific month, it is possible that your query leaves that entire month out of equation.
This means you need to list all dates from Jan to Dec and check against each user if they have paid or not. Which again requires a table with your requested date to compare.
Dedicated RDBMS provide temporary tables, SP, Functions which allows you to create higher level/complex queries. on the other hand ACE/JET engine provides less possibilities but there is a way around to get this done. (VBA)
In any case, you need to give the database specific date period in which you are looking for gaps. Either you can say current year or between yearX and yearY.
here how it could work:
create a temporary table called tbl_date
create a vba function to generate your requested date range
create a query (all_dates_all_users) where you select the requested dates & user id's (without a join) this will give you all dates x all users combination
create another query where you left join all_dates_all_users query with your user_payments query. (This will produce all dates with all users and join to your user_payments table)
perform your check whether user_payments is null. (if its null user x hasn't paid for that month)
Here is an example:
[Tables]
tbl_date : id primary (auto number), date_field (date/Time)
tbl_user_payments: pay_id (auto number, primary), user_id (number), pay_Date (Date/Time) this is your table modify it as per your requirements. I'm not sure if you have a dedicated user table so i use this payments table to get the user_id too.
[Queries]
qry_user_payments_all_month_all_user:
SELECT Year([date_field]) AS mYear, Month([date_field]) AS mMonth, qry_user_payments_user_group.user_id
FROM qry_user_payments_user_group, tbl_date
ORDER BY Year([date_field]), Month([date_field]), qry_user_payments_user_group.user_id;
qry_user_payments_paid_or_not_paid
SELECT qry_user_payments_all_month_all_user.mYear,
qry_user_payments_all_month_all_user.mMonth,
qry_user_payments_all_month_all_user.user_id,
IIf(IsNull([tbl_user_payments].[user_id]),"Not paid","Paid") AS [Paid?]
FROM qry_user_payments_all_month_all_user
LEFT JOIN tbl_user_payments ON (qry_user_payments_all_month_all_user.user_id = tbl_user_payments.user_id)
AND ((qry_user_payments_all_month_all_user.mMonth = month(tbl_user_payments.[pay_date]) AND (qry_user_payments_all_month_all_user.mYear = year(tbl_user_payments.[pay_date]) )) )
ORDER BY qry_user_payments_all_month_all_user.mYear, qry_user_payments_all_month_all_user.mMonth, qry_user_payments_all_month_all_user.user_id;
[Function]
Public Function FN_CRETAE_DATE_TABLE(iDate_From As Date, Optional iDate_To As Date)
'---------------------------------------------------------------------------------------
' Procedure : FN_CRETAE_DATE_TABLE
' Author : KRISH KM
' Date : 22/09/2015
' Purpose : will generate date period and check whether payments are received. A query will be opened with results
' CopyRights: You are more than welcome to edit and reuse this code. i'll be happy to receive courtesy reference:
' Contact : krishkm#outlook.com
'---------------------------------------------------------------------------------------
'
Dim From_month, To_Month As Integer
Dim From_Year, To_Year As Long
Dim I, J As Integer
Dim SQL_SET As String
Dim strDoc As String
strDoc = "tbl_date"
DoCmd.SetWarnings (False)
SQL_SET = "DELETE * FROM " & strDoc
DoCmd.RunSQL SQL_SET
If (IsMissing(iDate_To)) Or (iDate_To <= iDate_From) Then
'just current year
From_month = VBA.Month(iDate_From)
From_Year = VBA.Year(iDate_From)
For I = From_month To 12
SQL_SET = "INSERT INTO " & strDoc & "(date_field) values ('" & From_Year & "-" & VBA.Format(I, "00") & "-01 00:00:00')"
DoCmd.RunSQL SQL_SET
Next I
Else
From_month = VBA.Month(iDate_From)
To_Month = VBA.Month(iDate_To)
From_Year = VBA.Year(iDate_From)
To_Year = VBA.Year(iDate_To)
For J = From_Year To To_Year
For I = From_month To To_Month
SQL_SET = "INSERT INTO " & strDoc & "(date_field) values ('" & J & "-" & VBA.Format(I, "00") & "-01 00:00:00')"
DoCmd.RunSQL SQL_SET
Next I
Next J
End If
DoCmd.SetWarnings (True)
On Error Resume Next
strDoc = "qry_user_payments_paid_or_not_paid"
DoCmd.Close acQuery, strDoc
DoCmd.OpenQuery strDoc, acViewNormal
End Function
you can call this public function from button or form or debug window:
?FN_CRETAE_DATE_TABLE("2015-01-01","2015-10-01")
this will generate from jan to oct and check whether you received payments or not.
[Screen]:
Something like this, look for user where next month is not paid for, but the month after that is paid for:
select user_id, month(date) + 1
from tablename t1
where not exists (select 1 from tablename t2
where t2.user_id = t1.user_id
and month(t2.date) = month(t1.date) + 1)
and exists (select 1 from tablename t3
where t3.user_id = t1.user_id
and month(t3.date) > month(t1.date) + 2)
Note 1: No dbms is specified, so month() function is just pseudo code. ANSI SQL has extract(month from column_name).
Note 2: in ANSI SQL date is a reserved word and needs to be delimited as "date".
Note 3: Just realized this answer will only return the first month even if several (consecutive) are missing...
I have written a simple query for this but I realize that it's not the best solution. Other solutions are still welcome.
SELECT user_id,
MIN(date) AS min_date,
MAX(date) AS max_date,
COUNT(*) AS no_of_records,
round((MAX(date)-MIN(date))/30.4+1,0) AS months,
(months-no_of_records) AS diff
FROM test
GROUP BY user_id
HAVING (round((MAX(date)-MIN(date))/30.4+1,0)-COUNT(*)) > 0
ORDER BY 6 DESC;
Now we can take a look at columns "no_of_records" and "months". If they are not equal, there was a gap for this user.
I have two sql tables pricetbl and paymenttbl. Due to the fact that prices for item change everyday, I need to update the paymenttbl with prices of the right time. If a transaction is made after the date-effective of a product's price, then the transaction price in the paymenttbl is the same as the latest product's price (as long as the date-effective is smaller than transaction date). I don't only want to limit myself here, I want to iterate over the whole table to make changes to the data so every single transaction has the proper price.
PriceTbl
pID| Price| Date Effective
---------------------------
1 | 10 | 01-12-2014
2 | 20 | 01-02-2015
3 | 20 | 02-12-2014
2 | 40 | 20-03-2015
1 | 50 | 02-03-2015
4 | 34 | 20-02-2015
1 | 40 | 25-05-2015
PaymentTbl
payID | pID | transDate | Price
--------------------------------
1 | 1 | 02-12-2014| 05
2 | 1 | 04-03-2015| 10
3 | 2 | 21-04-2015| 35
4 | 3 | 03-12-2014| 15
Expected table paymenttbl after running query
payID | pID | transDate | Price
--------------------------------
1 | 1 | 02-12-2014| 10
2 | 1 | 04-03-2015| 50
3 | 2 | 21-04-2015| 40
4 | 3 | 03-12-2014| 20
What will be the fastest and easiest way to retrieve and update data? My current way takes up to like 30 minutes to update the table since I iterate over the paymenttbl then I iterate over the pricetbl and compare it to each item in the paymenttbl.
My Code:
Function updateWorktoDateEff()
Dim rt As DAO.Recordset
Dim wt As DAO.Recordset
Dim db As DAO.Database
Dim sqlrt As String
Dim sqlwt As String
Set db = CurrentDb
sqlrt = "select * from pricetbl where (dailyrate IS NOT NULL) and (dateeffective is not null) order by id, dailyrate"
sqlwt = "select * from paymenttbl where (id > 0) order by id, date"
Set rt = db.OpenRecordset(sqlrt)
Set wt = db.OpenRecordset(sqlwt)
Do While Not wt.EOF
Do While Not ratetable.EOF
If rt!ID = wt!ID Then
If rt <= wt!Date Then
wt.Edit
wt!Rate = ratetable!dailyrate
wt.Update
End If
ElseIf ratetable!ID > worktable!ID Then
Exit Do
End If
rt.MoveNext
Loop
rt.MoveFirst
wt.MoveNext 'move to the next worktbl record
Loop
End Function
simply iterating/retrieving the data wouldn't do it. I will also need to be able to alter it.
The most efficient way to perform this update is not to use recordsets and go row-by-row, but to use SQL to do a set-based update. I tried writing a single update query to get there, but MS-Access got fussy about the query not being updateable, so I instead wrote two queries: one to write to a temporary table (tempNewPrices) and one to update PaymentTbl using that temporary table.
Here's the first query to create the temporary table that includes the new column NewPrice:
SELECT PaymentTbl.payID, PaymentTbl.pID, PaymentTbl.transDate, PaymentTbl.Price,
(
SELECT [Price]
FROM PriceTbl pr
WHERE pr.pID = PaymentTbl.pID
AND pr.[Date Effective] = (
SELECT Max([Date Effective])
FROM PriceTbl pr2
WHERE pr2.pID = pr.pID
AND pr2.[Date Effective]< PaymentTbl.[transDate]
)
) AS NewPrice INTO tempNewPrices
FROM PaymentTbl;
and here's the update query to change the Price value in PaymentTbl:
UPDATE tempNewPrices
INNER JOIN PaymentTbl ON tempNewPrices.payID = PaymentTbl.payID
SET PaymentTbl.Price = [NewPrice];
This should be much more efficient than looping through recordsets.