<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Daniel Lifflander</title>
    <description>The latest articles on Forem by Daniel Lifflander (@dliff).</description>
    <link>https://forem.com/dliff</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F558664%2F976c9bf6-d65e-4c11-9472-4e3040647a5a.png</url>
      <title>Forem: Daniel Lifflander</title>
      <link>https://forem.com/dliff</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/dliff"/>
    <language>en</language>
    <item>
      <title>Decoding Django Sessions in PostgreSQL</title>
      <dc:creator>Daniel Lifflander</dc:creator>
      <pubDate>Fri, 19 Mar 2021 17:15:19 +0000</pubDate>
      <link>https://forem.com/arctype/decoding-django-sessions-in-postgresql-2gf3</link>
      <guid>https://forem.com/arctype/decoding-django-sessions-in-postgresql-2gf3</guid>
      <description>&lt;h1&gt;
  
  
  Sessions in Django
&lt;/h1&gt;

&lt;p&gt;Sessions are an important part of any HTTP-based web framework. They allow web servers to keep track of the identities of repeat HTTP clients without requiring them to re-authenticate for each request. There are several different ways to keep track of sessions. Some do not require the server to persist session data (like JSON Web Tokens), while others do. &lt;/p&gt;

&lt;p&gt;Django, a popular Python-based web framework, ships with a default session backend that does store persistent session data. There are several storage and caching options; you can elect to simply store sessions in the SQL database and look them up each time, store them in a cache like Redis or Memcached, or use both, with the caching engine set up in front of the database store. If you use one of the options that ultimately stores sessions in SQL the &lt;code&gt;django_session&lt;/code&gt; table will contain your user's sessions.&lt;/p&gt;

&lt;p&gt;Screenshots in this post come from &lt;a href="https://www.arctype.com/" rel="noopener noreferrer"&gt;Arctype&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Session Schema
&lt;/h1&gt;

&lt;p&gt;When perusing through your application's data, you may come across a problem that requires you to link a user's session data to their actual User entry (the &lt;code&gt;auth_user&lt;/code&gt; table). This happened to me recently and when I took a look at the session table's schema definition, I was surprised that the &lt;code&gt;user_id&lt;/code&gt; is not stored as a column. There are important design decisions as to why this is the case, but it is inconvenient to SQL'ers like myself.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh4.googleusercontent.com%2F_9tFxQCfjHPt8aShpNURt1sFefgQANunpxcO1YGaMYirhXLbYuX3rQlTA5UPoqt4J9P6IblDi-rXQcJtyASFWMQNX97lyXx708RkKukp_s2C35SvxXFk4TLuIVC2tTbz7mtxwuEZ" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh4.googleusercontent.com%2F_9tFxQCfjHPt8aShpNURt1sFefgQANunpxcO1YGaMYirhXLbYuX3rQlTA5UPoqt4J9P6IblDi-rXQcJtyASFWMQNX97lyXx708RkKukp_s2C35SvxXFk4TLuIVC2tTbz7mtxwuEZ" alt="No auth_user_id"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;session_key&lt;/code&gt; is the key that clients are provided. Generally, clients making a request will include the &lt;code&gt;session_key&lt;/code&gt; as part of a cookie. When the web server receives the request, it finds &lt;code&gt;session_key&lt;/code&gt;, if it exists, then queries to see if the key is known. If it is, it will then look at the correlated &lt;code&gt;session_data&lt;/code&gt; and retrieve metadata about the user and their session. &lt;/p&gt;

&lt;p&gt;This is how you are able to access something like &lt;code&gt;request.user&lt;/code&gt; in a Django request. The &lt;code&gt;user_id&lt;/code&gt; is fetched from the decoded &lt;code&gt;session_data&lt;/code&gt;, the built-in User object is populated based on the stored &lt;code&gt;user_id&lt;/code&gt;, and then the User object is available for use throughout the project's views.&lt;/p&gt;

&lt;p&gt;Some quick Googling showed me that by default session data is stored as JSON. I was already aware of Postgres' excellent JSON abilities (&lt;a href="https://www.arctype.com/blog/json-in-postgresql/" rel="noopener noreferrer"&gt;if you are not, check out this blog post&lt;/a&gt;), so I suspected this was something we could work with within the bounds of Postgres. This was great news for someone like myself who spends a lot of time in Postgres.&lt;/p&gt;
&lt;h1&gt;
  
  
  Building the Query
