SQL AND and OR logic error - sql

Using MS Access 2016
Very stuck on a query error. It only is using either the "Milestone" criteria or the "HierarchyLevel" criteria - depending on which control was updated last. The Status criteria works in all cases.
Can anyone see my error? The query should meet the criteria of: chosen Status, Milestone Name search string, chosen level or Null level (ie all Levels)
Thanks Kindly
SELECT
qry_Milestones_WithHierarchy.ID,
qry_Milestones_WithHierarchy.HierarchyLevel,
qry_Milestones_WithHierarchy.Milestone
FROM qry_Milestones_WithHierarchy
WHERE
( qry_Milestones_WithHierarchy.Milestone Like "*" & [Forms]![frm_SelectMilestone]![txt_SearchTerm].[Text] & "*" )
AND ( qry_Milestones_WithHierarchy.IDStatus = [Forms]![frm_SelectMilestone]![cbo_Status] )
AND
(
qry_Milestones_WithHierarchy.HierarchyLevel = [Forms]![frm_SelectMilestone]![cbo_HierarchyLevel]
OR
Len( [Forms]![frm_SelectMilestone]![cbo_HierarchyLevel].[Text] & "" ) = 0
)
;
UPDATE
Example Date
HierarchyLevel Milestone
4 NameTest1
4 NameDemo1
3 NameTest2
3 NameDemo2
When I only use the Milestone or the Level filter I get the correct results
Controls
TextBox(Milestone) = ""
CombboBox(Status) = 1
CombboBox(HierarchyLevel) = 4
Results
HierarchyLevel Milestone
4 NameTest1
4 NameDemo1
Level 3 records are wrongly being included in the result
Controls
TextBox(Milestone) = "Test"
CombboBox(Status) = 1
CombboBox(HierarchyLevel) = 4
Results
HierarchyLevel Milestone
4 NameTest1
3 NameTest2

Try using a IIf to switch the last statement. Note that the use of wildcards at the beginning of a search renders indexing on the field redundant so large datasets will make this very slow.
SELECT
qry_Milestones_WithHierarchy.ID,
qry_Milestones_WithHierarchy.HierarchyLevel,
qry_Milestones_WithHierarchy.Milestone
FROM qry_Milestones_WithHierarchy
WHERE ( qry_Milestones_WithHierarchy.Milestone Like "*" & [Forms]![frm_SelectMilestone]![txt_SearchTerm] & "*" )
AND ( qry_Milestones_WithHierarchy.IDStatus = [Forms]![frm_SelectMilestone]![cbo_Status] )
AND ( qry_Milestones_WithHierarchy.HierarchyLevel " & IIF ([Forms]![frm_SelectMilestone]![cbo_HierarchyLevel] is null ,"Like *", " = " & [Forms]![frm_SelectMilestone]![cbo_HierarchyLevel] ) & ") ;

Related

MS Access Between dates query is not working

