ORACLE GOLDEN GATE COLSEXCEPT not working - oracle-golden-gate

I am trying to replicate a tables from production into the data warehouse. There are columns that I don't want the transactions for when an update is performed only on those columns. I was under the impression that the COLSEXCEPT command is what I needed to use
(
https://docs.oracle.com/goldengate/1212/gg-winux/GWURF/gg_parameters160.htm#GWURF546
{COLS | COLSEXCEPT} (column_list) Selects or excludes columns for processing.
TABLE
)
In my table the BL_ARREARS_IND column needs to be excluded since it is not in my target table. When I update only the BL_ARREARS_IND column in the source it is still logging the transaction in turn sending it to the target server.
I have an extract and a pump set up.
EXTRACT extbill
SETENV (ORACLE_SID=******)
SETENV (ORACLE_HOME=/u01/app/oracle/product/11.2.0.4/dbhome_1)
USERID *****, PASSWORD *****
EXTTRAIL /u01/dwdev/oggdev/product/12.1.2/oggcore_2/dirdat/lb
TRANLOGOPTIONS ASMUSER SYS#ASM8, ASMPASSWORD ******
TABLE tmp.bill, &
KEYCOLS(ACCT_ID, BILL_SEQ_NO), &
**COLSEXCEPT(BL_ARREARS_IND);**
DISCARDFILE ./dwdev_ggdev_bill.dsc, APPEND
DiscardRollover at 02:00 ON SUNDAY
EXTRACT pumpbill
RMTHOST tst.corp.intranet, MGRPORT 7812
RMTTRAIL /u01/dwtst/oggdev/product/12.1.2/oggcore_2/dirdat/rb
TABLE tmp.bill **COLSEXCEPT(BL_ARREARS_IND);**
What am I missing?