&lt;/h1&gt;
&lt;h2&gt;
  
  
  First Look
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh5.googleusercontent.com%2FJnmZkNiqKarbp71cbKeBq_TnBvxSXEzsN4JuhZlkIoHGFEz4QH_cS5EwuSoTFaoxxjFsf4kHs1DgfFCdEuz929AplEUhuYys21wqBVjZvtnYbmqrGfhjvjqRuXuqRihh8c5lNbxm" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh5.googleusercontent.com%2FJnmZkNiqKarbp71cbKeBq_TnBvxSXEzsN4JuhZlkIoHGFEz4QH_cS5EwuSoTFaoxxjFsf4kHs1DgfFCdEuz929AplEUhuYys21wqBVjZvtnYbmqrGfhjvjqRuXuqRihh8c5lNbxm"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see in the first image, session_data doesn't appear to be JSON. The metadata stored as JSON is hiding behind &lt;a href="https://en.wikipedia.org/wiki/Base64" rel="noopener noreferrer"&gt;base64 encoding&lt;/a&gt;. Fortunately, we can easily base64 decode in Postgres. &lt;/p&gt;
&lt;h2&gt;
  
  
  Decoding from Base64
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh4.googleusercontent.com%2FtsBekjJ8kwZFv_jI9xFJvh1cYGLtANFP1X0zsyfLZuKFBfoDjZKtxw3xhBlPHSXUSFs_FVcEabOtifgXq-5U1JRuMewH-F-ycqr0EohQwsU6RSYwE7T8qTrwUm0RQRpF9nWf0hKV" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh4.googleusercontent.com%2FtsBekjJ8kwZFv_jI9xFJvh1cYGLtANFP1X0zsyfLZuKFBfoDjZKtxw3xhBlPHSXUSFs_FVcEabOtifgXq-5U1JRuMewH-F-ycqr0EohQwsU6RSYwE7T8qTrwUm0RQRpF9nWf0hKV"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is hardly more readable. We need to convert from binary data into text.&lt;/p&gt;
&lt;h2&gt;
  
  
  Encoding to Text
&lt;/h2&gt;

