I have the unfortunate task of having to import data from excel into a database on a regular basis. The table looks something like this:
IssueID References
1234 DocID1<cr>DocID2<cr>DocID3
1235 DocID1
1236 DocID2
1237 DocID2<cr>DocID3
References is a multi-line text field. What I'm trying to do is create a Docs table with one-to-many relationship to the Issue table, rather than having these multi-line references.
I have the following tables defined:
Issue: IssueKey, IssueID, IssueFields
Doc: DocKey, DocID, DocRev, DocOwner, etc
DocLink: LinkKey, DocKey, IssueKey
Since this will be run repeatedly, the Doc table will already exist with the DocIDs defined. So, what I want to do is have a query or VBA code search for each DocID in the References column and add a link based on IssueID if one does not already exist.
Simple, Right?
Jeff
Clarifications:
1) I had a third column called "Val1" to show that there were other columns, but that seemed to confuse the issue. There are actually many (way to many, most ignored) columns in the source table, but I only care about the two above.
2) I don't have to parse for a delimiter or anything too paranoid: References contains one or more uniquely defined document reference numbers (stored as text). So, a LIKE filter will turn up the list of IssueIDs on a case by case basis.
3) Here is an example of acceptable output:
IssueID References
1234 DocID1
1234 DocID2
1234 DocID3
1235 DocID1
1236 DocID2
1237 DocID2
1237 DocID3
The ideal solution would take the original excel table (top) and these two tables:
IssueKey IssueID
1 1234
2 1235
3 1236
4 1237
DocKey DocID
1 DocID1
2 DocID2
3 DocID3
And populate/update the link table:
LinkKey IssueKey DocKey
1 1 1
2 1 2
3 1 3
4 2 1
5 3 2
6 3 3
4) Here is an example of what I expected for a solution (creates #3 above). Unfortunately it crashes Access, so I can't tell if the syntax is correct (edited to reflect field names above).
SELECT Q1.IssueID, D1.DocID
FROM Docs AS D1, Issues AS Q1
WHERE Q1.IssueID IN
((SELECT Q2.IssueID from Issues AS Q2 where (Q2.References) Like D1.DocID));
5) Giving up on Access for the moment, I've got the following working in MySQL:
SELECT Q1.IssueID, D1.DocID
FROM Docs AS D1, Issues AS Q1
WHERE Q1.IssueID IN
((SELECT Q2.IssueID from Issues AS Q2 where (Q2.References) Like '%DocID1%'));
This works as I'd expect - I get every IssueID with a Reference to DocID1, repeated for every Doc in the table. With the above data it would look like:
IssueID References
1234 DocID1
1234 DocID2
1234 DocID3
1235 DocID1
1235 DocID2
1235 DocID3
Now I just want to replace the '%DocID1%' with '%'+D1.DocID+'%' - limiting the results to those document IDs which actually have a match. For some reason I'm getting zero records when I do this - I think I have the syntax for putting wildcards on the correlated field wrong.
6) The following works to provide #3 above in MySQL, but the same query translated to access crashes it:
SELECT Q1.IssueID, D1.DocID
FROM Docs AS D1, Issues AS Q1
WHERE Q1.IssueID IN
((SELECT Q2.IssueID from Issues AS Q2 where (Q2.References) Like
CONCAT('%',D1.DocID,'%')));
[in access it becomes ('' & D1.DocID & '')]
Conclusion: Access sucks
Since this is to run repeatedly, I would ask (strongly suggest) they provide me a proper file where the issueID and valid appear on every line. This is much easier to process. You need to know for sure what the values for these fields are to properly import to your system.
Based on the comments: IN SQL Server you can would build a function to split the data based on the charindex for commas. If you search Google for fn_split, you will find a sample of this. Not sure how you would do this in Access but it would probably be an interative process where you look for the last comma and move everything past it to a holding table and then get rid of the command, then do again until there are no more commas. It iseasiest to do imports like this to staging tables where you can manipulate the data the way you need it and then put the final result into your real tables.
This can easily be done in SQL. I have written a TVF (table-valued function) specifically for line-splitting text that demonstrates how:
In order to use this with your current table & data do this:
My first choice would be to put together a quick application in C# or VB.Net to handle this.
If that wasn't viable, I'd have an "Import" table which took everything as is. Then I would use a cursor to iterate the records in the table. Inside the cursor I'd keep track of the IssueId and Val1 and parse the References column to create my child records. This part I'd package into a stored procedure.
I'm having problems coming up with a set-based SQL solution here. I've done this kind of thing before, I had to refresh my memory somewhat, but I'm running into a problem. I think it's an issue (feature/bug?) with the engine but I could be doing something daft. Perhaps someone intimate with Jet/ACE and who can read VBA can take a look at the code at the end of this answer and hopefully take this forward...?
The basic approach is to use a Sequence table of integers with the
MID()
expression to parse the data column (which I've renamed to MyReferences becauseREFERENCES
is a SQL keyword).Here's some MS Access VBA to recreate the test tables/data using SQL DDL/DML. Notice the first
SELECT
query returns sub-strings and star- and end delimiters; obviously, we're looking for the row(s) where both delimiters are the delimiting character,CHR(13)
in this case. The secondSELECT
query merely adds search conditions for the desired delimiters but errors with 'Invalid procedure call'; this happens when theMID()
expression is called using invalid parameter values e.g.I guess what is happening is the optimizer is not using the subquery as a 'shortcut' and instead is evaluating the
MID()
expression before the search conditions from Sequence table. If so it's a bit dumb and I can't think of a way of forcing the order of evaluation.So, is is my or the engine at fault here?
FWIW here's a
PROCEDURE
I wrote years ago for parsing a delimited list into a table. It seems to work OK for values up to 255 characters; any more and you get a very nasty ACE/Jet engine error. Again, I don't see what the problem is other than the engine can't cope! Anyhow, my point is that this works (for small values) and I can't figure out why I can't adapt it to the problem at hand:This has been chosen as the answer:
However, I don't think this is safe.
Consider if one of the value for the column named 'References' contained this data:
and a value
DocID = 9
existed in the other table.The problem here is that
will evaluate to
TRUE
, which is probably undesirable.To address this problem, I think the values in the search/join condition should be made safe by surrounding the values using the delimiter character e.g.
Do you mean (typed, not tested):
EDIT re COMMENTS
If the DocIDs are of a fixed length, you could consider something on these lines:
You will need a sequence table with integers from 1 to at least max length of Reference.