Is it possible to create an SQL query to compare a field within a single table to see if a change has been made and if possible list the before and after?
I have the following SQL query written in Excel 2010 VBA, which connects to an Oracle PostGreSQL database
Dim au As String
au = "SELECT id, priority, flag, code " _
& "FROM hist WHERE ( aud_dt >= '18/05/2020' AND aud_dt <='18/05/2020' ) " _
Set rs = conn.Execute(au)
With ActiveSheet.QueryTables.Add(Connection:=rs, Destination:=Range("A1"))
.Refresh
End With
Where fields include:
"priority" is the field that I'd like to check for changes which will
be a single number between 0-9
"code" is the record that has been
assigned the priority and is a mixture of numbers and letters up to 7
characters
"flag" shows a 1 as the active record, and 2 as an edited
record
"id" refers to the user account
I'd ideally like to end up with something like: id | priority | flag | priority_old | flag_old | code
Which should show the before and after changes to the priority. If the record shows priority=3 and flag=2 and code=Ab12, there must also be record with a 1 flag, as that is now the active record. If it has the same priority number for the code I'm not interested in it as that just means something else was changed instead as I have not listed all the column fields.
If the active record now shows priority=4, flag=1 and code=Ab12, that would be exactly the record I need to see.
Consider a self-join query (possibly you need to adjust date filter in WHERE depending on when items change):
SELECT h1.id, h1.priority, h1.flag,
h2.priority AS priority_old, h2.flag AS flag_old, h1.code
FROM hist h1
LEFT JOIN hist h2
ON h1.code = h2.code
AND h1.priority <> h2.priority
AND h1.flag = 1
AND h2.flag <> 1
WHERE (aud_dt >= '2020-05-18' AND aud_dt <='2020-05-18')
Related
Using MS Access, I have a table called "Locations" and a table called "Routes". I want to create a form that will basically present the routes table to the user (i.e select "from" and select "to") and will filter the "to" locations based on what the user has selected for the "from" location, i.e. a cascading combo box. The corresponding "Route.ID" will be stored in a resultant table on completion of the form.
Location has the following columns -
ID
Name
Notes
1
London
Great Britain
2
Manchester
Great Britain
3
Alabama
USA
Routes has the following columns -
ID
From_LID
To_LID
KM
Notes
1
1
2
450
Using the M1 route
2
2
1
450
Using the M1 route
3
1
2
485
Using the inside routes
4
2
1
485
Using the inside routes
5
1
3
5450
Too far to consider
6
3
1
5450
Too far to consider
I want to create a form with a cascading combo box - "ComboFrom", "ComboTo". Where "ComboFrom" will search the table for a list of distinct IDs, and using a JOIN with the Locations table, I can display the location names. "ComboTo" is dependant on the ID value in "ComboFrom" and will therefore only return a list of locations where it matches the From_LocationID of the routes table.
I have the first part more or less done. ComboFrom has the below query:
SELECT DISTINCT Location.Location_ID, Location.[Location Name], Location.Description
FROM Location INNER JOIN Route ON Location.Location_ID = Route.From_LID
ORDER BY Location.[Location Name];
ComboTo has the below query:
SELECT Location.Location_ID, Location.[Location Name], Location.Description, Route.From_LID
FROM Location INNER JOIN Route ON Location.Location_ID = Route.To_LID
WHERE (((Route.From_LID)=[Forms]![fmrRoute1]![From_LID]))
ORDER BY Location.[Location Name];
The piece of code in the "Where" clause in the ComboTo field basically gets the input of the ID from ComboFrom and outputs the correct IDs from the corresponding To_LID list. I then add vba against the update events for ComboFrom to requery ComboTo field.
What I am having trouble figuring out is how I can get the corresponding route ID to display correctly. i.e if I choose "1" for ComboFrom, and "3" for ComboTo, the Route ID should show "5" and not "7" (where "7" is a new record in the Routes table). Do I have to have a separate query to search for ComboFrom and ComboTo and return the resultant Routes ID? If so, how do I do this and attach it to the field in the form that will automatically update every time the ComboFrom and ComboTo fields change?
Is there a better way to do what I am trying to do?
Thanks in advance for any assistance!
This looks like cascading combo boxes into filtering a form from those two unbound combo boxes.
You can google those two ideas.
The self join made this tricky. The key was ignoring the relationship between Location.ID and From_LID. That relationship is in the database but ignored (deleted) in the query.
this gives us the sql we will need for the second combo box record source.
SELECT Routes.From_LID, Routes.To_LID, First(Locations.LocationName) AS FirstOfLocationName
FROM Locations INNER JOIN Routes ON Locations.ID = Routes.To_LID
GROUP BY Routes.From_LID, Routes.To_LID
HAVING (((Routes.From_LID)=2))
ORDER BY First(Locations.LocationName)
'just need to replace the 2
Private Sub cmbFrom_AfterUpdate()
'cascade combobox
Dim strRowSource As String
strRowSource = "SELECT Routes.From_LID, Routes.To_LID, First(Locations.LocationName) AS FirstOfLocationName" & _
" FROM Locations INNER JOIN Routes ON Locations.ID = Routes.To_LID " & _
"GROUP BY Routes.From_LID, Routes.To_LID " & _
"HAVING (((Routes.From_LID) = " & Me.cmbFrom & "))" & _
"ORDER BY First(Locations.LocationName)"
Debug.Print strRowSource
Me.cmbTo.RowSource = strRowSource
Me.cmbTo.Visible = True
'a look and feel choice
End Sub
[![enter image description here][2]][2]
Private Sub cmbTo_AfterUpdate()
'filter form 'most of the time you want to create a search form where you filter to the records you want
Me.Filter = "From_LID = " & Me.cmbFrom & " AND To_LID = " & Me.cmbTo
Me.FilterOn = True
Me.Detail.Visible = True
End Sub
I am using Visual Studio 2017 and I installed the latest crystal reportviewer (22)
What I want is to click a button and create a report from the customer that is selected in the datagridview and the addresses that are shown in the second datagridview.
I managed to do all that but the problem is that a few fields contain numbers which need to be converted to text. An SQL query I would use to do this would be like:
SELECT c.customer_nr, c.status, s.rename FROM CUSTOMERS c INNER JOIN SETUP s on s.id = c.status WHERE s.afk = 'STA'
In my SETUP database I have the columns ID,AFK and RENAME so if the status would be 1 it would convert to text: "ACTIVE", if status = 2 it would convert to "INACTIVE" for example.
I could do something with a formula field like this:
IF ({c.status} = 1) THEN "ACTIVE" ELSE
IF ({c.status}) = 2 THEN "INACTIVE"
but that is not good because i could add another status or change the name in the database etc.
So then I tried with an SQL expression field and I put something like this:
(
SELECT "SETUP"."RENAME" FROM SETUP
WHERE "SETUP"."AFK" = 'STA' AND "SETUP"."ID" = "CUSTOMERS"."STATUS"
)
There must be something wrong because I get the correct conversion but there is only one address in the database but I get 7 pages all with the same address. There should only be one address like I get when I remove the SQL expression field. Where does it go wrong?
* EDIT *
I found the problem. When I create a new database that contains only unique id's then it works. In my original database I have multiple times the id's 1,2,3,4,5 but with different abbreviations in column AFK. Somehow the query looks for the id value and every time it finds this id no matter the AFK value it generates an entry for the address value.
Maybe in the future I will find out how this exactly works for now I have a workaround.
Create a new table for example CrRepSta and add the following entries:
ID,AFK,RENAME
1,STA,Active
2,STA,Inactive
etc
The new query:
(
SELECT "CrRepSta"."RENAME" FROM CrRepSta
WHERE "CrRepSta"."AFK" = 'STA' AND "CrRepSta"."ID" = "CUSTOMERS"."STATUS"
)
And by the way the statement "CrRepSta"."AFK" = 'STA' is not really needed.
I'm using MS Access
The SQL below updates the CurrNumTees field in the Parent tblContact records with the number of tblTorTee records that have an end date (which is not the ultimate effect I am aiming for, but I provide it as a starting point.
UPDATE tblContact
INNER JOIN tblTorTee ON tblContact.ContactId = tblTorTee.TorId
SET tblContact!CurNumTees = DCount("[tblTorTee.EndDate]",
"tbltortee","Torid = " & [ContactId]);
I need to update the CurrNumTees field with the number of records in tblTorTee that do not have an EndDate, in other words, that field is blank. I’ve tried using WHERE and HAVING and IS NULL in various combinations and locations, but without success. Could you help point me in the right direction?
The MS Access COUNT function does not count nulls, so I think you have to do this in two stages.
Firstly create a query like this:
SELECT TorId, IIF(ISNULL(EndDate),1,0) AS isN
FROM tblTorTee
WHERE EndDate IS NULL;
And save it as QryEndDateNull
Now you can run an Update Query like this:
UPDATE tblContact
SET tblContact.CurNumTees = DSUM("IsN","QryEndDateNull","TorId = " & [ContactID]);
Saving calculated data (data dependent on other data) is usually a bad design, especially aggregate data. Should just calculate when needed.
Did you try the IS NULL criteria within the DCount()?
UPDATE tblContact Set CurNumTees = DCount("*", "tblTorTee", "EndDate Is Null AND TorId = " & [ContactId]);
I'm using an Access 2007 database in ASP.Net where I need to extract data from a table if an item meets multiple 'AND' clauses.
For example:
Select * from tableA
where tableA.column1 = myvalue1
and (tableA.column2 = myvalue2
and tableA.column2 = myvalue3)
(The '(' and ')' parenthesis around the 'and' clauses are an Access requirement)
myvalue1 exists multiple times in column1 because column2 stores many different pieces of data for the column1 value.
However, I only want the results to show those column1 values which meet all of the column2 search criteria.
For example, column1 contains employee surnames, column2 contains the qualifications of the employee.
One employee in column1 may hold many qualifications in column2.
Searching with the 'OR' clause for employees who hold all the required qualifications for an assignment would produce a table with multiple rows for each employee.
What is required is a single row for each employee who holds every one of the required qualifications.
Let's say employee A has qualifications X, Y and Z. The employee's name will be in a row in column 1 each time a qualification is added to column2, so the employee's name appears in column1 3 times. Assume now employee B also has qualifications X, Y and Z but employee C has W, X and Y. If an assignment needs qualifications X,Y and Z I don't want employee C appearing in the results of a search as employee C won't hold the relevant qualifications.
When I use the above 'select' command with just one 'and' clause the search works fine, but with two 'and' clauses nothing is returned, even though I know there are items in column1 that meet both of the 'and' criteria.
What am I missing in the concatenation of the 'and' clauses?
The following solution will work, but performance will suffer. This will return 1 row per person, I can rewrite it to return both rows.
Select * from tableA
where tableA.column1 = myvalue1
and (tableA.column2 = myvalue2
and DCount("column2", "TableA", "column1 = """ & column1 & """ AND column2 = """ & myvalue3 & """" ) <> 0)
Your fundamental problem is that a select query without a group by or pivot clause only evaluates 1 row at a time. I'd probably pivot the data, concatenate it, and then evaluate it for better performance in the end. But that's more complicated, and requires more information on the table structure and what you want to do.
Based on new information, I'll submit this answer:
My gut feeling is that you can't do this in one simple query. This sounds cludgy, but I think you're going to need one query created that's a SELECT DISTINCT query, and that query will pull one record for each employee:
Select DISTINCT Column1 from tableA
Call this query something like qryEmployees
Next you're going to need to create a new table. Call it tblQualifications. It will contain 2 fields; EName and EQualifications. We're going to fill that in a minute.
Next you're going to need to loop through each record in qryEmployees and write the employee name and their qualifications into tblQualifications.
'Run through every Employee name
Do While qryEmployees.EOF = False
'Set up a temp variable, MyQual, and set it equal to Empty
MyQual = ""
'Pull all qualifications from TableA for the current Employee
Set rec = db.OpenRecordset ("Select Column1, column2 from TableA WHERE Column1 = '" & qryEmployees.Column1 & "'")
'Looping through the qualifications
Do While rec.EOF = False
'Add the qualification to the MyQual string
MyQual = MyQual & rec("Column2") & ", "
rec.MoveNext
Loop
'Now that we have all the qualifications in a string, trim off the last ", "
MyQual = left(MyQual, len(MyQual)-2)
'Open up tblQualifications and fill it with the Employee and a string of all of their qualifications
tblQualifications.AddNew
tblQualifications(0) = qryEmployees.Column1
tblQualifications(1) = MyQual
tblQualifications.Update
'Move on to the next Employee and repeat the process
qryEmployees.MoveNext
Loop
Now you have one table with Employee Name in one field, and all of his qualifications in the next. If you use the LIKE operator on this table, you should get your results.
Select * from tblQualifications
where tblQualifications.EName = myvalue1
and (tblQualifications.EQualifications LIKE myvalue2
and tblQualifications.EQualifications LIKE myvalue3)
It's not pretty, but it will work (with a little putzing with the code above, as I'm writing it off the top of my head and it's untested).
Thanks Johnny, your suggestion inspired me to write a slightly different approach to the solution.
I swapped the 'AND' to an 'OR' as others have suggested to extract any employee into a table if they have any of the qualifications.
The result is a table with every required qualification for every employee who has one of those qualifications.
I then did a count elsewhere of the number of qualifications required for the assignment and compared this to whether or not the employee had the required number of qualifications, as follows:
Dim objSearchTable As DataTable = DSSearch.Tables("Employees")
Dim i As Integer
If QualificationsCount > 0 Then
For i = 0 To objSearchTable.rows.count - 1
strEmployee = objSearchTable.Rows(i).Item("column1")
Dim foundRows As DataRow() = objSearchTable.[Select]("column1 = '" & strEmployee & "'")
Dim numberOfRecords As Integer = foundRows.count
If numberOfRecords < QualificationsCount Then
objSearchTable.Rows(i).Delete
End If
Next
End If
objSearchTable.AcceptChanges
This deletes every instance of an employee from the table if the count of qualifications is less than the number required.
The solution isn't as elegant as extracting only the correct employees from the database to begin with.
There is also an overhead on extracting every employee from the database, but the end result is a table with just one row for each employee with all the qualifications, which does what I need.
I'm using MS-ACCESS database.
From the prject I use and made some other questions the table NOEUDS and INFRA (that should be updated):
Table INFRA:
RECNO - NOEUD - SECURISE
00000008 C002 F
00000005 C009 F
00000001 C035 F
00000002 C001 F
00000003 C036 F
00000006 C012 F
00000007 C013 F
TABLE NOEUDS:
NOEUD TYPE_MAT N_AMONT
C021 COF 100
C022 COF 229
C023 COF 130
C002 COF 111
I want to create a query that checks on NOEUDS the nodes C* that are missing inside INFRA table, if not should be inserted a new one.
The problem is the RECNO field that works as a control and can not be duplicated (not primary key because all the DB is only a repositoty for the program that controls it).
All the fields are text so RECNO is a consecutive counting using HEX numbers as shown.
I used the query to select:
SELECT (SELECT MAX(CINT(INFRA.RECNO))+1 AS N FROM INFRA),
NOEUDS.NOEUD, "F" AS Expr2
FROM NOEUDS
WHERE (((NOEUDS.NOEUD) Like "C*"
And (NOEUDS.NOEUD) Not In (SELECT NOEUD FROM INFRA)));
The result was:
9 C021 F
9 C022 F
9 C023 F
SHOULD BE:
9 C021 F
A C022 F
B C023 F
I need some help on this one so I can insert the correct RECNO in hexadecimal counting after 00000019 passes to 0000001A and so on.
thanks in advance
UPDATE 1:
The program we use uses a Access database as storage. When I add a noeud using the program I have to insert some more info using the menus needed for the maps and as built information. The problem is that a lot info is redundant and the program can not handle it automatically. I am trying to work lees and insert the possible information using querys.
Every time I insert a noeud in noeuds table, is needed to insert a line in INFRA table only with RECNO (sequential counting from the last one), the NOEUD and some other info (to complete the autocad table tag). Since I have hundreds of Cxxx, Bxxx, Pxxx, Gxxx equipments I sabe for each project some hour of boring work.
I need help on counting a sequential way of adding RECNO for each NOEUD found in NOEUDS table that will be inserted in INFRA table.
UPDATE 2:
I'm inserting each noeud by hand. Is it possible to join in a way that it takes the list from the noeuds that I want to insert and insead of doing 1 by 1 it takes the list and does in a sequence?
the 2 queries are these:
Equipes I want to add at table INFRA:
SELECT NOEUDS.NOEUD
FROM NOEUDS
WHERE (((NOEUDS.NOEUD) Like "C*" And (NOEUDS.NOEUD) Not In (SELECT NOEUD FROM INFRA)));
Insertion by hand:
INSERT INTO INFRA ( recno, NOEUD, SECURISE )
SELECT (SELECT Right(String(8, "0") & Hex(Max(Val("&H" & RECNO)) + 1), 8) AS N FROM INFRA), NOEUDS.NOEUD, "F" AS Expr2
FROM NOEUDS
WHERE (NOEUDS.NOEUD=[INSERT CHAMBRE?]);
I think a VBA solution should be better than trying to do what you want with only SQL. If you don't have much VBA experience, it could still be achievable because the required VBA should be fairly basic. See if this code outline is enough to get you started.
Public Sub AddToInfra()
Const cstrQuery As String = "qryUnmatchedNoeuds" ' Note 1 '
Dim db As DAO.Database ' Note 2 '
Dim fld As DAO.Field
Dim rsFrom As DAO.Recordset
Dim rsTo As DAO.Recordset
Set db = CurrentDb
Set rsFrom = db.OpenRecordset(cstrQuery, dbOpenSnapshot)
Set rsTo = db.OpenRecordset("infra", dbOpenTable, dbAppendOnly)
Do While Not rsFrom.EOF
rsTo.AddNew
For Each fld In rsFrom.Fields ' Note 3 '
If Not fld.Name = "RECNO" Then
rsTo.Fields(fld.Name).Value = fld.Value
End If
Next fld
rsTo!RECNO = Next_InfraRecno ' Note 4 '
rsTo!SECURISE = "F" ' Note 5 '
rsTo.Update
rsFrom.MoveNext
Loop
rsTo.Close
rsFrom.Close
Set fld = Nothing
Set rsFrom = Nothing
Set rsTo = Nothing
Set db = Nothing
End Sub
Notes:
I used a saved query based on my best guess as to what you want. See the SQL below.
DAO.Database requires a reference to Microsoft DAO Object Library. If your Access version is 2000 or maybe Access XP, you may need to set that reference (from VBE main menu, Tools->References).
I decided the destination table would include fields which match the name and data type of the fields in the source recordset. If that doesn't work for you, substitute something like this for each of the common fields: rsTo!YourFieldNameHere.Value = rsTo!YourFieldNameHere.Value (And drop .Value if you prefer.)
Create a Next_InfraRecno() function to return the next RECNO value. Translate the approach we used earlier into a function. Post a new question if you run into trouble ... show us your code, error message and line which triggers the error (if any), and anything else we need to know. :-)
I got the impression you want SECURISE = "F" for each of the inserted rows.
In a comment you mentioned "Use field ANCIEN for storage of counting". I don't know what's involved for that and hope, whatever it is, you can integrate it into this code outline. If not, sorry. :-(
Here is the SQL for my qryUnmatchedNoeuds query:
SELECT n.DELETED, n.NOEUD
FROM
noeuds AS n
LEFT JOIN infra AS i
ON n.NOEUD = i.NOEUD
WHERE
(((n.NOEUD) Like "c*")
AND ((i.NOEUD) Is Null));
Although I don't understand your question very well, I hope this answer provides something you can use.
My INFRA table has a text column named RECNO. The table contains one row.
RECNO
00000019
This query give me "1A" as N.
SELECT Hex(Max(Val("&H" & RECNO)) + 1) AS N
FROM INFRA;
To pad N with zeros to a width of 8, I can use this query which gives me "0000001A" as N.
SELECT Right(String(8, "0") & Hex(Max(Val("&H" & RECNO)) + 1), 8) AS N
FROM INFRA;
Regarding the rest of your question, my instinct would be to open a recordset containing the unmatched NOEUDS.NOEUD values, then move through the recordset rows and insert each NOEUD value, your custom RECNO sequence number, and the "other info" into the INFRA table.