Record-at-a-time processing

  • Thread starter Thread starter GaryS
  • Start date Start date
G

GaryS

This is a new frontier for me and I don't have any
references on the topic, so I need some tips.

I need to send a table to a label-making printer. To make
the best use of white space on the label, I need to
examine 4 fields in each record and reformat them if
necessary.

Assume that the table (containing a small number of
records) has already been made. What I need to do is read
through it, one record at a time, reformat the fields
according to criteria I've developed, until EOF.

Before I can even get started, here are my basic questions:

1. How do I get started?

2. How do I get next record?

3. Once I have the current record, how do I refer to a
field in it (e.g., F1, F2 etc.)?

4. How do I change the value of a field?

5. How do I know when I'm done (i.e., test for EOF)?

I can learn from an example procedure that covers those
situations!

TIA
 
GaryS said:
This is a new frontier for me and I don't have any
references on the topic, so I need some tips.

I need to send a table to a label-making printer. To make
the best use of white space on the label, I need to
examine 4 fields in each record and reformat them if
necessary.

Assume that the table (containing a small number of
records) has already been made. What I need to do is read
through it, one record at a time, reformat the fields
according to criteria I've developed, until EOF.

Before I can even get started, here are my basic questions:

1. How do I get started?

2. How do I get next record?

3. Once I have the current record, how do I refer to a
field in it (e.g., F1, F2 etc.)?

4. How do I change the value of a field?

5. How do I know when I'm done (i.e., test for EOF)?

I can learn from an example procedure that covers those
situations!

While it is possible to do what you're asking, it seems to me much more
likely that you would use a report (based on a query of your table) to
generate your labels, with the formatting you need applied either in the
query or in the controls on the report. Unless there's some reason that
approach won't serve, it would save you any need to write code to handle
the record processing you describe.

Would you like to describe the kinds of formatting you need for these
labels, and anything special about them? Maybe we can tell you what to
put in your query or report.
 
Well, Dirk, are you sure you want this logic? Here it is,
although it would best be represented using a decision
table. (I STILL would like to know the basics of
sequential processing, since it's part of my Access
learning program.)

The label printer is a specialized printer for labels to
be affixed to plant tags.

Its layout can either be 2 or 3 lines, depending on the
contents of the 4 fields that need to be printed (genus,
species, variety, and most frequent common name).

The goal is to use 2 lines whenever possible, since the
font can be larger and therefore more readable from a
longer distance away. (The plant tags will be read by
people on a garden tour.)

There are 3 cases, dependent on the contents of the GSVC
fields, where 2 lines (and hence a larger font) can be
used, and 3 cases where 3 lines must be used.

I could give you those business rules if you want. But
the possibilities include examining the LEN(genus &
species), whether any combination of species, variety, or
common name is null.

I've laid out the decision table for those rules. The
output of this procedure will result in either combining
genus and species into one line (field), keeping them as
separate lines (because together they are too long to
fit), deciding which field should go into line 2 (field 2)
(which could be almost any of the fields except genus),
and deciding whether or not a 3rd line is necessary based
on length and nullity of the variety and common name.

It is much easier to program this in Access than in the
label driver software. The latter CAN select a layout
consisting of 2 lines of large font or 3 lines of smaller
font based on what is exported to it, but it is not
possible (or easy) to examine individual field contents
and perform moving and concatenation.

IOW, I have already laid out the logic for all this string
manipulation and determined that it would be *much easier*
(and more transparent to a maintenance programmer) to do
it programmatically and sequentially rather than try to
construct some obscure but tricky set of queries in SQL!

Hope this sheds some more light on the situation and hope
someone sees the need to address my original 5 questions.

Thanks.
 
(comments interspersed with quoted text)

GaryS said:
Well, Dirk, are you sure you want this logic? Here it is,
although it would best be represented using a decision
table. (I STILL would like to know the basics of
sequential processing, since it's part of my Access
learning program.)

That's fine, but it's important also to learn that relational database
systems are primarily geared toward set operations -- that's what SQL is
all about, really -- and whenever you are faced with a choice between
sequential record processing and doing the whole thing with a SQL
statement or two, the SQL approach will almost always be faster and more
efficient. I spent 20 years doing procedural programming, and I still
often find myself writing procedural code where a couple of SQL
statements would do the job.
The label printer is a specialized printer for labels to
be affixed to plant tags.