I misunderstood the COLSEXCEPT command. I thought a transaction would only be created if a change was made to a field that was not in the COLSEXCEPT list. It still creates a transaction, but just doesn't send those fields to the target database (still not sure why you would create a transaction to changes to fields you are excluding).
I'm sure there is a better way, but I was able to get it to work using what I call a workaround by applying a filter to the table in the extract. This was a cumbersome process because the table has 82 fields. Had to compare the BEFORE image to the AFTER image for all of the other 81 fields that I do need.
When you have this many arguments in a FILTER you have to increase the FUNCTIONSTACKSIZE to accomodate (which in my case required a DBA to make the change).
I know there has to be a better way to handle this, but here is the code that worked for me:
TABLE tmp.bill, KEYCOLS(ACCT_ID, BILL_SEQ_NO), COLSEXCEPT(BL_ARREARS_IND),
FILTER(ON UPDATE,((#BEFORE(ACCT_ID) <> #AFTER(ACCT_ID)) or (#BEFORE(BILL_SEQ_NO) <> #AFTER(BILL_SEQ_NO)) or (#BEFORE(ACTUAL_BALANCE_AMT) <> #AFTER(ACTUAL_BALANCE_AMT)) or (#STREQ(#BEFORE(AFTER_CYCLE_CHG_IND),#AFTER(AFTER_CYCLE_CHG_IND)) = 0) or (#BEFORE(AGGREGATION_ID) <> #AFTER(AGGREGATION_ID)) or (#STREQ(#BEFORE(APPLICATION_ID),#AFTER(APPLICATION_ID)) = 0) or (#STREQ(#BEFORE(BANK_ACCOUNT_NO),#AFTER(BANK_ACCOUNT_NO)) = 0) or (#STREQ(#BEFORE(BANK_CODE),#AFTER(BANK_CODE)) = 0) or (#STREQ(#BEFORE(BAN_STATUS),#AFTER(BAN_STATUS)) = 0) or (#STREQ(#BEFORE(BAN_STATUS_DATE),#AFTER(BAN_STATUS_DATE)) = 0) or (#BEFORE(BF_ADJ) <> #AFTER(BF_ADJ)) or (#STREQ(#BEFORE(BF_BTN),#AFTER(BF_BTN)) = 0) or (#BEFORE(BF_CALL_CHG) <> #AFTER(BF_CALL_CHG)) or (#BEFORE(BF_IMM_ADJ) <> #AFTER(BF_IMM_ADJ)) or (#BEFORE(BF_MON_SRV_CHG) <> #AFTER(BF_MON_SRV_CHG)) or (#BEFORE(BF_ONE_TIME_CRG) <> #AFTER(BF_ONE_TIME_CRG)) or (#BEFORE(BF_TAXS) <> #AFTER(BF_TAXS)) or (#STREQ(#BEFORE(BILLING_METHOD),#AFTER(BILLING_METHOD)) = 0) or (#STREQ(#BEFORE(BILL_CONF_STATUS),#AFTER(BILL_CONF_STATUS)) = 0) or (#STREQ(#BEFORE(BILL_DATE),#AFTER(BILL_DATE)) = 0) or (#STREQ(#BEFORE(BILL_DUE_DATE),#AFTER(BILL_DUE_DATE)) = 0) or (#STREQ(#BEFORE(BILL_STATUS_DATE),#AFTER(BILL_STATUS_DATE)) = 0) or (#STREQ(#BEFORE(BL_BAL_HANDLE_IND),#AFTER(BL_BAL_HANDLE_IND)) = 0) or (#STREQ(#BEFORE(CARRY_OVER_IND),#AFTER(CARRY_OVER_IND)) = 0) or (#BEFORE(CH_BAL_CHGS) <> #AFTER(CH_BAL_CHGS)) or (#STREQ(#BEFORE(CREDIT_CARD_NO),#AFTER(CREDIT_CARD_NO)) = 0) or (#STREQ(#BEFORE(CREDIT_CARD_TYPE),#AFTER(CREDIT_CARD_TYPE)) = 0) or (#STREQ(#BEFORE(CR_CARD_AUTH_CODE),#AFTER(CR_CARD_AUTH_CODE)) = 0) or (#STREQ(#BEFORE(CR_CARD_EXP_DATE),#AFTER(CR_CARD_EXP_DATE)) = 0) or (#BEFORE(CURR_CHARGE_AMT) <> #AFTER(CURR_CHARGE_AMT)) or (#BEFORE(CURR_CREDIT_AMT) <> #AFTER(CURR_CREDIT_AMT)) or (#BEFORE(CURR_OC_CHRG_AMT) <> #AFTER(CURR_OC_CHRG_AMT)) or (#BEFORE(CURR_RC_CHRG_AMT) <> #AFTER(CURR_RC_CHRG_AMT)) or (#BEFORE(CURR_UC_CHRG_AMT) <> #AFTER(CURR_UC_CHRG_AMT)) or (#STREQ(#BEFORE(CYCLE_CLOSE_DATE),#AFTER(CYCLE_CLOSE_DATE)) = 0) or (#BEFORE(CYCLE_CODE) <> #AFTER(CYCLE_CODE)) or (#BEFORE(CYCLE_RUN_MONTH) <> #AFTER(CYCLE_RUN_MONTH)) or (#BEFORE(CYCLE_RUN_YEAR) <> #AFTER(CYCLE_RUN_YEAR)) or (#BEFORE(DEPOSIT_PAID_AMT) <> #AFTER(DEPOSIT_PAID_AMT)) or (#BEFORE(DEPOSIT_REQ_AMT) <> #AFTER(DEPOSIT_REQ_AMT)) or (#STREQ(#BEFORE(DL_SERVICE_CODE),#AFTER(DL_SERVICE_CODE)) = 0) or (#BEFORE(DL_UPDATE_STAMP) <> #AFTER(DL_UPDATE_STAMP)) or (#BEFORE(FGN_COUNTY_TAX_AMT) <> #AFTER(FGN_COUNTY_TAX_AMT)) or (#BEFORE(FGN_FED_TAX_AMT) <> #AFTER(FGN_FED_TAX_AMT)) or (#BEFORE(FGN_LOCAL_TAX_AMT) <> #AFTER(FGN_LOCAL_TAX_AMT)) or (#BEFORE(FGN_STATE_TAX_AMT) <> #AFTER(FGN_STATE_TAX_AMT)) or (#BEFORE(FREE_MINUTE_STATUS) <> #AFTER(FREE_MINUTE_STATUS)) or (#BEFORE(LATE_PYM_BASE_AMT) <> #AFTER(LATE_PYM_BASE_AMT)) or (#BEFORE(MASTER_BAN) <> #AFTER(MASTER_BAN)) or (#BEFORE(MB_BILL_SEQ_NO) <> #AFTER(MB_BILL_SEQ_NO)) or (#BEFORE(NM_ADR_LINK_SEQ_NO) <> #AFTER(NM_ADR_LINK_SEQ_NO)) or (#BEFORE(NON_TELECOM_AMT) <> #AFTER(NON_TELECOM_AMT)) or (#BEFORE(NUM_OF_PRODUCTS) <> #AFTER(NUM_OF_PRODUCTS)) or (#BEFORE(OPERATOR_ID) <> #AFTER(OPERATOR_ID)) or (#BEFORE(PAST_DUE_AMT) <> #AFTER(PAST_DUE_AMT)) or (#STREQ(#BEFORE(PAYMENT_METHOD),#AFTER(PAYMENT_METHOD)) = 0) or (#BEFORE(PAY_ARR_AMT_DUE) <> #AFTER(PAY_ARR_AMT_DUE)) or (#BEFORE(PAY_ARR_AMT_REM) <> #AFTER(PAY_ARR_AMT_REM)) or (#STREQ(#BEFORE(PRD_CVRG_END_DATE),#AFTER(PRD_CVRG_END_DATE)) = 0) or (#STREQ(#BEFORE(PRD_CVRG_STRT_DATE),#AFTER(PRD_CVRG_STRT_DATE)) = 0) or (#BEFORE(PREV_BALANCE_AMT) <> #AFTER(PREV_BALANCE_AMT)) or (#STREQ(#BEFORE(PRODUCTION_DATE),#AFTER(PRODUCTION_DATE)) = 0) or (#STREQ(#BEFORE(PRODUCTION_REQUEST),#AFTER(PRODUCTION_REQUEST)) = 0) or (#STREQ(#BEFORE(PRODUCTION_TYPE),#AFTER(PRODUCTION_TYPE)) = 0) or (#BEFORE(PRODUCTS_NUM_CALLS) <> #AFTER(PRODUCTS_NUM_CALLS)) or (#BEFORE(PRODUCTS_NUM_MINS) <> #AFTER(PRODUCTS_NUM_MINS)) or (#BEFORE(PYM_RECEIVED_AMT) <> #AFTER(PYM_RECEIVED_AMT)) or (#STREQ(#BEFORE(SYS_CREATION_DATE),#AFTER(SYS_CREATION_DATE)) = 0) or (#STREQ(#BEFORE(SYS_UPDATE_DATE),#AFTER(SYS_UPDATE_DATE)) = 0) or (#BEFORE(TAX_FED_AMT) <> #AFTER(TAX_FED_AMT)) or (#BEFORE(TAX_FED_USF_AMT) <> #AFTER(TAX_FED_USF_AMT)) or (#BEFORE(TAX_LOC_AMT) <> #AFTER(TAX_LOC_AMT)) or (#BEFORE(TAX_ROAM_AIR) <> #AFTER(TAX_ROAM_AIR)) or (#BEFORE(TAX_ROAM_SRV_CHARGE) <> #AFTER(TAX_ROAM_SRV_CHARGE)) or (#BEFORE(TAX_ROAM_TOLL) <> #AFTER(TAX_ROAM_TOLL)) or (#BEFORE(TAX_STT_AMT) <> #AFTER(TAX_STT_AMT)) or (#BEFORE(TELECOM_AMT) <> #AFTER(TELECOM_AMT)) or (#BEFORE(TOTAL_BILLED_ADJUST) <> #AFTER(TOTAL_BILLED_ADJUST)) or (#BEFORE(TOTAL_BILLED_CHARGE) <> #AFTER(TOTAL_BILLED_CHARGE)) or (#BEFORE(TOTAL_COUNTY_TAX) <> #AFTER(TOTAL_COUNTY_TAX)) or (#BEFORE(TOTAL_DUE_AMT) <> #AFTER(TOTAL_DUE_AMT)) or (#BEFORE(TOTAL_TAX_FEES) <> #AFTER(TOTAL_TAX_FEES))));

That code wasn't able to compare nulls. I came up with this macro which gave me the right output.
Filename: check_col.mac
Contents:
MACRO #check_col
PARAMS (#ICOL)
BEGIN
(
(0 = #IF (#COLTEST (#BEFORE(#ICOL), NULL, MISSING),0,1) AND 1 = #IF (#COLTEST (#ICOL, NULL, MISSING), 0, 1))
OR
(1 = #IF (#COLTEST(#BEFORE(#ICOL), NULL, MISSING), 0, 1) AND 0 = #IF (#COLTEST (#ICOL, NULL, MISSING), 0, 1))
OR
(0 = #STREQ(#BEFORE(#ICOL),#ICOL))
)
END;
You can use this macro in the INC file for the mapping like the below:
INCLUDE dirprm/check_col.mac
TABLE S1.T1, KEYCOLS(COL1, COL2), COLSEXCEPT(COL123), FILTER(ON UPDATE,(#check_col(COL3) or #check_col(COL4));
If you map more than 5 columns in this fashion, the code will throw a STACK SIZE NOT ENOUGH error, FUNCTIONSTACKSIZE = 5000 solves it. Let me know what negative effects this param has got.

Related

Inserting python variables into Sqlite database

I am sorting out an error for my database so I have tried to isolate the problem. This function is the problem but more specifically the c.execute("INSERT INTO details VALUES(?, ?)",(e ,cp ,)).
This causes this error:
c.execute("INSERT INTO details VALUES(?, ?)",(e ,cp ,))
sqlite3.InterfaceError: Error binding parameter 0 - probably unsupported type.
I'll put the rest of the function underneath:
import sqlite3
def echecker():
email = False
password = False
right = 0
e = "the#test.com"
p = "test"
cp = "test"
space = 0
conn = sqlite3.connect("U+P.db")
c = conn.cursor()
c.execute("""SELECT email FROM details""")
data = c.fetchall()
conn.commit()
index = 0
for row in data:
if row[index] == e:
right = right + 1
else:
print("right")
print()
e = list(e)
print(e)
while space < len(e) and right == 0 and email == False:
if e[0] == "#" and e[space] == "#":
space = space + 1
print("wrong")
elif e[space] == "#":
email = True
print("right")
else:
space = space + 1
print("wrong")
print()
if p == cp:
password = True
print("right")
print()
if password == True and email == True and right == 0:
conn = sqlite3.connect("U+P.db")
print("a")
c = conn.cursor()
print("a")
c.execute("INSERT INTO details VALUES(?, ?)",(e ,cp ,))
print("a")
conn.commit()
print("siuu")
echecker()
Well, you reassign e to a list e = list(e). Of course the subsequent attempt to supply e as a query parameter will fail.

SQL Loop Executing after intended

I am attempting to run this code, I know the elements of the Loop are working, and I know the update statement does update. When I run the script with some print statements, it prints the UPDATE information first, then the LOOP information, therefore the update has no information.
PROCEDURE UpdateGridStats(p_grid_name VARCHAR2, p_region_name VARCHAR2) IS
CURSOR c1 IS
SELECT grd.globalid grid_globalid, wp.globalid workpoint_globalid, wp.feature_class_name workpoint_fcname,
tt.work_order_task_type task_type_name
FROM workorderpoint_evw wp, rpt_grid grd, workordertasktype_evw tt
WHERE grd.grid_name = p_grid_name
AND wp.work_order_task_type_globalid = tt.globalid
AND grd.rpt_region = p_region_name
AND sde.st_relation_operators.st_within_f(wp.shape, grd.shape) = 1;
v_count NUMBER := 0;
v_pole_insp_count NUMBER := 0;
v_pole_damage_count NUMBER := 0;
v_cond_damage_count NUMBER := 0;
BEGIN
FOR work_rec IN c1
LOOP
BEGIN
v_count := v_count + 1;
IF work_rec.task_type_name = 'Pole Inspection'
THEN
v_pole_insp_count := v_pole_insp_count + 1;
END IF;
IF work_rec.task_type_name = 'Pole Damage'
THEN
v_pole_damage_count := v_pole_damage_count + 1;
END IF;
EXCEPTION
WHEN NO_DATA_FOUND THEN
dbms_output.put_line('==> No data found for work record ');
END;
END LOOP;
dbms_output.put_line(v_pole_damage_count || ',' ||v_pole_insp_count);
UPDATE rpt_grid grd SET da_pole_count = v_pole_damage_count, ins_structure_count = v_pole_insp_count
WHERE grd.grid_name = p_grid_name
AND grd.rpt_region = p_region_name;
END UpdateGridStats;
Maybe you just forgot to commit your changes?
Here is a simpler version of your stored procedure.
CREATE OR REPLACE PROCEDURE update_grid_stats(p_grid_name VARCHAR2, p_region_name VARCHAR2)
IS
v_ins_structure_count rpt_grid.ins_structure_count%TYPE;
v_da_pole_count rpt_grid.da_pole_count%TYPE;
BEGIN
UPDATE rpt_grid grd
SET (ins_structure_count, da_pole_count) =
(
SELECT
COUNT(CASE WHEN tt.work_order_task_type = 'Pole Inspection' THEN 1 END),
COUNT(CASE WHEN tt.work_order_task_type = 'Pole Damage' THEN 1 END)
FROM workorderpoint_evw wp
JOIN workordertasktype_evw tt ON wp.work_order_task_type_globalid = tt.globalid
WHERE sde.st_relation_operators.st_within_f(wp.shape, grd.shape) = 1
)
WHERE grd.grid_name = p_grid_name
AND grd.rpt_region = p_region_name
RETURNING ins_structure_count, da_pole_count
INTO v_ins_structure_count, v_da_pole_count;
dbms_output.put_line(
SQL%ROWCOUNT || ' rows got updated. Values: ' ||
'ins_structure_count = ' || v_ins_structure_count ||
', da_pole_count = ' || v_da_pole_count
);
COMMIT;
END update_grid_stats;
The variables and the RETURNING clause are merely needed for the output. If you don't need the output, you can remove them.
if I understand correcatally, the proc works OK, but the print statemnts are not. I beleve the print operations are on a different thread. To overcome this issue, try to replace your pront statment with raiserror function :
declare #mag varchar(max) = 'Hllo world'
raiserror ( #ERR_MSG ,0, 1) with nowait;

MS SQL Case in Where Clause testing against NULL or Argument

I have a query against a UDF where I want to allow the user to pass in either ALL or a specific EType.
If they pass in ALL, I want to accept all ETypes where it is not null.
I have searched thru SO for examples and not seem to meet my particular situation.
Where am I going wrong?
Declare
#company varchar(4),
#charge_cov bit,
#EType varchar(8);
set #company = '123'
set #charge_cov =1
set #EType = 'ALL'
select e.emp_id,
dbo.format_emp_number(pd.EN) as EN,
dbo.format_emp_number(pd.MEN) as MEN,
pd.EType
from dbo.employee_payroll_data(NULL) pd
inner join employee e on (e.emp_id=pd.emp_id)
where pd.EType = case when #EType='ALL' then pd.EType
else #EType ) END
and pd.EType is not null
and e.emp_number is not null
and e.charge_cov = 1
and lc.pr_co_code = #company
Try below code:
WHERE (((1 = (CASE WHEN #EType = 'ALL' THEN 1 ELSE 0 END)))
OR ((pd.Etype = (CASE WHEN #EType <> 'ALL' THEN #EType ELSE '' END))))
AND pd.Etype IS NOT NULL

What's the best way to parse an SQL fragment string into a List(of string) for a Listbox control?

I'm trying to take this string:
(("DISPLAY_NAME" like N'sadf%') And ("ID" = 2) And ("IsCRITERION" = null))
and parse it into a List(of string) so that it can be displayed like:
(
(
"DISPLAY_NAME" like N'sadf%'
)
And
(
"ID" = 2
)
Or
(
"IsCRITERION" = null
)
)
I'm close but don't quite have it. My code currently looks like:
Dim filterlist As New List(Of String)
Dim temp As String = String.Empty
Dim lvl As Integer = 0
Dim pad As String = String.Empty
For Each chr As Char In originalString '--- filter is the string i posted above
Select Case chr.ToString.ToLower()
Case "("
filterlist.Add(pad.PadLeft(lvl * 5) & chr)
lvl += 1
Case ")"
filterlist.Add(pad.PadLeft(lvl * 5) & temp)
If lvl > 0 Then lvl -= 1
filterlist.Add(pad.PadLeft(lvl * 5) & chr)
'If lvl > 0 Then lvl -= 1
temp = String.Empty
Case Else
temp &= chr
End Select
Next
'--- Removes the empty line produced by generating the List(of String)
filterlist = filterlist.Where(Function(s) Not String.IsNullOrWhiteSpace(s)).ToList()
listSelectedCriteria.DataSource = filterlist
listSelectedCriteria.DataBind()
Unfortunately, the above code produces something close to what I desire but the "And"s and "Or"s are not in the right places:
(
(
"DISPLAY_NAME" like N'sadf%'
)
(
And "ID" = 2
)
(
Or "IsCRITERION" = null
)
)
Would using regular expressions be better? Thanks for the help
Probably the "best" way (although that's getting into "primarily opinion-based" territory) would be to use a parser, but assuming that your input is limited to similar looking strings, here's what I came up with:
Dim originalString = "((""DISPLAY_NAME"" like N'sadf%') And (""ID"" = 2) And (""IsCRITERION"" = null))"
Dim filterlist = New List(Of String)()
Dim temp = New StringBuilder()
Dim lvl = 0
Dim addLine =
Sub(x As String)
filterlist.Add(New String(" ", lvl * 4) & x.Trim())
End Sub
For Each c In originalString
Select Case c
Case "("
If temp.Length > 0 Then
addLine(temp.ToString())
temp.Clear()
End If
addLine("(")
lvl += 1
Case ")"
If temp.Length > 0 Then
addLine(temp.ToString())
temp.Clear()
End If
lvl -= 1
addLine(")")
Case Else
temp.Append(c)
End Select
Next
If temp.Length > 0 Then
addLine(temp.ToString())
temp.Clear()
End If
filterlist.Dump() ' LINQPad ONLY
This results in:
(
(
"DISPLAY_NAME" like N'sadf%'
)
And
(
"ID" = 2
)
And
(
"IsCRITERION" = null
)
)
However, you will probably end up having to add code as you find different inputs that don't quite work how you want.
Instead of looking at each characters, I would start be doing a split. And then add/remove padding depending on what character is at the start.
Dim tempString As String = "((""DISPLAY_NAME"" like N'sadf%') And (""ID"" = 2) And (""IsCRITERION"" = null))"
Dim curPadding As String = ""
Const padding As String = " "
Dim result As New List(Of String)
For Each s As String In Text.RegularExpressions.Regex.Split(tempString, "(?=[\(\)])")
If s <> "" Then
If s.StartsWith("(") Then
result.Add(curPadding & "(")
curPadding &= padding
result.Add(curPadding & s.Substring(1).Trim())
ElseIf s.StartsWith(")") Then
curPadding = curPadding.Substring(padding.Length)
result.Add(curPadding & ")")
result.Add(curPadding & s.Substring(1).Trim())
Else
result.Add(curPadding & s)
End If
End If
Next

Resize multidimensional array and sort them by date

I think my code successfully creates the multi dimensional array because I get the right amount when I count it with UBound(DataArray).
But I get null value when I try to display one of the data as Response.Write DataArray(1,0).
Any help appreciated!
sDateArray = Split(DateArray, ",")
sVenueArray = Split(VenueArray, ",")
Dim DataArray()
For i = 0 to uBound(sDateArray)-1
ReDim DataArray(i, 1)
DataArray(i, 0) = sDateArray(i)
DataArray(i, 1) = sVenueArray(i)
Next
Response.Write UBound(DataArray) & "<br /><br />"
DataArray(1,0)
Response.Write DataArray(1,0)
Try Redim Preserve DataArray(i, 1) instead of ReDim DataArray(i, 1)
...or...
sDateArray = Split(DateArray, ",")
sVenueArray = Split(VenueArray, ",")
Dim DataArray(uBound(sDateArray)-1, 1)
For i = 0 to uBound(sDateArray)-1
DataArray(i, 0) = sDateArray(i)
DataArray(i, 1) = sVenueArray(i)
Next
Response.Write UBound(DataArray) & "<br /><br />"
' DataArray(1,0) ' <== commented out cos I think this might be an error - ?
Response.Write DataArray(1,0)
Ok I was bored so I wrote this.
May not be perfect - it's been a while since I used Classic ASP
Function SortByDate(a_input)
x = UBound(a_input, 1) - 1
if( x < 1 ) Then
Response.Write "<p>Invalid input array - first element is empty</p>"
Stop
End If
Dim a_output(x, 1)
Dim earliest_date
For j=0 To x
earliest_date = -1
For i=0 To UBound(a_input, 1) - 1
If a_input(0, i) <> "" Then
If earliest_date = -1 Then
earliest_date = i
Else
If CDate(a_input(0,i)) > CDate(a_input(0,earliest_date)) Then
earliest_date = i
End If
End If
End If
Next
a_output(0, i) = a_input(0, earliest_date)
a_output(1, i) = a_input(1, earliest_date)
a_input(0, earliest_date) = "" ' this one is done so skip next time '
Next
SortByDate = a_output
End Function

Resources