I'm struggling with a sorting problem.
I've got a table which is as follows:
aspect_id (int)
aspect_text (memo)
root_id (int) which has as a foreign key a aspect_id
I've got a non cyclic tree with the following dummy data:
aspect_id aspect_text root_id
1 root null
2 aspect1 1
3 aspect2 1
4 aspect3 2
5 aspect5 4
In the example the data is sorted correctly, in my database its not. I want to sort that it starts at the root element, then finds a child, output that child and does that recursively.
With CTE it is fairly doable. Access doesn't support this. With CTE it would be something like:
WITH aspectTree (aspect_id, root_id, Level#) AS
(
Select
aspect.aspect_id,
aspect.root_id,
0
FROM aspect
WHERE aspect.aspect_id = 44
UNION ALL
SELECT
aspect.aspect_id,
aspect.root_id,
T.Level# + 1
FROM aspect
INNER JOIN aspectTree AS T
On T.aspect_id = aspect.root_id
)
SELECT * FROM aspectTree;
If performance is not a consideration, this fairly simple solution would work:
Public Function GetLevel(ByVal lngNodeId As Long) As Long
Dim varRootId As Variant
varRootId = DLookup("root_id", "aspect", "aspect_id=" & lngNodeId)
If IsNull(varRootId) Then
GetLevel = 0
Else
GetLevel = GetLevel(varRootId) + 1
End If
End Function
You could then use that function in your ORDER BY clause:
SELECT aspect.*
FROM aspect
ORDER BY GetLevel([aspect_id]), aspect_text
I don't know if the following will work for you but here you go using Bill of Materials algorithms.
Bill Of Materials
BOM, with Joe Celko Nested Sets
Its full of test code, but i did something that works in vb code. Its really ugly and slow, but it works. Im now cleaning it up, just got it working. The solution is a recursive function. The function calls on itself if it finds that the node has childs. It seemed to overwrite the arrays, that why its an array of arrays. The code is hideous, but it works and thats all i needed. The database is and will stay small (<1000 records) so speed is not an issue. Thanks for the comments and answers, if someones knows i better solution, i would love to hear it.
Private Function Fillarray(value As Integer)
Dim done As Boolean
j = j + 1
esql = "select aspect_id from aspect where root_id = " & value
Set rec(j) = db.OpenRecordset(esql)
Dim k As Integer
k = j
Do While Not rec(k).EOF
done = True
arra(i) = rec(k).Fields(0)
Dim temp1 As String
temp1 = DLookup("[aspects]", "[aspect]", "[aspect_id] = " & rec(k).Fields(0))
db.Execute "INSERT INTO sortedaspect (aspect_id, aspect) VALUES (" & rec(k).Fields(0) & ", '" & temp1 & "')"
esql = "select aspect_id from aspect where root_id = " & rec(k).Fields(0)
Set rec(90) = db.OpenRecordset(esql)
Do While Not rec(90).EOF And done
'fix this without a loop,you only need to know if it has childs...
Fillarray (rec(k).Fields(0))
done = False
Loop
'next child
rec(k).MoveNext
'value = arra(i)
i = i + 1
'MsgBox arra(i - 1)
Loop
End Function
Related
In my database there are 3 column which is Name, Age, Gender.
In the program, I only want to use 1 search button. When the button is clicked, the program determine which 3 of the textbox has input and search for the right data.
How do you work with the query? For example if Name and Gender has text, the query :
"Select * from table Where (Name = #name) AND (Gender = #gender)"
And when only name is entered, I only query for the name. Must I check textbox by textbox whether there is user input and then write multiple query for each of them? Or is there a better way to do this?
Edit (29/5/16) : I tried doing this another way like this
myCommand = New MySqlCommand("Select * from project_record Where
(FloatNo = #floatNo OR FloatNo = 'None') AND
(DevCompanyName = #devCompanyName OR DevCompanyName = 'None') AND
(DevType = #devType OR DevType = 'None') AND
(LotPt = #lotPt OR LotPt = 'None') AND
(Mukim = #mukim OR Mukim = 'None') AND
(Daerah = #daerah OR Daerah = 'None') AND
(Negeri = #negeri OR Negeri = 'None') AND
(TempReference = #tempRef OR TempReference = 'None')", sqlConn)
But as you can guess already it will not work efficiently as well because if I only enter input for DevType and leave other textboxes blank, the query will not pull up all the records for DevType only. It will just display as no records.
Select * from table
Where (Name = #name OR #name is Null)
AND (Gender = #gender OR #gender is Null)
...
it should be one query
Other answers have explained how to simplify the query. It is especially important to get rid of the ORs, since they inhibit any use of indexes.
Once you have the query build cleanly, you need to think about the dataset and decide which columns are usually used for filtering. Then make a few INDEXes for them. You won't be able to provide 'all' possible indexes, hence my admonition that you think about the dataset.
When building indexes, you can have single-column or multiple-column indexes. For your type of data, I would suggest starting with several 2-column indexes. Make sure each index starts with a different column.
For Where (Name = #name) AND (Gender = #gender), here are some notes:
INDEX(gender) is useless because of low 'cardinality';
INDEX(gender, name) might be useful, but the following would be better:
INDEX(name)
Things like name and DevCompanyName are virtually unique, so a 1-column index is probably good.
If you had gender and age, then INDEX(age, gender) might be useful.
MySQL will almost never use two indexes for a single SELECT.
By the way, the construction of the WHERE could be done in a Stored Procedure. You would need CONCAT, PREPARE, etc.
Original answer
(scroll down to see update)
Can you try the following:
build a list only including values of the textboxes that have an input
set a string of the join the items of that list together with the " AND " string
append that string to your standard SELECT statement
The code looks like this:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim Predicate1 As String = Me.TextBox1.Text
Dim Predicate2 As String = Me.TextBox2.Text
Dim Predicate3 As String = Me.TextBox3.Text
Dim PredicateList As New List(Of String)
Dim WhereClause As String
Dim Query As String
If Predicate1 <> String.Empty Then
PredicateList.Add("Name=""" & Predicate1 & """")
End If
If Predicate2 <> String.Empty Then
PredicateList.Add("Age=""" & Predicate2 & """")
End If
If Predicate3 <> String.Empty Then
PredicateList.Add("Gender=""" & Predicate3 & """")
End If
WhereClause = String.Join(" AND ", PredicateList.ToArray)
Query = "SELECT * FROM TABLE WHERE " & WhereClause
MessageBox.Show(Query)
End Sub
Update
Further to the comments re SQL injection, here is an updated sample.
Dim Command As SqlClient.SqlCommand
Dim Predicate1 As String = Me.TextBox1.Text
Dim Predicate2 As String = Me.TextBox2.Text
Dim Predicate3 As String = Me.TextBox2.Text
Dim ParameterList As New List(Of SqlClient.SqlParameter)
Dim PredicateList As New List(Of String)
Dim BaseQuery As String = "SELECT * FROM TABLE WHERE "
If Predicate1 <> String.Empty Then
PredicateList.Add("name = #name")
ParameterList.Add(New SqlClient.SqlParameter("#name", Predicate1))
End If
If Predicate2 <> String.Empty Then
PredicateList.Add("age = #age")
ParameterList.Add(New SqlClient.SqlParameter("#age", Predicate2))
End If
If Predicate3 <> String.Empty Then
PredicateList.Add("gender = #gender")
ParameterList.Add(New SqlClient.SqlParameter("#gender", Predicate3))
End If
Command = New SqlClient.SqlCommand(BaseQuery & String.Join(" AND ", PredicateList.ToArray))
Command.Parameters.AddRange(ParameterList.ToArray)
COALESCE is your friend here. You can use it to make the where clause ignore comparisons where the parameter is NULL.
Select * from table Where (Name = COALESCE(#name,table.Name))
AND (Gender = COALESCE(#gender,table.Gender))
So, if the #name parameter is NULL, COALESCE(#name,table.Name) will return the value of the 'Name' column of the current row and (Name = COALESCE(#name,table.Name)) will always be true.
This assumes that if no value is entered in a textbox the corresponding parameter will be NULL. If instead it is a value such as 'None', you can use the NULLIF function to map 'None' to NULL
Select * from table Where
(Name = COALESCE( NULLIF( #name, 'None'), table.Name))
AND (Gender = COALESCE( NULLIF( #gender, 'None'), table.Gender))
How to implement a more efficient search?
The answer partly depends on what your definition of efficient is. I suspect you mean less code and fewer if blocks etc. But fundamentally, running a new SELECT * query to apply a filter is inefficient because your base data set can be all the rows and you just fiddle with the users View of it.
I have a DB with random data in columns for Fish, Color (string), Bird, Group (int) and Active which should be similar enough for Name, Age and Gender in the question - or that other long thing at the bottom.
DataTable
Fill a datatable and bind it to a DGV:
' form level object
Private dtSample As DataTable
...
' elsewhere
Dim sql = "SELECT Id, Name, Descr, `Group`, Fish, Bird, Color, Active FROM Sample"
Using dbcon As MySqlConnection = New MySqlConnection(MySQLConnStr)
' create SELECT command with the Query and conn
Dim cmd As New MySqlCommand(sql, dbcon)
...
daSample.Fill(dtSample)
daSample.FillSchema(dtSimple, SchemaType.Source)
End Using
dgv2.DataSource = dtSample
Going forward, we can filter the user's view of that table without issuing a new query.
Filter Controls
If some of the fields are limited to certain selections, for instance Gender, you can use a ComboBox instead of a TextBox. This is to help the user succeed and avoid typos (Make or Mael instead of Male; or here, correctly spelling Baracuda I mean Baraccuda, er Barracuda correctly.
For illustration purposes, Fish is something where the user can type in anything at all, but Bird is constrained to a set of choices. If there is a Bird table, cboBird can be bound or populated from it. But you may also be able to populate it from the master/base table:
Dim birds = dtSample.AsEnumerable.Where(Function(d) d.IsNull(5) = False).
Select(Function(d) d.Field(Of String)("Bird")).
Distinct.
ToArray()
cboBird.Items.AddRange(birds)
If "Finch" is a legal choice but there are none in the database, it wont show in the list. Depending on the app, this can be a Good Thing:
If the user filters on Finch and there a no resulting records, you won't need a MessageBox or StatusBar message explaining the empty result set.
If something is not in the list, you are signalling up front that there are none of those. It then becomes a matter of training why a known element isnt in the list.
On the other hand, you'd have to repopulate those filter controls each time before they are used in case new records were added recently. If the controls are on a Dialog or different TabPage, this is easy to do as needed.
It isnt always applicable, but it can help the user avoid typos.
It depends on the app whether either method is of value.
DBNull / 'none'
I am not sure why you are adding 'none' to each clause. If someone want to see all the 'John` or all the 'Cod' records, it doesn't seem like they would also be interested in 'none'. Personally, Null/DBNull seems a better way to handle this, but it is easy to add or not add either form.
It would seem more valuable to filter to just those with DBNull/None. The code above for the Bird List filters out DBNull and I would do so for none as well. Then, before the result is added to the ComboBox, add a `None' item first so it is at the top.
Again it depends on what the app does; Or = 'None', may make perfect sense in this case.
Filter
Using a TextBox for Fish and Group, a ComboBox for Bird and Color and a CheckBox for Active, the code can form the filter thusly:
Dim filterTerms As New List(Of String)
Dim filterFmt = "{0} = '{1}' "
' OR:
' Dim filterFmt = "{0} = '{1}' OR {0} Is Null"
' OR:
' Dim filterFmt = "{0} = '{1}' OR {0} = 'none'"
If String.IsNullOrEmpty(tbSearchFish.Text) = False Then
Dim txt = tbSearchFish.Text.Replace("'", "''")
filterTerms.Add(String.Format(filterFmt, "Fish", txt))
End If
If cboBird.SelectedIndex > -1 Then
filterTerms.Add(String.Format(filterFmt, "Bird", cboBird.SelectedItem.ToString))
End If
If String.IsNullOrEmpty(tbGroup.Text) = False Then
Dim n As Int32
If Int32.TryParse(tbGroup.Text, n) Then
filterTerms.Add(String.Format(filterFmt, "[Group]", n))
End If
End If
If cboColor.SelectedIndex > -1 Then
filterTerms.Add(String.Format(filterFmt, "Color", cboColor.SelectedItem.ToString))
End If
If chkActive.Checked Then
' NOTE: I do not have TreatTinyAsBoolean turned on
' for some reason
filterTerms.Add(String.Format(filterFmt, "Active", "1"))
End If
If filterTerms.Count > 0 Then
Dim filter = String.Join(" AND ", filterTerms)
dtSample.DefaultView.RowFilter = filter
Dim rows = dtSample.DefaultView.Count
End If
Use whichever filterFmt is appropriate for what the app needs to do
A filter term is only added to the list if the related control has a value (as per above, this could include a 'None').
For the TextBox, it escapes any embedded ticks such as might be found in names like O'Malley or D'Artgnan. It replaces one tick with two.
Since Group is a numeric, a valid Int32 input is tested
If there are elements in the filterTerms list, a filter string is created
The filter is applied to the DefaultView.Filter (you can use also use a DataView or a BindingSource) so that the code need not query the database to provide filter capabilities.
Rows will tell you how many rows are in the current View.
The only halfway tricky one is a Boolean like Gender or Active because those actually resolve to three choices: {Any/Either, A, B}. For that, I would use a ComboBox and ignore it for SelectedIndex 0 as well. I didn't bother with this because the Combo concept is amply covered. Result:
Is it More "Efficient"?
It still depends.
It doesn't re-query the database to get rows the app can already have.
No new DBConnection, DBCommand or other DBProvider objects are created, just a list.
No need to dynamically create a SQL statement with N parameters in a loop to avoid SQL injection/special words and chars.
It doesn't even query the database for the items for the filter terms. If there is a static list of them in the DB, they could be loaded once, the first time they use the filters.
It is easy to remove the filter, no need to query yet again without WHERE clauses.
A ComboBox where applicable helps the user find what they want and avoid typos.
Is the SQL "cleaner". more "efficient? The code doesn't really mess with new SQL, just some WHERE clauses.
Is there less code? I have no idea since we just see the result. It doesnt string me as a lot of code to do what it does.
In my database there are 3 column which is Name, Age, Gender. In the program, I only want to use 1 search button. When the button is clicked, the program determine which 3 of the textbox has input and search for the right data.
And when only name is entered, I only query for the name. Must I check textbox by textbox whether there is user input and then write multiple query for each of them? Or is there a better way to do this?
SELECT * FROM `table`
WHERE (`name` = #name AND `name` IS NOT NULL)
OR (`age` = #age AND (`age`>0 OR `age` IS NOT NULL))
OR (`gender` = #gender AND `gender` IS NOT NULL);
With the above query if all text boxes have value, the result will not be one record (as if you where using logical AND between fields). If you want only that record you will filter it server-side with php from the rest of the results.
You can check the results on your own in this Fiddle
EDIT
In order to solve the above inconvenience (not bringing easily single results when needed) i got a little help from this answer and re-wrote the above query as:
SELECT *, IF(`name`=#name, 10, 0) + IF(`age`=#age, 10, 0) + IF(`gender`=#gender, 10, 0) AS `weight`
FROM `table`
WHERE (`name` = #name AND `name` IS NOT NULL)
OR (`age` = #age AND (`age`>0 OR `age` IS NOT NULL))
OR (`gender` = #gender AND `gender` IS NOT NULL)
HAVING `weight`=30;
OR to still get all records with a weight on result
SELECT *, IF(`name`=#name, 10, 0) + IF(`age`=#age, 10, 0) + IF(`gender`=#gender, 10, 0) AS `weight`
FROM `table` WHERE (`name` = #name AND `name` IS NOT NULL)
OR (`age` = #age AND (`age`>0 OR `age` IS NOT NULL))
OR (`gender` = #gender AND `gender` IS NOT NULL)
ORDER BY `weight` DESC;
You were pretty close. Let's look at
(FloatNo = #floatNo OR FloatNo = 'None')
So you want the field either to be the given input or 'None'? But there are (supposedly) no records in your table with FloatNo 'None'. What you really want to do is find out whether the input is none (i.e. empty):
(FloatNo = #floatNo OR #floatNo = '')
And for the case the user types in a blank by mistake, you can ignore this, too:
(FloatNo = #floatNo OR TRIM(#floatNo) = '')
The whole thing:
myCommand = New MySqlCommand(
"Select * from project_record Where
(FloatNo = #floatNo OR TRIM(#floatNo) = '') AND
(DevCompanyName = #devCompanyName OR TRIM(#devCompanyName) = '') AND
(DevType = #devType OR TRIM(#devType) = '') AND
(LotPt = #lotPt OR TRIM(#lotPt) = '') AND
(Mukim = #mukim OR TRIM(#mukim) = '') AND
(Daerah = #daerah OR TRIM(#daerah) = '') AND
(Negeri = #negeri OR TRIM(#negeri) = '') AND
(TempReference = #tempRef OR TRIM(#tempRef) = '')", sqlConn)
What is wrong with your approach?
Just change
(FloatNo = #floatNo OR FloatNo = 'None')
to
(FloatNo = #floatNo OR FloatNo = '' or FloatNo IS NULL)
And do that for every criteria.
Your query will respect empty values and NULL values after that.
I am looking for a smarter way to check if a text in a record already exists.
Basically I wrote a database which stores minutes.
Since only the latest information should be stored in a textbox (there the entries are made) and "old" information goes to a history box I am looking for a way to prevent double entries in this history box.
For that reason I used the InStr Function to check if the the first 10 letters already exists in the history field it doesn't add the information. I start at position 100 since there is this header I made to differ between the entries.
Actually it works most the times but I could not figure out why it doesn't add information many times then this information is actually new so I look for a smarter way or does somebody see a mistake?
Private Sub txt_Comments_W9_LostFocus()
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' History function.
' Since only the latest information should be stored at the text field the old/other information will be stored at the history box
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Dim temp As String
Dim newComment As String
Dim Version As String
temp = " "
newComment = " "
Version = "<b>" & "****************************************" & "Version " & Date & ": " & "****************************************" & "</b>"
newComment = Version & txt_Comments_W9.Value
If Len(Nz(txt_History_W9.Value)) = 0 Then
If Len(Nz(txt_Comments_W9.Value)) = 0 Then
Exit Sub
Else
txt_History_W9.Value = "<div>" & newComment & "<BR>"
End If
Else
temp = txt_History_W9.Value
If InStr(100, temp, Mid(txt_Comments_W9.Value, 1, 10)) = 0 Then
txt_History_W9.Value = "<div>" & temp & newComment & "<BR>"
End If
End If
End Sub
Thanks
If Len(Nz(txt_History_W9.Value)) returns 0 you will skip the check after your else. Is that on purpose?
Also, is there a reason why you are using Mid(txt_Comments_W9.Value, 1, 10) instead of Left(txt_Comments_W9.Value, 10).
And if the arguments for Instr are as follows: InStr([start,]string1,string2[,compare]), It can return:
If string1 is "" - InStr returns 0
If string1 is Null - InStr returns Null
If string2 is "" - InStr returns start
If string2 is Null - InStr returns Null
If string2 is not found - InStr returns 0
If string2 is found within string1 - InStr returns the position at which match is found
If start > Len(string1) - InStr returns 0
Make sure you know what the InStr Function returns in case of no match.
The way this table is Structured is very odd. Your table design should ideally be something like.
tbl_MinutesHistory
------------------
minID | minDate | minEntree | minNotes
--------+---------------+---------------+--------------------------------
1 | 03/04/2014 | Paul | Meeting for Boss birthday
2 | 05/05/2014 | Eugin | Meeting to elect new boss
3 | 02/06/2014 | Francis | Company shutting down meeting
Then your history box (I guess it is a Listbox) would include everything except the last date. Rowsource would be something like
SELECT minDate, minEntree, minNotes
FROM tbl_MinutesHistory
WHERE minID Not In (SELECT Max(minID) As maxOfID FROM tbl_MinutesHistory);
You are making things more complicated with code !
Good Afternoon to All,
I have a question concerning on SQL Queries. is it possible to use an array as a parameter to a query using the "IN" command?
for example,
int x = {2,3,4,5}
UPDATE 'table_name' set 'field' = data WHERE field_ID IN (x)
the reason I am asking this is to avoid an iterative SQL Statement when I have to update data in a database.
I also thought of using a for each statement in for the UPDATE Query but I don't know if it will affect the performance of the query if it will slow down the system if ever 100+ records are updated.
I am using VB.Net btw.
My Database is MySQL Workbench.
I have gotten the answer. I just simply need to convert each elements to a String then concatenate it with a "," for each element. so the parameter that i will pass will be a string.
ANSWER:
int x = {2,3,4,5}
will become
string x = "2,3,4,5"
My Query string will become "UPDATE tablename SET field=value WHERE ID IN("&x&")"
Thank you to all who helped.
If you have the query in a variable (not a stored procedure) and you don't have a huge amount of ids, you could built your own IN. I haven't tested the speed of this approach.
This code won't compile, it's just to give you an idea.
query = "SELECT * FROM table WHERE col IN ("
For t = 0 TO x.Length-1
If t > 0 Then query &= ","
query &= "#var" & t
Next
query &= ")"
...
For t = 0 TO x.Length-1
cmd.Parameters.Add("#var" & t, SqlDbType.Int).Value = x(t)
Next
i am not familiar with mySQL, but when dealing with MSSQL, I normally have a split function in DB so that I can use it to split concatenated integer values as a table, at VB side, something like:
Dim myIds = {1, 2, 3, 4}
Dim sql = <sql>
SELECT m.* FROM dbo.tblMyData m
INNER JOIN dbo.fncSplitIntegerValues(#Ids, ',') t ON t.id = m.Id
</sql>.Value
Using con As New SqlConnection("My connection string..."),
cmd As New SqlCommand(sql, con)
cmd.Parameters.Add("#Ids", SqlDbType.VarChar).Value =
myIds.Select(Function(m) m.ToString).Aggregate(Function(m, n) m & "," & n)
con.Open()
Dim rdr = cmd.ExecuteReader(CommandBehavior.CloseConnection)
While rdr.Read()
Console.WriteLine(rdr.GetValue(0))
' do something else...
End While
End Using
dbo.fncSplitIntegerValues is a db function to split concatenated integer varchar into integer Id with given separator.
it's important not to use plain sql, instead, use sql parameters.
Note: above sample is not tested...
I'm using MS Access with a MySQL database. One table has records which represent 'documents':
Table: doc
Primary Key: doc_id
These records can be linked together via a link table:
Table: link
Primary Key: link_id
Foreign Key: doc_id_A
Foreign Key: doc_id_B
Thus the records may be linked in a chain, eg Doc A linked to Doc B, which is linked to Doc C, etc, and also, Doc A may be linked to any number of other documents.
In reality, the 'family' of inter-related documents wouldn't exceed 20 records.
I'm looking for an efficient MySQL proc, or VBA function - or a query - to find all the members of a 'family' for one specified record.
Any suggestions would be most welcome!
So the link table gives a self join to doc, you absolutely need a doc to be able to:
Link to itself
... as many times as it likes
Link to another doc
... multiple times
Be separately linked to by the same other doc
... multiple times
Store information about the link
So your link table could have 10 separate links 1-1, 1-1, 1-1, 1-2, 1-2, 1-2, 2-1, 2-1, 2-1, 2-2 etc. with just 2 docs in the 'family'.
I expect when you look at this list you will probably think that you don't need most of them, a lot of the inefficiency in your solution might be coming from this unnecessary flexibility. My favoured suggestion would be to start with a strict hierarchy and build minimally from there.
But here is my answer anyway, it's tested and working in Access-2010 and local tables. ADODB should work just as well with linked tables.
Option Compare Database
Option Explicit
Const MaxInFamily = 30
'Requires a reference to "Microsoft ActiveX Data Objects 2.x Library" (VBA Menu: Tools, references)
Function GetFamily(id As Long) As Long()
Dim Found(MaxInFamily) As Long
Dim MaxFound As Integer
Dim CurrentSearch As Integer
Dim Sql As String
Dim rs As New ADODB.Recordset
Found(1) = id
MaxFound = 1
For CurrentSearch = 1 To MaxInFamily
If CurrentSearch > MaxFound Then Exit For
Sql = "SELECT doc_id_2 as NewID FROM link WHERE doc_id_1 = " & Found(CurrentSearch) _
& " AND doc_id_2 NOT IN (" & ArrayToCsv(Found, MaxFound) & ")" _
& " UNION " _
& " SELECT doc_id_1 FROM link WHERE doc_id_2 = " & Found(CurrentSearch) _
& " AND doc_id_1 NOT IN (" & ArrayToCsv(Found, MaxFound) & ")"
rs.Open Sql, CurrentProject.Connection
Do While Not rs.EOF
MaxFound = MaxFound + 1
Found(MaxFound) = rs("NewID").Value
rs.MoveNext
Loop
rs.Close
Next CurrentSearch
GetFamily = Found
End Function
Function ArrayToCsv(SourceArray() As Long, ItemCount As Integer) As String
Dim Csv As String
Dim ArrayIndex As Integer
For ArrayIndex = 1 To ItemCount
Csv = Csv & SourceArray(ArrayIndex)
If ArrayIndex < ItemCount Then Csv = Csv & ", "
Next ArrayIndex
ArrayToCsv = Csv
End Function
Duplicated results and queries are avoided by excluding items already found at the server, and as the link is unidirectional I've used a UNION query to look both ways at once. At most it'll do MaxInFamily round trips to the server.
Unfortunately, it cannot be done in MySQL given such a table structure as MySQL does not support recursive queries. I suggest to explore the answers in Mysql recursion? - there is some advice as to how to store the data in MySQL to be able to write such queries.
You also have three other options:
In case you know the max. depth of such families, and it's not huge, you can still achieve it with mySQL. For a depth of 3 levels, it would look like this:
SELECT A.doc_id_a as a, B.doc_id_a a_child, C.doc_id_a as a_sub_child
FROM links as A, links as B, links as C
WHERE A.doc_id_a = your_doc_id AND
A.doc_id_b = B.doc_id_a AND
B.doc_id_b = C.doc_id_a
Following the same logic, you can add as many layers as you need. The only thing is - you'll need to get result from all columns and find unique values (and there can be many rows, if the relationship is not 1-1 all the time).
A second option is to do this in VBA. I do not know enough to provide the code, but essentially it could look like this (a recursive approach):
family = array();
family = getFamily('your_doc_id', family);
function getFamily(id) {
children = DB->getColumn('SELECT doc_id_b FROM links WHERE doc_id_a = ?', id);
if (empty(children)) return family;
else {
foreach (children as child) {
family[] = getFamily(child);
}
}
}
Lastly, you can switch to PostgreSQL, which supports recursive queries :)
(http://www.postgresql.org/docs/8.4/static/queries-with.html).
I had a similar problem some time ago. Instead of creating links between the documents I used a separate table containing the families:
Table: LinkTable
PK: LinkId, DocumentID
FK: DocumentID
Instead of having pairs of document IDs, each time you create a new family, you introduce a new LinkId.
LinkId | DocumentID
1 | 7
1 | 9
1 | 13
2 | 4
2 | 22
2 | 23
2 | 30
3 | 6
3 | 80
Here you have three families with the document IDs:
{ 7, 9, 13 },
{ 4, 22, 23, 30 },
{ 6, 80 }
It is easier to retrieve families, however it requires a somewhat more complicated logic for inserting and removing documents to and from families.
I am trying to compare two recordsets. the first rs1 has random records. The second rs2 has the standard values for those records. Initially I am looking to take each of the records and see if they match with the standard set of values in the second recordset. There are four fields in each record set to be compared and all four must match.
I just need some help in the loop. I am trying to write the non matching records to an excel file.
Here is what I have so far
While Not rs1.EOF
With rs1
.MoveFirst
With rs2
.MoveFirst
While Not rs2.EOF
counter = counter + 1
a = 0
If rs1!Kk = rs2!Kk Then a = a + 1
If rs1!CC = rs2!CC Then a = a + 1
If rs1!HN = rs2!HN Then a = a + 1
If rs3!TN = rs2!TN Then a = a + 1
If a > 3 Then GoTo correct
.MoveNext
If rs2.EOF Then
If rs!Table_Name <> "table1" Then
i = i + 1
j = 1
counter = counter + 1
objSht.Cells(i, j).Value = "casenum" & rs1.Fields(1)
j = j + 1
stat_counter = stat_counter + 1
End If
If i = 65500 Then
Set wbexcel = objexcel.ActiveWorkbook
''//Set objSht = wbexcel.Worksheets("Sheet2")
Set objSht = wbexcel.Worksheets.Add
i = 2
End If
End If
correct:
rs1.MoveNext
Wend
End With
End With
Also any ideas on how i can segregate based on 2 of fields matching with standard and 3 of the fields matching with the standard values
Are the recordsets already sorted? I'm guessing that's the case since you move to the next rs2 on a non match. Personally i'd specify a sort to make 100% sure.
Also I'd test this pretty thoroughly with a small test dataset with a few edge cases to make sure you get what you expect.
With the above in mind your code looks like it'd work but i have a few small recommendations to make it easier to read.
First i'd recommend ditching the nested With rs1 and With rs2. Just refer to each recordset explicitly so you can clearly see what is happening to each rs. eg:
If a > 3 Then GoTo correct
.MoveNext
becomes
If a > 3 Then GoTo correct
rs2.MoveNext
Next your if statements with a = a + 1 could do with some tidying. eg:
If rs1!Kk = rs2!Kk and rs1!CC = rs2!CC and rs1!HN = rs2!HN and rs3!TN = rs2!TN then
''// Do Nothing or maybe increase a count or whatever :)
else
WriteToExcel(objSht , rs1.fields)
end if
You'll need to write a function called WriteToExcel() but this will make the next step easier. I think you want to write to different sheets depending on the matches?
If rs1!Kk = rs2!Kk and rs1!CC = rs2!CC and rs1!HN = rs2!HN and rs3!TN = rs2!TN then
''// Do Nothing
else if rs1!Kk = rs2!Kk and rs1!CC = rs2!CC and rs1!HN = rs2!HN then
WriteToExcel(objSht2 , rs1.fields)
else
WriteToExcel(objSht , rs1.fields)
end if
You may also want to look at switches in the case where you need any two matches, rather than specific matches as above... oh and variable j seems a bit superfluous.
My gut says you are doing something sub-optimally; however, in the if statement If rs2.EOF Then, why not add a comparison to a and then redirect to a different Excel file for 0, 1, 2 and 3
Bruit Force and Ignorance, but definitely segregated.