Jamie Collins said:
As you know, I don't understand the CREATE TABLE syntax well enough to
know
what to do when it doesn't actually create a table. As a consequence, I
don't know what if anything you are telling me about what I would like to
do.
[First a flippant comment to set the mood:] Personalised answers eh?
-- people will talk <g>; High maintenance client, aren't you <g>?;
You sure like to get your money's worth <g>!; I can take rejection
(sob, sob) <g>; Don't understand SQL or won't understand SQL <vbg>?
etc -- take you pick.
I am trying to learn SQL, and VBA, and a lot of other things. My job has
other requirements, too, some of which involve learning new things, and all
of which involve time. Sometimes I need to make choices. I am learning SQL
as well as I can and as other requirements of my job and life allow, so it's
not a question of my being incapable or unwilling to learn. I do what I
can.
I finally learned that I need to use SQL Server Compatible Syntax in order
to use CREATE TABLE. At least, that's how I understood it. If I look at a
CREATE TABLE statement from your first reply:
CREATE TABLE Reports (
report_ID INTEGER IDENTITY(1, 1) NOT NULL PRIMARY KEY);
I can see that the intention is to create a table named Reports with
report_ID as an integer PK. When I went to the immediate window and
entered:
DoCmd.RunSQL "CREATE TABLE ... PRIMARY KEY"
it created a table with an autonumber PK (rather to my surprise). However,
I don't know what (1,1) signifies.
By the way, I do understand that <<other columns here>> is not part of the
SQL. Even I can figure out that one.
CREATE TABLE ReportReasons (
report_ID INTEGER NOT NULL
REFERENCES Reports (report_ID),
reason VARCHAR(12) NOT NULL,
UNIQUE (reason, report_ID)
)
;
is to create a table named ReportReasons, with report_ID an integer field
that is required, but duplicates are OK (a foreign key, to my way of
thinking). By the way, I ran the code to create Reasons first. Anyhow,
REFERENCES apparently establishes a relationship with Reports. Then there
is VARCHAR(12). I have no guess what that means. Maybe I was supposed to
create ReportReasons before Reasons. Anyhow, there is no relationship with
Reasons, although there is in the VBA version of the SQL you provided in
this post. I realize that there should be. What I don't get is if I should
have been able to figure that out from the SQL.
Then there is:
SELECT R1.report_ID, IIF(A1.reason IS NULL, 'Other (' & R1.reason &
')', R1.reason) AS reason
FROM ReportReasons AS R1
LEFT JOIN Reasons AS A1
ON R1.reason = A1.reason
;
I don't know what I am supposed to do with this. It seems to be a query
sort of thing, but is not stored as a named query, but I can't use RunSQL,
which gives me an error message about needing a valid SQL string or
something like that.
What you are not telling me is what you, the OP, expect from me, the
respondent.
In my original post I wondered about using a multi-select list box in case
there are several reasons. If not that, maybe some unbound check boxes to
accomplish the same thing. After making one or several selections, each
selection would become a record in the ReportReasons. Then I asked:
"Is the multi-select list box is a workable strategy? If it is, could the
same principle be applied to a series of unbound check boxes on the form
when a new report record is created? I can't work out how I would turn
multiple selections into multiple records. Or am I over-complicating this?
"
That was what I wanted to know. Those were the specific question for which
I sought answers. You took it several steps further by pointing out that I
would be storing text whether the reason is one of the standard reasons
(Equipment Failure, let's say), or Other (A wild animal ran off with the
finished product). Excellent point, and well worth implementing, now that I
understand it. I still don't know if I can implement it as I had hoped with
something like a multi-select list box for the standard reasons, and maybe
an input box or something for the Other reason if there is one.
When you posted the message to which I am now replying I saw not only the
many-to-many relationship between Reports and Reasons, but also the
ReasonsUndefined in a one-to-many with Reports, and the union query to bring
together ReasonsDefined and ReasonsUndefined. I suppose the union query
could be the source of a subform that would be visible if the Reports record
is not new, with an option to make the original subform (the one used to
select a Reason) visible if it is necessary to add another Reason, or
something like that.
I could suggest you execute each semicolon-separated SQL statement
against an ADO connection to the mdb (or similar) of your choice,
first removing any obvious comments such as "<<other columns here>>"
because Access/Jet SQL syntax does not support explicit comments
(shame).
I explained how I got the SQL to work. I don't know if this is what you
mean by the above.
I could take a few minutes of my time to do this for you and post a
VBA procedure which you can paste into any VBA environment (e.g. a
Standard Module in the Visual Basic Editor of an Excel workbook) that
will create a new mdb with tables, constraints and test data, run a
test query and show the results in a messagebox, before shelling-out
Access to open the mdb in the user interface for examination there
e.g.
Sub JustForBruceM()
On Error Resume Next
Kill Environ$("temp") & "\DropMe.mdb"
On Error GoTo 0
Dim cat
Set cat = CreateObject("ADOX.Catalog")
With cat
' Create mdb
.Create _
"Provider=Microsoft.Jet.OLEDB.4.0;" & _
"Data Source=" & _
Environ$("temp") & "\DropMe.mdb"
With .ActiveConnection
' Create tables
.Execute _
"CREATE TABLE Reports ( " & vbCr & "report_ID" & _
" INTEGER IDENTITY(1, 1) NOT NULL" & _
" PRIMARY KEY, " & vbCr & "report_name CHAR(15)" & _
" NOT NULL UNIQUE" & vbCr & ") " & vbCr & ";"
.Execute _
"CREATE TABLE Reasons ( " & vbCr & "reason" & _
" VARCHAR(12) NOT NULL PRIMARY KEY " & vbCr & ")" & _
" " & vbCr & ";"
.Execute _
"CREATE TABLE ReportReasonsDefined" & _
" ( " & vbCr & "report_ID INTEGER NOT NULL" & _
" " & vbCr & "REFERENCES Reports (report_ID)," & _
" " & vbCr & "reason VARCHAR(12) NOT NULL" & _
" " & vbCr & "REFERENCES Reasons (reason)," & _
" " & vbCr & "PRIMARY KEY (reason, report_ID) " & vbCr & ")" & _
" " & vbCr & ";"
.Execute _
"CREATE TABLE ReportReasonsUndefined" & _
" ( " & vbCr & "report_ID INTEGER NOT NULL PRIMARY KEY" & _
" " & vbCr & "REFERENCES Reports (report_ID)," & _
" " & vbCr & "reason VARCHAR(12) NOT NULL," & _
" " & vbCr & "CONSTRAINT undefined_reason_cannot_be_defined" & _
" " & vbCr & "CHECK (NOT EXISTS ( " & vbCr & "SELECT" & _
" * " & vbCr & "FROM ReportReasonsDefined" & _
" AS D1, " & vbCr & "ReportReasonsUndefined" & _
" AS U1 " & vbCr & "WHERE D1.reason = U1.reason))" & _
" " & vbCr & ") " & vbCr & ";"
' Insert test data
.Execute _
"INSERT INTO Reasons (reason) VALUES" & _
" ('Because');"
.Execute _
"INSERT INTO Reasons (reason) VALUES" & _
" ('Felt like it');"
.Execute _
"INSERT INTO Reasons (reason) VALUES" & _
" ('Was asked to');"
.Execute _
"INSERT INTO Reports (report_ID," & _
" report_name) VALUES (1, 'Sales');"
.Execute _
"INSERT INTO ReportReasonsDefined" & _
" (report_ID, reason) VALUES (1," & _
" 'Was asked to');"
.Execute _
"INSERT INTO ReportReasonsDefined" & _
" (report_ID, reason) VALUES (1," & _
" 'Felt like it');"
.Execute _
"INSERT INTO ReportReasonsUndefined" & _
" (report_ID, reason) VALUES (1," & _
" 'MVP advised');"
' Create Procedure (Access = stored Query object)
.Execute _
"CREATE PROC GetReportReasons " & _
" AS " & vbCr & "SELECT D1.report_ID AS report_ID," & _
" D1.reason AS reason" & vbCr & "FROM ReportReasonsDefined" & _
" AS D1 " & vbCr & "UNION ALL " & vbCr & "SELECT U1.report_ID," &
_
" 'Other (' & U1.reason & ')' " & vbCr & "FROM" & _
" ReportReasonsUndefined AS U1;"
Dim rs
Set rs = .Execute( _
"EXECUTE GetReportReasons;")
MsgBox rs.GetString
End With
Set .ActiveConnection = Nothing
End With
On Error Resume Next
Shell "msaccess.exe " & Environ$("temp") & "\DropMe.mdb"
End Sub
[As I think I mentioned to you before, I formerly did this in
virtually all my posts but I didn't get any feedback, and asked
whether you'd find it helpful, but I didn't get any feedback.]
I could post a link to one of the many SQL tutorial sites that explain
CREATE TABLE syntax (e.g.
http://www.firstsql.com/tutor6.htm#createtable)
and urge you to learn a skill that if not vital then certainly adds
interest if you work with at least one SQL product.
Thanks for the link. It has taken me well over two hours to try out the
things you have suggested, and to run the code and study how it all fits
together, so I really don't have time to study it now, as other aspects of
my job demand my attention. I mention the time I have put into trying to
understand this because I would like to disabuse you of the notion you seem
to have that I am asking to be spoon fed the answer.
I could point out that my SQL DDL is more of a thumbnail sketch than
an implementation script and shouldn't be interpreted too literally; I
could post the same in Tutorial D and I'm sure if you 'suspended
disbelief' for a few moments you could figure out the suggested design
(actually I couldn't post in Tutorial D but you get what I mean <g>).
I thought I understood what you were saying about selecting the actual text
of the Reason rather than a Yes/No field plus text in another field, but I
did not see how I was supposed to incorporate Other. Since that was one of
the questions in my OP, I assumed you meant I was supposed to get from the
thumbnail sketches how to implement that. That was when I got frustrated.
I could reciprocate in kind and post a description of a suggested
structure but, as you know, language is a very important part of the
process of communicating ideas and approaches, and you have not
indicated your expected structure of such a description, despite me
asking you to previously (but I do recall you agreeing in principle
that posting SQL DDL is a good idea), consequently there would be a
high risk of me investing my time unwisely if such a description is
fails to transcend culture, dialect, style, skill level, etc e.g. you
could have a single-column lookup table for your five 'defined'
reasons -- let's call this table Reasons -- then create a
'relationship table' (a.k.a. 'junction table', 'join table', etc) to
model the relationship between reports and reasons -- let's call this
relationship table ReportReasonsDefined -- then create a many-to-many
foreign key Relationship with RI enforced between these two tables but
with a unique constraint (primary key or index no duplicates) on
report_ID (i.e. the foreign key from the Reports table) and reason
(foreign key from the Reasons table) so that there is no duplicate
rows (redundancy) i.e. where a report has the same two defined
reasons. Then you could have a almost identical table for reasons
defined as 'Other' or, put another way, 'undefined' reasons -- let's
call this table ReportReasonsUndefined -- the difference being there
is no 'undefined reasons' table with which to create a relationship
and, because your spec suggests to me that a report can only have one
reason defined as 'Other', therefore the unique constraint (primary
key or index no duplicates) should this time be on report_ID (i.e. the
foreign key from the Reports table). There remains an issue you
haven't addressed in your spec: can the undefined reason (i.e. the
description associated with a reason defined as 'Other' in your
existing design) be the same as a defined reason?
As I think you went on to speculate, Other would different from a defined
reason. I doubt a user would select "Equipment Failure", then type in
"Equipment Failure" under Other. If they do, I'll retrain them or
something. It wouldn't be a big problem.
I think it is a safe
to assume the modality of such a business rule is alethic i.e. if it
was violated the data would lose meaning (an undefined reason that is
defined appears nonsense) therefore there should be a constraint to
enforce the rule. The only way I can think of implementing this in
Access/Jet is to use a table-level CHECK constraint which does a
simple NOT EXISTS check in the ReportReasonsUndefined table for a row
in the ReportReasonsDefined table.
Is a table level check constraint known by another name, because I don't
quite understand what you mean. Anyhow, as I mentioned, it is not worth
worrying about it.
Actually, I can think of another
*approach*, more on this below. The arbitrary distinction between
defined and undefined can be kept 'under the covers' by exposing the
data *combined* in a virtual table (VIEW or stored SELECT Query) --
let's call this virtual table ReportReasons -- via a UNION ALL of the
two tables, exposing either as a concatenation of the label 'Other: '
plus the undefined reason or showing 'Other' for the reason and having
a separate column for 'other reason description', applying an
appropriate label (e.g. '{{NA}}') to the set of defined reasons in the
resultset. And here's the aforementioned alternative approach to the
table-level CHECK constraint: accept there will be duplication
(redundancy) between defined and undefined reason and use UNION in the
virtual table to 'filter out' the duplicates in the resultset. [Please
be aware that this description took about half an hour to write where
as the original SQL DDL sketch took about five minutes to produce plus
another five to correct and wrap as a VBA function.]
I could also be open to taking an alternative approach, just let me
know.
If you have any feedback on any of the points raised above, please
post it here.
One possibility is to have said: "There is a many-to-many relationship
between Reports and Reasons, which means a ReasonsDefined junction table is
needed to resolve the relationship. For UndefinedReasons (Other), there
could be another table structured like ReasonsDefined, but in a separate
one-to-many relationship with Reports. A union query could bring the tables
together as the record source for a subform containing both defined and
undefined reasons. I don't see how you could select several options and
have them turn into several separate records in ReasonsDefined. You would
need to enter them one at a time."
Or something like that. My point is that I am capable of understanding such
concepts, and you could have avoided spending a half hour writing code. If
I needed clarification, I could have followed up with another question. As
it is I'm not sure if I'm supposed to feel guilty about the amount of time
you spent crafting the latest response, or what exactly, but please
understand that I appreciate the effort you have put into the reply, and
have as always made every effort to understand.