Importing RBA Exchange Rates

RBA Exchange Rate Historical Data ETL into Azure

The Reserve Bank Of Australia maintains an historical exchange rate data page. I have downloaded Exchange Rates ‐ Daily ‐ 2023 to Current. I will:

The source file has the Exchange Date on the vertical axis and the Exchange Rate for each currency on the horizontal access (as well as the TWI):

Firstly I will create a table to hold the final data in Azure:

            
  1. create table forex
  2. (
  3. exchg_id int not null identity,
  4. exchg_date date not null,
  5. exchg_currency varchar(4) not null,
  6. exchg_rate decimal(19,4) null,
  7. constraint PK_forex primary key (exchg_id),
  8. constraint UQ_EXCHG_DATE_CURRENCY unique(exchg_date, exchg_currency)
  9. )

The Unique Constraint UQ_EXCHG_DATE_CURRENCY not only ensures data integrity and 3rd normal form but will also act as an index and will be very useful where results are ordered by exchg_date, exchg_currency.

The following Python code takes the source CSV and transforms it into an output file with three columns: Date, Currency and Exchange Rate. The third parameter, format_for_sql determines whether the output file is a SQL select statement which could be run directly in SQL Server Manegement Studio or a flat file for use in ETL.

            
  1. import os
  2. import sys
  3. CLI_SEPARATOR = "======================================================================\n"
  4. CLI_HEADER = "\nprocess_forex_csv\nProcesses CSV version of RBA Exchange Rate history:\nhttps://www.rba.gov.au/statistics/historical-data.html#exchange-rates\nfrom supplied format to a format ready for ETL"
  5. CLI_USAGE = "Usage:\nprocess_forex_csv.py [input_filename] [output_filename] [format_as_sql (y/n)] [append_to_output]\n"
  6. OUTPUT_HEADER = "Date,Currency,Rate\n"
  7. FILE_HEADER_1 = "EXCHANGE RATES ,,,,,,,,,,,,,,,,,,,,,,,"
  8. FILE_HEADER_2 = "HISTORICAL DAILY EXCHANGE RATES OF THE AUSTRALIAN DOLLAR AGAINST:"
  9. FOREX_HEADER = "Series ID"
  10. months = {
  11. "jan": "01",
  12. "feb": "02",
  13. "mar": "03",
  14. "apr": "04",
  15. "may": "05",
  16. "jun": "06",
  17. "jul": "07",
  18. "aug": "08",
  19. "sep": "09",
  20. "oct": "10",
  21. "nov": "11",
  22. "dec": "12"
  23. }
  24. def isNull(val): return True if str(val).upper() == 'NULL' else False
  25. class ProcessForexCSV:
  26. def __init__(self, input_filename, output_filename, format_for_sql, append_to_output):
  27. self.input_filename = input_filename
  28. self.output_filename = output_filename
  29. self.format_for_sql = format_for_sql
  30. self.append_to_output = append_to_output
  31. self.currencies = []
  32. def process_file(self):
  33. input_file_path = os.path.join(os.getcwd(), self.input_filename)
  34. output_file_path = os.path.join(os.getcwd(), self.output_filename)
  35. with open(input_file_path, "rt") as f:
  36. new_lines = ""
  37. line = f.readline()
  38. if line.find(FILE_HEADER_1) < 0 and line.find(FILE_HEADER_2) < 0:
  39. print("Invalid file format.")
  40. return
  41. line = f.readline()
  42. while line.find(FOREX_HEADER) < 0:
  43. line = f.readline()
  44. tokens = line.split(",")
  45. i = 0
  46. for token in tokens:
  47. if i > 0:
  48. self.currencies.append(token[len(token)-3:].strip())
  49. i += 1
  50. print("Currencies:\n{0}".format(CLI_SEPARATOR))
  51. for currency in self.currencies:
  52. print("{0}".format(currency))
  53. line = f.readline().strip()
  54. line_count = 0
  55. while line != "":
  56. line_count += 1
  57. line_tokens = line.split(",")
  58. token_count = len(line_tokens)
  59. exchg_date = line_tokens[0]
  60. original_exchg_date = exchg_date
  61. date_tokens = exchg_date.split("-")
  62. exchg_date = date_tokens[2] + months[date_tokens[1].lower()].zfill(2) + date_tokens[0].zfill(2)
  63. i = 1
  64. if self.format_for_sql and line_count == 1:
  65. line = "VALUES\n"
  66. new_lines += line
  67. while i < token_count:
  68. if line_tokens[i] == "":
  69. line_tokens[i] = "NULL"
  70. exchg_line = ""
  71. if not self.format_for_sql:
  72. exchg_line = "{0},{1},{2}\n".format(original_exchg_date, self.currencies[i-1], line_tokens[i])
  73. else:
  74. exchg_line = " ('" + exchg_date + "','" + self.currencies[i-1] + "'," + line_tokens[i] + "),\n"
  75. i += 1
  76. if exchg_line != "":
  77. #print(exchg_line)
  78. new_lines += exchg_line
  79. line = f.readline().strip()
  80. f.close()
  81. # get rid of the final "union all"
  82. if self.format_for_sql:
  83. new_lines = new_lines[:len(new_lines)-len(",\n")]
  84. else:
  85. new_lines = new_lines[:len(new_lines)-len("\n")]
  86. #print(new_lines)
  87. file_mode = "at" if self.append_to_output else "wt"
  88. with open(output_file_path, file_mode) as f2:
  89. if not self.format_for_sql and not self.append_to_output:
  90. f2.write(OUTPUT_HEADER)
  91. f2.write(new_lines)
  92. f2.close()
  93. print("{0}\n{1} exchange rates {2} to {3}\n{4}".format(CLI_SEPARATOR, line_count, "appended" if self.append_to_output else "written", self.output_filename, CLI_SEPARATOR))
  94. if __name__ == "__main__":
  95. print(CLI_HEADER)
  96. print(CLI_SEPARATOR)
  97. print(CLI_USAGE)
  98. if len(sys.argv) == 5:
  99. input_filename = sys.argv[1]
  100. output_filename = sys.argv[2]
  101. format_for_sql = True if str(sys.argv[3]).lower() == "y" else False
  102. append_to_output = True if str(sys.argv[4]).lower() == "y" else False
  103. process = ProcessForexCSV(input_filename, output_filename, format_for_sql, append_to_output)
  104. process.process_file()
  105. else:
  106. print("Invalid number of parameters.")

