Just some background information. My table, (HireHistory) has 50 columns in it (horizontal). I have a Form (HireHistoryForm) which has a 2 text boxes (HistoryMovieID and HistoryCustomerID) and a button (the button runs the query 'HireHistoryQuery')
Here's an excerpt of my data (the CustomerID's are along the top):
So what I need is so that if a number is entered into the HistoryCustomerID box, then it displays that column. e.g. if the value entered is '1', then in my query it will show all records from column 1.
If a number is entered into the HistoryMovieID box (e.g. 0001) then it displays all instances of that MovieID for the specific CustomerID's. i.e. In column 1 is the ID's, so for ID=1 it will show "0001 on 19/05/2006" then will go on to find the next instance of '0001' etc.
For the HistoryCustomerID I tried to put this into my 'Field' for the query:
=[Forms]![HireHistoryForm]![HistoryCustomerID]
But it didn't work. My query just returned a column labelled '10' and the rows were just made up of '10'.
If you could help I'd greatly appreciate it. :)
No offense intended (or as little as possible, anyway), but that is a horrible way to structure your data. You really need to restructure it like this:
CustomerID MovieID HireDate
---------- ------- --------
1 0001 19/05/2006
1 0003 20/10/2003
1 0007 13/08/2003
...
2 0035 16/08/2012
2 0057 06/10/2012
...
If you keep your current data structure then
You'll go mad, and
It's extremely unlikely that anyone else will go anywhere near this problem.
Edit
Your revised data structure is a very slight improvement, but it still works against you. Consider that in your other question here you are essentially asking for a way to "fix" your data structure "on the fly" when you do a query.
The good news is that you can run a bit of VBA code once to convert your data structure to something workable. Start by creating your new table, which I'll call "HireHistoryV2"
ID - AutoNumber, Primary Key
CustomerID - Number(Long Integer), Indexed (duplicates OK)
MovieID - Text(4), Indexed (duplicates OK)
HireDate - Date/Time, Indexed (duplicates OK)
The VBA code to copy your data to the new table would look something like this:
Function RestructureHistory()
Dim cdb As DAO.Database, rstIn As DAO.Recordset, rstOut As DAO.Recordset
Dim fld As DAO.Field, a() As String
Set cdb = CurrentDb
Set rstIn = cdb.OpenRecordset("HireHistory", dbOpenTable)
Set rstOut = cdb.OpenRecordset("HireHistoryV2", dbOpenTable)
Do While Not rstIn.EOF
For Each fld In rstIn.Fields
If fld.Name Like "Hire*" Then
If Not IsNull(fld.Value) Then
a = Split(fld.Value, " on ", -1, vbBinaryCompare)
rstOut.AddNew
rstOut!CustomerID = rstIn!CustomerID
rstOut!MovieID = a(0)
rstOut!HireDate = CDate(a(1))
rstOut.Update
End If
End If
Next
Set fld = Nothing
rstIn.MoveNext
Loop
rstOut.Close
Set rstOut = Nothing
rstIn.Close
Set rstIn = Nothing
Set cdb = Nothing
MsgBox "Done!"
End Function
Note: You appear to be using dd/mm/yyyy date formatting, so check the date conversions carefully to make sure that they converted properly.
Related
DEFINE TEMP-TABLE tt_pay_terms NO-UNDO
FIELD pt_terms_code LIKE payment_terms.terms_code
FIELD pt_description LIKE payment_terms.description.
DEFINE VARIABLE htt AS HANDLE NO-UNDO.
htt = TEMP-TABLE tt_pay_terms:HANDLE.
FOR EACH platte.payment_terms
WHERE (
active = true
AND system_id = "000000"
)
NO-LOCK:
CREATE tt_pay_terms.
ASSIGN
pt_terms_code = payment_terms.terms_code.
pt_description = payment_terms.description.
END.
htt:WRITE-JSON("FILE", "/dev/stdout", FALSE).
I have written this query and it returns data like this
[pt_terms_code] => 0.4%!N(MISSING)ET46
[pt_description] => 0.4%! (MISSING)DAYS NET 46
While I believe (from using a SQL query) that the data should be
0.4%45NET46
0.4% 45 DAYS NET 46
I'm making an assumption that the % is probably some special character (as I've run into similar issues in the past). I've tried pulling all the data from the table, and I get the same result, (ie, not creating a temp table and populating it with all the only the two fields I want).
Any suggestions around this issue?
I'm still very new to 4gl, so the above query might be terribly wrong. All comments and criticisms are welcome.
I suspect that if you try this:
FOR EACH platte.payment_terms NO-LOCK
WHERE ( active = true AND system_id = "000000" ):
display
payment_terms.terms_code
payment_terms.description
.
END.
You will see what the query actually returns. (WRITE-JSON is adding a layer after the query.) You will likely discover that your data contains something unexpected.
To my eye the "%" looks more like formatting -- the terms are likely 0.4%.
You then seem to have some issues in the contents of the description field. My guess is that there was a code page mismatch when the user entered the data and that there is gibberish in the field as a result.
I'm new to programming and I'm trying to copy the content of a form field to another form field in the same Word document like this:
Sub Copyfield()
Dim Temp As String
Temp = ActiveDocument.FormFields("Field1").Result
ActiveDocument.FormFields("Field2").Result = Temp
End Sub
My problem is that my "Field1" is a piece of text of more than 255 characters which seems to be a problem with "Result". I know there is a very similar topic here: Passing MS-Access string >255 characters to MS-Word field but I still don't have the 50 reputation to comment on that thread.
Could anyone please help me understand how to implement the changes in my code?
Well, here's one possibility. Since I don't have your environment it was easier for me to test text in the document rather than another form field with so much content. You'll need to adjust the code accordingly.
The key is to get the Selection "inside" the form field so that it doesn't hit the "protection barrier". Just using FormField.Select puts the focus at the beginning of the field, which VBA is seeing as "protected". Moving one character to the right corrects that and long text can then be assigned to the Selection. But the field needs to have content.
So what my code is doing is "slicing off" the first word of the text to go into the form field. That's short enough to assign to the Result property and lets the Selection move to its right. Then the rest - the long text - can be assigned to the Selection.
You'll probably want to assign the entire FormField.Result to a string variable, then manipulate that string.
Sub WriteLongTextToFormField()
Dim ffld As word.FormField
Dim doc As word.Document
Dim rng As word.Range
Dim s1 As String, s2 As String
Set doc = ActiveDocument
'Get the long text
Set rng = doc.Range(doc.Paragraphs(1).Range.Start, doc.Paragraphs(6).Range.End)
'Split off a bit to go into FormField.Result
s1 = rng.Words(1)
rng.MoveStart wdWord, 1
'The rest of the long text, to be assigned to Selection.Text
s2 = rng.Text
Set ffld = doc.FormFields("Text1")
ffld.result = s1
ffld.Select
Selection.MoveRight wdCharacter, 1
Selection.Text = s2
End Sub
Ok, after 3 days at the border of madness, finally thanks to the help of #Cindy Meister (and some serious personal digging), I made it work. Maybe it's not a big deal for you geniouses out there but believe me for me it was like seeing everything in Matrix code (from the movie guys, the movie).
I want to post it and share because I tried to find it in every corner of our Internet and part of the extraterrestrial one and I couldn't. So hopefully it will be useful for another programming illiterate / dumb person (as myself).
Here is the code:
Sub CopyField()
Dim ffld As Word.FormField
Dim doc As Word.Document
Dim rng As String
Dim s1 As String, s2 As String
Set doc = ActiveDocument
'Get the long text
rng = ActiveDocument.FormFields("Field1").Result
'Split off a bit to go into FormField.Result
s1 = Left(rng, 4) 'Keeps the first 4 characters of the rng string starting from left to right this can be adapted
'The rest of the long text, to be assigned to Selection.Text
s2 = Mid(rng, 5) 'Starting from the 5th character from the left keeps the rest of the string
Set ffld = doc.FormFields("Field2")
ffld.Result = s1
ffld.Select
Selection.MoveRight wdCharacter, 1
ActiveDocument.Unprotect 'Unprotects the document!
Selection.Text = s2
ActiveDocument.Protect Type:=wdAllowOnlyFormFields, NoReset:=True 'Protects the document again!
ActiveDocument.Bookmarks("Field1").Select ' "Sends cursor" back to Field1
End Sub
Big part of the code is originally by #Cindy Meister... I just adapted it to my situation where I had 2 form fields instead of paragraphs. I also had to add some lines to unprotect the document at a certain point in order to make it work (ask the Pros for the reason) and a final instruction to come back to "Field1" (which is some pages up) after the process. Finally just a note for my dumb fellows: I added the macro "on exit" on the "Field1" properties to automatize the process.
Huge thanks Cindy again and I hope you help in my dark programming moments again ! (please do)
:)
Some context before the question.
Imagine file FileA having around 50 fields of different types. Instead of all programs using the file, I tried having a service program, so the file could only be accessed by that service program. The programs calling the service would then receive a DataStructure based on the file structure, as an ExtName. I use SQL to recover the information, so, basically, the procedure would go like this :
Datastructure shared by service program :
D FileADS E DS ExtName(FileA) Qualified
Procedure called by programs :
P getFileADS B Export
D PI N
D PI_IDKey 9B 0 Const
D PO_DS LikeDS(FileADS)
D LocalDS E DS ExtName(FileA) Qualified
D NullInd S 5i 0 Array(50) <-- Since 50 fields in fileA
//Code
Clear LocalDS;
Clear PO_DS;
exec sql
SELECT *
INTO :LocalDS :nullind
FROM FileA
WHERE FileA.ID = :PI_IDKey;
If SqlCod <> 0;
Return *Off;
EndIf;
PO_DS = LocalDS;
Return *On;
P getFileADS E
So, that procedure will return a datastructure filled with a record from FileA if it finds it.
Now my question : Is there any way I can assign the %nullind(field) = *On without specifying EACH 50 fields of my file?
Something like a loop
i = 1;
DoW (i <= 50);
if nullind(i) = -1;
%nullind(datastructure.field) = *On;
endif;
i++;
EndDo;
Cause let's face it, it'd be a pain to look each fields of each file every time.
I know a simple chain(n) could do the trick
chain(n) PI_IDKey FileA FileADS;
but I really was looking to do it with SQL.
Thank you for your advices!
OS Version : 7.1
First, you'll be better off in the long run by eliminating SELECT * and supplying a SELECT list of the 50 field names.
Next, consider these two web pages -- Meaningful Names for Null Indicators and Embedded SQL and null indicators. The first shows an example of assigning names to each null indicator to match the associated field names. It's just a matter of declaring a based DS with names, based on the address of your null indicator array. The second points out how a null indicator array can be larger than needed, so future database changes won't affect results. (Bear in mind that the page shows a null array of 1000 elements, and the memory is actually relatively tiny even at that size. You can declare it smaller if you think it's necessary for some reason.)
You're creating a proc that you'll only write once. It's not worth saving the effort of listing the 50 fields. Maybe if you had many programs using this proc and you had to create the list each time it'd be a slight help to use SELECT *, but even then it's not a great idea.
A matching template DS for the 50 data fields can be defined in the /COPY member that will hold the proc prototype. The template DS will be available in any program that brings the proc prototype in. Any program that needs to call the proc can simply specify LIKEDS referencing the template to define its version in memory. The template DS should probably include the QUALIFIED keyword, and programs would then use their own DS names as the qualifying prefix. The null indicator array can be handled similarly.
However, it's not completely clear what your actual question is. You show an example loop and ask if it'll work, but you don't say if you had a problem with it. It's an array, so a loop can be used much like you show. But it depends on what you're actually trying to accomplish with it.
for old school rpg just include the nulls in the data structure populated with the select statement.
select col1, ifnull(col1), col2, ifnull(col2), etc. into :dsfilewithnull where f.id = :id;
for old school rpg that can't handle nulls remove them with the select statement.
select coalesce(col1,0), coalesce(col2,' '), coalesce(col3, :lowdate) into :dsfile where f.id = :id;
The second method would be easier to use in a legacy environment.
pass the key by value to the procedure so you can use it like a built in function.
One answer to your question would be to make the array part of a data structure, and assign *all'0' to the data structure.
dcl-ds nullIndDs;
nullInd Ind Dim(50);
end-ds;
nullIndDs = *all'0';
The answer by jmarkmurphy is an example of assigning all zeros to an array of indicators. For the example that you show in your question, you can do it this way:
D NullInd S 5i 0 dim(50)
/free
NullInd(*) = 1 ;
Nullind(*) = 0 ;
*inlr = *on ;
return ;
/end-free
That's a complete program that you can compile and test. Run it in debug and stop at the first statement. Display NullInd to see the initial value of its elements. Step through the first statement and display it again to see how the elements changed. Step through the next statement to see how things changed again.
As for "how to do it in SQL", that part doesn't make sense. SQL sets the values automatically when you FETCH a row. Other than that, the array is used by the host language (RPG in this case) to communicate values back to SQL. When a SQL statement runs, it again automatically uses whatever values were set. So, it either is used automatically by SQL for input or output, or is set by your host language statements. There is nothing useful that you can do 'in SQL' with that array.
Searched around a bit and could not find an existing post with my exact question.
I am creating an Access (2003) database utilizing multiple forms and tables that need to communicate and share information with one another (i.e. - first/last name, etc.). The main form/table, "Personnel", will hold a majority of the information, yet the other forms/tables will only hold information pertinent to certain situations/circumstances, so not every entry in "Personnel" will have a concurrent entry in "Hired", for example. If a record with the same name already exists, I would like a MsgBox to tell me so and open that entry (in the form, for editing), if not a new entry would be created, auto-populated with pre-selected fields (i.e. - first/last name).
After an option is selected from a pull-down menu, the appropriate form is accessed/opened, "Hired", for example (This part works).
The copying information across forms does not work.
Dim rstPers, rstHired As DAO.recordset
Dim LastName As String
If status = "hired" Then
DoCmd.OpenForm "Hired Information"
Set rstPers Forms!Personnel.RecordsetClone
Set rstHired Forms![Hired Information].RecordsetClone
????
...
End If
I have tried to do this multiple ways but nothing seems to work. Nothing populates in the new Form or table. Am I looking at it the wrong way? Should I try a different approach?
Hope my explanation makes sense. Thanks for any help.
-Charles
You have a bad approach indeed.
Your forms are linked to a table. So you should avoid as much as possible to manipulate or retrieve a form's data directly using a recordsetclone, instead try to retrieve this data directly from the table.
So if you want to open your "hired information" form:
Dim RS As Recordset
Dim IDperson As String
IDperson = me.ID ' or whatever
Set RS = CurrentDb.OpenRecordset("SELECT ID FROM TableHired WHERE ID='" & IDperson & "'")
If RS.BOF Then
' No record exist - Open form in record creation
DoCmd.OpenForm "FormHired", acNormal, , , acFormAdd
Else
' record exists, open form and position it oon the record's ID
DoCmd.OpenForm "FormHired", acNormal, , "ID='" & RS!ID & "'", acFormEdit
End If
Of course it won't work like this as you didn't provide enough info.
Review this code and adapt it with your fields name (IDs), table name (TableHired) and FormName (FormHired) and following the situation on where and how you will trigger it. Also if your ID is not a string, you should remove the quotes in the SQL
I’m trying to create either a report or form that displays data in essentially a “calendar” form.
I have a course query that is (simplified) as “Course name”; “course days”; “course times”---
Course; Days; Times
PSY 1; MW; 8A-9A
SOC 150; M; 8A-11A
ANTH 2; Tu; 8A-9A
ANTH 199; MW; 8A-9A
In Access, I’m trying to create a form based on the query that would give me a matrix of the following:
Columns: Times in hour increments
Rows: Days of week
So, for example, with the above data, it would appear like this:
Edit: Yargh, I can't submit an image unfortunately. So, here is a link to a "course schedule" that is essentially what I'm trying to do: Schedule
I have no idea where to start with this. Any tips (or links)?
Edit:
One idea I have is to create a form with a field for every possible cell in the matrix (so, for example, there would be one "Monday, 8-9A" field--and that field would be a filter on the query that ONLY displays results where "day" contains "M" and BeginTime or EndTime or between 8A and 9A). Unfortunately, I'm not sure how to do that.
You can do something close to what you seem to want as an Access form, but it's not easy. This screen capture displays your sample data in a Datasheet View form whose record source is an ADO disconnected recordset. It uses conditional formatting to set the text box background color when the text box value is not Null. Your picture suggested a different color for each Course, but I didn't want to deal with that when more than one Course can be scheduled in the same time block ... my way was simpler for me to cope with. :-)
The code to create and load the disconnected recordset is included below as GetRecordset(). The form's open event sets its recordset to GetRecordset().
Private Sub Form_Open(Cancel As Integer)
Set Me.Recordset = GetRecordset
End Sub
Note I stored your sample data differently. Here is my Class_sessions table:
Course day_of_week start_time end_time
------ ----------- ---------- -----------
PSY 1 2 8:00:00 AM 9:00:00 AM
PSY 1 4 8:00:00 AM 9:00:00 AM
SOC 150 2 8:00:00 AM 11:00:00 AM
ANTH 2 3 8:00:00 AM 9:00:00 AM
ANTH 199 2 8:00:00 AM 9:00:00 AM
ANTH 199 4 8:00:00 AM 9:00:00 AM
This is the function to create the disconnected recordset which is the critical piece for this approach. I developed this using early binding which requires a reference for "Microsoft ActiveX Data Objects [version] Library"; I used version 2.8. For production use, I would convert the code to use late binding and discard the reference. I left it as early binding so that you may use Intellisense to help you understand how it works.
Public Function GetRecordset() As Object
Dim rsAdo As ADODB.Recordset
Dim fld As ADODB.Field
Dim db As DAO.Database
Dim dteTime As Date
Dim i As Long
Dim qdf As DAO.QueryDef
Dim rsDao As DAO.Recordset
Dim strSql As String
Set rsAdo = New ADODB.Recordset
With rsAdo
.Fields.Append "start_time", adDate, , adFldKeyColumn
For i = 2 To 6
.Fields.Append WeekdayName(i), adLongVarChar, -1, adFldMayBeNull
Next
.CursorType = adOpenKeyset
.CursorLocation = adUseClient
.LockType = adLockPessimistic
.Open
End With
strSql = "PARAMETERS block_start DateTime;" & vbCrLf & _
"SELECT day_of_week, Course, start_time, end_time" & vbCrLf & _
"FROM Class_sessions" & vbCrLf & _
"WHERE [block_start] BETWEEN start_time AND end_time" & vbCrLf & _
"ORDER BY day_of_week, Course;"
Set db = CurrentDb
Set qdf = db.CreateQueryDef(vbNullString, strSql)
dteTime = #7:00:00 AM#
Do While dteTime < #6:00:00 PM#
'Debug.Print "Block start: " & dteTime
rsAdo.AddNew
rsAdo!start_time = dteTime
rsAdo.Update
qdf.Parameters("block_start") = dteTime
Set rsDao = qdf.OpenRecordset(dbOpenSnapshot)
Do While Not rsDao.EOF
'Debug.Print WeekdayName(rsDao!day_of_week), rsDao!Course
rsAdo.Fields(WeekdayName(rsDao!day_of_week)) = _
rsAdo.Fields(WeekdayName(rsDao!day_of_week)) & _
rsDao!Course & vbCrLf
rsAdo.Update
rsDao.MoveNext
Loop
dteTime = DateAdd("h", 1, dteTime)
Loop
rsDao.Close
Set rsDao = Nothing
qdf.Close
Set qdf = Nothing
Set GetRecordset = rsAdo
End Function
Actually, if you look at the following video of mine, you can see a calendar created in Access that runs inside of a browser with the new Access Web publishing feature.
http://www.youtube.com/watch?v=AU4mH0jPntI
So, all you really need to do here is format a form with text boxes and setup some code to fill them. VBA or even the above video shows this is quite easy for Access.
I doubt that you will find an easy solution for this problem in Access forms or reports.
The issue is that you need to format different cells differently, and that cells can span multiple rows and have to be merged.
If I were you, I would go in either of these 2 directions:
Drive Excel from Access, because you can format and merge cells independently
Use the Web Browser Control to display HTML that you construct yourself using tables, or a more high-level library like FullCalendar
I would be partial to tryingthe Web Browser and find the right library that can properly format the data.
I know this post is quite old, but I had the same requirement but I got round it by doing the following:
I created a module that would write HTML code (as a text file) to produce the data using a table.
I used the colspan feature of tables to enable me to dynamically produce the view I needed.
The HTML file was created on the Form_Open event and by pointing the webbrowser control to the HTML file the view shows the latest data.
A css file is used to handle the visual display of the HTML file so that it looks similar to the Access forms already in use.
If anyone is interested I can post some code to further illustrate.