<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ ETL - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ ETL - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sat, 25 Jul 2026 22:29:25 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/etl/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ SQLAlchemy makes ETL magically easy ]]>
                </title>
                <description>
                    <![CDATA[ By Peter Gleeson One of the key aspects of any data science workflow is the sourcing, cleaning, and storing of raw data in a form that can be used upstream. This process is commonly referred to as “Extract-Transform-Load,” or ETL for short. It is imp... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/sqlalchemy-makes-etl-magically-easy-ab2bd0df928/</link>
                <guid isPermaLink="false">66d460b737bd2215d1e245b6</guid>
                
                    <category>
                        <![CDATA[ analytics ]]>
                    </category>
                
                    <category>
                        <![CDATA[ backend ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Backend Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ data-engineering ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Data Science ]]>
                    </category>
                
                    <category>
                        <![CDATA[ ETL ]]>
                    </category>
                
                    <category>
                        <![CDATA[ General Programming ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ SQL ]]>
                    </category>
                
                    <category>
                        <![CDATA[ sqlalchemy ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tech  ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Fri, 29 Dec 2017 08:37:13 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/1*G7XlxVd4okqhBrn6_WhMaQ.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Peter Gleeson</p>
<p>One of the key aspects of any data science workflow is the sourcing, cleaning, and storing of raw data in a form that can be used upstream. This process is commonly referred to as “Extract-Transform-Load,” or ETL for short.</p>
<p>It is important to design efficient, robust, and reliable ETL processes, or “data pipelines.” An inefficient pipeline will make working with data slow and unproductive. A non-robust pipeline will break easily, leaving gaps.</p>
<p>Worse still, an unreliable data pipeline will silently contaminate your database with false data that may not become apparent until damage has been done.</p>
<p>Although critically important, ETL development can be a slow and cumbersome process at times. Luckily, there are open source solutions that make life much easier.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/IWyl3vAwg96qzltFcC8xa57pHLZkoASbhVhB" alt="Image" width="800" height="618" loading="lazy"></p>
<h4 id="heading-what-is-sqlalchemy">What is SQLAlchemy?</h4>
<p>One such solution is a Python module called SQLAlchemy. It allows data engineers and developers to define schemas, write queries, and manipulate SQL databases entirely through Python.</p>
<p>SQLAlchemy’s Object Relational Mapper (ORM) and Expression Language functionalities iron out some of the idiosyncrasies apparent between different implementations of SQL by allowing you to associate Python classes and constructs with data tables and expressions.</p>
<p>Here, we’ll run through some highlights of SQLAlchemy to discover what it can do and how it can make ETL development a smoother process.</p>
<h4 id="heading-setting-up">Setting up</h4>
<p>You can install SQLAlchemy using the pip package installer.</p>
<pre><code>$ sudo pip install sqlalchemy
</code></pre><p>As for SQL itself, there are many different versions available, including MySQL, Postgres, Oracle, and Microsoft SQL Server. For this article, we’ll be using SQLite.</p>
<p>SQLite is an open-source implementation of SQL that usually comes pre-installed with Linux and Mac OS X. It is also available for Windows. If you don’t have it on your system already, you can follow <a target="_blank" href="https://www.tutorialspoint.com/sqlite/sqlite_installation.htm">these instructions</a> to get up and running.</p>
<p>In a new directory, use the terminal to create a new database:</p>
<pre><code>$ mkdir sqlalchemy-demo &amp;&amp; cd sqlalchemy-demo
$ touch demo.db
</code></pre><h4 id="heading-defining-a-schema">Defining a schema</h4>
<p>A <strong>database schema</strong> defines the structure of a database system, in terms of tables, columns, fields, and the relationships between them. Schemas can be defined in raw SQL, or through the use of SQLAlchemy’s ORM feature.</p>
<p>Below is an example showing how to define a schema of two tables for an imaginary blogging platform. One is a table of users, and the other is a table of posts uploaded.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> sqlalchemy <span class="hljs-keyword">import</span> *
<span class="hljs-keyword">from</span> sqlalchemy.ext.declarative <span class="hljs-keyword">import</span> declarative_base
<span class="hljs-keyword">from</span> sqlalchemy.orm <span class="hljs-keyword">import</span> sessionmaker
<span class="hljs-keyword">from</span> sqlalchemy.sql <span class="hljs-keyword">import</span> *

engine = create_engine(<span class="hljs-string">'sqlite:///demo.db'</span>)
Base = declarative_base()

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Users</span>(<span class="hljs-params">Base</span>):</span>
    __tablename__ = <span class="hljs-string">"users"</span>
    UserId = Column(Integer, primary_key=<span class="hljs-literal">True</span>)
    Title = Column(String)
    FirstName = Column(String)
    LastName = Column(String)
    Email = Column(String)
    Username = Column(String)
    DOB = Column(DateTime)

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Uploads</span>(<span class="hljs-params">Base</span>):</span>
    __tablename__ = <span class="hljs-string">"uploads"</span>
    UploadId = Column(Integer, primary_key=<span class="hljs-literal">True</span>)
    UserId = Column(Integer)
    Title = Column(String)
    Body = Column(String)
    Timestamp = Column(DateTime)

Users.__table__.create(bind=engine, checkfirst=<span class="hljs-literal">True</span>)
Uploads.__table__.create(bind=engine, checkfirst=<span class="hljs-literal">True</span>)
</code></pre>
<p>First, import everything you need from SQLAlchemy. Then, use <code>create_engine(connection_string)</code> to connect to your database. The exact connection string will depend on the version of SQL you are working with. This example uses a relative path to the SQLite database created earlier.</p>
<p>Next, start defining your table classes. The first one in the example is <code>Users</code>. Each column in this table is defined as a class variable using SQLAlchemy’s <code>Column(type)</code>, where <code>type</code> is a data type (such as <code>Integer</code>, <code>String</code>, <code>DateTime</code> and so on). Use <code>primary_key=True</code> to denote columns which will be used as primary keys.</p>
<p>The next table defined here is <code>Uploads</code>. It’s very much the same idea — each column is defined as before.</p>
<p>The final two lines actually create the tables. The <code>checkfirst=True</code> parameter ensures that new tables are only created if they do not currently exist in the database.</p>
<h4 id="heading-extract">Extract</h4>
<p>Once the schema has been defined, the next task is to <strong>extract</strong> the raw data from its source. The exact details can vary wildly from case to case, depending on how the raw data is provided. Maybe your app calls an in-house or third-party API, or perhaps you need to read data logged in a CSV file.</p>
<p>The example below uses two APIs to simulate data for the fictional blogging platform described above. The <code>Users</code> table will be populated with profiles randomly generated at <a target="_blank" href="https://randomuser.me/">randomuser.me</a>, and the <code>Uploads</code> table will contain lorem ipsum-inspired data courtesy of <a target="_blank" href="http://jsonplaceholder.typicode.com/">JSONPlaceholder</a>.</p>
<p>Python’s <code>Requests</code> module can be used to call these APIs, as shown below:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> requests

url = <span class="hljs-string">'https://randomuser.me/api/?results=10'</span>
users_json = requests.get(url).json()
url2 = <span class="hljs-string">'https://jsonplaceholder.typicode.com/posts/'</span>
uploads_json = requests.get(url2).json()
</code></pre>
<p>The data is currently held in two objects (<code>users_json</code> and <code>uploads_json</code>) in JSON format. The next step will be to transform and load this data into the tables defined earlier.</p>
<h4 id="heading-transform">Transform</h4>
<p>Before the data can be loaded into the database, it is important to ensure that it is in the correct format. The JSON objects created in the code above are nested, and contain more data than is required for the tables defined.</p>
<p>An important intermediary step is to <strong>transform</strong> the data from its current nested JSON format to a flat format that can be safely written to the database without error.</p>
<p>For the example running through this article, the data are relatively simple, and won’t need much transformation. The code below creates two lists, <code>users</code> and <code>uploads</code>, which will be used in the final step:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> datetime <span class="hljs-keyword">import</span> datetime, timedelta
<span class="hljs-keyword">from</span> random <span class="hljs-keyword">import</span> randint

users, uploads = [], []

<span class="hljs-keyword">for</span> i, result <span class="hljs-keyword">in</span> enumerate(users_json[<span class="hljs-string">'results'</span>]):
    row = {}
    row[<span class="hljs-string">'UserId'</span>] = i
    row[<span class="hljs-string">'Title'</span>] = result[<span class="hljs-string">'name'</span>][<span class="hljs-string">'title'</span>]
    row[<span class="hljs-string">'FirstName'</span>] = result[<span class="hljs-string">'name'</span>][<span class="hljs-string">'first'</span>]
    row[<span class="hljs-string">'LastName'</span>] = result[<span class="hljs-string">'name'</span>][<span class="hljs-string">'last'</span>]
    row[<span class="hljs-string">'Email'</span>] = result[<span class="hljs-string">'email'</span>]
    row[<span class="hljs-string">'Username'</span>] = result[<span class="hljs-string">'login'</span>][<span class="hljs-string">'username'</span>]
    dob = datetime.strptime(result[<span class="hljs-string">'dob'</span>],<span class="hljs-string">'%Y-%m-%d %H:%M:%S'</span>)    
    row[<span class="hljs-string">'DOB'</span>] = dob.date()

    users.append(row)

<span class="hljs-keyword">for</span> result <span class="hljs-keyword">in</span> uploads_json:
    row = {}
    row[<span class="hljs-string">'UploadId'</span>] = result[<span class="hljs-string">'id'</span>]
    row[<span class="hljs-string">'UserId'</span>] = result[<span class="hljs-string">'userId'</span>]
    row[<span class="hljs-string">'Title'</span>] = result[<span class="hljs-string">'title'</span>]
    row[<span class="hljs-string">'Body'</span>] = result[<span class="hljs-string">'body'</span>]
    delta = timedelta(seconds=randint(<span class="hljs-number">1</span>,<span class="hljs-number">86400</span>))
    row[<span class="hljs-string">'Timestamp'</span>] = datetime.now() - delta

    uploads.append(row)
</code></pre>
<p>The main step here is to iterate through the JSON objects created before. For each result, create a new Python dictionary object with keys corresponding to each column defined for the relevant table in the schema. This ensures that the data is no longer nested, and keeps only the data needed for the tables.</p>
<p>The other step is to use Python’s <code>datetime</code> module to manipulate dates, and transform them into <code>DateTime</code> type objects that can be written to the database. For the sake of this example, random <code>DateTime</code> objects are generated using the <code>timedelta()</code> method from Python’s DateTime module.</p>
<p>Each created dictionary is appended to a list, which will be used in the final step of the pipeline.</p>
<h4 id="heading-load">Load</h4>
<p>Finally, the data is in a form that can be <strong>loaded</strong> into the database. SQLAlchemy makes this step straightforward through its Session API.</p>
<p>The Session API acts a bit like a middleman, or “holding zone,” for Python objects you have either loaded from or associated with the database. These objects can be manipulated within the session before being committed to the database.</p>
<p>The code below creates a new session object, adds rows to it, then merges and commits them to the database:</p>
<pre><code class="lang-python">Session = sessionmaker(bind=engine)
session = Session()

<span class="hljs-keyword">for</span> user <span class="hljs-keyword">in</span> users:
    row = Users(**user)
    session.add(row)

<span class="hljs-keyword">for</span> upload <span class="hljs-keyword">in</span> uploads:
    row = Uploads(**upload)
    session.add(row)

session.commit()
</code></pre>
<p>The <code>sessionmaker</code> factory is used to generate newly-configured <code>Session</code> classes. <code>Session</code> is an everyday Python class that is instantiated on the second line as <code>session</code>.</p>
<p>Next up are two loops which iterate through the <code>users</code> and <code>uploads</code> lists created earlier. The elements of these lists are dictionary objects whose keys correspond to the columns given in the <code>Users</code> and <code>Uploads</code> classes defined previously.</p>
<p>Each object is used to instantiate a new instance of the relevant class (using Python’s handy <code>some_function(**some_dict)</code> trick). This object is added to the current session with <code>session.add()</code>.</p>
<p>Finally, when the session contains the rows to be added, <code>session.commit()</code> is used to commit the transaction to the database.</p>
<h4 id="heading-aggregating">Aggregating</h4>
<p>Another cool feature of SQLAlchemy is the ability to use its Expression Language system to write and execute backend-agnostic SQL queries.</p>
<p>What are the advantages of writing backend-agnostic queries? For a start, they make any future migration projects a whole lot easier. Different versions of SQL have somewhat incompatible syntaxes, but SQLAlchemy’s Expression Language acts as a lingua franca between them.</p>
<p>Also, being able to query and interact with your database in a seamlessly Pythonic way is a real advantage to developers who’d prefer work entirely in the language they know best. However, SQLAlchemy will also let you work in plain SQL, for cases when it is simpler to use a pre-written query.</p>
<p>Here, we will extend the fictional blogging platform example to illustrate how this works. Once the basic Users and Uploads tables have been created and populated, a next step might be to create an <strong>aggregated</strong> table — for instance, showing how many articles each user has posted, and the time they were last active.</p>
<p>First, define a class for the aggregated table:</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UploadCounts</span>(<span class="hljs-params">Base</span>):</span>
    __tablename__ = <span class="hljs-string">"upload_counts"</span>
    UserId = Column(Integer, primary_key=<span class="hljs-literal">True</span>)
    LastActive = Column(DateTime)
    PostCount = Column(Integer)

UploadCounts.__table__.create(bind=engine, checkfirst=<span class="hljs-literal">True</span>)
</code></pre>
<p>This table will have three columns. For each <code>UserId</code>, it will store the timestamp of when they were last active, and a count of how many posts they have uploaded.</p>
<p>In plain SQL, this table would be populated using a query along the lines of:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> upload_counts
<span class="hljs-keyword">SELECT</span>
  UserId,
  <span class="hljs-keyword">MAX</span>(<span class="hljs-built_in">Timestamp</span>) <span class="hljs-keyword">AS</span> LastActive,
  <span class="hljs-keyword">COUNT</span>(UploadId) <span class="hljs-keyword">AS</span> PostCount
<span class="hljs-keyword">FROM</span>
  uploads
<span class="hljs-keyword">GROUP</span> <span class="hljs-keyword">BY</span> <span class="hljs-number">1</span>;
</code></pre>
<p>In SQLAlchemy, this would be written as:</p>
<pre><code class="lang-python">connection = engine.connect()

query = select([Uploads.UserId,
    func.max(Uploads.Timestamp).label(<span class="hljs-string">'LastActive'</span>),
    func.count(Uploads.UploadId).label(<span class="hljs-string">'PostCount'</span>)]).\ 
    group_by(<span class="hljs-string">'UserId'</span>)

results = connection.execute(query)

<span class="hljs-keyword">for</span> result <span class="hljs-keyword">in</span> results:
    row = UploadCounts(**result)
    session.add(row)

session.commit()
</code></pre>
<p>The first line creates a <code>Connection</code> object using the <code>engine</code> object’s <code>connect()</code> method. Next, a query is defined using the <code>select()</code> function.</p>
<p>This query is the same as the plain SQL version given above. It selects the <code>UserId</code> column from the <code>uploads</code> table. It also applies <code>func.max()</code> to the <code>Timestamp</code> column, which identifies the most recent timestamp. This is labelled <code>LastActive</code> using the <code>label()</code> method.</p>
<p>Likewise, the query applies <code>func.count()</code> to count the number of records that appear in the <code>Title</code> column. This is labelled <code>PostCount</code>.</p>
<p>Finally, the query uses <code>group_by()</code> to group results by <code>UserId</code>.</p>
<p>To use the results of the query, a for loop iterates over the row objects returned by <code>connection.execute(query)</code>. Each row is used to instantiate an instance of the <code>UploadCounts</code> table class. As before, each row is added to the <code>session</code> object, and finally the session is committed to the database.</p>
<h4 id="heading-checking-out">Checking out</h4>
<p>Once you have run this script, you may want to convince yourself that the data have been written correctly into the <code>demo.db</code> database created earlier.</p>
<p>After quitting Python, open the database in SQLite:</p>
<pre><code>$ sqlite3 demo.db
</code></pre><p>Now, you should be able to run the following queries:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> <span class="hljs-keyword">users</span>;

<span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> uploads;

<span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> upload_counts;
</code></pre>
<p>And the contents of each table will be printed to the console! By scheduling the Python script to run at regular intervals, you can be sure the database will be kept up-to-date.</p>
<p>You could now use these tables to write queries for further analysis, or to build dashboards for visualisation purposes.</p>
<h4 id="heading-reading-further">Reading further</h4>
<p>If you’ve made it this far, then hopefully you’ll have learned a thing or two about how SQLAlchemy can make ETL development in Python much more straightforward!</p>
<p>It is not possible for a single article to do full justice to all the features of SQLAlchemy. However, one of the project’s key advantages is the depth and detail of its documentation. You can dive into it <a target="_blank" href="http://docs.sqlalchemy.org/en/latest/">here</a>.</p>
<p>Otherwise, check out <a target="_blank" href="https://github.com/crazyguitar/pysheeet/blob/master/docs/notes/python-sqlalchemy.rst">this cheatsheet</a> if you want to get started quickly.</p>
<p>The full code for this article can be found in <a target="_blank" href="https://gist.github.com/anonymous/a2fc91fdb87dbfaee365f6707e94c442">this gist</a>.</p>
<p>Thanks for reading! If you have any questions or comments, please leave a response below.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
