Excel Macro to extract rows, dependent on previous days value and at 30 items plus a complete category? - email

I have an excel file, which initially imports stock data from our cloud based accounting program through .iqy web query.
The column headings are:
A1= Quantity B1= Item C1= Description D1= Bin Code
Now I have created a macro which;
Referesh's the data
Range("A1").QueryTable.Refresh False
Delete's all zero stock items
Dim intRow
Dim intLastRow
intLastRow = Range("A65536").End(xlUp).Row
For intRow = intLastRow To 1 Step -1
Rows(intRow).Select
If Cells(intRow, 1).Value = 0 Or Cells(intRow, 1) = "" Then
Cells(intRow, 1).Select
Selection.EntireRow.Delete
End If
Next intRow
Auto Sort by Bin Code
Range("A1:D1").Select
Selection.AutoFilter
Range("A2").Select
Range("A1:D1668").Sort Key1:=Range("D1"), Order1:=xlAscending, Header:= _
xlGuess, OrderCustom:=1, MatchCase:=False, Orientation:=xlTopToBottom, _
DataOption1:=xlSortNormal
Save the Master list
Dim sFileName As String, sPath As String
sPath = "C:\stock\ms\Master List "
sFileName = Format(Now(), "dd'mm'yy")
ActiveWorkbook.SaveAs (sPath & sFileName)
Now this is the tricky bit,
At least 30 items a day need to be checked, however a bin can not be incomplete! So once 30 items have been selected the script needs to check to see if the next item is in the same bin as the 30th item, and include this in the extraction. So lets say item 30 is in bin 10A2, and also item 31, 32, 33, 34, so all in all 34 items (rows) need to be extracted into a new workbook and saved.
This process must start from the previous days sample, so the mechanics should go like this:
look in c\stock\sl\Sample List -1 dd'mm'yy sample list for -1 day, look at the last item bin number, say 10A1,
take the next rows bin number, 10A2,
from the first row which has 10A2, select 30 rows,
Continue till the bin number changes.
save that file as Sample List dd'mm'yy in c\stock\sl\
email Sample List dd'mm'yy to NNN#NNN.com
This should be able to repeat. Also on Saturday and Sunday the company is not open, so on mondays it should look back on friday, and so forth, also accounting for public holidays.
Any help with this would be a life saver? I don't mind if you want to change the file names so that this issue with holidays can be addressed. However, somewhere a time stamp needs to be placed for the files.

You might want to check out the Dictionary object, it would probably help in this task. If you have any questions along the way ask another question. Not sure if someone else would want to give you a more thorough answer to this question.
Your project might be big enough that you would want to work with classes too.

Please avoid every Select in your code.
For instance,
Range("A1:D1").Select
Selection.AutoFilter
can be replaced by:
Range("A1:D1").AutoFilter

Related

JCL SORT - Need to add fields from different rows of input to a single output row

