LINQ Compare two datatables with .except vb - vb.net

Here is my situation, and I could use some help.
I have three datatables. 1 and 2 are from querying a database, and the the third is a table that will hold a final formatted table.
dt1 has 8 columns,
dt2 has 5 columns,
and a final formatted dtJoined.
I have already added all the rows from dt1 to dtJoined.
Each table has a key ID field, I need to find the rows from dt2 that are not in dt1 and add them to the formatted dtJoined.
This is what I have so far:
Dim query1 = (From a In dt1.AsEnumerable()
Where Not a.IsNull("CustZ")
Select a.Field(Of String)("id").Trim)
Dim query2 = (From a In dt2.AsEnumerable()
Select a.Field(Of String)("id").Trim)
This works fine (as in no errors), I pull out the key field of each table so I can compare the datarow
Next I want to compare query2 to query1 and only return the id's not in dt1.
Dim notDt1 = query2.Except(query1).ToDataTable
This is where I get an error.
The error is:
An unhandled exception of type
'System.Reflection.TargetParameterCountException' occurred in
mscorlib.dll
Additional information: Parameter count mismatch.
I have searched this error, and haven't come across anything I could apply to my situation. I am lost because each of the row collections only has one column.
If I can get past this error, my next step would be to join dt2 on the keys that were not in dt1 so I can get the columns that were removed from when I compared the tables.
Dim queryGetAllColumns = (From a In dt2.AsEnumerable()
Join b In notDt1.AsEnumerable()
On a("id") Equals b("id")
Select a).CopyToDataTable()
Then loop through queryGetAllColumns and add the rows to the formatted dtJoined table.

I believe your issue is you are trying to call .ToDataTable on a variable notDt1 that is of type IEnumerable(Of String) which results from your queries. Just leave off the .ToDataTable as unnecessary.

Related

Linq group join failing, if the result has empty rows

