I am writing this in C# using .NET 3.5. I have a System.Data.DataSet object with a single DataTable that uses the following schema:
Id : uint
AddressA: string
AddressB: string
Bytes : uint
When I run my application, let's say the DataTable gets filled with the following:
1 192.168.0.1 192.168.0.10 300
2 192.168.0.1 192.168.0.20 400
3 192.168.0.1 192.168.0.30 300
4 10.152.0.13 167.10.2.187 80
I'd like to be able to query this DataTable where AddressA is unique and the Bytes column is summed together (I'm not sure I'm saying that correctly). In essence, I'd like to get the following result:
1 192.168.0.1 1000
2 10.152.0.13 80
I ultimately want this result in a DataTable that can be bound to a DataGrid, and I need to update/regenerate this result every 5 seconds or so.
How do I do this? DataTable.Select() method? If so, what does the query look like? Is there an alternate/better way to achieve my goal?
EDIT: I do not have a database. I'm simply using an in-memory DataSet to store the data, so a pure SQL solution won't work here. I'm trying to figure out how to do it within the DataSet itself.
For readability (and because I love it) I would try to use LINQ:
var aggregatedAddresses = from DataRow row in dt.Rows
group row by row["AddressA"] into g
select new {
Address = g.Key,
Byte = g.Sum(row => (uint)row["Bytes"])
};
int i = 1;
foreach(var row in aggregatedAddresses)
{
result.Rows.Add(i++, row.Address, row.Byte);
}
If a performace issue is discovered with the LINQ solution I would go with a manual solution summing up the rows in a loop over the original table and inserting them into the result table.
You can also bind the aggregatedAddresses directly to the grid instead of putting it into a DataTable.
most efficient solution would be to do the sum in SQL directly
select AddressA, SUM(bytes) from ... group by AddressA
I agree with Steven as well that doing this on the server side is the best option. If you are using .NET 3.5 though, you don't have to go through what Rune suggests. Rather, use the extension methods for datasets to help query and sum the values.
Then, you can map it easily to an anonymous type which you can set as the data source for your grid (assuming you don't allow edits to this, which I don't see how you can, since you are aggregating the data).
I agree with Steven that the best way to do this is to do it in the database. But if that isn't an option you can try the following:
Make a new datatable and add the columns you need manually using DataTable.Columns.Add(name, datatype)
Step through the first datatables Rows collection and for each row create a new row in your new datatable using DataTable.NewRow()
Copy the values of the columns found in the first table into the new row
Find the matching row in the other data table using Select() and copy out the final value into the new data row
Add the row to your new data table using DataTable.Rows.Add(newRow)
This will give you a new data table containing the combined data from the two tables. It won't be very fast, but unless you have huge amounts of data it will probably be fast enough. But try to avoid doing a LIKE-query in the Select, for that one is slow.
One possible optimization would be possible if both tables contains rows with identical primary keys. You could then sort both tables and step through them fetching both data rows using their array index. This would rid you of the Select call.
Related
I have a DataTable containing 10000+ rows, resulting from a SQL Server query. The DataTable is used as the DataSource for a DataGridView, like so (simplified code):
MyBindingSource = New BindingSource(MyDataTable, Nothing)
MyDataGridView.DataSource = MyBindingSource
As this takes a looong time to load, I would like to limit the number of rows displayed in the DataGridView, somehow.
I can't use TOP in my query, because I need all the data present in the DataTable for filtering later on, without re-setting the DataSource (MyBindingSource.Filter = MyFilter).
Also, I can't use the Filter property for limiting the number of rows, because there's no relevant data in the query result that I can use for this. To get around this, I've thought about adding TSQL's ROW_NUMBER to the query result (MyBindingSource.Filter = "[RowNumberField] < 100"), but this would only work when no other fields are used in the filter.
Any ideas?
Here are a two options:
I would simply implement pagination on all of your views (filtered or unfiltered) using this technique of using a BindingNavigator GUI control which uses a BindingSource object to identify page breaks.
You can also filter by more than one criteria by using an OR operator but I don't see how that helps you with your current approach because your row numbers will have to be recalculated after each filter e.g. [RowNumberField] < 100 might return 100 rows with no filter by only 10 after a filter.
What you could do is move the filtering logic to your SQL query and then always show only the first X rows based on the row number (which I assume you are dynamically adding each time using TSQL's ROW_NUMBER()).
The advantage to this approach is that you can perform much more powerful filtering in TSQL and it keeps you Data Source smaller.
If you do take this approach, be careful about mixing your queries in with your view logic - I would recommend the Repository Pattern.
I have a tableadapter (SettlementCurrentTableAdapter) with a relation. parent table is (SettlementCurrent) child table is (Trips).
I need to SUM each row (tripLoadedMiles) in (Trips) and display the total in a label (labelWelcomeSetTotalMiles).
I would like to do this in code and not sql as i use this tableadapter to display other labels.
Can someone help me figure this out. i am new to C# and i use Visual C#.
this.settlementCurrentTableAdapter.Fill(this.myLeaseDataSet.SettlementCurrent);
MyLeaseDataSet.SettlementCurrentRow settlementCurrent;
settlementCurrent = myLeaseDataSet.SettlementCurrent.Last();
var settlementCurrentID = settlementCurrent.setID;
this.tripsTableAdapter.FillByTripSetID(this.myLeaseDataSet.Trips, settlementCurrentID);
MyLeaseDataSet.TripsDataTable settlementTrips;
settlementTrips = myLeaseDataSet.Trips;
foreach (DataRow row in myLeaseDataSet.Trips.Rows)
{
NOT SURE ON THE CODE HERE
}
Your first step is to extract the data you want out of your data adapter. Since I don't have a table model in your question, it is best to look at the table adapter overview on MSDN, and click on the related links and articles to figure out the basic usage of this object. If you were to post your table structure, I could theoretically give you some code for it, but I'm reluctant to do that - you will have to work with this object a lot of it's how you're talking to your database, so it's imperative you learn how it functions.
Your goal in this learning is to figure out how to get all the miles figures out of your table adapter. You usually do this by going over one row at a time, and picking out the values you want to add them to a more C# idiomatic data structure, such as a a List/IEnumerable, or an Array.
After you have the data you need in one of these structures, you can get the sum of everything by using LINQ. Depending on how much you pulled out, it can be as simple as one call (if this is something like an Array of ints):
var totalMiles = tripLoadedMiles.Sum();
Or as elaborate as you might need (if this is a List of a custom object that has both SettlementCurrentId and Miles, both stored as ints):
var totalMiles = tripLoadedMiles.Select(x => x.SettlementCurrentId == someRandomRequiredSettlement).Sum(y => y.Miles);
In the way you're using things, this is how you're looking at totaling it in this situation.
var total = 0;
foreach (DataRow row in myLeaseDataSet.Trips.Rows)
{
total += int.Parse(row["tripTotalMiles"].ToString());
}
// Use the total
I'm using a SqlDataReader to add row by row into a datatable like follows:
while (reader.Read())
{
dataTable.LoadDataRow(reader.CurrentRow(), LoadOption.PreserveChanges);
}
This works, but I need to be able to avoid adding duplicate rows to the dataTable. I would love to be able to use the Contains or Find methods from the dataTable, but I can't find a way to turn the object[] from reader.CurrentRow() into a DataRow to compare to without adding it to a datatable.
I've looked into the option of making a hashset of the object[]s, and then adding them all at once to the datatable at the end, but I forgot that the default object IEqualityComparer only compares the reference.
Is there a feasible way of doing this without removing the duplicates at the end?
If removing the duplicates is the only way to go, what is the best way to do that?
EDIT:
I'm splitting distinct rows from the database into separate datatables in code. Each row from the query result is distinct, but sections of each row are not. Unfortunately I need to do exactly what my question is asking, as the results from the query are already distinct.
You didn't provide a ton of detail, but I hope this is comprehensive.
If you need a single column to be unique, then in your Columns collection in your datatable, specify the column like this:
DataTable appeals = new DataTable("Appeals");
appeals.Columns["PriorAppealNumber"].Unique = true;
DataColumn keyField = new DataColumn("AppealNumber", typeof(string));
appeals.Columns.Add(keyField);
If the uniqueness needs to span multiple rows, this is the method:
var myUniqueConstraint = new UniqueConstraint( new DataColumn[] {appeals.Columns[0], appeals.Columns[1], appeals.Columns[2]} );
appeals.Constraints.Add(myUniqueConstraint);
That will enforce the constraints BEFORE you try to commit back to the source database.
The easiest way is to actually make sure there are no duplicate rows at all - if you're querying relational database use DISTINCT - that will return only unique rows.
I want to take a table as represented by a multidimensional string array (column names in another array) and use a SQL SELECT statement to get a subset of rows.
The Catch:
the table is input by the user (different table each time)
the SQL is also input by the user
Do I need to:
Create a table in SQL
Populate the table in SQL
Query the table
or is the a simpler solution? E.g. converting the multidimensional array to a DataTable and then running the SQL on that object?
I think you would be able to use DataTable for this. It's normally used to store data retrieved from a database but you can populate it manually. The best part is the DataTable.Select() method, which allows you to write just the WHERE clause of a query and it will return the matching rows.
You can create your own expression tree representing the query the user enters. This is how Linq works, under the hood. If you could give an example of what your trying to achieve it may help also what your going to write the application in c# for web for example.
For instance if you letting your users enter new rows, in some kind of GUI to a table then you could, do this in a datagrid and enable column filter to achieve the result mentioned above?
In a web app you could have an input box above each column users can enter data to filter that column on.
My program that I am writing's purpose arose with this issue:
There are two users, each user saves to a .MDB file. One user has half the updated / correct information (the other half is outdated) and the other user has half the information (the other half is outdated).
User1: 25% + 25% = 50% current information needed the other 50% is outdated
User1 works on 2 out of the 4 items.
User2: 25% + 25% = 50% current information needed the other 50% is outdated
User2 works on 2 out of the 4 items.
I need to take that 50% (2 out of the 4 items) from lets say...User1 and add it to User2 making it 100% current (4 out of 4 items).
Their SQL style table structure is (should be anyways) identical (but if possible I would like to provide an event where for some a new table was added I would know)
If I could find out how to get all the table names from the DataTable, I could systematically array through the DataTable and replace the tables with the tables from the other .MDB file that I know need to be updated. I know DataSet has "DataSet.Tables" ...but that doesn't help me very much.
If I can do that, I can also add the tables to a combo box and create functionality to where whatever the combo box says, thats the table I will list on my Datagrid.
If any of you have any ideas on how to go about doing this (or if you even understand what i'm saying) please let me know. I'm 70% done with ths project, and these seem to be my last logic road blocks. I think I explained this right.
How do I list just the Table names
in a DataTable object.
What are your ideas on taking specific Tables out of a .MDB file and adding them to another .MDB file?
How would I go about inputting a ComboList drop-down box that included all the tables names...when I changed the table name it would list those contents on a Datagrid.
Is there a way to list tables on a Datagrid, and when you click on a Table it lists the contents of that table (kind of like a Tree structure).
EDIT:
I think he is right! I think DataTables are just one table whereas DataSets are sets of Tables. With that in mind, how do I list all the tables in a .MDB file into a DataSet? That would fix my problem perfectly.
I thought that a DataTable object was only a single table and a DataSet was what contained one to many DataTables.
If you are looking for the actual name of your DataTable, that would be accessed through the DataTable.TableName property.
Edit: If you are wanting to add DataTables into a DataSet object, just create a new DataSet and then use the .Add() method.
Dim DS as new DataSet
Dim DT as new DataTable("TableName")
DS.Add(DT)
You should then be able to loop through your DataSet and retrieve table names by accessing each DataTable's TableName property:
For each table as DataTable in DS.Tables
Console.Writeline(table.TableName)
Next