Its layout can either be 2 or 3 lines, depending on the
contents of the 4 fields that need to be printed (genus,
species, variety, and most frequent common name).

The goal is to use 2 lines whenever possible, since the
font can be larger and therefore more readable from a
longer distance away. (The plant tags will be read by
people on a garden tour.)

There are 3 cases, dependent on the contents of the GSVC
fields, where 2 lines (and hence a larger font) can be
used, and 3 cases where 3 lines must be used.

Is there some mechanism for switching the printer from large font to
small font on the fly? A control code or escape sequence, perhaps? If
you're sending a print file to the printer, and some of the "pages"
(labels) to be printed can be in a large font while others must be in a
small font, can the printer driver determine this on its own, or do you
have to tell it somehow? If so, how?
I could give you those business rules if you want. But
the possibilities include examining the LEN(genus &
species), whether any combination of species, variety, or
common name is null.

I've laid out the decision table for those rules. The
output of this procedure will result in either combining
genus and species into one line (field), keeping them as
separate lines (because together they are too long to
fit), deciding which field should go into line 2 (field 2)
(which could be almost any of the fields except genus),
and deciding whether or not a 3rd line is necessary based
on length and nullity of the variety and common name.

It is much easier to program this in Access than in the
label driver software. The latter CAN select a layout
consisting of 2 lines of large font or 3 lines of smaller
font based on what is exported to it, but it is not
possible (or easy) to examine individual field contents
and perform moving and concatenation.

IOW, I have already laid out the logic for all this string
manipulation and determined that it would be *much easier*
(and more transparent to a maintenance programmer) to do
it programmatically and sequentially rather than try to
construct some obscure but tricky set of queries in SQL!

Hope this sheds some more light on the situation and hope
someone sees the need to address my original 5 questions.

This is what I think I would do, subject to a better understanding of
the capabilities and requirements of this printer. I would write a
function that would receive as arguments the four GSVC fields and return
a string containing the formatted entry to be printed, consisting of 2
or 3 lines separated by new-line characters (the standard carriage
return/line feed combination). Thus, this function would embody all
your business rules and string manipulation, operating on the fields of
one record at a time.

Suppose that function had a signature like this:

Sub fncFormatGSVCForPrinter( _
Genus, Species, Variety, CommonName) _
As String

Then I would create a report, designed for the label printer, with a
recordsource query like this:

SELECT
fncFormatGSVCForPrinter(
[Genus], [Species], [Variety], [CommonName])
AS GSVC
FROM PlantData;

I'm assuming that [Genus], [Species], [Variety], and [CommonName] are
fields in the source table, which I'm calling "PlantData". If the
report needs to be sorted, any fields required for sorting it would also
have to be included in the SELECT list. In the report design, the label
content would then be represented as a single text box bound to the
calculated field [GSVC]. Generating the labels would then be a simple
matter of printing the report.

I can foresee a possible hitch in this approach. That is if you need to
send a special binary escape sequence to the printer to switch fonts
between 2-line and 3-line entries. In that case, I think you really
will have to process the records and send both text and control
sequences to the printer on a record by record basis (though you could
still use the fncFormatGSVCForPrinter() function to encapsulate the
formatting logic). On the other hand, if there is a control or escape
sequence that can be embedded in the text sent to the printer, you could
have the function itself create that sequence and embed it in the
returned string, or else you could have VBA code in the report's
Detail_Format event that modifies the FontSize property of the text box
depending on how many lines are in it. This last actually sounds to me
like the most likely way to do it -- that should allow the printer
driver to do its thing without you having to know what the control codes
are.

Note, by the way, that if you (wrongheadedly, IMO) wanted to save the
output of fncFormatGSVCForPrinter() as a separate field in each record,
you could do it using a simple update query that calls the function;
like this:

UPDATE PlantData
SET GSVC =
fncFormatGSVCForPrinter(
Genus, Species, Variety, CommonName);

where GSVC is a text field you have previously defined in the table.

The update query, in turn, would be more efficient than the following
code using sequential recordset processing:

Dim rs As DAO.Recordset

Set rs = CurrentDb.OpenRecordset("PlantData")