I'm quite new to using Linq queries, and I'm trying to do a group join between two data tables ( to simulate a left join in sql ), which is failing if some of the rows miss values.
I've tried the query below:
(From date_amotiq
In amotiq_data_dt.AsEnumerable
Group Join v05_va33_data In v05_join_va33_zalvunloading.AsEnumerable
On date_amotiq("MATNR").ToString Equals v05_va33_data("ALV_PN").ToString
And date_amotiq("KDMAT").ToString Equals v05_va33_data("CUST_PN").ToString
And date_amotiq("UIP_Formatted").ToString
Equals v05_va33_data("Unloading_Point").ToString Into main_sht_data = Group
From v05_va33_data in main_sht_data.DefaultIfEmpty()
Select main_sheet_dt.Clone.LoadDataRow(New Object()
{"RO1W",
date_amotiq("MATNR"), date_amotiq("KDMAT"),date_amotiq("UIP_Formatted"),
date_amotiq("RDATUM"), "",
main_sht_data.FirstOrDefault().Item(0)
},False))
.CopyToDataTable
I know that "main_sht_data" is generated as an enumerable and I've tried to get the values with FirstOrDefault and ElementOrDefault, which return a datarow, but it fails as soon as I hit an empty row.
Could you please assist?
I managed to get it working by using if(v05_va33_data is nothing,"",v05_va33_data("Sales_Doc_Type"), and so on, for all fields which could be empty.

Creating datatable from 2 datatables created at runtime VB.net

I have a pretty long code which creates two datatables and fills them with data at runtime. And i need to create one table, but not the way merge does it but like adding columns from one datatable as new columns(but filled with data) to the other one. Best example i can thing of is Join in SQL but i will try to draw some example. I can not put my code here as i said it't too long.
Example:
(only column names but imagine that under each |something| is a lot of rows)
Table1:
Date|AA|AA2|AA3|AA4|AA5|AA6|
Table2:
Date|BB|BB2|BB3|BB4|BB5|BB6|
Result:
Date|AA|AA2|AA3|AA4|AA5|AA6|BB|BB2|BB3|BB4|BB5|BB6|
The DataTable.Merge method is perfectly capable of yielding your desired result. As your two tables have only one column in common Date, I am assuming that it is PrimaryKey of at least Table 1.
Dim dt1 As New DataTable
Dim pk As DataColumn = dt1.Columns.Add("Date", GetType(DateTime))
dt1.PrimaryKey = {pk}
dt1.Columns.Add("C1")
dt1.Columns.Add("C2")
dt1.Rows.Add(#1/1/2018#, "c1", "c2")
Dim dt2 As New DataTable
Dim pk2 As DataColumn = dt2.Columns.Add("Date", GetType(DateTime))
dt2.Columns.Add("C3")
dt2.Columns.Add("C4")
dt2.Rows.Add(#1/1/2018#, "c3", "c4")
dt2.Rows.Add(#1/2/2018#, "c3a", "c4a")
Dim dt3 As DataTable = dt1.Copy
dt3.Merge(dt2)
DataGridView1.DataSource = dt3
This code yield this result:

How to write one to one LINQ Query

I am a beginner and learning LINQ in VB.Net. I have one table A with column
workID (PK) and idAccount.
And another table B with column
Bid(PK), BName and workID(FK) .
There is one to one relationship between table A and table B.
Now I want to put/copy both the table data to another table C which has column as
workID, idAccount, BName. But I don't know how to write a LINQ query and get both table data and put it in 3rd table. Please help me. I have tried below till now. Below is the code snippet of my project.
Public Function Hello(ByVal dt As A, ByVal dtm As B)
Dim dtReturn As New C
If dt IsNot Nothing AndAlso dt.Any Then
Dim row As WorkOrderRow `row corresponding to the C Table
For Each r In dt
row = dtReturn.NewWorkRow 'traversing the row
With row
.WorkID = r.WorkID
.idAccount = r.idAccount
End With
dtReturn.AddWorkOrderActivityRow(row)
Next
End If
End Function
its working totally fine but I need to put the data of B too. With above code I am able to copy only data of table A. Kindly guide me how should I write my LINQ query and traverse it.
I should be able to do something like
With row
.WorkID = x.WorkID
.idAccount = x.idAccount
.BName = x.BName
End With
x being the row generated by the query.
You probably would like to join the result of A and B first (sample here: https://msdn.microsoft.com/en-us/library/bb311040.aspx), then create rows of C. This is much easier.
Dim newCs = (From a In tableA
Join b In tableB On a.workID Equals b.workID
Select New TableC With {
.workID = a.workID,
.idAccount = a.idAccount,
.BName = b.BName
})

Access SQL Randomizer Not working as intended

I'm using the below mentioned code to select a record ID from an Access Database that wasn't already selected in the last day and add it to an array.
The general goal is that a record that matches the initial "Difficulty" criteria will be retrieved so long as either the record was never selected before OR the record wasn't chosen in the last 2 days. After the loop is done, I should have x amount of unique record ID's and add those onto an array for processing elsewhere.
Private Function RetrieveQuestionID(questionCount As Integer)
' We're using this retrieve the question id's from the database that fit our arrangements.
Dim intQuestArray(0 To questionCount) As Integer
Dim QuestionConnection As New OleDb.OleDbConnection("PROVIDER=Microsoft.ACE.OLEDB.12.0;Data Source = |DataDirectory|\Database\MillionaireDB.accdb;")
QuestionConnection.Open()
For i As Integer = 1 To intNoOfQuestions
'TODO: If there are no valid questions, pull up any of them that meets the difficulty requirement....
Dim QuestionConnectionQuery As New OleDb.OleDbCommand("SELECT Questions.QuestionID FROM Questions WHERE (((Questions.QuestionDifficulty)=[?])) AND (((Questions.LastDateRevealed) Is Null)) OR (Questions.LastDateRevealed >= DateAdd('d',-2,Date())) ORDER BY Rnd((Questions.QuestionID) * Time());", QuestionConnection)
QuestionConnectionQuery.Parameters.AddWithValue("?", intQuestionDifficulty(i - 1).ToString)
Dim QuestionDataAdapter As New OleDb.OleDbDataAdapter(QuestionConnectionQuery)
Dim QuestionDataSet As New DataSet
QuestionDataAdapter.Fill(QuestionDataSet, "Questions")
intQuestArray(i - 1) = QuestionDataSet.Tables("Questions").Rows(0).Item(0)
Dim QuestionConnectionUpdateQuery As New OleDb.OleDbCommand("UPDATE Questions SET Questions.LastDateRevealed = NOW() WHERE Questions.QuestionID = [?]", QuestionConnection)
QuestionConnectionUpdateQuery.Parameters.AddWithValue("?", intQuestArray(i - 1).ToString)
QuestionConnectionUpdateQuery.ExecuteNonQuery()
Next
QuestionConnection.Close()
Return intQuestArray
End Function
However, looping through the array will show that there are records are somehow being repeated even though the record updates during the loop.
Is there another way to loop through the database and pull up these records? I even attempted to move the .Open() and .Close() statements to within the For...Next loop and I'm given worse results than before.
As Steve wrote, the >= should be a < .
In addition, your WHERE clause is missing parentheses around the OR part.
It should be (without all unnecessary parentheses):
SELECT Questions.QuestionID
FROM Questions
WHERE Questions.QuestionDifficulty=[?]
AND ( Questions.LastDateRevealed Is Null
OR Questions.LastDateRevealed < DateAdd('d',-2,Date()) )
ORDER BY Rnd(Questions.QuestionID * Time());
Also have a look at How to get random record from MS Access database - it is suggested to use a negative value as parameter for Rnd().

How to tell if a record is still in a SQL table - using linq maybe?

I have an SQL database, which is a "feeder" table. I put records in said table, a 3rd party package consumes (and deletes) them. All hunky dory - until the 3rd party package isn't running. In thinking about how to detect that, I thought to myself... "well... what if I read all the keys in the table (its not very big - max a few dozen records), and kept them, and then in, say, 5 minutes, I checked if any were still in the table ?"
It may not be a brilliant solution, but it sent me off thinking about Linq and whether you could do such a thing (I haven't used Linq before).
So, if I read all the record keys into a DataTable object and then, five minutes later, read all the records into another DataTable object, I can do a Linq select, joining the two DataTable objects on the key column, and then look at the results of "Count", and if one or more, chances are the data in the table isn't being consumed.
Or... is there a "cleverer" way than that ?
Create a DELETE trigger which records in a separate table the timestamp of the last delete. An additional INSERT trigger would record the timestamp of the last insert statement.
Compare the two timestamps.
You could return the identity column value (assuming there is one) after your insert and record it in a separate table along with its commit datetime they just pull outstanding records with;
SELECT * FROM feeder_table F
INNER JOIN other_table T ON (F.id = T.id)
WHERE DATEDIFF(MINUTE, T.commitdate, GETDATE()) > 5
That way your not persisting data in memory so it will work between application restarts/across machines.
(If this is just for fault detection you would only need to store the last inserted id.)
This is one way:
DataTable t1 = GetData(); // returns a datatable with an Int16 "Id" column
// time passes... a shabby man enters and steals your lamp
DataTable t2 = GetData();
// some data changes have occurred
t2.Rows.Add(null, DateTime.Now.AddSeconds(10), "Some more");
t2.Rows[1].Delete();
EnumerableRowCollection<DataRow> rows1 = t1.AsEnumerable();
EnumerableRowCollection<DataRow> rows2 = t2.AsEnumerable();
var keys1 = rows1.Select(row => (Int16)row["Id"]).ToList();
var keys2 = rows2.Select(row => (Int16)row["Id"]).ToList();
// how many keys from t1 are still in t2
Console.WriteLine("{0} rows still there", keys1.Count(id => keys2.Contains(id)));
But this is more what I had in mind:
DataTable t1 = GetData(); // returns a datatable with an Int16 "Id" column
// time passes... your lamp is getting dim
DataTable t2 = GetData();
// some data changes have occurred
t2.Rows.Add(null, DateTime.Now.AddSeconds(10), "Some more");
t2.Rows[1].Delete();
EnumerableRowCollection<DataRow> rows1 = t1.AsEnumerable();
EnumerableRowCollection<DataRow> rows2 = t2.AsEnumerable();
// how many rows from r1 are still in r2
int n = (from r1 in rows1
join r2 in rows2 on (Int16)r1["Id"] equals (Int16)r2["Id"]
select r1).Count();
...which is the "linq/join" method I alluded to in the original question.