I'm an Access DB beginner. I have a database with a SearchForm where the user can enter search criteria, click the Search button and populate the subform with the filtered results.
The query has simple query based on what the user enters in fields in the search form
Like "*" & [Forms]![SampleSearchF]![txtMicrochip] & "*" which work well, but my date filter does not produce any results:
Between [Forms]![SampleSearchF]![DateReceivFrom] And [Forms]![SampleSearchF]![DateReceivTo]
The table fields that the date search is based on are Data Type:Date/Time , Format: Short Date
The Search Form fields are Format Short Date
The subform fields are also Short Date
SearchButton is a requery macro
And when I have the this query criteria in the query, none of the search functions work.
Any suggestions where I could look to solve the issue? Any help is appreciated.
Here is my SQL code for the search query,
FROM IndividualsT INNER JOIN SamplesT ON IndividualsT.AnimalID = SamplesT.AnimalID
WHERE (((IndividualsT.SpeciesName) Like "*" & [Forms]![SampleSearchF]![txtSpeciesName] & "*") AND
((IndividualsT.Microchip) Like "*" & [Forms]![SampleSearchF]![txtMicrochip] & "*") AND
((IndividualsT.Name) Like "*" & [Forms]![SampleSearchF]![txtName] & "*") AND
((SamplesT.Location) Like "*" & [Forms]![SampleSearchF]![txtLocation] & "*") AND
((SamplesT.SampleReceived) Between [Forms]![SampleSearchF]![DateReceivFrom] And [Forms]![SampleSearchF]![DateReceivTo]));
SamplesT
SampleID
AnimalID
SampleReceived
Location
CollectionDate
1
1
18/08/2021
Tassie
10/08/2021
7
1
15/09/2021
Berlin
25/09/2021
13
12
25/09/2021
Sydney
4/09/2021
14
12
24/09/2021
New York
1/09/2021
IndividualsT
AnimalID
SpeciesName
Microchip
Name
1
Parrot
1234
Hugo
12
Koala
853
Sherlock
Likely, your issue is the WHERE logic when form fields are empty. When empty, LIKE expressions return as ** which means anything, so no rows are filtered out. However, empty dates conflict with BETWEEN clause. Consider using NZ to return the column itself if form fields are empty:
(
SamplesT.SampleReceived
BETWEEN NZ([Forms]![SampleSearchF]![DateReceivFrom], SamplesT.SampleReceived)
AND NZ([Forms]![SampleSearchF]![DateReceivTo], SamplesT.SampleReceived)
);
Always specify the data type of the parameters:
Parameters
[Forms]![SampleSearchF]![txtSpeciesName] Text ( 255 ),
[Forms]![SampleSearchF]![txtMicrochip] Text ( 255 ),
[Forms]![SampleSearchF]![txtName] Text ( 255 ),
[Forms]![SampleSearchF]![txtLocation] Text ( 255 ),
[Forms]![SampleSearchF]![DateReceivFrom] DateTime,
[Forms]![SampleSearchF]![DateReceivTo] DateTime;
Select *
From YourTable
Where ...

INNER JOIN VBA Access

I'm trying to do a dynamic SQL statement in which the command will add the field "Drawn" in a certain area of the depending on the value of j. This is because there are several instances where the field "Rev" in table "Revision" appears. I created this statement in the query design but only to update a specific field in the table, not dependent on my counter j. I'm getting 'JOIN expression not supported'. Can anyone help me with this issue?
Select Case selection(n)
Case "A"
j = 1
Case "B"
j = 2
Case "0"
j = 3
Case "1"
j = 4
Case "2"
j = 5
End Select
With rs
While Not .EOF
DoCmd.RunSQL "SELECT Area.Drawn " & _
"FROM (Revision INNER JOIN RevDesc ON Revision.Rev" & j & " = RevDesc.Rev) " & _
"INNER JOIN Area ON Revision.LineNumber = ArealineNmuber ;"
.MoveNext
wend
end with
Probably you looking for method like OpenRecordset...
Lopping Recordset use result fields to generate simple Update with you can run by RunSQL method.
EDIT:
example update:
UPDATE [Revision]
SET [Drawn] = (SELECT [Drawn] FROM [Area] WHERE [Area].[LineNumber] = [Revision].[LineNumber] )
WHERE Exists(SELECT 1 FROM [RevDesc]
WHERE [RevDesc].[Rev] = [Revision].[Rev]);

VB6 ADO: Returning Result Sets based on values of first columns