With rs
Do Until .EOF
.Edit
!GSVC =
fncFormatGSVCForPrinter( _
!Genus, !Species, !Variety, !CommonName)
.Update
.MoveNext
Loop
.Close
EndWith

Set rs = Nothing

But you should not be storing calculated data in the table, in the first
place.
 
Hi, Dirk,

thanks for your quick and extensive reply. I haven't
tried to apply any of your suggestions yet, but that will
happen soon. Meanwhile, to shed some more light on the
situation I'm dealing with regarding the label printer.

The printer is driven by it's own user interface software,
the P-touch Editor (it's a P-touch labeler by Brother).
It prints on a continuous tape rather than discrete labels.

From an information input point of view, it allows the
user either to key in the contents of a label directly
into a "template" they call a Layout; OR supply
a "database file" as the source. The keying-in is suited
for making one-shot labels, the database suited for a
batch of labels. The latter is the situation I'm dealing
with.

The P-touch Editor accepts a limited number of formats for
input, but that includes an Access97 (only!) *.mdb file.
I've gotten that to work fine.

In database mode, the Editor lets you associate fields in
the table with fields in the label Layout. You can signal
new lines (with a proprietary NL sequence) within a Layout
field, but a given layout field can only have one font
characteristic. So, if you want the flexibility of
separate font sizes for each label line, you cannot split
a field over multiple lines--you have to use another field.

I have tried to do some rudimentary manipulating of
mapping input fields (genus etc.) to layout fields (lines)
within the P-touch Editor, but there are things you can't
do, so my strategy is now to prep the input as much as
possible at the source, which is Access.

The essence of the problem is to map 4 table fields
(genus, species, variety, commonname) into 2 lines if
possible (readability) or at most 3 lines based on the
content of the 4 fields.

The central constraint is that the P-touch Editor gets its
raw batch data from an Access97 *.mdb file. Manual
intervention is always required to get the Editor to do
anything, so the entire process, from pushing a button in
Access to finished labels, cannot be fully automated, but
I can come close!

You write: ">That's fine, but it's important also to
learn that relational database
systems are primarily geared toward set operations -- that's what SQL is
all about, really -- "

Yes, I know . . . I remember sitting through discussions
between Ted Codd and Charlie Bachman back in the late 60's
debating just that issue. I have also taught database
modeling and design for about 25 years (creak, groan).
And I also realize that sequential processing is not the
paradigm of choice when dealing with relational systems.

That said, I also am aware that Access does provide
sequential processing functionality, and I think I've
found a decent reason to use it under these
circumstances. Even if you disagree, I think you'd at
least sympathize with my curiosity to learn how to use
those tools!

OK, so, given that I have to send an .mdb table to the
Editor that contains the fields Line1, Line2, Line3 and a
SortKey (TBD), where Line2 and Line3 could be empty
depending on the 4 fields of botanical information, do you
still think we could put together reasonably transparent
SQL to do it? (Maybe your answer is already embedded in
your earlier response, I'll have to see.)

Re: Not storing calculated data in a table

Especially in large databases, it is often reasonable to
trade better retrieval response for redundantly storing
data . . . as long as the derivation business rules
are "automatically" enforced. I would certainly like to
know how to make this happen in an Access table:

E.g., To add a calculated field "Taxum" which represents
the full botanical name in its proper format, including
single quotes around Variety (if present) and no extra
blanks, Variety always capitalized but species not, etc.
The Taxum is the main user interface to this system and
not the individual fields genus, species, and variety.
Right now, every time I want to display or otherwise
operate on Taxum, I invoke a fncFormBotanicalName(G,S,V).
I would sure like to be able instead to have a field in
the table called BotanicalName that would automatically be
calculated when you saved a record. Can this be done?

Thanks again for your thought and time, Dirk.

Gary Schuldt
Syncretics.com


-----Original Message-----
(comments interspersed with quoted text)

GaryS said:
Well, Dirk, are you sure you want this logic? Here it is,
although it would best be represented using a decision
table. (I STILL would like to know the basics of
sequential processing, since it's part of my Access
learning program.)

That's fine, but it's important also to learn that relational database
systems are primarily geared toward set operations -- that's what SQL is
all about, really -- and whenever you are faced with a choice between
sequential record processing and doing the whole thing with a SQL
statement or two, the SQL approach will almost always be faster and more
efficient. I spent 20 years doing procedural programming, and I still
often find myself writing procedural code where a couple of SQL
statements would do the job.
The label printer is a specialized printer for labels to
be affixed to plant tags.

Its layout can either be 2 or 3 lines, depending on the
contents of the 4 fields that need to be printed (genus,
species, variety, and most frequent common name).

The goal is to use 2 lines whenever possible, since the
font can be larger and therefore more readable from a
longer distance away. (The plant tags will be read by
people on a garden tour.)

There are 3 cases, dependent on the contents of the GSVC
fields, where 2 lines (and hence a larger font) can be
used, and 3 cases where 3 lines must be used.

Is there some mechanism for switching the printer from large font to
small font on the fly? A control code or escape sequence, perhaps? If
you're sending a print file to the printer, and some of the "pages"
(labels) to be printed can be in a large font while others must be in a
small font, can the printer driver determine this on its own, or do you
have to tell it somehow? If so, how?
I could give you those business rules if you want. But
the possibilities include examining the LEN(genus &
species), whether any combination of species, variety, or
common name is null.

I've laid out the decision table for those rules. The
output of this procedure will result in either combining
genus and species into one line (field), keeping them as
separate lines (because together they are too long to
fit), deciding which field should go into line 2 (field 2)
(which could be almost any of the fields except genus),
and deciding whether or not a 3rd line is necessary based
on length and nullity of the variety and common name.

It is much easier to program this in Access than in the
label driver software. The latter CAN select a layout
consisting of 2 lines of large font or 3 lines of smaller
font based on what is exported to it, but it is not
possible (or easy) to examine individual field contents
and perform moving and concatenation.

IOW, I have already laid out the logic for all this string
manipulation and determined that it would be *much easier*
(and more transparent to a maintenance programmer) to do
it programmatically and sequentially rather than try to
construct some obscure but tricky set of queries in SQL!

Hope this sheds some more light on the situation and hope
someone sees the need to address my original 5
questions.

This is what I think I would do, subject to a better understanding of
the capabilities and requirements of this printer. I would write a
function that would receive as arguments the four GSVC fields and return
a string containing the formatted entry to be printed, consisting of 2
or 3 lines separated by new-line characters (the standard carriage
return/line feed combination). Thus, this function would embody all
your business rules and string manipulation, operating on the fields of
one record at a time.

Suppose that function had a signature like this:

Sub fncFormatGSVCForPrinter( _
Genus, Species, Variety, CommonName) _
As String

Then I would create a report, designed for the label printer, with a
recordsource query like this:

SELECT
fncFormatGSVCForPrinter(
[Genus], [Species], [Variety], [CommonName])
AS GSVC
FROM PlantData;

I'm assuming that [Genus], [Species], [Variety], and [CommonName] are
fields in the source table, which I'm calling "PlantData". If the
report needs to be sorted, any fields required for sorting it would also
have to be included in the SELECT list. In the report design, the label
content would then be represented as a single text box bound to the
calculated field [GSVC]. Generating the labels would then be a simple
matter of printing the report.

I can foresee a possible hitch in this approach. That is if you need to
send a special binary escape sequence to the printer to switch fonts
between 2-line and 3-line entries. In that case, I think you really
will have to process the records and send both text and control
sequences to the printer on a record by record basis (though you could
still use the fncFormatGSVCForPrinter() function to encapsulate the
formatting logic). On the other hand, if there is a control or escape
sequence that can be embedded in the text sent to the printer, you could
have the function itself create that sequence and embed it in the
returned string, or else you could have VBA code in the report's
Detail_Format event that modifies the FontSize property of the text box
depending on how many lines are in it. This last actually sounds to me
like the most likely way to do it -- that should allow the printer
driver to do its thing without you having to know what the control codes
are.

Note, by the way, that if you (wrongheadedly, IMO) wanted to save the
output of fncFormatGSVCForPrinter() as a separate field in each record,
you could do it using a simple update query that calls the function;
like this:

UPDATE PlantData
SET GSVC =
fncFormatGSVCForPrinter(
Genus, Species, Variety, CommonName);

where GSVC is a text field you have previously defined in the table.

The update query, in turn, would be more efficient than the following
code using sequential recordset processing:

Dim rs As DAO.Recordset

Set rs = CurrentDb.OpenRecordset("PlantData")

With rs
Do Until .EOF
.Edit
!GSVC =
fncFormatGSVCForPrinter( _
!Genus, !Species, !Variety, ! CommonName)
.Update
.MoveNext
Loop
.Close
EndWith

Set rs = Nothing

But you should not be storing calculated data in the table, in the first
place.

--
Dirk Goldgar, MS Access MVP
www.datagnostics.com

(please reply to the newsgroup)


.
 
Dirk,

I've had a chance to review the recommendations you
tendered in the post I'm replying to now. I have some
questions about it as follows:

1. Suppose I modify the fncFormatGSVCForPrinter to add
the output arguments Line1, Line2, and Line3, with the
function value itself returning the SortKey. So the
signature would be:

fncFormatGSVCForPrinter(
Genus, Species, Variety, CommonName,
Line1, Line2, Line3)

So far, so good?

2. Can I still use an Update query?

You write, for your version of the function (4 args
instead of 7):

" . . .if you (wrongheadedly, IMO) wanted to save the
output of fncFormatGSVCForPrinter() as a separate field in
each record,
you could do it using a simple update query that calls the
function;
like this:

UPDATE PlantData
SET GSVC =
fncFormatGSVCForPrinter(
Genus, Species, Variety, CommonName);

where GSVC is a text field you have previously defined in
the table."

My question: With Line1, Line2, and Line3 as additional
outputs of the function, how would I set corresponding
fields to those values with an update query that invokes
the function only once? I don't see the syntax.

3. The sequential recordset processing approach

I can see modifying your suggested code as follows:

Dim rs As DAO.Recordset

Set rs = CurrentDb.OpenRecordset("PlantData")

With rs
Do Until .EOF
.Edit
!SortKey =
fncFormatGSVCForPrinter( _
!Genus, !Species, !Variety, !
CommonName, !Line1, !Line2, !Line3)
.Update
.MoveNext
Loop
.Close
EndWith

Set rs = Nothing

Would that work with those 3 additional arguments (as 3
fields in the PlantData)?

(What does the Set rs = Nothing do?)

I think we are making progress.

Gary
 
GaryS said:
Dirk,

I've had a chance to review the recommendations you
tendered in the post I'm replying to now. I have some
questions about it as follows:

1. Suppose I modify the fncFormatGSVCForPrinter to add
the output arguments Line1, Line2, and Line3, with the
function value itself returning the SortKey. So the
signature would be:

fncFormatGSVCForPrinter(
Genus, Species, Variety, CommonName,
Line1, Line2, Line3)

So far, so good?

I'm following you.
2. Can I still use an Update query?

You write, for your version of the function (4 args
instead of 7):

" . . .if you (wrongheadedly, IMO) wanted to save the
output of fncFormatGSVCForPrinter() as a separate field in
each record,
you could do it using a simple update query that calls the
function;
like this:

UPDATE PlantData
SET GSVC =
fncFormatGSVCForPrinter(
Genus, Species, Variety, CommonName);

where GSVC is a text field you have previously defined in
the table."

My question: With Line1, Line2, and Line3 as additional
outputs of the function, how would I set corresponding
fields to those values with an update query that invokes
the function only once? I don't see the syntax.

You can't do it that way, I'm sorry to say. You can't use output
parameters to update fields in a Jet query. What you could do in a
query is pass the function an extra parameter to tell it which return
value you want: SortKey, Line1, Line2, or Line3. The function would
calculate all 4 of the values, but only pass back the one you wanted.
So you would set up your update query like this:

UPDATE PlantData SET
SortKey = fncFormatGSVCForPrinter(
Genus, Species, Variety, CommonName,
"SortKey"),

Line1 = fncFormatGSVCForPrinter(
Genus, Species, Variety, CommonName,
"Line1"),

Line2 = fncFormatGSVCForPrinter(
Genus, Species, Variety, CommonName,
"Line2"),

Line3 = fncFormatGSVCForPrinter(
Genus, Species, Variety, CommonName,
"Line3");

If I were writing the function, I'd save the keys passed in static
variables so that I could calculate the 4 return values only once, the
first time the function is called for a given Genus, Species, Variety,
and CommonName, but still hand them back when called for. That would be
moderately more efficient.

Note that you could also use this approach in a recordsource query for a
report, without storing the calculated return values in the table. But
I don't intend to flog this particular dead horse any farther.
3. The sequential recordset processing approach

I can see modifying your suggested code as follows:

Dim rs As DAO.Recordset

Set rs = CurrentDb.OpenRecordset("PlantData")

With rs
Do Until .EOF
.Edit
!SortKey =
fncFormatGSVCForPrinter( _
!Genus, !Species, !Variety, !
CommonName, !Line1, !Line2, !Line3)
.Update
.MoveNext
Loop
.Close
EndWith

Set rs = Nothing

Would that work with those 3 additional arguments (as 3
fields in the PlantData)?

Yes, that would work, but to set the output parameters you'd have to use
assignment statements in the function like these:

Line1.Value = <value for line 1>
Line2.Value = <value for line 2>
Line3.Value = <value for line 3>

You can't just say

Line1 = <value for line 1> '*** won't work!

For that matter, you could just pass the recordset object itself and set
all four values via the recordset, as in:

Function fncFormatGSVCForPrinter( _
rs As DAO.Recordset )

Dim strSortKey As String
Dim varLine1 As Variant
Dim varLine2 As Variant
Dim varLine3 As Variant

' ... compute values for the above variables ...

With rs
.Edit
!SortKey = strSortKey
!Line1 = varLine1
!Line2 = varLine2
!Line3 = varLine3
.Update
End With

End Function

Then you'd call the function from your looping code like this:

Dim rs As DAO.Recordset

Set rs = CurrentDb.OpenRecordset("PlantData")

With rs
Do Until .EOF
fncFormatGSVCForPrinter rs
.MoveNext
Loop
.Close
EndWith

Set rs = Nothing
(What does the Set rs = Nothing do?)

In an ideal world, nothing. In a less than ideal world, it ensures that
the recordset object rs is destroyed when we're done with it, just in
case VB "forgets" to destroy it on exit from the procedure. I don't
think this is a real issue any more, but there have been reported cases
in the past where VB failed to destroy local object variables when they
went out of scope. Destroying in explicitly is just insurance on my
part.
 
Dirk, I think the light's beginning to dawn . . .
so in conclusion:

You can't do it that way, I'm sorry to say. You can't use output
parameters to update fields in a Jet query.

Makes sense to me.

If I were writing the function, I'd save the keys passed in static
variables so that I could calculate the 4 return values only once, the
first time the function is called

That did occur to me, but I wasn't sure there was a
defined order of evaluation for multiple SETs so that I
could infallibly rely on the first SET being executed
first! You imply that there is, though.

The second reservation I had involves the use of static
variables as you suggest. I don't know how the underlying
engine handles multiple users (multiple threads). In many
systems I've worked on you only put "constants" in static,
since their values apply to all threads simultaneously.

The third reservation is that passing a control variable
into a routine compromises its "black box" nature: You
have to know something about how its innards work to use
it correctly (i.e., invoke it in the correct sequence).


Regarding your suggested function:
Function fncFormatGSVCForPrinter( _
rs As DAO.Recordset )

Dim strSortKey As String
Dim varLine1 As Variant
Dim varLine2 As Variant
Dim varLine3 As Variant

' ... compute values for the above variables ...

With rs
.Edit
!SortKey = strSortKey
!Line1 = varLine1
!Line2 = varLine2
!Line3 = varLine3
.Update
End With

End Function

I think I learned something about the recordset data
type: That it includes a specification
of "currentrecord". Regardless of the syntax, what you're
really passing is the current record and not the full
recordset. Correct me if I'm wrong.


Lastly, re: Nothing
In an ideal world, nothing. In a less than ideal world, it ensures that
the recordset object rs is destroyed when we're done with
it, . . .

Yup: If you REALLY want the garbage taken away, haul it
to the dump yourself rather than trusting it'll be picked
up on the regular rounds.

Thanks for all the help.

Gary
 
GaryS said:
Dirk, I think the light's beginning to dawn . . .
so in conclusion:



Makes sense to me.



That did occur to me, but I wasn't sure there was a
defined order of evaluation for multiple SETs so that I
could infallibly rely on the first SET being executed
first! You imply that there is, though.

No, I don't know whether you can rely on that or not, but I was
proposing a structure (which I've used in the past) that simply
determines whether the parameters have changed between calls. That way
it doesn't matter in what order the function is called for any given
record. Let me give you a quick example:

'---- start of function model -----
Function fncFormatGSVCForPrinter( _
InputGenus, InputSpecies, _
InputVariety, InputCommonName, _
OutputWanted As String) _
As String

' Declare current inputs
Static Genus As String
Static Species As String
Static Variety As String
Static CommonName As String

' Declare calculated outputs
Static SortKey As Variant
Static Line1 As Variant
Static Line2 As Variant
Static Line3 As Variant

Dim fNewInput As Boolean

' Check input parameters. Concatenate with "" to
' transform Null values into strings.
If InputGenus & "" <> Genus Then
Genus = InputGenus & ""
fNewInput = True
End If
If InputSpecies & "" <> Species Then
Species = InputSpecies & ""
fNewInput = True
End If
If InputVariety & "" <> Variety Then
Variety = InputVariety & ""
fNewInput = True
End If
If InputCommonName & "" <> CommonName Then
CommonName = InputCommonName & ""
fNewInput = True
End If

' If the inputs have changed, recalculate the outputs.
If fNewInput Then

' ... here's where the code to calculate SortKey,
' Line1, Line2, and Line3 based on Genus, Species,
' Variety, and CommonName goes ...

End If

' Return the particular calculated value the caller
' has requested.
Select Case OutputWanted
Case "SortKey" : fncFormatGSVCForPrinter = SortKey
Case "Line1" : fncFormatGSVCForPrinter = Line1
Case "Line2" : fncFormatGSVCForPrinter = Line2
Case "Line3" : fncFormatGSVCForPrinter = Line3
End Select

End Function
'---- end of function model -----
The second reservation I had involves the use of static
variables as you suggest. I don't know how the underlying
engine handles multiple users (multiple threads). In many
systems I've worked on you only put "constants" in static,
since their values apply to all threads simultaneously.

In Access, multiple users aren't multiple threads. Each user has his
own copy of the application with its own address space, and the code is
executed in that copy. Yes, I think it would be possible for the
function to be called from separate threads within the same application
instance, but that would be an unusual circumstance and would have no
effect on a function designed as I've described except to make it less
efficient by forcing it to discard the saved values prematurely and
recalculate new ones.
The third reservation is that passing a control variable
into a routine compromises its "black box" nature: You
have to know something about how its innards work to use
it correctly (i.e., invoke it in the correct sequence).

I'm not sure I follow you here. The proposed design doesn't require the
function to be called in a particular sequence. Given the same inputs,
it will produce the same outputs.
Regarding your suggested function:


I think I learned something about the recordset data
type: That it includes a specification
of "currentrecord". Regardless of the syntax, what you're
really passing is the current record and not the full
recordset. Correct me if I'm wrong.

You're both wrong and right. You are in fact passing the whole
recordset object to the function (or rather, you're passing a reference
to the recordset object, wherever it lives in memory), but among the
properties of a recordset object are those identifying and relating to
the current record, and allowing the fields of the current record to be
modified. When you pass the recordset, you pass it in its current
state, which includes the current record. If you were to use any of the
object's record-navigation methods in the called routine, they would
also affect the object in the calling routine.
 
Dirk,

No, I don't know whether you can rely on that or not, but I was
proposing a structure (which I've used in the past) that simply
determines whether the parameters have changed between
calls.

OK, I didn't see that before. I read the code and it
makes sense.
You're both wrong and right. You are in fact passing the whole
recordset object to the function (or rather, you're passing a reference
to the recordset object, wherever it lives in memory), but among the
properties of a recordset object are those identifying and relating to
the current record, and allowing the fields of the current record to be
modified. When you pass the recordset, you pass it in its current
state, which includes the current record.

OK, that reinforces what I was reading last night about
navigating tables using data control objects in VBA.

Thanks, Dirk, for your patience, perseverance and detailed
responses to my posts! I feel like I have a much better
understanding of the tools I can use to solve this little
string manipulation problem. And the code you supplied
will greatly reduce the number of syntax errors I'm bound
to get while putting this all together.

I also appreciate the side tips and tricks, such as
' Check input parameters. Concatenate with "" to
' transform Null values into strings.
I'm sure I would have puzzled many hours over why the test
I'd devised for a "nothing" string was failing. I guess
it's because in VB nulls aren't converted to empty strings
in all contexts (but they have to be converted in the
concatenation operation for it to work!).

Gary
 
GaryS said:
Thanks, Dirk

You're welcome.
I also appreciate the side tips and tricks, such as
I'm sure I would have puzzled many hours over why the test
I'd devised for a "nothing" string was failing.

Been there, done that, got the headache.
I guess
it's because in VB nulls aren't converted to empty strings
in all contexts (but they have to be converted in the
concatenation operation for it to work!).

To which you may add the fact that nothing is equal to Null -- or
rather, the result of the comparison "x = Null" is always Null, even is
x is also Null. So you can't write "If x = Null Then ..." and get
anything useful out of it. The only way to test for a Null value is to
use the IsNull() function (in VBA) or the test "[FieldName Is Null" in
SQL. *But* you can test for "Null or zero-length-string" by
concatenating the value with a zero-length string, thereby converting it
to a ZLS.

Speaking of handy tips, it's sometimes convenient to know that the
operation

X & Null --> CStr(X)

while the operation

X + Null --> Null

This can be handy in assembling strings from separate fields, some of
which may be Null. For example, taking name parts and assembling a
full-name field:

SELECT
[LastName] & (", "+[FirstName]) &
(" "+[MiddleName]) & (" "+[Suffix]) & (", "+[Honorific])
As FullName
FROM Employees;
 
Dirk,

This can be handy in assembling strings from separate
fields, some of which may be Null. <<

AMEN!! Like, my last two projects have been:

1. Address database, which involved forming a full name
(!)

2. The plant db, where, as you have seen, I needed to
form the botanical name.

Lacking knowledge of the above tip, I used nested Trims,
but it took me a little time to figure it out.

I would have to say, though, the fact that X + Null ->
Null is pretty counter-intuitive, so I probably would
still use the nested Trims, since they seem more
straightforward.

Speaking of nesting, has this thread reached a record
number of levels?? The only reason I haven't jumped out
and reposted the "Tips" issue is that your style of reply
fits my objective, which is to learn Access. Many MVP's
reply with "an answer" without explaining why it works. I
now realize I'd seen the trick with concatenating possibly
null fields but hadn't generalized it to the rule X +
Null --> Null.

You're a great teacher! I'm formulating a post asking
about a general approach to updating many-to-many
relationships via a form, and it would be great to have
you respond if you see it!


Gary
-----Original Message-----
GaryS said:
Thanks, Dirk

You're welcome.
I also appreciate the side tips and tricks, such as
I'm sure I would have puzzled many hours over why the test
I'd devised for a "nothing" string was failing.

Been there, done that, got the headache.
I guess
it's because in VB nulls aren't converted to empty strings
in all contexts (but they have to be converted in the
concatenation operation for it to work!).

To which you may add the fact that nothing is equal to Null -- or
rather, the result of the comparison "x = Null" is always Null, even is
x is also Null. So you can't write "If x = Null Then ..." and get
anything useful out of it. The only way to test for a Null value is to
use the IsNull() function (in VBA) or the test "[FieldName Is Null" in
SQL. *But* you can test for "Null or zero-length-string" by
concatenating the value with a zero-length string, thereby converting it
to a ZLS.

Speaking of handy tips, it's sometimes convenient to know that the
operation

X & Null --> CStr(X)

while the operation

X + Null --> Null

This can be handy in assembling strings from separate fields, some of
which may be Null. For example, taking name parts and assembling a
full-name field:

SELECT
[LastName] & (", "+[FirstName]) &
(" "+[MiddleName]) & (" "+[Suffix]) & (", "+[Honorific])
As FullName
FROM Employees;

--
Dirk Goldgar, MS Access MVP
www.datagnostics.com

(please reply to the newsgroup)


.
 
GaryS said:
I would have to say, though, the fact that X + Null ->
Null is pretty counter-intuitive,

You can say that again!
so I probably would
still use the nested Trims, since they seem more
straightforward.

Whatever works best for you.
Speaking of nesting, has this thread reached a record
number of levels??

Hah! Not hardly. Of course, the deepest threads have involved
"religious wars" of one sort or another.
I'm formulating a post asking
about a general approach to updating many-to-many
relationships via a form, and it would be great to have
you respond if you see it!

I'll keep an eye out for it, but now I have to go make the donuts. ;-)
 
Back
Top