I’ve run into a problem, as I cannot get a proper working LINQ statement here.
Suppose I have a DataTable with x rows and I have to sort based on the sum of the Quantity column. Then I have a condition Requested Quantity = 20. I need to find the rows equal to the exact sum of RequestedQuantity, but only where the combination of 3 rows is equal to it.
+-----+----------+
| Bin | Quantity |
+-----+----------+
| 1 | 10 |
| 2 | 5 |
| 3 | 5 |
| 4 | 10 |
| 5 | 15 |
+-----+----------+
I can’t seem to figure out the proper LINQ syntax to get this to work. My starting point is this:
From row In StorageBins.AsEnumerable.GroupBy( _
Convert.ToDouble(Function (x) x("Quantity"), cultureInfo)).Sum( _
Function (y) Convert.ToDouble(y("Quantity"), cultureInfo) = _
Double.Parse(RequestedQuantity,cultureInfo))
Initially, I am just trying to get any rows that are equal to my condition. My end-goal, however, is getting any three rows that exactly sum up to my Requested quantity.
I’m not an expert in LINQ, unfortunately. I hope some of you might be!
Maybe I'm missing something, but this actually seems like a pretty complicated problem. Pick any 3 records, but only 3, that add up to exactly 20. How many rows are there in the database? Because this could get to be quite a few potential combinations pretty quickly. And what do you do after you get the 3? Do you have to go back through recursively and group up the other records as well? Or you just need the first set of 3 that add up to 20?
Assuming you just need the first 3, I would do something like this:
Get the first record that is less that 20. Remove it from your input list and put it into your target set.
Then get the first record that is less than 20 minus the first value. ie if the first value was a '5', get records that are less than 15 (20 minus 5). This ensures you 'leave room' for the third value. Remove it from the original list and into your target set.
Then get the first record that is exactly 20 minus number one minus number two. Remove it from the input list and into the target set.
Now you would have to do this in iterators. If there is no value that meets the third criterion, release the third value from your target set and put it back in your input list. Then go back to step 2 and pick the next record that matches step 2 (and ideally that is not equal to the previous value). And if you exhaust all of the iterations through step 2, go back to step one and pick the next value there, and start the whole thing over again...
Unless I'm misunderstanding your requirement...
Consider the following data:
Item | Overall | Individual | newColumn
A | Fail | Pass | blank
A | Fail | Fail | blank
B | Fail | Pass | issue
B | Fail | Pass | issue
C | Pass | Pass | blank
I have the logic built out for the first 3 columns already. There are two levels of fails in this data:
overall, and
individual.
If any of the individual fail, the overall fails. Sometimes the overall can fail even though all the individuals are fine. This logic is already built out.
I am trying to find a formula for the newColumn. If all the individuals are a pass for a given item (example item B), but the overall is still a fail, the cell should return the text "issue". It is ok if it returns issue twice, not sure if you can non-dupe that part. I've tried various forms of countifs/and/ors and creating columns that count distinct values but I always find a scenario where it will break the logic.
Try this:
=IF(COUNTIFS($A$2:$A$6,A2,$C$2:$C$6,"Fail"),"blank",IF(B2="Fail","Issue","blank"))
As required
If you add a new column with the formula:
=IF(B2="Fail",IF(COUNTIFS(A:A,A2,C:C,"fail")=0,"issue",""),"")
Then this should work on the assumptions:
For each item if one of the overalls are false they are all false
The only two possible values are "Pass" and "Fail" for columns B & C
If you require the word blank instead of a blank cell then use:
=IF(B2="Fail",IF(COUNTIFS(A:A,A2,C:C,"fail")=0,"issue","blank"),"blank")
i have a Datagrid that stores the number pencils produced each day of the month it looks like this:
Pencil | day 1 | day 2 | day 3 | ... |day 31
Red 0 0 13 0 0
blue 5 1 0 8 0
yellow 0 9 5 0 0
I need to save this data into SQL table but im not sure what's the most efficent way to design the table in SQL.
I was thinking about creating a table in SQL with the fields:
pencilmodel
date
quantity
and then in vb.net making a loop that saves 1 by 1 each cell of the datagrid in to the table, but i dont think this is the best way since i will have like 30 rows and a month has 31 days max so it will be 30*31= 930 times.
Im using VB.net and SQL Server
i would create the table that way (as you suggested):
ID | pencilmodel | ProducedDate | Quantity
1 blue dd-mm-yyyy 7
2 red dd-mm-yyyy 4
3 yellow dd-mm-yyyy 6
also, dont loop and insert each row to database, its not efficient, add it to a dataset first and then update it using DataAdapter.Update or bind a dataset to the datagrid view:
How to: Bind Data to the Windows Forms DataGridView Control
I dont know if this one is relevant but why dont you create a fields based on the date and time? lets say like this in your PC
12/14/2016
You can create a program that will create a field for you everyday for example when the day passes by then add a column look like this.
__________________________________
|12/14/2016|12/15/2016|12/15/2016|
so what will happen is you dont need to loop in DGV you just do your INSERT COMMAND
you just need some modifications and validations in here like
if Date_Has_Been_Changed then
Create Table Add Columns
End If
Let's say I have two columns.
3.5463 11
4.5592 12
1.6993 111
0.92521 112
1.7331 121
2.1407 122
1.4082 1111
2.0698 1112
2.3973 1121
2.4518 1122
1.1719 1211
1.153 1212
0.67139 1221
0.64744 1222
1.3705 11111
0.9557 11112
0.64868 11121
0.7325 11211
0.58874 11212
0.86673 11221
0.17075 11222
0.64026 12111
0.80229 12112
0.43422 12122
1.0405 12211
0.63376 12212
0.56491 12221
0.34626 12222
0.81631 111111
0.91837 111112
0.70013 111121
0.87384 111122
1.1474 111211
0.47411 111221
0.12249 111222
0.56728 112111
0.88169 112112
0.14509 112121
0.68655 112211
0.36274 112212
1.1652 121111
0.99314 121112
0.42024 121121
0.23937 121122
1.0346 122111
0.64642 122112
0.15632 122121
0.41725 122122
0.40793 122211
In the first column, there is a number. With every one of those numbers, in the second column, is an associated ID. Now, there are some blank rows that do not contain any numbers in them.
Define one of these numbers to be a "daughter" of another number if the ID of the first number is the same as the ID of the second, with an extra digit on the end. For example, both IDs 11211 and 11212 are daughters of 1121, because the ID of 1121 has an extra digit, either a 1 or a 2, added onto the end to form the ID of its daughters. Thus, 1121 is the parent of both 11211 and 11212.
Here is what I want the macro to do. It must output a third column which contains, for every row, a cumulative sum of the number of the first column in that row, plus the parent number of that number, and the parent number of the parent number, etc. all the way up until it reachers either 11 or 12. It will begin by simply outputting the numbers in column 1 for 11 and 12 in the third column. Then, in a loop beginning with 111, it will add up the cumulative sum of every row (the number in that row plus the third column output of the parent), only if that row has a number and an id, and only if the parent exists and has an output in column 3. So for example, the number in the 3rd column of the row with ID 11222 should be the number in column 1 of that row, plus that of 1122, plus that of 112, plus that of 11. So, 0.17075+2.4518+0.92521+3.5463, or 7.09406. However, if you try to do this for ID 111221, you will notice that the row where the parent 11122 should be is empty. Thus, the parent does not exist, and no value will be outputted in column 3 for 111221.
I would greatly appreciate it if someone has some time on their hands to code up this VBA macro for me in exchange for an accepted solution.
Thanks
I don't think a macro is needed, just some formulas. First, I put a header on my columns of data, such as "value," and "id." If you then highlight the column labels (i.e., A and B) and sort by B ("id") then A ("value"), you'll group your blank rows. You can then delete those rows. Now you have the data almost ready. When I did this, I converted the id column to text, as opposed to a number value, so if I sort the table by id, the pattern will be, "11, 111, 1111," and so on, instead of, "11, 12, 111, 112, 121." Then, I added columns to separate the separate characters or levels of the ids. This is to help with parents and children. You can use text-to-columns, or a MID formula, but what I did was have 6 more columns to the right. For each id row, each column would either have a "1," a "2," or a blank (null) value. Then I added another column, calling it "level." I used a formula like COUNTA across all my id splitting columns. So, for 11, my level value was 2. 111 would be 3, 11221 would be 5, and so on. This gives me the id level (parent, child, grandchild, etc). Then I added my final column to the right to compute my cumulative sum of the values. In concept I have one big nested IF statement, but in practice, I needed two. My formula says, if the row above me has a lower level number (i.e., it is some kind of parent), add the value of the current row to the value of the above row. Otherwise, keep going up a row till I do get a parent, and add the current row value to that number.
My final formula for all but the first 5 rows of data was (in the 6th row of data):
=if(K6
rest of answer is below
=if(K6<K7,L6+C7,if(K5<K7,L5+C7,if(K4<K7,L4+C7,if(K3<K7,L3+C7,if(K2<K7,L2+C7,C7)))))
The values were column C, the original id in column D, the id split columns were E through J, the level column was K, and my formula was in L. This formula can be copied down the table. For the first 4 rows, you just need 1 less IF statement each row you go up. The fifth row of data might take the above formula; it depends how it will deal with the column headers in row one. The formula on the 4 row of data might be:
=if(K4<K5,L4+C5,if(K3<K5,L3+C5,if(K2<K5,L2+C5,if(K1<K5,L1+C5,C5))))
I'm still learning how to format these comments, so I'll try to provide a sample of the layout I have...
C D E F G H I J K L
1 value id 1 2 3 4 5 6 lvl cumul_sum
2 3.546300 11 1 1 2 3.546300
3 1.699300 111 1 1 1 3 5.245600
4 1.408200 1111 1 1 1 1 4 6.653800
5 1.370500 11111 1 1 1 1 1 5 8.024300
6 0.816310 111111 1 1 1 1 1 1 6 8.840610
7 0.918370 111112 1 1 1 1 1 2 6 8.942670
8 0.955700 11112 1 1 1 1 2 5 7.609500
So for example, the number in the 3rd column of the row with ID 11222 should be the number in column 1 of that row, plus that of 1122, plus that of 112, plus that of 11. So, 0.17075+2.4518+0.92521+3.5463, or 7.09406.However, if you try to do this for ID 111221, you will notice that the row where the parent 11122 should be is empty. Thus, the parent does not exist, and no value will be outputted in column 3 for 111221.
As a native worksheet array formula¹ in D1,
=IF(LEN(B1), SUM(SUMIFS(A$1:INDEX(A:A, MATCH(1E+99, A:A)),
B$1:INDEX(B:B, MATCH(1E+99, A:A)), LEFT(B1, ROW(INDIRECT("2:"&LEN(B1)))))), TEXT(,))
The above does not compensate for missing parents (null string). It totals everything it can find and uses zero for missing parents.
As a VBA UDF² in E1,
Function conditionalCumulativeSum(nums As Range, _
ids As Range, sib As Range, _
Optional nullOnBlank As Boolean = True)
Dim i As Integer
'truncate any full column reference to the UsedRange
Set nums = Intersect(nums, nums.Parent.UsedRange)
'match the nums and ids ranges
Set ids = ids.Resize(nums.Rows.Count, nums.Columns.Count)
For i = Len(sib.Value2) To 2 Step -1
If nullOnBlank And IsError(Application.Match(--Left(sib, i), ids, 0)) Then
conditionalCumulativeSum = vbNullString
Exit For
End If
conditionalCumulativeSum = conditionalCumulativeSum + _
Application.SumIfs(nums, ids, Left(sib, i))
Next i
If i = 0 Then conditionalCumulativeSum = vbNullString
End Function
The above defaults to return a null string when it encounters any missing parent through the hereditary chain. This can be turned off by adding FALSE as the optional fourth parameter and then the UDF will behave identically to the native formula.
Results from sample data
¹ Array formulas need to be finalized with Ctrl+Shift+Enter↵. If entered correctly, Excel with wrap the formula in braces (e.g. { and }). You do not type the braces in yourself. Once entered into the first cell correctly, they can be filled or copied down or right just like any other formula. Try and reduce your full-column references to ranges more closely representing the extents of your actual data. Array formulas chew up calculation cycles logarithmically so it is good practise to narrow the referenced ranges to a minimum. See Guidelines and examples of array formulas for more information.
² A User Defined Function (aka UDF) is placed into a standard module code sheet. Tap Alt+F11 and when the VBE opens, immediately use the pull-down menus to Insert ► Module (Alt+I,M). Paste the function code into the new module code sheet titled something like Book1 - Module1 (Code). Tap Alt+Q to return to your worksheet(s).
I have a table built off a dataset containing timesheet data with possible multiple entries per day (day_date) for a given person. The table is grouped on day_date. The field for hours is effort_hr (see dataset and report layout below).
The table generates a single row with one column for each day (as expected).
For each day I want only one value (total hours for person) so the expression is Sum(Fields!effort_hr.Value) This is properly adding up all the hours for each day.
Now I add a total column at the end of the row to see ALL the hours for the whole timesheet. The expression in the total column cell is Sum(Fields!effort_hr.Value) which is exactly the same as the daily ones. Again, this is adding up all hours for the timesheet.
So this is working great.
I now need a new row that only shows a max of 8 hours per day. So if the person works less, it shows less, but if the person works more, show a max of 8.
In this case, the daily column expression is:
IIF(Sum(Fields!effort_hr.Value)>8.0,8.0,Sum(Fields!effort_hr.Value))
And again, it displays perfectly for each day.
The total for this row is where I run into trouble. I have tried so many ways, but I cannot get the total for the columns in this row. The report keeps showing an #Error in the cell. The report saves fine and there is no error in the expr.
The problem seems to come from the fact that there are 2 values for a given day. So in other words, for 5 days, the person has 6 entries. When I try it for a person with only 5 entries, no problem.
I have tried:
Sum(IIF(Sum(Fields!effort_hr.Value)>8.0,8.0,Sum(Fields!effort_hr.Value)))
RunningValue(IIF(Sum(Fields!effort_hr.Value)>8.0,8.0,Sum(Fields!effort_hr.Value)),Sum,Nothing)
I either get an #Error, or I get the wrong total. Is there any way to just get a total for the cell values in the table? The daily numbers are correct, just give me the total at the end (like Excel).
I could do this in the SQL, but that would mess up other parts of this report.
DataSet:
res_name | day_date | effort_hr
J. Doe | Apr 6, 2015 | 2
J. Doe | Apr 6, 2015 | 9
J. Doe | Apr 7, 2015 | 8
J. Doe | Apr 8, 2015 | 7
J. Doe | Apr 9, 2015 | 10
J. Doe | Apr 10, 2015 | 9
REPORT TABLE Layout:
| Apr 6 | Apr 7 | Apr 8 | Apr 9 | Apr 10 | Totals
Total | 11 | 8 | 7 | 10 | 9 | 45
Reg | 8 | 8 | 7 | 8 | 8 | 39
OT | 3 | 0 | 0 | 2 | 1 | 6
Problem:
Row 1 Column Total works great and gives 45 hours ;
Row 2 Column Total either gives #Error, 41, or some other wrong number - just need it to total the actual values of each cell in the row ;
same problem for Row 3 total
Thanks in advance for your time!
Posting another answer as the previous one has become so long.
I referred to this MSDN link, and used the selected answer. Apparently we need to use custom code to achieve this (if you are not willing to change your dataset and have the calculated values in there).
Right click on report --> report properties --> Go to tab 'Code' --> Paste this
Dim public nettotal as Double
Public Function Getvalue (ByVal subtotal AS Double) AS Double
nettotal = nettotal+ subtotal
return subtotal
End Function
Public Function Totalvalue()
return nettotal
End Function
In the row group expression of second row put
= code.Getvalue(IIF(Sum(Fields!Efforts.Value)>8.0,8.0,Sum(Fields!Efforts.Value)))
In the Total cell expression (for second row) put
=code.Totalvalue()
Save and run, you should see following result.
I used your input data and tried to create the report in given format. I used following function for Row 2 Total
=Sum(IIF(Fields!Efforts.Value>8.0,8.0,Fields!Efforts.Value),"DataSet1",Recursive)
This shows sum as 39 for second row. You can try and let me know if it works for you. If it doesn't I will list the exact steps how I created Matrix and groups.
Note: Don't forget to put your dataset name in the second argument of function Sum. And Recursive, as clear by name, applies Sum recursively for the group.
Update: I followed following steps.
1. Add a Matrix on the report.
2. Under Column group section on Matrix, Select any column name from the dataset. (Otherwise it won't show any columns in the next step)
2. Right click Column --> Add Group --> (Under column group) Add Parent Group. Select Day as Group By --> OK. It will create a new row. Put expression Sum(Efforts) in first row. And your expression =IIF(Sum(Fields!Efforts.Value)>8.0,8.0,Sum(Fields!Efforts.Value)) in the second row.
Right click on the column group section in the group pane --> Select Add Total --> After. It will add new column at the end of Matrix. Put expression Sum(Efforts) in first row and expression =Sum(IIF(Fields!Efforts.Value>8.0,8.0,Fields!Efforts.Value),"DataSet1",Recursive) in the second row.
Save and run you should see following in the report.
Remember to change the names of columns and dataset as par your code.
This is an idea on how to do such grouping, obviously you'd need to do changes for the headers and the 3rd row etc.
HTH.