&lt;p&gt;The "encode" function in Postgres allows you to "Encode binary data into a textual representation."&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh3.googleusercontent.com%2FKjSRQdT-q24VzqzUPHrwhAWvikm5Rr8w_IwFcSrqSrW8Gz1zlzyTTtMgmRkd76KBk39i9a48BraOkaN2Csz91lDJQfYQ30ZrOAUqlLkEfPI9CoftXy8ziOQVGOo3JCnOST72ZTE_" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh3.googleusercontent.com%2FKjSRQdT-q24VzqzUPHrwhAWvikm5Rr8w_IwFcSrqSrW8Gz1zlzyTTtMgmRkd76KBk39i9a48BraOkaN2Csz91lDJQfYQ30ZrOAUqlLkEfPI9CoftXy8ziOQVGOo3JCnOST72ZTE_"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, we can finally see something human readable. Here is one of the full decoded results in text format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;11fcbb0d460fd406e83b60ae082991818a1321a4:{"_auth_user_hash":"39308b9542b9305fc038d28a51088905e14246a1","_auth_user_backend":"x.alternate_auth.Backend","_auth_user_id":"52135"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Extracting JSON
&lt;/h2&gt;

&lt;p&gt;What we have here is a JSON blob prefaced by a colon and a hash of sorts. We're only interested in the JSON blob. A quick way to extract just the text past the hash and colon is to find the position of the first colon and extract all characters after it. &lt;/p&gt;

&lt;p&gt;To accomplish this, we can utilize both the &lt;code&gt;RIGHT&lt;/code&gt; function, which returns n characters at the end of a &lt;code&gt;string&lt;/code&gt; and the &lt;code&gt;POSITION&lt;/code&gt; function, which returns the position of a character in a string. &lt;code&gt;POSITION&lt;/code&gt; will only return the position of the first instance of the string for which you are searching.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;RIGHT&lt;/code&gt; function accepts a negative index. A negative index extracts characters from the right side of the string EXCEPT the characters indicated by the negative index.&lt;/p&gt;

&lt;p&gt;To further construct this query, we are going to break it out into 2 parts using CTEs. CTEs are helpful when you have built and selected a nontrivial column and you need to use it more than once. If we continued with only one &lt;code&gt;SELECT&lt;/code&gt;, we'd have to type the &lt;code&gt;encode(decode(session_data, 'base64'), 'escape')&lt;/code&gt; part multiple times. This is messy and if you decide to change how you wanted to parse the encoded data, you'd have to change the function calls in 2 places.&lt;/p&gt;

&lt;p&gt;Here is our updated query which extracts the JSON part.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh5.googleusercontent.com%2FfDSLzbzeaXVf6CBv0RjPXoIjn7Hq9Cf6tH5AMoeKG_-YDkg349lz1MWZMOoZIQsx628ljjy7Kwx5waTYf95m004hSWV2JqfJBvtu29LSj2X4_wgb4rbFrCl2ZOfM1mNZ2pUAmoYA" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh5.googleusercontent.com%2FfDSLzbzeaXVf6CBv0RjPXoIjn7Hq9Cf6tH5AMoeKG_-YDkg349lz1MWZMOoZIQsx628ljjy7Kwx5waTYf95m004hSWV2JqfJBvtu29LSj2X4_wgb4rbFrCl2ZOfM1mNZ2pUAmoYA"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Full result example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{"_auth_user_hash":"396db3c0f4ba3d35b350a","_auth_user_backend":"x.alternate_auth.Backend","_auth_user_id":"52646"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  JSON Validation
&lt;/h2&gt;

&lt;p&gt;Now that the column is parsable as JSON we can continue. However, if you try to cast text to JSON in Postgres when the text is not valid JSON, Postgres will throw an error and stop your query. In my database, some of the sessions were not parseable JSON. Here's a way to quickly make sure the text looks like parseable JSON.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;where
    substring(decoded, position(':' in decoded) + 1, 1) = '{'
    and right(decoded, 1) = '}'`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any string that does not begin and end with curly braces will be filtered out.&lt;/p&gt;

&lt;p&gt;This does not guarantee it will be able to parse, but for my database of several million sessions it did the job. You could write a custom Postgres function to verify JSON parsability, but it would be slower. &lt;/p&gt;

&lt;h2&gt;
  
  
  JSON Casting
&lt;/h2&gt;

&lt;p&gt;With a &lt;code&gt;WHERE&lt;/code&gt; clause to exclude invalid session metadata, it's time to cast our string to Postgres' JSON type and extract the &lt;code&gt;_auth_user_id&lt;/code&gt; key from the JSON. Depending on your Django configuration, this key could be different. Once an object is cast to JSON type, you can query a JSON value by key using the &lt;code&gt;object-&amp;gt;'key'&lt;/code&gt; syntax. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh6.googleusercontent.com%2F2SXKXjcI1ApcgKdflabXhVYbF9lNWaPRjHzwru-wS1q9c-3ac9Me4IBSWadfUR5zffa-MsNDB8-Xr_dvD__E4uj7KdrshxeAIv9-WlY0Ex1xOoQWBn9fSS2G2GFBbmTuz-So8Esi" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh6.googleusercontent.com%2F2SXKXjcI1ApcgKdflabXhVYbF9lNWaPRjHzwru-wS1q9c-3ac9Me4IBSWadfUR5zffa-MsNDB8-Xr_dvD__E4uj7KdrshxeAIv9-WlY0Ex1xOoQWBn9fSS2G2GFBbmTuz-So8Esi"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  String Cleanup
&lt;/h2&gt;

&lt;p&gt;We're getting close! When casting from JSON to &lt;code&gt;text&lt;/code&gt;, Postgres adds double quotes around it. Ultimately we want the user_id field to be an &lt;code&gt;int&lt;/code&gt;, but Postgres will not parse a string that includes double quotes into an &lt;code&gt;int&lt;/code&gt;. Even JavaScript won't allow that!&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;TRIM&lt;/code&gt; function with the &lt;code&gt;BOTH&lt;/code&gt; will strip the specified character from both ends of a string, leaving us with a clean string that can easily be cast into an integer. &lt;/p&gt;

&lt;h2&gt;
  
  
  Final Query
&lt;/h2&gt;

&lt;p&gt;Here is our final query after trimming the excess double quotes and casting to &lt;code&gt;int&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh3.googleusercontent.com%2FS0s1BhO0IdH3e9hXgDXYPTWBavwuIJmpHPJ198CVb92rlsu43L6JZW-yNFRLtl0IGMt3j8tqnSFtYw-I6sdTqfQ_Bql_t0W3jIDqYMxJPkmWGhzvE4P6RqnsOlnaiZbL7wSPjZJV" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh3.googleusercontent.com%2FS0s1BhO0IdH3e9hXgDXYPTWBavwuIJmpHPJ198CVb92rlsu43L6JZW-yNFRLtl0IGMt3j8tqnSFtYw-I6sdTqfQ_Bql_t0W3jIDqYMxJPkmWGhzvE4P6RqnsOlnaiZbL7wSPjZJV"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, as the sample result shows, we have linked &lt;code&gt;session_key&lt;/code&gt; to Django &lt;code&gt;auth_user&lt;/code&gt; id.&lt;/p&gt;

&lt;p&gt;Here is the full query in copyable form:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;with step1 as (
  select
    session_key,
    encode(decode(session_data, 'base64'), 'escape') :: text as decoded
  from
    django_session
)
select
  session_key,
  trim(
    both '"'
    from
      (
        right(
          decoded,
          0 - position(':' in decoded)
        ) :: json -&amp;gt; '_auth_user_id'
      ) :: text
  ) :: int as user_id
from
  step1
where
  substring(decoded, position(':' in decoded) + 1, 1) = '{'
  and right(decoded, 1) = '}'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Using a Materialized View for Quick Querying
&lt;/h1&gt;

&lt;p&gt;If your database has a lot of users you'll notice this query is very slow. Making a materialized view will allow you to repeatedly query the result from a persistent view without re-running the SQL.&lt;/p&gt;

&lt;p&gt;When you create the materialized view (and anytime you refresh it), the source code for the view will be run and it will be populated with rows from the result. Be sure to refresh the view when you need up-to-date data!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;create materialized view mv_django_session_user as
with step1 as (
…
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To refresh:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;refresh materialized view mv_django_session_user;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;Encoding and string manipulation in Postgres is a little more tedious than it would be in common languages used for web applications like Python, Ruby, or PHP, but it is very satisfying to build a view entirely in Postgres that quickly extracts the exact data you want and allows you to directly join to other tables.&lt;/p&gt;

&lt;p&gt;The next time you need to extract data encoded by a web framework or another 3rd-party, check Postgres for the answer!&lt;/p&gt;

&lt;p&gt;Screenshots in this post come from &lt;a href="https://www.arctype.com/" rel="noopener noreferrer"&gt;Arctype&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>django</category>
      <category>webdev</category>
      <category>postgres</category>
      <category>security</category>
    </item>
    <item>
      <title>Trigger Warning! A Complete Guide to SQL Triggers - Setting up DB Tracking in Postgres</title>
      <dc:creator>Daniel Lifflander</dc:creator>
      <pubDate>Tue, 12 Jan 2021 17:00:00 +0000</pubDate>
      <link>https://forem.com/arctype/trigger-warning-a-complete-guide-to-sql-triggers-setting-up-db-tracking-in-postgres-14</link>
      <guid>https://forem.com/arctype/trigger-warning-a-complete-guide-to-sql-triggers-setting-up-db-tracking-in-postgres-14</guid>
      <description>&lt;p&gt;I was 12 years old when I first heard about SQL triggers. My brother, Jonathan, had just begun his software career at a startup. Jonathan came home one day frustrated by a database full of convoluted SQL triggers.&lt;/p&gt;

&lt;p&gt;With my only programming experience being recreating my favorite video game in VB6, I had little consolation to offer.&lt;/p&gt;

&lt;p&gt;Fast forward 16 years, and now I can see from my brother’s perspective. In the world of open source start-up style full-stack development (think Django, Rails, Javascript, PHP, MySQL, Postgres..), ORMs are very popular and features like SQL triggers are far less conventional.&lt;/p&gt;

&lt;p&gt;But there is still value with SQL triggers. During my time working on custom ERP-like software, SQL triggers were an invaluable tool. When building highly data oriented software, especially when the data is of financial nature and accuracy is of high demand, you're more likely to see data being manipulated at a lower level, in a more direct way.&lt;/p&gt;

&lt;p&gt;This article contains all the information I wish I could have shared with my brother on how to effectively use SQL triggers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Table of contents
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;What is a SQL Trigger?&lt;/li&gt;
&lt;li&gt;How to Create a SQL Trigger - Syntax&lt;/li&gt;
&lt;li&gt;Postgres Trigger Example 1: Creating a Time Clock&lt;/li&gt;
&lt;li&gt;Postgres Trigger Example 2: Creating an Audit Table&lt;/li&gt;
&lt;li&gt;Additional Considerations for Triggers&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is a SQL Trigger?
&lt;/h2&gt;

&lt;p&gt;SQL Triggers, also called Database Triggers, allow you to tell your SQL engine (for these examples, Postgres) to run a piece of code when an event happens, or even before the event.&lt;/p&gt;

&lt;p&gt;In Postgres, you delineate the code to run by creating a function whose return type is &lt;code&gt;trigger&lt;/code&gt;. In some other engines like MySQL, the code block is a part of and inside the trigger.&lt;/p&gt;

&lt;p&gt;Before I discuss what the different event types are and the specific syntax for creating a trigger, why would you want to use a database trigger?&lt;/p&gt;

&lt;h3&gt;
  
  
  Advantages of using SQL Triggers
&lt;/h3&gt;

&lt;h5&gt;
  
  
  Maintaining data integrity
&lt;/h5&gt;

&lt;p&gt;Database triggers have a variety of uses and are an excellent tool to marshal strict data integrity. Alternate solutions like Django's model hooks may fail if you have other application servers or users accessing the database who aren't aware of the specific business logic coded in your application.&lt;/p&gt;

&lt;h5&gt;
  
  
  Separating business logic
&lt;/h5&gt;

&lt;p&gt;Coding critical business logic within the application code also presents problems when the business logic is updated. If you had a business requirement to multiply an incoming number by 10, and you wanted to revise this logic to multiply the number by 20, changing the logic in SQL would guarantee that every piece of data from that exact deploy time on would be affected by the new logic.&lt;/p&gt;

&lt;p&gt;The SQL server acts as a single point of truth. If the logic is implemented on multiple application servers, you can no longer expect a clean, definitive change in behavior.&lt;/p&gt;

&lt;h5&gt;
  
  
  Atomic transactions
&lt;/h5&gt;

&lt;p&gt;Natural atomicity is another desirable feature bundled with triggers. Since the event and the trigger function are all part of one atomic transaction, you know with absolute certainty that the trigger will fire if the event fires. They are as one, in perfect SQL matrimony.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Create a SQL Trigger - Postgres Syntax
&lt;/h2&gt;

&lt;p&gt;Here are the components to creating a trigger for your database:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Trigger Event Type&lt;/li&gt;
&lt;li&gt;Before or After the event&lt;/li&gt;
&lt;li&gt;Effect of the Trigger&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--eaiZtk4C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.arctype.com/content/images/2021/01/Screen-Shot-2021-01-10-at-23.30.24.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--eaiZtk4C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.arctype.com/content/images/2021/01/Screen-Shot-2021-01-10-at-23.30.24.png" alt="Trigger Warning! Guide to SQL Triggers - Setting up Database Tracking and Auditing in PostgreSQL"&gt;&lt;/a&gt;SQL Trigger Anatomy&lt;/p&gt;

&lt;h3&gt;
  
  
  Trigger event types
&lt;/h3&gt;

&lt;p&gt;Database triggers will monitor tables for specific events. Here are some examples of different events that can activate a trigger:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Changing data: &lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A database trigger can also list more than one of these events.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;UPDATE&lt;/code&gt; was one of the listed events, you can pass in a list of columns that should activate the trigger. If you don't include this list, updating any column will activate it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trigger &lt;code&gt;BEFORE&lt;/code&gt; or &lt;code&gt;AFTER&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;A trigger can run either &lt;code&gt;BEFORE&lt;/code&gt; or &lt;code&gt;AFTER&lt;/code&gt; an event.&lt;/p&gt;

&lt;p&gt;If you want to block an event like an &lt;code&gt;INSERT&lt;/code&gt;, you will want to run &lt;code&gt;BEFORE&lt;/code&gt;. If you need to be sure the event actually is going to occur, &lt;code&gt;AFTER&lt;/code&gt; is ideal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Effect of the trigger
&lt;/h3&gt;

&lt;p&gt;A trigger can run either per row, or per statement. Let's say you run a single &lt;code&gt;UPDATE&lt;/code&gt; statement that changes 5 rows in a table.&lt;/p&gt;

&lt;p&gt;If you specify &lt;code&gt;FOR EACH ROW&lt;/code&gt; in the trigger, then the trigger will run 5 times. If you specified &lt;code&gt;FOR EACH STATEMENT&lt;/code&gt;, then it would only run once.&lt;/p&gt;

&lt;p&gt;And of course we can't forget the actual code to run when the trigger is activated. In Postgres, is placed in a function and separated from the trigger. Separating the trigger from the code it runs creates cleaner code and allows multiple triggers to execute the same code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Postgres Trigger Example #1: Creating a Time Clock
&lt;/h2&gt;

&lt;p&gt;A time clock records when an employee comes and leaves from work and calculates his/her total hours worked. Let's create an example time clock and look at how we can use triggers to prevent employees from inputting invalid data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up the DB schema
&lt;/h3&gt;

&lt;p&gt;The design of this schema treats each punch in and out as separate events. Each event is a row in the &lt;code&gt;time_punch&lt;/code&gt; table. Alternatively, you could also make each employee" shift" an event and store both the punch in and punch out time in one row.&lt;/p&gt;

&lt;p&gt;In a future post I’ll do a deep dive on how to define a strong database schema. &lt;a href="https://arctype.com"&gt;Sign up for Arctype&lt;/a&gt; and get SQL/database guides and tips sent straight to your inbox! &lt;/p&gt;

&lt;p&gt;For this example I've gone ahead and defined the schema for our tables. The code below creates an &lt;code&gt;employee&lt;/code&gt; and &lt;code&gt;time_punch&lt;/code&gt; table and inserts some time punch data for a new employee, Bear.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;employee&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;serial&lt;/span&gt; &lt;span class="k"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="nb"&gt;varchar&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;time_punch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;serial&lt;/span&gt; &lt;span class="k"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;employee_id&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="k"&gt;references&lt;/span&gt; &lt;span class="n"&gt;employee&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;is_out_punch&lt;/span&gt; &lt;span class="nb"&gt;boolean&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;punch_time&lt;/span&gt; &lt;span class="nb"&gt;timestamp&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;insert&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="n"&gt;employee&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;values&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Bear'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;insert&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="n"&gt;time_punch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;employee_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;is_out_punch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;punch_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;values&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2020-01-01 10:00:00'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2020-01-01 11:30:00'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bear has clocked in at 10:00am and out at 11:30am (long day at work). Let's write a SQL query to calculate how long Bear has worked.&lt;/p&gt;

&lt;p&gt;Take a pause here and think about how you would solve this given our schema and using only SQL.&lt;/p&gt;




&lt;h3&gt;
  
  
  Using SQL to calculate time worked
&lt;/h3&gt;

&lt;p&gt;The solution I decided on looks at each "out" punch and matches it with its preceding "in" punch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;tp1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;punch_time&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;tp2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;punch_time&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;time_worked&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;time_punch&lt;/span&gt; &lt;span class="n"&gt;tp1&lt;/span&gt;
&lt;span class="k"&gt;join&lt;/span&gt; &lt;span class="n"&gt;time_punch&lt;/span&gt; &lt;span class="n"&gt;tp2&lt;/span&gt;
&lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;tp2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;tps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
    &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;time_punch&lt;/span&gt; &lt;span class="n"&gt;tps&lt;/span&gt;
    &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;tps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;tp1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
    &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;tps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;employee_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tp1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;employee_id&lt;/span&gt;
    &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="n"&gt;tps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_out_punch&lt;/span&gt;
    &lt;span class="k"&gt;order&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;tps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;desc&lt;/span&gt; &lt;span class="k"&gt;limit&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;tp1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;employee_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;tp1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_out_punch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;time_worked&lt;/span&gt;
&lt;span class="c1"&gt;-------------&lt;/span&gt;
 &lt;span class="mi"&gt;01&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this query I select all the out punches, then I join them to the closest preceding "in" punch. Subtract the timestamps, and we get how many hours Bear worked for each shift!&lt;/p&gt;

&lt;p&gt;One of the issues with this schema is that it is possible for you to insert several "in" or "out" punches in a row. With the query we’ve created, this would introduce ambiguities that could lead to inaccurate calculations and employees getting paid more or less than they should.&lt;/p&gt;

&lt;h3&gt;
  
  
  SQL &lt;code&gt;INSERT BEFORE&lt;/code&gt; trigger example - preserving data integrity
&lt;/h3&gt;

&lt;p&gt;We need something to prevent the in/out pattern from being interrupted. Unfortunately check constraints only look at the row being inserted or updated and cannot factor in data from other rows.&lt;/p&gt;

&lt;p&gt;This is a perfect situation to use a database trigger!&lt;/p&gt;

&lt;p&gt;Let's create a trigger to prevent an &lt;code&gt;INSERT&lt;/code&gt; event that would break our pattern. First we'll create the "trigger function". This function is what the trigger will execute when the event type is detected.&lt;/p&gt;

&lt;p&gt;A trigger function is created like a regular Postgres function, except that it returns a &lt;code&gt;trigger&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;or&lt;/span&gt; &lt;span class="k"&gt;replace&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;fn_check_time_punch&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="k"&gt;trigger&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;psql&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;
  &lt;span class="k"&gt;begin&lt;/span&gt;
    &lt;span class="n"&gt;if&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_out_punch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;tps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_out_punch&lt;/span&gt;
      &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;time_punch&lt;/span&gt; &lt;span class="n"&gt;tps&lt;/span&gt;
      &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;tps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;employee_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;employee_id&lt;/span&gt;
      &lt;span class="k"&gt;order&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;tps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;desc&lt;/span&gt; &lt;span class="k"&gt;limit&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="n"&gt;if&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;psql&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="k"&gt;language&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;new&lt;/code&gt; keyword represents the values of the row that is to be inserted. It also is the object you can return to allow the insert to continue. Alternatively, when &lt;code&gt;null&lt;/code&gt; is returned this stops the insertion.&lt;/p&gt;

&lt;p&gt;This query finds the &lt;code&gt;time_punch&lt;/code&gt; before and ensures its in/out value is not the same as what's being inserted. If the values are the same, then the trigger returns &lt;code&gt;null&lt;/code&gt; and the time_punch is not recorded. Otherwise, the trigger returns &lt;code&gt;new&lt;/code&gt; and the &lt;code&gt;insert&lt;/code&gt; statement is allowed to continue.&lt;/p&gt;

&lt;p&gt;Now we'll link the function as a trigger to the &lt;code&gt;time_punch&lt;/code&gt; table. &lt;code&gt;BEFORE&lt;/code&gt; is crucial here. If we ran this trigger as an &lt;code&gt;AFTER&lt;/code&gt; trigger it would run too late to stop the insertion.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;trigger&lt;/span&gt; &lt;span class="n"&gt;check_time_punch&lt;/span&gt; &lt;span class="k"&gt;before&lt;/span&gt; &lt;span class="k"&gt;insert&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;time_punch&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;each&lt;/span&gt; &lt;span class="k"&gt;row&lt;/span&gt; &lt;span class="k"&gt;execute&lt;/span&gt; &lt;span class="k"&gt;procedure&lt;/span&gt; &lt;span class="n"&gt;fn_check_time_punch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's try to insert another "out" punch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;insert&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="n"&gt;time_punch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;employee_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;is_out_punch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;punch_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;values&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2020-01-01 13:00:00'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can see from the output that the trigger prevented the insertion of two subsequent out punches for the same employee.&lt;/p&gt;

&lt;p&gt;It is also possible to raise an exception from the trigger so that your application (or person running the SQL query) receives a failure notice instead of the insert count simply being 0.&lt;/p&gt;

&lt;h2&gt;
  
  
  Postgres Trigger Example #2: Creating an Audit Table
&lt;/h2&gt;

&lt;p&gt;Accurately storing employee punch data is critical for businesses. This type of data often ends up directly translating to an employee's salary, and on the other end, a company's payroll cost.&lt;/p&gt;

&lt;p&gt;Because of the importance of this data, let’s say the company wants to be able to recreate all the historical states of the table in the event that an irregularity is discovered.&lt;/p&gt;

&lt;p&gt;An audit table accomplishes this by keeping track of every change to a table. When a row is updated on the main table, a row will be inserted into the audit table recording its past state.&lt;/p&gt;

&lt;p&gt;I will use our &lt;code&gt;time_punch&lt;/code&gt; table to demonstrate how to create an automatically updating audit table using triggers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create the audit table
&lt;/h3&gt;

&lt;p&gt;There are several ways to keep an audit or history table. Let's create a separate table that will store the past states of time_punch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;time_punch_audit&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;serial&lt;/span&gt; &lt;span class="k"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;change_time&lt;/span&gt; &lt;span class="nb"&gt;timestamp&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;change_employee_id&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="k"&gt;references&lt;/span&gt; &lt;span class="n"&gt;employee&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;time_punch_id&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="k"&gt;references&lt;/span&gt; &lt;span class="n"&gt;time_punch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;punch_time&lt;/span&gt; &lt;span class="nb"&gt;timestamp&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This table stores:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Time the punch was updated&lt;/li&gt;
&lt;li&gt;Employee who updated it&lt;/li&gt;
&lt;li&gt;ID of the punch that was changed&lt;/li&gt;
&lt;li&gt;Punch time before the punch was updated&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before we create our trigger, we first need to add a &lt;code&gt;change_employee_id&lt;/code&gt; column to our &lt;code&gt;time_punch&lt;/code&gt; table. This way the trigger will know which employee made every change to the &lt;code&gt;time_punch&lt;/code&gt; table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;alter&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;time_punch&lt;/span&gt; &lt;span class="k"&gt;add&lt;/span&gt; &lt;span class="k"&gt;column&lt;/span&gt; &lt;span class="n"&gt;change_employee_id&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="k"&gt;references&lt;/span&gt; &lt;span class="n"&gt;employee&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(An alternative solution without adding any columns to time_punch could be to revoke update permission on this table, and force users of this database to use a custom function like &lt;code&gt;update_time_punch(id, change_user_id, ...)&lt;/code&gt;)&lt;/p&gt;

&lt;h3&gt;
  
  
  SQL &lt;code&gt;UPDATE AFTER&lt;/code&gt; trigger example - inserting data
&lt;/h3&gt;

&lt;p&gt;After an update happens to our &lt;code&gt;time_punch table&lt;/code&gt;, this trigger runs and stores the &lt;code&gt;OLD&lt;/code&gt; punch time value in our audit table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;or&lt;/span&gt; &lt;span class="k"&gt;replace&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;fn_change_time_punch_audit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="k"&gt;trigger&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;psql&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;
  &lt;span class="k"&gt;begin&lt;/span&gt;
    &lt;span class="k"&gt;insert&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="n"&gt;time_punch_audit&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;change_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;change_employee_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time_punch_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;punch_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;values&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;change_employee_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;old&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;punch_time&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;psql&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="k"&gt;language&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;trigger&lt;/span&gt; &lt;span class="n"&gt;change_time_punch_audit&lt;/span&gt; &lt;span class="k"&gt;after&lt;/span&gt; &lt;span class="k"&gt;update&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;time_punch&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;each&lt;/span&gt; &lt;span class="k"&gt;row&lt;/span&gt; &lt;span class="k"&gt;execute&lt;/span&gt; &lt;span class="k"&gt;procedure&lt;/span&gt; &lt;span class="n"&gt;fn_change_time_punch_audit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;NOW()&lt;/code&gt; function returns the current date and time from the SQL server's perspective. If this were linked up to an actual application, you'd want to consider passing in the exact time the user actually made the request to avoid discrepancies from latency.&lt;/p&gt;

&lt;p&gt;During an update trigger, the &lt;code&gt;NEW&lt;/code&gt; object represents what values the row will contain if the update is successful. You can use a trigger to "intercept" an insert or update by simply assigning your own values to the &lt;code&gt;NEW&lt;/code&gt; object. The &lt;code&gt;OLD&lt;/code&gt; object contains the row's values pre-update.&lt;/p&gt;

&lt;p&gt;Let's see if it works! I have inserted a second user named Daniel who will be the editor of Bear's time punches.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;punch_time&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;time_punch&lt;/span&gt;
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;     &lt;span class="n"&gt;punch_time&lt;/span&gt;      
&lt;span class="c1"&gt;---------------------&lt;/span&gt;
 &lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;01&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;01&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm going to run the query below twice to simulate 2 edits that increase the time by 5 minutes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;update&lt;/span&gt; &lt;span class="n"&gt;time_punch&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="n"&gt;punch_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;punch_time&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="s1"&gt;'5 minute'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;change_employee_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here is the audit table, reflecting the past punch times:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;        &lt;span class="n"&gt;change_time&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;punch_time&lt;/span&gt;      
&lt;span class="c1"&gt;----------------------------+----------+---------------------&lt;/span&gt;
 &lt;span class="mi"&gt;2021&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;01&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;06&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;56&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;44116&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;Daniel&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;01&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;01&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;
 &lt;span class="mi"&gt;2021&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;01&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;06&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;55&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;133855&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;Daniel&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;01&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;01&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Additional Considerations for Triggers
&lt;/h2&gt;

&lt;p&gt;There are a few things to be wary of with database triggers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Maintaining triggers over time&lt;/li&gt;
&lt;li&gt;Connected trigger logic&lt;/li&gt;
&lt;li&gt;Developer expertise&lt;/li&gt;
&lt;/ol&gt;

&lt;h5&gt;
  
  
  Maintaining triggers over time
&lt;/h5&gt;

&lt;p&gt;Business logic in application code is naturally documented as it changes over time by way of &lt;code&gt;git&lt;/code&gt; or another source control system. It's easy for a developer to see some logic in a codebase and do a quick &lt;code&gt;git log&lt;/code&gt; and see a list of changes.&lt;/p&gt;

&lt;p&gt;Managing changes over time with SQL triggers and functions is more complicated, less standardized, and requires more thought and planning.&lt;/p&gt;

&lt;h5&gt;
  
  
  Connected trigger logic
&lt;/h5&gt;

&lt;p&gt;Triggers can also set off other triggers, quickly complicating the results of what could appear to be an innocent &lt;code&gt;INSERT&lt;/code&gt; or &lt;code&gt;UPDATE&lt;/code&gt;. This risk is also inherent in application code with side effects.&lt;/p&gt;

&lt;h5&gt;
  
  
  Developer expertise
&lt;/h5&gt;

&lt;p&gt;Awareness of triggers is also far lower among some developer circles so introducing them increases the investment in training that will be required for new developers to successfully work on your project.&lt;/p&gt;

&lt;p&gt;SQL can initially be a clumsy and frustrating language to learn as many of the patterns you learn to build a query are "inside out" from how you'd extract data in a procedural language.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Triggers to Level Up Your SQL Game
&lt;/h2&gt;

&lt;p&gt;I hope that these examples have helped you develop a better understanding of database triggers. I have had great successes using triggers for data problems where traceability, consistency, and accuracy are paramount.&lt;/p&gt;

&lt;p&gt;Making the decision to introduce triggers to your application is one that should be made after careful thought - and I for one hope you get an opportunity to implement and explore one of SQL's most fun and intriguing features!&lt;/p&gt;

&lt;p&gt;If you're interested in continuing to improve your SQL skills, consider checking out &lt;a href="http://www.arctype.com/"&gt;Arctype&lt;/a&gt;. Arctype is a modern SQL editor designed to simplify the process of working with databases and SQL. Join our growing community and &lt;a href="http://www.arctype.com/downloads"&gt;download Arctype today&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>postgres</category>
      <category>database</category>
    </item>
  </channel>
</rss>