Task:
I have a table with 8 columns and there is about 100,000 rows in it.
Year Cycle PHSRCode Blanket LastName FirstName Status ExcusedStatus
2012 5 GW1-01 null CASTILLO LILIBETH Yes FALSE
2012 5 GW1-01 null CLAVERIA PAMELA Yes FALSE
2012 5 GW1-01 1 RAMOS LAILANI Yes FALSE
2012 5 GW1-01 2 SIY ZERZENDEE Yes FALSE
2012 5 GW1-01 null SANTOS MARILIN Yes FALSE
2012 5 GW1-01 null BALDERAS JULIET No FALSE
For the query result, I need to return all 8 columns and 100,000 rows with an additional 4 columns whose values can be derived from the values of the 8 columns and 100,000 rows of the same table.
Basically, the output should be like this:
Year Cycle PHSRCode Blanket LastName FirstName Status ExcusedStatus RawActualRate RawTargetRate RawActualReach
2012 5 GW1-01 null CASTILLO LILIBETH Yes FALSE 1 1 1
Columns:
raw_actual_rate - Can be derived by: If Status = 'Yes', 1, 0
raw_target_rate - Can be derived by: If Status <> '', 1, 0
raw_actual_reach - This is where it gets complicated. Can be derived by: If Status = 'No', 0, 1/ [count if Status='Yes' AND (Year + Cycle + PHSRCode + Blanket + LastName + FirstName) = Row Result of the same columns
raw_target_reach - This is where is gets complited again. Can be derived by: 1/ [count if Status='Yes' AND (Year + Cycle + PHSRCode + Blanket + LastName + FirstName) = Row Result of the same columns
Question:
Can this be done in one query statement? I wish to not use any looping statements through code since getting the result this way takes a bit of time (10-20 mins).
What I have so far:
This is the SQL statement that I am using. However, I am stuck with the last part since tb1 is not returning any values yet:
Private Function sql_string1v2() As String
Dim sql As String
sql = "SELECT "
sql = sql & "tb1.ID, "
sql = sql & "tb1.PeriodYear, "
sql = sql & "tb1.PeriodCycle, "
sql = sql & "tb1.PeriodZone, "
sql = sql & "tb1.PHSRCode, "
sql = sql & "tb1.Blanket, "
sql = sql & "tb1.LastName, "
sql = sql & "tb1.FirstName, "
sql = sql & "tb1.MiddleName, "
sql = sql & "tb1.PRC, "
sql = sql & "tb1.Specialty, "
sql = sql & "tb1.HCPType, "
sql = sql & "tb1.Class, "
sql = sql & "tb1.Room, "
sql = sql & "tb1.Institution, "
sql = sql & "tb1.Address, "
sql = sql & "tb1.Region, "
sql = sql & "tb1.Province, "
sql = sql & "tb1.City, "
sql = sql & "tb1.Brick, "
sql = sql & "tb1.Type, "
sql = sql & "tb1.Affiliation, "
sql = sql & "tb1.Frequency, "
sql = sql & "tb1.Status, "
sql = sql & "tb1.MissCallReason,"
sql = sql & "tb1.FlexiCallZone, "
sql = sql & "tb1.Process, "
sql = sql & "tb1.DateAdded, "
sql = sql & "tb1.Encoder, "
sql = sql & "tb1.ExcusedStatus, "
sql = sql & "IIF(tb1.Status = 'Yes', 1, 0) AS raw_actual_rate, "
sql = sql & "IIF(tb1.Status <> '', 1, 0) AS raw_target_rate, "
sql = sql & "(SELECT (1/COUNT(ID)) AS raw_actual_reach FROM tblDCM as tb2 WHERE tb2.Status = 'Yes' AND tb2.PeriodYear = tbl1.PeriodYear AND PeriodCycle = tb1.PeriodCycle AND PHSRCode = tb1.PHSRCode AND ((tb1.Blanket IS NOT NULL) AND Blanket = tb1.Blanket) OR (LastName = tb1.LastName AND FirstName = tb1.FirstName)) AS raw_actual_reach, "
sql = sql & "FROM tblDCM as tb1 "
sql = sql & "WHERE LEFT(tb1.PHSRCode,2) = 'N0'"
sql_string1v2 = sql
End Function
This should be a comment, but I don't have enough reputation yet to post comments.
That said, I can offer some advice.
First, edit your question to include the target database (access, MSSQL, Oracle etc.) as the subset of SQL commands can vary greatly by the platform you're querying against.
Second, this is purely a SQL question and you should forget about the vb6 aspect until you have the query working as expected.
I can say with certainty that raw_actual_reach is going to be a problem the way you have it currently written. For one not all of your fields include their alias prefix and when comparing fields with identical names, you have to include the alias as a prefix or you will encounter errors.
Also, you've got an extra comma at the end of the field list (that or you missed a line of code when you were copy/pasting.) I've pulled the query out of your code sample and have a corrected one below. There still may be issues with this query depending on the db platform you're querying against, but now at least the syntax is consistent. HTH.
SELECT tb1.ID, tb1.PeriodYear, tb1.PeriodCycle, tb1.PeriodZone, tb1.PHSRCode, tb1.Blanket, tb1.LastName, tb1.FirstName,
tb1.MiddleName, tb1.PRC, tb1.Specialty, tb1.HCPType, tb1.Class, tb1.Room, tb1.Institution, tb1.Address, tb1.Region,
tb1.Province, tb1.City, tb1.Brick, tb1.Type, tb1.Affiliation, tb1.Frequency, tb1.Status, tb1.MissCallReason, tb1.FlexiCallZone,
tb1.Process, tb1.DateAdded, tb1.Encoder, tb1.ExcusedStatus, IIF(tb1.Status = 'Yes', 1, 0) AS raw_actual_rate, IIF(tb1.Status <> '', 1, 0) AS raw_target_rate,
(
SELECT (1/COUNT(ID)) AS raw_actual_reach
FROM tblDCM as tb2
WHERE tb2.Status = 'Yes' AND tb2.PeriodYear = tbl1.PeriodYear AND tb2.PeriodCycle = tb1.PeriodCycle AND tb2.PHSRCode = tb1.PHSRCode AND
((tb1.Blanket IS NOT NULL) AND tb2.Blanket = tb1.Blanket) OR
(tb2.LastName = tb1.LastName AND tb2.FirstName = tb1.FirstName)
) AS raw_actual_reach
FROM tblDCM as tb1
WHERE LEFT(tb1.PHSRCode,2) = 'N0'

How to use ROWNUM in VB.net to retrieve data from a MS-Access DB

i'm having troubles with a SELECT query. This is the string generated by my program.
SELECT
CODLFA, POSLFA, ARTLFA, DESLFA, CANLFA,
DOCLFA, DTPLFA, DCOLFA, PIVLFA, TIVLFA, CE1LFA
FROM
F_LFA
WHERE
CODLFA IN
(
SELECT
CODFAC
FROM
F_FAC
WHERE
TIPFAC = '2'
AND FECFAC >= 09-02-2013
AND CLIFAC = 21
AND ROWNUM = 1
ORDER BY CODFAC
)
AND ARTLFA = '00259'
AND CE1LFA = '011'
When i put this in the SQL query designer in MS Access a window shows up asking me to enter the ROWNUM number, then it shows the correct row. But in my app written in VB.net it doesn't works. Here is where the query is constructed. Every other queries in my program are working fine.But this one doesn't retrieve any values. That's why i put the Fill command inside a try statement and there is where it fails because newAdapter is empty i guess.
For Each LABLrow As DataRow In lalbaranes.Rows
'obteniendo lineas de facturas que coincidan con la fecha, productos y cliente.
newAdapter = New OleDb.OleDbDataAdapter("SELECT CODLFA, POSLFA, ARTLFA, DESLFA, CANLFA, DOCLFA, DTPLFA, DCOLFA, PIVLFA, TIVLFA, CE1LFA FROM F_LFA WHERE CODLFA IN (SELECT CODFAC FROM F_FAC WHERE TIPFAC = '2' AND FECFAC >= " & ALBfecha & " AND CLIFAC = " & CLIabl & " AND ROWNUM = 1 ORDER BY CODFAC) AND ARTLFA = '" & LABLrow(1) & "' AND CE1LFA = '" & LABLrow(6) & "'", dataConnection)
newCommand = New OleDb.OleDbCommandBuilder(newAdapter)
'graba los resultados en la tabla lboletas
lboletas.Clear()
Try
newAdapter.Fill(lboletas)
newCommand.Dispose()
newAdapter.Dispose()
How should i select only the first row in F_FAC?
PD: Sorry for my bad English.
ROWNUM is an Oracle "pseudocolumn" for which there is no direct equivalent in Access SQL. Access prompts you for a value because ROWNUM is not recognized as a column name so it asks you to supply a value. If you were to inspect the error message you receive when you try the query in VB.NET you would see
No value given for one or more required parameters.
Again, this is because ROWNUM is not valid Access SQL.
For your purposes the Access equivalent would be...
SELECT TOP 1 FROM ...
...but be aware that TOP 1 in Access may return more than one row if there is a "tie" in the ORDER BY sort. In your case, if there were two rows with the same [CODFAC] then SELECT TOP 1 ... in Access would actually return two rows.

SQL for parsing multi-line data?

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
This has been chosen as the answer:
Q2.References LIKE ("*" & D1.DocID & "*"));
However, I don't think this is safe.
Consider if one of the value for the column named 'References' contained this data:
DocID1<cr>DocID999<cr>DocID3
and a value DocID = 9 existed in the other table.
The problem here is that
"DocID1<cr>DocID999<cr>DocID3" LIKE "*" & "DocID9" & "*"
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.
(CHR(13) & Q2.References & CHR(13)) LIKE ("*" & CHR(13) & D1.DocID & CHR(13) & "*"));
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.
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 suggest that you research SQL Server Integration Services (SSIS). This tool was created to do this kind of data import/export as quickly as possible with as little code.
Read about it. Do some hands on labs to see if any examples are close to what your trying to do.
http://en.wikipedia.org/wiki/SQL_Server_Integration_Services
http://www.microsoft.com/downloads/details.aspx?familyid=b1145e7a-a4e3-4d14-b1e7-d1d823b6a447&displaylang=en
Do you mean (typed, not tested):
Dim rs As DAO.Recordset
Dim rsIn As DAO.Recordset ''Or ADO if you link directly to Excel
Set rs=CurrentDB.OpenRecordset( _
"SELECT * FROM DocLinks dl INNER JOIN Docs d ON dl.DocKey=d.DocKey")
Do While Not rsIn.EOF
astrDocs=Split(rsIn!References, vbCrLf)
For Each strDoc In astrDocs
rs.FindFirst "DocID='" & strDoc & "'"
If rs.NoMatch Then
strSQL="INSERT INTO DocLinks (DocID, IssueID) " _
& "VALUES ('" strDoc & "'," & rsIn!IssueID & ")"
CurrentDB.Execute strSQL, dbFailOnError
End If
Next
rsIn.MoveNext
Loop
EDIT re COMMENTS
If the DocIDs are of a fixed length, you could consider something on these lines:
SELECT Sequence.Seq
, ImportTable.IssueID
, Mid(Replace([References],"<cr>",""),[seq],6) AS Docs
FROM Sequence, ImportTable
WHERE ([seq]+5) Mod 6=0)
AND Mid(Replace([References],"<cr>",""),[seq],6))<>""
AND Mid(Replace([References],"<cr>",""),[seq],6))
Not In (SELECT DocID FROM Docs)
You will need a sequence table with integers from 1 to at least max length of Reference.
This can easily be done in SQL. I have written a TVF (table-valued function) specifically for line-splitting text that demonstrates how:
ALTER function [dbo].[fnSplit3](
#parameter varchar(Max) -- the string to split
, #Seperator Varchar(64) -- the string to use as a seperator
)
RETURNS #Items TABLE(
ID INT -- the element number
, item VARCHAR(8000) -- the split-out string element
, OffSet int -- the original offest
--( not entirley accurate if LEN(#Seperator) > 1 because of the Replace() )
)
AS
BEGIN
/*
"Monster" Split in SQL Server 2005
From Jeff Moden, 2008/05/22
BYoung, 2008/06/18: Modified to be a Table-Valued Function
And to handle CL/LF or LF-only line breaks
Test: (scripts all procs & views in master)
Select Lines.Item
From Master.sys.syscomments C
CROSS APPLY dbo.fnSplit3(C.text, char(13)+char(10)) Lines
Order by C.ID, Lines.ID
Test2: (scripts all triggers in your database)
Select Lines.Item
From sys.sql_modules M
Join sys.objects O on O.object_id = M.object_id
CROSS APPLY dbo.fnSplit3(M.definition, char(13)+char(10)) Lines
Where O.Type = 'TR'
Order by O.create_date, Lines.ID
*/
Declare #Sep char(1)
Set #Sep = char(10) --our seperator character (convenient, doesnt affect performance)
--NOTE: we make the #Sep character LF so that we will automatically
-- parse out rogue LF-only line breaks.
--===== Add start and end seprators to the Parameter so we can handle
-- all the elements the same way
-- Also change the seperator expressions to our seperator
-- character to keep all offsets = 1
SET #Parameter = #Sep+ Replace(#Parameter,#Seperator,#Sep) +#Sep
-- This reduces run-time about 10%
;WITH cteTally AS
(--==== Create a Tally CTE from 1 to whatever the length
-- of the parameter is
SELECT TOP (LEN(#Parameter))
ROW_NUMBER() OVER (ORDER BY t1.object_id) AS N
FROM Master.sys.system_Columns t1
CROSS JOIN Master.sys.system_Columns t2
)
INSERT into #Items
SELECT ROW_NUMBER() OVER (ORDER BY N) AS Number,
SUBSTRING(#Parameter, N+1, CHARINDEX(#Sep, #Parameter, N+1)-N-1) AS Value
, N+1
FROM cteTally
WHERE N < LEN(#Parameter)
AND SUBSTRING(#Parameter, N, 1) = #Sep --Notice how we find the seperator
Return
END
In order to use this with your current table & data do this:
SELECT Issues.IssueID, Lines.Item as Reference
From Issues
Cross Apply dbo.fnSplit3(Issues.Reference, char(13)) Lines
Order By IssueID, Reference
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 because REFERENCES 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 second SELECT query merely adds search conditions for the desired delimiters but errors with 'Invalid procedure call'; this happens when the MID() expression is called using invalid parameter values e.g.
SELECT MID('A', 0, 0)
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?
Sub main()
Dim sql As String
sql = _
"DROP TABLE ImportTable;"
On Error Resume Next ' Table may not exist
CurrentProject.Connection.Execute sql
On Error GoTo 0
sql = _
"DROP TABLE Sequence;"
On Error Resume Next ' Table may not exist
CurrentProject.Connection.Execute sql
On Error GoTo 0
sql = _
"CREATE TABLE ImportTable ( " & _
"IssueID INTEGER NOT NULL UNIQUE, MyReferences VARCHAR(90) NOT NULL);"
CurrentProject.Connection.Execute sql
sql = _
"INSERT INTO ImportTable VALUES (1234, 'DocID1' & Chr(13) & 'DocID22' & Chr(13) & 'DocID3');"
CurrentProject.Connection.Execute sql
sql = _
"CREATE TABLE Sequence (seq INTEGER NOT NULL UNIQUE);"
CurrentProject.Connection.Execute sql
sql = _
"INSERT INTO Sequence VALUES (-1);"
CurrentProject.Connection.Execute sql
sql = _
"INSERT INTO [Sequence] (seq) SELECT Units.nbr + Tens.nbr" & _
" FROM ( SELECT" & _
" nbr FROM ( SELECT 0 AS nbr FROM [Sequence] UNION" & _
" ALL SELECT 1 FROM [Sequence] UNION ALL SELECT 2 FROM" & _
" [Sequence] UNION ALL SELECT 3 FROM [Sequence] UNION" & _
" ALL SELECT 4 FROM [Sequence] UNION ALL SELECT 5 FROM" & _
" [Sequence] UNION ALL SELECT 6 FROM [Sequence] UNION" & _
" ALL SELECT 7 FROM [Sequence] UNION ALL SELECT 8 FROM" & _
" [Sequence] UNION ALL SELECT 9 FROM [Sequence] ) AS" & _
" Digits ) AS Units, ( SELECT nbr * 10 AS nbr FROM" & _
" ( SELECT 0 AS nbr FROM [Sequence] UNION ALL SELECT" & _
" 1 FROM [Sequence] UNION ALL SELECT 2 FROM [Sequence]" & _
" UNION ALL SELECT 3 FROM [Sequence] UNION ALL SELECT" & _
" 4 FROM [Sequence] UNION ALL SELECT 5 FROM [Sequence]" & _
" UNION ALL SELECT 6 FROM [Sequence] UNION ALL SELECT" & _
" 7 FROM [Sequence] UNION ALL SELECT 8 FROM [Sequence]" & _
" UNION ALL SELECT 9 FROM [Sequence] ) AS Digits )" & _
" AS Tens;"
CurrentProject.Connection.Execute sql
sql = _
"SELECT DT1.IssueID, DT1.parsed_text, DT1.delimiter_1, DT1.delimiter_2 " & _
"FROM ( " & _
"SELECT I1.IssueID, MID(I1.MyReferences, S1.seq, S2.seq - S1.seq - LEN(CHR(13))) AS parsed_text, " & _
" MID(CHR(13) & I1.MyReferences & CHR(13), S1.seq, LEN(CHR(13))) AS delimiter_1, " & _
" MID(CHR(13) & I1.MyReferences & CHR(13), S2.seq, LEN(CHR(13))) AS delimiter_2 " & _
"FROM ImportTable AS I1, Sequence AS S1, Sequence AS S2 " & _
"WHERE S1.seq < S2.seq " & _
"AND S2.seq - S1.seq - LEN(CHR(13)) > 0 " & _
"AND S1.seq BETWEEN 1 AND LEN(CHR(13)) + LEN(I1.MyReferences) + LEN(CHR(13)) " & _
"AND S2.seq BETWEEN 1 AND LEN(CHR(13)) + LEN(I1.MyReferences) + LEN(CHR(13)) " & _
") AS DT1;"
Dim rs As ADODB.Recordset
Set rs = CurrentProject.Connection.Execute(sql)
MsgBox rs.GetString
sql = _
"SELECT DT1.IssueID, DT1.parsed_text, DT1.delimiter_1, DT1.delimiter_2 " & _
"FROM ( " & _
"SELECT I1.IssueID, MID(I1.MyReferences, S1.seq, S2.seq - S1.seq - LEN(CHR(13))) AS parsed_text, " & _
" MID(CHR(13) & I1.MyReferences & CHR(13), S1.seq, LEN(CHR(13))) AS delimiter_1, " & _
" MID(CHR(13) & I1.MyReferences & CHR(13), S2.seq, LEN(CHR(13))) AS delimiter_2 " & _
"FROM ImportTable AS I1, Sequence AS S1, Sequence AS S2 " & _
"WHERE S1.seq < S2.seq " & _
"AND S2.seq - S1.seq - LEN(CHR(13)) > 0 " & _
"AND S1.seq BETWEEN 1 AND LEN(CHR(13)) + LEN(I1.MyReferences) + LEN(CHR(13)) " & _
"AND S2.seq BETWEEN 1 AND LEN(CHR(13)) + LEN(I1.MyReferences) + LEN(CHR(13)) " & _
") AS DT1 " & _
"WHERE DT1.delimiter_1 = CHR(13) " & _
"AND DT1.delimiter_2 = CHR(13);"
Set rs = CurrentProject.Connection.Execute(sql)
MsgBox rs.GetString
End Sub
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:
CREATE PROCEDURE ListToTable
(
delimted_text MEMO,
delimiter VARCHAR(4) = ','
)
AS
SELECT MID(I1.input_string, S1.seq, MIN(S2.seq) - S1.seq - LEN(delimiter)) AS param
FROM
(
SELECT DISTINCT delimted_text AS input_string
FROM Sequence AS S3
WHERE S3.seq BETWEEN 1 AND LEN(delimted_text)
) AS I1, Sequence AS S1, Sequence AS S2
WHERE MID(delimiter & I1.input_string & delimiter, S1.seq, LEN(delimiter)) = delimiter
AND MID(delimiter & I1.input_string & delimiter, S2.seq, LEN(delimiter)) = delimiter
AND S1.seq < S2.seq
AND S1.seq BETWEEN 1 AND LEN(delimiter) + LEN(delimted_text) + LEN(delimiter)
AND S2.seq BETWEEN 1 AND LEN(delimiter) + LEN(delimted_text) + LEN(delimiter)
GROUP
BY I1.input_string, S1.seq
HAVING LEN(MID(I1.input_string, S1.seq, MAX(S2.seq) - S1.seq - LEN(delimiter))) > 0;
I think using the word "parse" in the title has confused the crap out of everyone. The bug in Access was that a correlated query performed on a query (instead of a table) causes a hang. So instead, I created a temporary table that ads the References column (with the multi-line text) to the Issues table so I have access to the other fields. The final query creates the link table described above, along with the DocID and IssueID for reference:
SELECT Q1.IssueID, Q1.IssueKey, D1.DocKey, D1.DocID
FROM Issues AS Q1, Documents AS D1
WHERE Q1.IssueID in
(SELECT Q2.IssueID FROM Issues AS Q2 WHERE Q2.References LIKE ("*" & D1.DocID & "*"));
The inner select pulls the list of issues which has a given document in the references column. The outer select performs this for each document, resulting in the aggregate list.