I have a 6 row input file which consists of a field(Position 1 to 6) that contains a different value on every line. Based on the different values contained in this field the other fields (From position 7 -80) will be moved to to a single row in output.
E.G.
Input:
035MI 88122
035ST 72261
035SU 317786762
105 06616858
1601 11
1651 0000000140006PC
Output:
1 8812272261317786762 06616858 11 0000000140006PC
I need to find out how to read these all in as different rows and then output to a single row. I've tried using something similar to the code to this:
SORT FIELDS=COPY
INREC IFTHEN=(WHEN=(1,6,CH,EQ,C'035MI '),
OVERLAY=(3:7,5)),
But this will move the data onto the correct position on seperate rows like this:
1 8812272261317786762
1 06616858
1 11
1 0000000140006PC
So now I think I need to do a sort in one step and a merge in another step. I would prefer to do it in one if it's possible however. I'd appreciate any help on this. Thanks.
You add a sequence-number to each record.
The use WHEN=GROUP to copy data from one record to one or more subsequent records.
You use OUTFIL INLUDE= to just pick up the final record.
OPTION COPY
INREC IFTHEN=(WHEN=INIT,
OVERLAY=(81:SEQNUM,1,ZD)),
IFTHEN=(WHEN=GROUP,
BEGIN=(81,1,CH,EQ,C'1'),
PUSH=(somestuff),
RECORDS=6),
IFTHEN=(WHEN=GROUP,
BEGIN=(81,1,CH,EQ,C'2'),
PUSH=(somestuff),
RECORDS=5),
IFTHEN=(WHEN=GROUP,
BEGIN=(81,1,CH,EQ,C'3'),
PUSH=(somestuff),
RECORDS=4),
IFTHEN=(WHEN=GROUP,
BEGIN=(81,1,CH,EQ,C'4'),
PUSH=(somestuff),
RECORDS=3),
IFTHEN=(WHEN=GROUP,
BEGIN=(81,1,CH,EQ,C'5'),
PUSH=(somestuff),
RECORDS=2),
OUTFIL INCLUDE=(81,1,CH,EQ,C'6'),
BUILD=(1,80)
You need to do a bit of planning. The sixth record will contain all the data, but perhaps not yet in the order that you want. Either with an IFTHEN=(WHEN=(logical expression) (to identify the sixth record) on the INREC or with the BUILD on the OUTREC, you can do your final formatting.
You need to change the something each time, it will be receivingposition:sourceposition,length
The DFSORT manuals are very good, there is a Getting Started for those new to the product, and everything you'll ever need is in the Application Programming Guide,

Merge data from many sheets on to one

In excel, I have several sheets (around 50), each with an identical header in columns A, B and C, and then up to 199 rows of data (row 1 = header, rows 2-200 = data). The naming routine is as Wk 1 Mon, Wk 2 Tue, etc, all the way up to Wk 10 Fri
What I would like to do is display all of the data from these tabs in one list, on one sheet. I could potentially do this by referencing each cell from each sheet, one under the other, but the problem is that not all sheets actually have data right the way down to row 200 (some have just the header), and I wish to skip empty rows.
I have absolutely no clue how to approach this in Excel. My understanding of VLOOKUP and the like is rudimentary at best; I'm not sure if I could even achieve what is required by using that family of functions.
I've also looked in to the Consolidation feature of Excel, but I don't think that is what I need in this scenario.
Could someone please suggest how I may achieve my goals. I would prefer to do this via worksheet only functions, but I'd be open to VBA if there was an easy enough solution.
Try this bit of VBA. It basically scrolls through each worksheet, finds the last row and pastes it on the bottom of the first sheets data. It's a bit crude in methodology but it does work!
Dim ws As Worksheet
For Each ws In Worksheets
i = i + 1
If i = 1 Then
FirstSheet = ws.Name
ElseIf i > 1 Then
ws.Activate
LastCell = Cells(65536, 1).End(xlUp).Row
Range("A1:C" & LastCell).Select
Selection.Copy
Worksheets(FirstSheet).Activate
Cells(Cells(65536, 1).End(xlUp).Row + 1, 1).Select
ActiveCell.PasteSpecial xlPasteValuesAndNumberFormats
End If
Next ws

updating existing data on google spreadsheet using a form?

I want to build kind of an automatic system to update some race results for a championship. I have an automated spreadsheet were all the results are shown but it takes me a lot to update all of them so I was wondering if it would be possible to make a form in order to update them more easily.
In the form I will enter the driver name and the number o points he won on a race. The championship has 4 races each month so yea, my question is if you guys know a way to update an existing data (stored in a spreadsheet) using a form. Lets say that in the first race, the driver 'X' won 10 points. I will insert this data in a form and then call it from the spreadsheet to show it up, that's right. The problem comes when I want to update the second race results and so on. If the driver 'X' gets on the second race 12 points, is there a way to update the previous 10 points of that driver and put 22 points instead? Or can I add the second race result to the first one automatically? I mean, if I insert on the form the second race results can it look for the driver 'X' entry and add this points to the ones that it previously had. Dunno if it's possible or not.
Maybe I can do it in another way. Any help will be much appreciated!
Thanks.
Maybe I missed something in your question but I don't really understand Harold's answer...
Here is a code that does strictly what you asked for, it counts the total cumulative value of 4 numbers entered in a form and shows it on a Spreadsheet.
I called the 4 questions "race number 1", "race number 2" ... and the result comes on row 2 so you can setup headers.
I striped out any non numeric character so you can type responses more freely, only numbers will be retained.
form here and SS here (raw results in sheet1 and count in Sheet2)
script goes in spreadsheet and is triggered by an onFormSubmit trigger.
function onFormSubmit(e) {
var sh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet2');
var responses = []
responses[0] = Number(e.namedValues['race number 1'].toString().replace(/\D/g,''));
responses[1] = Number(e.namedValues['race number 2'].toString().replace(/\D/g,''));
responses[2] = Number(e.namedValues['race number 3'].toString().replace(/\D/g,''));
responses[3] = Number(e.namedValues['race number 4'].toString().replace(/\D/g,''));
var totals = sh.getRange(2,1,1,responses.length).getValues();
for(var n in responses){
totals[0][n]+=responses[n];
}
sh.getRange(2,1,1,responses.length).setValues(totals);
}
edit : I changed the code to allow you to change easily the number of responses... range will update automatically.
EDIT 2 : a version that accepts empty responses using an "if" condition on result:
function onFormSubmit(e) {
var sh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet2');
var responses = []
responses[0] = Number((e.namedValues['race number 1']==null ? 0 :e.namedValues['race number 1']).toString().replace(/\D/g,''));
responses[1] = Number((e.namedValues['race number 2']==null ? 0 :e.namedValues['race number 2']).toString().replace(/\D/g,''));
responses[2] = Number((e.namedValues['race number 3']==null ? 0 :e.namedValues['race number 3']).toString().replace(/\D/g,''));
responses[3] = Number((e.namedValues['race number 4']==null ? 0 :e.namedValues['race number 4']).toString().replace(/\D/g,''));
var totals = sh.getRange(2,1,1,responses.length).getValues();
for(var n in responses){
totals[0][n]+=responses[n];
}
sh.getRange(2,1,1,responses.length).setValues(totals);
}
I believe you can found everything you want here.
It's a form url, when you answer this form you'll have the url of the spreadsheet where the data are stored. One of the information stored is the url to modify your response, if you follow the link it will open the form again and update the spreadsheet in consequence. the code to do this trick is in the second sheet of the spreadsheet.
It's a google apps script code that need to be associated within the form and triggered with an onFormSubmit trigger.
It may be too late now. I believe we need a few things (I have not tried it)
A unique key to map each submitted response, such as User's ID or email.
Two Google Forms:
a. To request the unique key
b. To retrieve relevant data with that unique key
Create a pre-filled URL (See http://www.cagrimmett.com/til/2016/07/07/autofill-google-forms.html)
Open the URL from your form (See Google Apps Script to open a URL)

Display table field through query using form

Just some background information. My table, (HireHistory) has 50 columns in it (horizontal). I have a Form (HireHistoryForm) which has a 2 text boxes (HistoryMovieID and HistoryCustomerID) and a button (the button runs the query 'HireHistoryQuery')
Here's an excerpt of my data (the CustomerID's are along the top):
So what I need is so that if a number is entered into the HistoryCustomerID box, then it displays that column. e.g. if the value entered is '1', then in my query it will show all records from column 1.
If a number is entered into the HistoryMovieID box (e.g. 0001) then it displays all instances of that MovieID for the specific CustomerID's. i.e. In column 1 is the ID's, so for ID=1 it will show "0001 on 19/05/2006" then will go on to find the next instance of '0001' etc.
For the HistoryCustomerID I tried to put this into my 'Field' for the query:
=[Forms]![HireHistoryForm]![HistoryCustomerID]
But it didn't work. My query just returned a column labelled '10' and the rows were just made up of '10'.
If you could help I'd greatly appreciate it. :)
No offense intended (or as little as possible, anyway), but that is a horrible way to structure your data. You really need to restructure it like this:
CustomerID MovieID HireDate
---------- ------- --------
1 0001 19/05/2006
1 0003 20/10/2003
1 0007 13/08/2003
...
2 0035 16/08/2012
2 0057 06/10/2012
...
If you keep your current data structure then
You'll go mad, and
It's extremely unlikely that anyone else will go anywhere near this problem.
Edit
Your revised data structure is a very slight improvement, but it still works against you. Consider that in your other question here you are essentially asking for a way to "fix" your data structure "on the fly" when you do a query.
The good news is that you can run a bit of VBA code once to convert your data structure to something workable. Start by creating your new table, which I'll call "HireHistoryV2"
ID - AutoNumber, Primary Key
CustomerID - Number(Long Integer), Indexed (duplicates OK)
MovieID - Text(4), Indexed (duplicates OK)
HireDate - Date/Time, Indexed (duplicates OK)
The VBA code to copy your data to the new table would look something like this:
Function RestructureHistory()
Dim cdb As DAO.Database, rstIn As DAO.Recordset, rstOut As DAO.Recordset
Dim fld As DAO.Field, a() As String
Set cdb = CurrentDb
Set rstIn = cdb.OpenRecordset("HireHistory", dbOpenTable)
Set rstOut = cdb.OpenRecordset("HireHistoryV2", dbOpenTable)
Do While Not rstIn.EOF
For Each fld In rstIn.Fields
If fld.Name Like "Hire*" Then
If Not IsNull(fld.Value) Then
a = Split(fld.Value, " on ", -1, vbBinaryCompare)
rstOut.AddNew
rstOut!CustomerID = rstIn!CustomerID
rstOut!MovieID = a(0)
rstOut!HireDate = CDate(a(1))
rstOut.Update
End If
End If
Next
Set fld = Nothing
rstIn.MoveNext
Loop
rstOut.Close
Set rstOut = Nothing
rstIn.Close
Set rstIn = Nothing
Set cdb = Nothing
MsgBox "Done!"
End Function
Note: You appear to be using dd/mm/yyyy date formatting, so check the date conversions carefully to make sure that they converted properly.

Generate unique 3 letter/number code and compare to existing ones in PHP/MySQL

I'm making a code generation script for UN/LOCODE system and the database has unique 3 letter/number codes in every country. So for example the database contains "EE TLL", EE being the country (Estonia) and TLL the unique code inside Estonia, "AR TLL" can also exist (the country code and the 3 letter/number code are stored separately). Codes are in capital letters.
The database is fairly big and already contains a huge number of locations, the user has also the possibility of entering the 3 letter/number him/herself (which will be checked against the database before submission automatically).
Finally neither 0 or 1 may be used (possible confusion with O and I).
What I'm searching for is the most efficient way to pick the next available code when none is provided.
What I've came up with:
I'd check with AAA till 999, but then for each code it would require a new query (slow?).
I could store all the 40000 possibilities in an array and subtract all the used codes that are already in the database... but that uses too much memory IMO (not sure what I'm talking about here actually, maybe 40000 isn't such a big number).
Generate a random code and hope it doesn't exist yet and see if it does, if it does start over again. That's just risk taking.
Is there some magic MySQL query/PHP script that can get me the next available code?
I will go with number 2, it is simple and 40000 is not a big number.
To make it more efficient, you can store a number representing each 3-letter code. The conversion should be trivial because you have a total of 34 (A-Z, 2-9) letters.
I would for option 1 (i.e. do a sequential search), adding a table that gives the last assigned code per country (i.e. such that AAA..code are all assigned already). When assigning a new code through sequential scan, that table gets updated; for user-assigned codes, it remains unmodified.
If you don't want to issue repeated queries, you can also write this scan as a stored routine.
To simplify iteration, it might be better to treat the three-letter codes as numbers (as Shawn Hsiao suggests), i.e. give a meaning to A-Z = 0..25, and 2..9 = 26..33. Then, XYZ is the number X*34^2+Y*34+Z == 23*1156+24*34+25 == 27429. This should be doable using standard MySQL functions, in particular using CONV.
I went with the 2nd option. I was also able to make a script that will try to match as close as possible the country name, for example for Tartu it will try to match T** then TA* and if possible TAR, if not it will try TAT as T is the next letter after R in Tartu.
The code is quite extensive, I'll just post the part that takes the first possible code:
$allowed = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ23456789';
$length = strlen($allowed);
$codes = array();
// store all possibilities in a huge array
for($i=0;$i<$length;$i++)
for($j=0;$j<$length;$j++)
for($k=0;$k<$length;$k++)
$codes[] = substr($allowed, $i, 1).substr($allowed, $j, 1).substr($allowed, $k, 1);
$used = array();
$query = mysql_query("SELECT code FROM location WHERE country = '$country'");
while ($result = mysql_fetch_array($query))
$used[] = $result['code'];
$remaining = array_diff($codes, $used);
$code = $remaining[0];
Thanks for your opinion, this will be the key to transport codes all over the world :)