If format_for_sql is 'y' an SQL statement with 18,009 VALUES is produced. You only need add the insert at the top:

        VALUES
            ('20230103','USD','0.6828'),
            ('20230103','TWI','61.40'),
            ('20230103','RCR','4.6994'),
            ('20230103','RJY','88.48'),
            ('20230103','EUR','0.6400'),
            ('20230103','SKW','867.16'),
            ('20230103','KPS','0.5656'),
            ('20230103','RSD','0.9134'),
            ('20230103','IRE','56.51'),
            ('20230103','RTB','23.47'),
            ('20230103','NZD','1.0760'),
            ('20230103','NTD','20.97'),
            ('20230103','RMR','2.9975'),
            ('20230103','RIR','10635'),
            ('20230103','RVD','16039'),
            ('20230103','AED',NULL),
            ('20230103','NGK',NULL),
            ('20230103','HKD','5.3316'),
            ('20230103','RCD',NULL),
            ('20230103','ARD',NULL),
            ('20230103','RSF','0.6310'),
            ('20230103','PHP','38.07'),
            ('20230103','DR','0.5131'),
            ...
            ('20260213','USD','0.7086'),
            ('20260213','TWI','65.10'),
            ('20260213','RCR','4.8940'),
            ('20260213','RJY','108.59'),
            ('20260213','EUR','0.5973'),
            ('20260213','SKW','1022.76'),
            ('20260213','KPS','0.5206'),
            ('20260213','RSD','0.8952'),
            ('20260213','IRE','64.27'),
            ('20260213','RTB','22.03'),
            ('20260213','NZD','1.1743'),
            ('20260213','NTD','22.29'),
            ('20260213','RMR','2.7628'),
            ('20260213','RIR','11936'),
            ('20260213','RVD','18402'),
            ('20260213','AED',NULL),
            ('20260213','NGK','3.0386'),
            ('20260213','HKD','5.5388'),
            ('20260213','RCD','0.9650'),
            ('20260213','ARD',NULL),
            ('20260213','RSF','0.5456'),
            ('20260213','PHP','41.09'),
            ('20260213','DR','0.5135')
        

If format_as_sql is 'n' a flat CSV file is produced:

        Date,Currency,Rate
        03-Jan-2023,USD,0.6828
        03-Jan-2023,TWI,61.40
        03-Jan-2023,RCR,4.6994
        03-Jan-2023,RJY,88.48
        03-Jan-2023,EUR,0.6400
        03-Jan-2023,SKW,867.16
        03-Jan-2023,KPS,0.5656
        03-Jan-2023,RSD,0.9134
        03-Jan-2023,IRE,56.51
        03-Jan-2023,RTB,23.47
        03-Jan-2023,NZD,1.0760
        03-Jan-2023,NTD,20.97
        03-Jan-2023,RMR,2.9975
        03-Jan-2023,RIR,10635
        03-Jan-2023,RVD,16039
        03-Jan-2023,AED,NULL
        03-Jan-2023,NGK,NULL
        03-Jan-2023,HKD,5.3316
        03-Jan-2023,RCD,NULL
        03-Jan-2023,ARD,NULL
        03-Jan-2023,RSF,0.6310
        03-Jan-2023,PHP,38.07
        03-Jan-2023,DR,0.5131
        ...
        13-Feb-2026,USD,0.7086
        13-Feb-2026,TWI,65.10
        13-Feb-2026,RCR,4.8940
        13-Feb-2026,RJY,108.59
        13-Feb-2026,EUR,0.5973
        13-Feb-2026,SKW,1022.76
        13-Feb-2026,KPS,0.5206
        13-Feb-2026,RSD,0.8952
        13-Feb-2026,IRE,64.27
        13-Feb-2026,RTB,22.03
        13-Feb-2026,NZD,1.1743
        13-Feb-2026,NTD,22.29
        13-Feb-2026,RMR,2.7628
        13-Feb-2026,RIR,11936
        13-Feb-2026,RVD,18402
        13-Feb-2026,AED,NULL
        13-Feb-2026,NGK,3.0386
        13-Feb-2026,HKD,5.5388
        13-Feb-2026,RCD,0.9650
        13-Feb-2026,ARD,NULL
        13-Feb-2026,RSF,0.5456
        13-Feb-2026,PHP,41.09
        13-Feb-2026,DR,0.5135
        

It actually works out a lot easier to use the SQL statements rather than construct an ETL package. Just need to add one line at the top. Though for a more complex ETL project we would create staging tables and use an ETL platform such as SSIS. This will be the subject of my next SQL Blog.

            
  1. insert forex(exchg_date, exchg_currency, exchg_rate)
  2. VALUES
  3. ('20230103','USD','0.6828'),
  4. ('20230103','TWI','61.40'),
  5. ('20230103','RCR','4.6994'),
  6. ('20230103','RJY','88.48'),
  7. ('20230103','EUR','0.6400'),
  8. ('20230103','SKW','867.16'),
  9. ('20230103','KPS','0.5656'),
  10. ('20230103','RSD','0.9134'),
  11. ('20230103','IRE','56.51'),
  12. ('20230103','RTB','23.47'),
  13. ('20230103','NZD','1.0760'),
  14. ('20230103','NTD','20.97'),
  15. ('20230103','RMR','2.9975'),
  16. ('20230103','RIR','10635'),
  17. ('20230103','RVD','16039'),
  18. ('20230103','AED',NULL),
  19. ('20230103','NGK',NULL),
  20. ('20230103','HKD','5.3316'),
  21. ('20230103','RCD',NULL),
  22. ('20230103','ARD',NULL),
  23. ('20230103','RSF','0.6310'),
  24. ('20230103','PHP','38.07'),
  25. ('20230103','DR','0.5131'),
  26. ...

18009 rows affected

Select USD Exchange Rate ordered by date:

SSMS Select USD Result

You can visualise the data from the Forex menu above.