{"id":112449,"date":"2026-10-14T12:00:00","date_gmt":"2026-10-14T12:00:00","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=112449"},"modified":"2026-09-29T15:12:11","modified_gmt":"2026-09-29T15:12:11","slug":"how-to-create-roles-databases-and-tables-in-postgresql-getting-started-with-postgresql-part-two","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/postgresql\/how-to-create-roles-databases-and-tables-in-postgresql-getting-started-with-postgresql-part-two\/","title":{"rendered":"How to create roles, databases and tables in PostgreSQL (getting started with PostgreSQL, part two)"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\"><strong>Setting up PostgreSQL the right way starts long before your first table exists. In this guide, you&#8217;ll learn how to create a dedicated role instead of relying on the <code>postgres<\/code> superuser, spin up a database with proper ownership, organize it with schemas, and build out your first tables with primary and foreign keys. <\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Every step includes the exact <code>psql<\/code> commands and the real terminal output you should expect to see, so you can follow along in your own container and know immediately if something&#8217;s off.<\/strong><\/p>\n\n\n\n<div id=\"callout-block_c720401a15b335798b07925ea00aa4ff\" class=\"callout alignnone\">\n    <div class=\"child-last:mb-0 child-first:mt-0 bg-gray-50 dark:bg-gray-950 p-4xl my-3xl\">\n\n<p class=\"wp-block-paragraph\"><strong>Important note before you continue&#8230;<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is the second part in Grant Fritchey&#8217;s complete set of step-by-step walkthroughs (guides) for getting started with PostgreSQL, designed for any IT professional or hobbyist who wants to add PostgreSQL to their existing skillset. <a href=\"https:\/\/www.red-gate.com\/simple-talk\/collections\/how-to-get-started-with-postgresql-a-complete-set-of-hands-on-classes\/\" target=\"_blank\" rel=\"noreferrer noopener\">Click here for the full series<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Originally intended to be proctored classes given in a live, in-person environment, Grant has aimed to make the documentation and scripts clear enough to walk through them independently from home.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Before getting started, please check that you have <a href=\"https:\/\/www.red-gate.com\/simple-talk\/collections\/how-to-get-started-with-postgresql-a-complete-set-of-hands-on-classes\/#:~:text=What%20do%20I%20need%20to%20follow%20this%20walkthrough%3F\" target=\"_blank\" rel=\"noreferrer noopener\">everything you need.<\/a><\/p>\n\n<\/div>\n<\/div> \n\n\n<h2 class=\"wp-block-heading\" id=\"h-how-to-create-a-role-in-postgresql\">How to create a role in PostgreSQL<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>One of the more important aspects of PostgreSQL to understand is <em>ownership<\/em>. The ownership of any object &#8211; whether a schema inside a database, or the database <em>itself<\/em> &#8211; is fundamental to PostgreSQL <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/postgresql\/securing-postgresql-against-injection-misconfiguration-and-privilege-escalation\/\" target=\"_blank\" rel=\"noreferrer noopener\">security<\/a> and behaviors. <\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">With that in mind, instead of first creating a database to work on within PostgreSQL, we&#8217;ll start by creating a <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/postgresql\/postgresql-basics-roles-and-privileges\/\" target=\"_blank\" rel=\"noreferrer noopener\">role<\/a>. With the role created, we can then log in to that role and validate that it works. Once it&#8217;s working, we can move on to creating a database to work on within PostgreSQL.<\/p>\n\n\n\n<div id=\"callout-block_c720401a15b335798b07925ea00aa4ff\" class=\"callout alignnone\">\n    <div class=\"child-last:mb-0 child-first:mt-0 bg-gray-50 dark:bg-gray-950 p-4xl my-3xl\">\n\n<p class=\"wp-block-paragraph\"><strong>Important note<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Older versions of PostgreSQL have <em>users<\/em> instead of <em>roles<\/em>. That&#8217;s why the&nbsp;<code>CREATE USER<\/code>&nbsp;command is still a part of PostgreSQL, but it simply now acts as an alias to create a role.<\/p>\n\n<\/div>\n<\/div> \n\n\n<h2 class=\"wp-block-heading\" id=\"h-how-to-run-a-script-in-postgresql-with-psql\">How to run a script in PostgreSQL with psql<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/github.com\/ScaryDBA\/GettingStartedWithPostgreSQL\/blob\/main\/05.%20Creating%20a%20Role\/creatingrole.md#running-a-script-with-psql\"><\/a>Instead of typing in <code>psql<\/code> or copying and pasting into it, we&#8217;ll let <code>psql<\/code> run a script for us this time. Externally, in your operating system, we&#8217;re going to copy the file&nbsp;<code>creatingrole.sql<\/code>&nbsp;into the volume we created with the container. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In Linux, you can use the Files GUI (graphical user interface), <em>or<\/em> issue a command like<br><code>cp \"05. Creating a Role\/creatingrole.sql\" \/home\/user\/bu<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you&#8217;re in Windows and <em>not<\/em> running through the <a href=\"https:\/\/learn.microsoft.com\/en-us\/windows\/wsl\/install\" target=\"_blank\" rel=\"noreferrer noopener\">Windows Subsystem for Linux (WSL)<\/a>, use either File Explorer <em>or<\/em> a command similar to <code>Copy-Item \"05. Creating a Role\\creatingrole.sql\" C:\\Users\\grant\\bu<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Of course, you&#8217;ll have to modify these commands for the paths on your own system. What this does is place the file into the local folder visible inside the container as a <em>volume<\/em>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Do note that the container must be running. You&#8217;ll want to open a <a href=\"https:\/\/en.wikipedia.org\/wiki\/Bash_(Unix_shell)\" target=\"_blank\" rel=\"noreferrer noopener\">Bash shell<\/a> to the container if you&#8217;ve closed it <mark>(see earlier in the guide INSERT LINK)<\/mark>. Once connected to Bash in the container, you&#8217;ll see something like <code>root@543bcc6b5270:\/#<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We can then validate that the file exists by running the <code>ls \/var\/lib\/postgresql\/bu<\/code> command within the Bash shell, adjusting the path as appropriate to your system. The output will look something like this:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">root@bc83314d37ff:\/# ls \/var\/lib\/postgresql\/bu\ncreatingrole.sql<\/pre><\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-how-to-execute-a-script-in-postgresql-with-psql\">How to execute a script in PostgreSQL with <code>psql<\/code><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Now that we know the script is available, we can execute it through <code>psql<\/code> using the command<br><code>psql -d postgres -h localhost -U postgres -f \"\/var\/lib\/postgresql\/bu\/creatingrole.sql\"<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This should run successfully to create a role called&nbsp;<code>radiodev<\/code>&nbsp;with a hard-coded password&nbsp;(<code>*securepassword*<\/code>). While still in Bash, you could also run a simple query to see if the role actually exists, and if it has the permission to create a database. The command we issued through the script is:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">CREATE ROLE radiodev WITH\n    LOGIN\n    CREATEDB\n    PASSWORD '*securepassword*';<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Now, let&#8217;s break this down a little. The first part should be straightforward enough: <code>CREATE ROLE radiodev<\/code>&nbsp;means we&#8217;re creating a role called &#8216;<code>radiodev<\/code>&#8216;. The&nbsp;<code>WITH<\/code>&nbsp;command then allows us to add some additional behaviors to the role. We&#8217;re also creating this role as one that can connect to the cluster through the&nbsp;<code>LOGIN<\/code>&nbsp;process. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Then, we&#8217;re giving it an <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/postgresql\/postgresql-basics-roles-and-privileges\/\" target=\"_blank\" rel=\"noreferrer noopener\">explicit permission<\/a> &#8211; the ability to create databases&nbsp;(<code>CREATEDB<\/code>) &#8211; and we&#8217;re supplying a password for the role. We won&#8217;t need the password in this guide, since we&#8217;ll be doing the work through <code>psql<\/code> running as a local connection (therefore, a trusted one.) <\/p>\n\n\n\n<div id=\"callout-block_c720401a15b335798b07925ea00aa4ff\" class=\"callout alignnone\">\n    <div class=\"child-last:mb-0 child-first:mt-0 bg-gray-50 dark:bg-gray-950 p-4xl my-3xl\">\n\n<p class=\"wp-block-paragraph\"><strong>Important note<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">There are, of course, additional options available for <code>CREATE ROLE<\/code>. Read more on Simple Talk <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/postgresql\/postgresql-basics-roles-and-privileges\/\" target=\"_blank\" rel=\"noreferrer noopener\">here<\/a>, or <a href=\"https:\/\/www.postgresql.org\/docs\/current\/sql-createrole.html\" target=\"_blank\" rel=\"noreferrer noopener\">take a look in the PostgreSQL documentation<\/a>.<\/p>\n\n<\/div>\n<\/div> \n\n\n<h2 class=\"wp-block-heading\" id=\"h-how-to-validate-the-creation-of-a-role-in-postgresql\">How to validate the creation of a role in PostgreSQL<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">I&#8217;ll need to leave Bash to operate within my OS. I&#8217;ll then use the appropriate command to copy the file into the volume folder, before heading back into Bash to execute the query:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">## leave bash\nexit\n\n## copy the file\ncp \"5. Creating a Role\/creatingrolevalidation.sql\" \/home\/user\/bu\n\n## go back into bash\ndocker exec -it StartingPostgreSQL \"bash\"\n\n## run the query\npsql -d postgres -h localhost -U postgres -f \"\/var\/lib\/postgresql\/bu\/creatingrolevalidation.sql\"<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">The results should come back like this:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \"> rolname  | rolcanlogin | rolcreaterole | rolcreatedb \n----------+-------------+---------------+-------------\n radiodev | t           | f             | t<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Going forward, we&#8217;ll copy any (or all) files at the same time, rather than going in and out of Bash to do it. Of course, you can use other mechanisms as well. Later in the series <mark>(INSERT LINK HERE)<\/mark>, I&#8217;ll introduce additional tools for querying PostgreSQL on top of <code>psql<\/code> that can make these operations easier.<\/p>\n\n\n\n<section id=\"my-first-block-block_5cb3f3f44029fa375ada0e16ba75f8b5\" class=\"my-first-block alignwide\">\n    <div class=\"bg-brand-600 text-base-white py-5xl px-4xl rounded-sm bg-gradient-to-r from-brand-600 to-brand-500 red\">\n        <div class=\"gap-4xl items-start md:items-center flex flex-col md:flex-row justify-between\">\n            <div class=\"flex-1 col-span-10 lg:col-span-7\">\n                <h3 class=\"mt-0 font-display mb-2 text-display-sm\">Get started with PostgreSQL &#8211; free book download<\/h3>\n                <div class=\"child:last-of-type:mb-0\">\n                                            &#8216;Introduction to PostgreSQL for the data professional&#8217;, written by Grant Fritchey and Ryan Booz, covers all the basics of how to get started with PostgreSQL.                                    <\/div>\n            <\/div>\n                                            <a href=\"https:\/\/www.red-gate.com\/hub\/books\/introduction-to-postgresql-for-the-data-professional\/\" class=\"btn btn--secondary btn--lg\" aria-label=\"Download your free copy: Get started with PostgreSQL - free book download\">Download your free copy<\/a>\n                    <\/div>\n    <\/div>\n<\/section>\n\n\n<h2 class=\"wp-block-heading\" id=\"h-how-to-test-a-role-in-postgresql\">How to test a role in PostgreSQL<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">We can now use the new role with our PostgreSQL database. To see it in action, we can use the<br><code>psql -d postgres -h localhost -U radiodev -c \"\\conninfo\"<\/code> script.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A couple of points here. In our initial use of <code>psql<\/code>, we typed in commands directly. Then, in the beginning of this part of the guide, we executed the scripts from within files. Now, we&#8217;re simply passing a command through the&nbsp;<code>-c<\/code>&nbsp;flag. Putting SQL (or, in this case, a meta command) inside double quotes will allow the command to be executed. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The output from my machine is:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">      Connection Information\n      Parameter       |   Value   \n----------------------+-----------\n Database             | postgres\n Client User          | radiodev\n Host                 | localhost\n Host Address         | ::1\n Server Port          | 5432\n Options              | \n Protocol Version     | 3.0\n Password Used        | false\n GSSAPI Authenticated | false\n Backend PID          | 441\n SSL Connection       | false\n Superuser            | off\n Hot Standby          | off<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Importantly here, <em>&#8216;Superuser&#8217;<\/em> is set to the value <em>&#8216;off&#8217;<\/em>. Put simply, this is a different role on the cluster &#8211; not the principal administrator (<code>postgres<\/code>).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We&#8217;re now ready to create our first database in PostgreSQL!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-how-to-create-a-database-in-postgresql\">How to create a database in PostgreSQL<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Now that we have a role with the permission to create databases in PostgreSQL, we could immediately create one <em>as<\/em> that role. However, first, I want to show some other ways of creating a database in PostgreSQL.<\/p>\n\n\n\n<div id=\"callout-block_c720401a15b335798b07925ea00aa4ff\" class=\"callout alignnone\">\n    <div class=\"child-last:mb-0 child-first:mt-0 bg-gray-50 dark:bg-gray-950 p-4xl my-3xl\">\n\n<p class=\"wp-block-paragraph\"><strong>You may also be interested in:<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/postgresql\/creating-a-database-and-tables-in-postgresql-learning-postgresql-with-grant\/\" target=\"_blank\" rel=\"noreferrer noopener\">Creating a Database and Tables in PostgreSQL: Learning PostgreSQL with Grant<\/a><\/p>\n\n<\/div>\n<\/div> \n\n\n<h3 class=\"wp-block-heading\" id=\"h-how-to-create-a-database-in-postgresql-as-a-superuser-and-should-you\">How to create a database in PostgreSQL as a superuser (and should you?)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">If we were to issue a&nbsp;<code>CREATE DATABASE<\/code>&nbsp;command like this, that database would be owned by&nbsp;<code>postgres<\/code> (the <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/postgresql\/postgresql-basics-roles-and-privileges\/#:~:text=ownership%20and%20privileges.-,Superusers,-One%20last%20thing\" target=\"_blank\" rel=\"noreferrer noopener\">superuser<\/a>): <code>psql -d postgres -h localhost -U postgres -c \"CREATE DATABASE my_first_db;\"<\/code>. Not only would this create a database owned by&nbsp;<code>postgres<\/code> but, since we gave it no other option, it&#8217;s just using the defaults. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>PostgreSQL has two template databases: <code>template0<\/code> and <code>template1<\/code>. <code>Template0<\/code> is for the system and should be left alone, while <code>Template1<\/code> is the default template for creating databases.<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Since this is a brand new cluster with no other databases or templates created, this method is perfectly OK for our purposes. You <em>can<\/em> create custom templates or even use other databases as a template when creating a new database, but that&#8217;s beyond the (simple) scope of what we&#8217;re doing here.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-why-you-shouldn-t-create-a-database-in-postgresql-as-a-superuser\">Why you <em>shouldn&#8217;t<\/em> create a database in PostgreSQL as a superuser<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>On the other hand, there are a number of reasons we <em>don&#8217;t<\/em> want to use the superuser (<code>postgres<\/code>) to create databases in PostgreSQL. First, and most importantly, it violates the <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/postgresql\/postgresql-basics-roles-and-privileges\/#principle-of-least-privilege:~:text=can%20help%20you-,Principle%20of%20Least%20Privilege,-One%20other%20key\" target=\"_blank\" rel=\"noreferrer noopener\">principal of least privilege<\/a>.<\/strong> <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is because the superuser bypasses a lot of security constraints, so we&#8217;re potentially exposing things in the system. Remember: ownership and permissions in PostgreSQL cascade from the role that created the object, and even a database in PostgreSQL is an object! <\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>You want a clear, easily-to-audit ownership chain, which is why it&#8217;s best to start with defining ownership through roles <em>other<\/em> than the superuser.<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We can look at the owner of a database using a simple query:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">psql -d postgres -h localhost -U postgres -c \"SELECT\n\tpd.datname,\n\tpd.datdba,\n\tpg_catalog.pg_get_userbyid(pd.datdba) AS db_owner\nFROM\n\tpg_catalog.pg_database AS pd\nWHERE\n\tpd.datname = 'my_first_db';\"<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">We don&#8217;t have to use the login to create the database. It&#8217;s perfectly possible to assign ownership on creation with<br><code>psql -d postgres -h localhost -U postgres -c \"CREATE DATABASE my_second_db OWNER radiodev;\"<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We then see the following results:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">   datname   | datdba | db_owner \n-------------+--------+----------\n my_first_db |     10 | postgres<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Altering the script as follows:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">psql -d postgres -h localhost -U postgres -c \"SELECT\n\tpd.datname,\n\tpd.datdba,\n\tpg_catalog.pg_get_userbyid(pd.datdba) AS db_owner\nFROM\n\tpg_catalog.pg_database AS pd\nWHERE\n\tpd.datname = 'my_second_db';\"<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">We then get the following results:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">   datname    | datdba | db_owner \n--------------+--------+----------\n my_second_db |  16388 | radiodev\n<\/pre><\/div>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-how-to-create-a-database-in-postgresql-the-simple-way\">How to create a database in PostgreSQL (the simple way)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">For the rest of the guide, we&#8217;ll be using a database called <code>ham_shack<\/code>. And, since we&#8217;re going to let the database owner <em>also<\/em> own the other objects we create within&nbsp;<code>ham_shack<\/code>,&nbsp;we now need to actually create the database. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To do this, we can just let the defaults handle things for us:<br><code>psql -d postgres -h localhost -U radiodev -c \"CREATE DATABASE ham_shack;\"<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We can do it like this because, other than the owner, there are no database options we need to worry about right now.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">With the database created, let&#8217;s connect to it through interactive <code>psql<\/code> instead of issuing commands all the time: <br><code>psql -d ham_shack -h localhost -U radiodev<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Since we&#8217;re connected to a database <em>other<\/em> than <code>postgres<\/code>, the resulting connection will look different than what we&#8217;ve seen in other parts of the guide: <code>ham_shack=&gt;<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s also issue a few commands and queries to satisfy ourselves that we are indeed in a new database. First, we could run the tried and tested <code>\\conninfo<\/code> meta command, giving us:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">      Connection Information\n      Parameter       |   Value   \n----------------------+-----------\n Database             | ham_shack\n Client User          | radiodev\n Host                 | localhost\n Host Address         | ::1\n Server Port          | 5432\n Options              | \n Protocol Version     | 3.0\n Password Used        | false\n GSSAPI Authenticated | false\n Backend PID          | 937\n SSL Connection       | false\n Superuser            | off\n Hot Standby          | off<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Another way would be to query using a built-in function:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">SELECT\n\tpd.datname,\n\tpg_catalog.pg_get_userbyid(pd.datdba) AS db_owner\nFROM\n\tpg_catalog.pg_database AS pd\nWHERE\n\tdatname = current_database();<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">You can simply copy and paste this command into <code>psql<\/code>. Since nothing is executed until the statement terminator is reached &#8211; the semi-colon (;) &#8211; you can easily have multi-line commands. The function in use is&nbsp;<code>current_database()<\/code>,&nbsp;which returns the database we&#8217;re connected to. The results are:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">  datname  | db_owner \n-----------+----------\n ham_shack | radiodev<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">As you can see, we&#8217;re now in a new database owned by the role we just created. Let&#8217;s exit <code>psql<\/code> for the moment, using <code>\/q<\/code> &#8211; but we will still be connected to the container through Bash. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">With our role and database in place, it&#8217;s time to start to work within the database. Specifically, creating a schema.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-how-to-create-a-schema-in-postgresql\">How to create a schema in PostgreSQL<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Similar to other data platforms, <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/postgresql\/postgresql-schema-learning-postgresql-with-grant\/\" target=\"_blank\" rel=\"noreferrer noopener\">PostgreSQL uses the concept of a schema<\/a> as a mechanism for organizing a database. A schema can also be used for <a href=\"https:\/\/www.red-gate.com\/simple-talk\/data-security-privacy-compliance\/\" target=\"_blank\" rel=\"noreferrer noopener\">security<\/a>, data isolation, and as a means of logically storing related objects.<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">By default, each database (including the <code>postgres<\/code> database) comes with the <code>public<\/code> schema. While many databases simply store all tables in this&nbsp;schema, it&#8217;s better to store your tables, views, etc., within <em>defined<\/em> schema. This is partly for security, but it&#8217;s mainly just good management practice.<\/p>\n\n\n\n<div id=\"callout-block_c720401a15b335798b07925ea00aa4ff\" class=\"callout alignnone\">\n    <div class=\"child-last:mb-0 child-first:mt-0 bg-gray-50 dark:bg-gray-950 p-4xl my-3xl\">\n\n<p class=\"wp-block-paragraph\"><strong>Other essential reading<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/postgresql\/postgresql-schema-learning-postgresql-with-grant\/\" target=\"_blank\" rel=\"noreferrer noopener\">PostgreSQL Schema: Learning PostgreSQL with Grant<\/a><\/p>\n\n<\/div>\n<\/div> \n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s start by connecting directly to <code>psql<\/code> and seeing what schema are already in the database, if any: <br><code>psql -d ham_shack -h localhost -U radiodev<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Once connected to <code>psql<\/code>, we can take a look at the schema in the&nbsp;<code>ham_shack<\/code>&nbsp;database using the <code>\/dn<\/code> meta command. The results are:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">      List of schemas\n  Name  |       Owner       \n--------+-------------------\n public | pg_database_owner<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">So, yes, this confirms that it&#8217;s only the default schema (<code>public<\/code>). However, if we want more information, the meta commands can actually show you more than just a simple list. We can make the call with <code>\\dn+<\/code>, which gives us additional information (mainly on the privileges for the schema in question):<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">                                       List of schemas\n  Name  |       Owner       |           Access privileges            |      Description       \n--------+-------------------+----------------------------------------+------------------------\n public | pg_database_owner | pg_database_owner=UC\/pg_database_owner+| standard public schema\n        |                   | =U\/pg_database_owner                   | <\/pre><\/div>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-how-to-create-your-first-schema-in-postgresql\">How to create your first schema in PostgreSQL<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s create a schema called&nbsp;<code>gear<\/code>&nbsp;from within <code>psql<\/code>: <br><code>CREATE SCHEMA gear;<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Using the&nbsp;<code>\\dn<\/code>&nbsp;meta command, we can now see our new schema:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">  Name  |       Owner       \n--------+-------------------\n gear   | radiodev\n public | pg_database_owner<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">However, let&#8217;s now focus on the&nbsp;<code>search_path<\/code>. First, let&#8217;s see what&#8217;s currently in it by running the <code>SHOW search_path;<\/code> command.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">On a completely new database such as this, it should return:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">   search_path   \n-----------------\n \"$user\", public<\/pre><\/div>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-what-is-the-postgresql-show-command\">What is the PostgreSQL <code>SHOW<\/code> command?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>The&nbsp;PostgreSQL <code>SHOW<\/code>&nbsp;command is used to return <a href=\"https:\/\/www.postgresql.org\/docs\/8\/runtime-config.html\" target=\"_blank\" rel=\"noreferrer noopener\">runtime configuration<\/a> parameters. This could be settings such as <code>timezone<\/code> or <code>max_connections<\/code> but, in our case, we&#8217;re going for the&nbsp;<code>search_path<\/code>. <\/strong><\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-what-is-the-postgresql-search-path\">What is the PostgreSQL <code>search_path<\/code>?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>The&nbsp;<code>search_path<\/code>&nbsp;is where the role you&#8217;re operating within looks <em>through<\/em> the database for objects. If you don&#8217;t have a schema in the search path, you won&#8217;t be able to see objects within that schema in order to query them. <\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The order of the&nbsp;<code>search_path<\/code>&nbsp;matters as well, as PostgreSQL will look within the first schema in the search path for an object. If it finds it there, it won&#8217;t continue to look through the other schema, assuming you haven&#8217;t used the schema when referencing the object. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you <em>do<\/em> use the schema in the object name (such as&nbsp;<code>gear.radio<\/code>&nbsp;for a table name), and you have access to that schema, the <code>search_path<\/code> doesn&#8217;t matter. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">However, if you simply reference the table&nbsp;<code>radio<\/code> with the <code>search_path<\/code> we currently have, you won&#8217;t find that table &#8211; even if you have the right access.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The&nbsp;<code>\"$user\"<\/code>&nbsp;is a token that resolves to the currently connected role. This is where you could create a schema called&nbsp;<code>radiodev<\/code>&nbsp;and it would automatically be referenced by our login. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Thankfully, modifying the <code>search_path<\/code> is easy:<br><code>SET search_path TO \"$user\", gear, public;<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you don&#8217;t include&nbsp;<code>\"$user\"<\/code>&nbsp;or&nbsp;<code>public<\/code>&nbsp;when you change the&nbsp;<code>search_path<\/code>, they&#8217;ll be removed &#8211; so be careful. However, using the&nbsp;<code>SET<\/code>&nbsp;command will only change the&nbsp;<code>search_path<\/code>&nbsp;for the duration of the current connection.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"h-how-to-add-the-gear-schema-to-the-role\">How to add the <code>gear<\/code> schema to the role<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>In order to change the PostgreSQL&nbsp;<code>search_path<\/code>&nbsp;permanently, you have to either&nbsp;<code>ALTER<\/code>&nbsp;the role itself, or the database. <\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Here&#8217;s how to add the&nbsp;<code>gear<\/code>&nbsp;schema to the role:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">ALTER ROLE radiodev SET search_path TO \"$user\", gear, public;<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">This change will only take affect on <em>new<\/em> connections for this role. You would either have to use the&nbsp;<code>SET<\/code>&nbsp;command along with the&nbsp;<code>ALTER ROLE<\/code>, or, disconnect and reconnect to the database. However, before we test the new and improved&nbsp;<code>search_path<\/code>, let&#8217;s add one more schema:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">CREATE SCHEMA people;\nALTER ROLE radiodev SET search_path TO \"$user\", gear, people, public;\n\\q<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Now, if you connect again &#8211; using the Docker command to Bash (from part 1 of the series) to connect to <code>psql<\/code> &#8211; and run the&nbsp;<code>SHOW<\/code>&nbsp;command, you&#8217;ll see the following:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">          search_path          \n-------------------------------\n \"$user\", gear, people, public<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">One point to add: there&#8217;s no way to simply&nbsp;<code>ALTER<\/code>&nbsp;the <code>search_path<\/code> to add a schema. You <em>must<\/em> use the&nbsp;<code>SET<\/code>&nbsp;or&nbsp;<code>ALTER ROLE<\/code>&nbsp;commands, as laid out, with the full list of items you want in the&nbsp;<code>search_path<\/code>. If you leave items <em>off<\/em> the&nbsp;<code>search_path<\/code>, they simply won&#8217;t be there.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Up next: creating tables!<\/p>\n\n\n\n<section id=\"my-first-block-block_f98c7ce31dbd233a16ccc019dcb66a29\" class=\"my-first-block alignwide\">\n    <div class=\"bg-brand-600 text-base-white py-5xl px-4xl rounded-sm bg-gradient-to-r from-brand-600 to-brand-500 red\">\n        <div class=\"gap-4xl items-start md:items-center flex flex-col md:flex-row justify-between\">\n            <div class=\"flex-1 col-span-10 lg:col-span-7\">\n                <h3 class=\"mt-0 font-display mb-2 text-display-sm\">Move fast. Govern at scale.<\/h3>\n                <div class=\"child:last-of-type:mb-0\">\n                                            Redgate Flyway Enterprise embeds guardrails in the database layer, so every change is policy-checked, deterministic, and traceable.                                    <\/div>\n            <\/div>\n                                            <a href=\"https:\/\/www.red-gate.com\/products\/flyway\/enterprise\/\" class=\"btn btn--secondary btn--lg\" aria-label=\"Try for free: Move fast. Govern at scale.\">Try for free<\/a>\n                    <\/div>\n    <\/div>\n<\/section>\n\n\n<h2 class=\"wp-block-heading\" id=\"h-how-to-create-a-table-in-postgresql\">How to create a table in PostgreSQL<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">With a database and a schema in place, we can begin to create tables in PostgreSQL. I&#8217;m not going to fully explore all the possible mechanisms around table creation and management in this guide &#8211; however, we will both&nbsp;<code>CREATE<\/code>&nbsp;<em>and<\/em>&nbsp;<code>ALTER<\/code>&nbsp;tables so that you get a real sense of the syntax within PostgreSQL.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This will include all the basic PostgreSQL behaviors such as <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/postgresql\/data-types-in-postgresql-learning-postgresql-with-grant\/\" target=\"_blank\" rel=\"noreferrer noopener\">data types<\/a>, <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/empty-thoughts-working-with-null\/\" target=\"_blank\" rel=\"noreferrer noopener\">nullability<\/a>, and <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/the-secret-life-of-database-keys\/\" target=\"_blank\" rel=\"noreferrer noopener\">primary and foreign keys<\/a>. <\/p>\n\n\n\n<div id=\"callout-block_c720401a15b335798b07925ea00aa4ff\" class=\"callout alignnone\">\n    <div class=\"child-last:mb-0 child-first:mt-0 bg-gray-50 dark:bg-gray-950 p-4xl my-3xl\">\n\n<p class=\"wp-block-paragraph\"><strong>You may also be interested in:<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/postgresql\/creating-a-database-and-tables-in-postgresql-learning-postgresql-with-grant\/\" target=\"_blank\" rel=\"noreferrer noopener\">Creating a Database and Tables in PostgreSQL: Learning PostgreSQL with Grant<\/a><\/p>\n\n<\/div>\n<\/div> \n\n\n<h3 class=\"wp-block-heading\" id=\"h-the-create-table-command-in-postgresql\">The <code>CREATE TABLE<\/code> command in PostgreSQL<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>The essential command to add a table to a database in PostgreSQL is&nbsp;<code>CREATE TABLE<\/code>. Much of this command&#8217;s behavior is similar to other data platforms, but not <em>everything<\/em>, as I&#8217;ll explain.<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To begin, here&#8217;s an example of the basic syntax to create a new table:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">CREATE TABLE antenna\n(antenna_id int GENERATED BY DEFAULT AS IDENTITY,\nantenna_name text NOT NULL\n);<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">The basic syntax is clear. You supply the name of the table at the start, and the parenthesis are then opened and closed to contain the columns being created. <\/p>\n\n\n\n<div id=\"callout-block_c720401a15b335798b07925ea00aa4ff\" class=\"callout alignnone\">\n    <div class=\"child-last:mb-0 child-first:mt-0 bg-gray-50 dark:bg-gray-950 p-4xl my-3xl\">\n\n<p class=\"wp-block-paragraph\"><strong>Other essential reading:<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/postgresql\/postgresql-basics-essential-psql-tips-and-tricks\/\" target=\"_blank\" rel=\"noreferrer noopener\">PostgreSQL Basics: Essential psql Tips and Tricks<\/a><\/p>\n\n<\/div>\n<\/div> \n\n\n<p class=\"wp-block-paragraph\">In this simple example, we&#8217;re creating two parenthesis&#8217;:&nbsp;<code>antenna_id<\/code>&nbsp;and&nbsp;<code>antenna_name<\/code>, both&nbsp;within the&nbsp;<code>antenna<\/code>&nbsp;table.  Do note that, with PostgreSQL syntax, all objects are stored as <em>lower case<\/em>. There&#8217;s no mixed case (or spaces) unless you use double quotes around the object name at all times &#8211; for example,&nbsp;<code>\"AntennaName\"<\/code>. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">However, do avoid using double quotes. We can instead implement &#8220;snake case&#8221;, which uses underscores to separate words in object names, making them clearer to understand. Just bear in mind that the object name length limit is 63 bytes (63 characters) <em>unless<\/em> you&#8217;re using <a href=\"https:\/\/en.wikipedia.org\/wiki\/UTF-8\" target=\"_blank\" rel=\"noreferrer noopener\">UTF-8<\/a>, in which case it&#8217;s 15-31 chars.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We&#8217;re referencing two data types for these two columns:&nbsp;<code>int<\/code>&nbsp;and&nbsp;<code>text<\/code>. You don&#8217;t need to define a size for <code>int<\/code> (integer) because it&#8217;s fixed-length, but you <em>do<\/em> for <code>text<\/code>. <\/p>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"h-a-note-on-using-toast-in-postgresql\">A note on using <code>TOAST<\/code> in PostgreSQL<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>While PostgreSQL does supports fixed-length string columns (such as&nbsp;<code>VARCHAR(50)<\/code>&nbsp;for a variable length column limited to 50 characters), common practice is to simply use&nbsp;<code>text<\/code>,&nbsp;and then let <a href=\"https:\/\/www.postgresql.org\/docs\/current\/storage-toast.html\" target=\"_blank\" rel=\"noreferrer noopener\"><code>TOAST<\/code> (The Oversized-Attribute Storage Technique)<\/a> deal with the data. <\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Arguments could be had that this is a poor design choice. However, since it&#8217;s so common, I&#8217;ve chosen to follow that standard in this guide.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Back to the walkthrough, and we&#8217;ve set up our first column &#8211; an&nbsp;<code>IDENTITY<\/code>&nbsp;column. This will be auto-incremented and generated on the fly. I chose to set it as&nbsp;<code>BY DEFAULT<\/code>&nbsp;instead of&nbsp;<code>ALWAYS<\/code>&nbsp;to allow for the need for an&nbsp;<code>IDENTITY INSERT<\/code>&nbsp;in the future.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To run the query, connect to <code>psql<\/code> and enter <code>psql -d ham_shack -h localhost -U radiodev<\/code> into the command line. Execute the query and then use the <code>\/dt<\/code> meta command to open up a list of tables in the database.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The output should be:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">           List of tables\n Schema |  Name   | Type  |  Owner   \n--------+---------+-------+----------\n gear   | antenna | table | radiodev<\/pre><\/div>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"h-why-schemas-are-so-important\">Why schemas are so important<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Note that, despite not supply a schema name to the table, the table is in the&nbsp;<code>gear<\/code>&nbsp;schema. This is because <strong>every table <em>must<\/em> belong to a schema &#8211; including all objects, functions, views, etc., within a database. <\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If a schema is <em>not<\/em> supplied,&nbsp;<code>search_path<\/code>&nbsp;is used to determine where the object is created. Remember from earlier, when we left off with this&nbsp;<code>search_path<\/code>?<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">          search_path          \n-------------------------------\n \"$user\", gear, people, public<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">How this works is similar to trying to find an object: when you create an object, the <code>search_path<\/code> is used. It&#8217;ll try to find both a valid schema <em>and<\/em> one you have permissions to create an object for. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You may also recall that the &#8220;<code>$user<\/code>&#8221; schema is a default, and is linking to a schema based on the role. However, that schema doesn&#8217;t actually exist in our current configuration, so it went to the&nbsp;<code>gear<\/code>&nbsp;schema next. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">And, since&nbsp;<code>radiodev<\/code>&nbsp;is the owner of the database, the role had permission to place the table there. This illustrates how important it is to reference the schema when creating objects within PostgreSQL.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A more appropriate example would be to create a&nbsp;<code>radio<\/code>&nbsp;table like this:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">CREATE TABLE gear.radio\n(radio_id int GENERATED BY DEFAULT AS IDENTITY,\nradio_name text NOT NULL,\nbrand text,\nPRIMARY KEY (radio_id));<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">To explain: <em>we<\/em> are supplying the schema now, so there&#8217;s no more ambiguity. You&#8217;ll note that the column&nbsp;<code>brand<\/code>&nbsp;doesn&#8217;t specify if it&#8217;s nullable or not. If it&#8217;s <em>not<\/em> defined, the column <em>can<\/em> contain <code>NULL<\/code> values. We&#8217;ve also created a <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/learn\/primary-key-primer-for-sql-server\/\" target=\"_blank\" rel=\"noreferrer noopener\">primary key<\/a> constraint. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><em>We can leave the name off because PostgreSQL defaults to&nbsp;<code>&lt;table name&gt;_pkey<\/code>, but it can be added if necessary.<\/em><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s create one more table before we move on to&nbsp;<code>ALTER<\/code>:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">CREATE TABLE people.operator\n(operator_id int GENERATED BY DEFAULT AS IDENTITY,\nfirst_name varchar(50),\nlast_name varchar(50),\ncallsign varchar(14) NOT NULL,\nPRIMARY KEY (operator_id));<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Nothing of significance to note in this table. I did however decide to illustrate the use of constrained length columns.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-the-alter-table-command-in-postgresql\">The <code>ALTER TABLE<\/code> command in PostgreSQL<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s start with adding a primary key to the&nbsp;<code>antenna<\/code>&nbsp;table, since one was left off:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">ALTER TABLE gear.antenna \nADD CONSTRAINT PRIMARY KEY (antenna_id);<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">The syntax should be straightforward, since it&#8217;s the same as what we&#8217;ve used when creating tables. Finally, let&#8217;s add two more tables to the database:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">-- QSOs, radio contacts\nCREATE TABLE people.contact\n(call_operator_id int REFERENCES people.operator,\nlogged_operator_id int REFERENCES people.operator,\nfreq_mhz NUMERIC(9,6) NOT NULL,\ncontact_date TIMESTAMP NOT NULL,\nradio_id int REFERENCES gear.radio,\nPRIMARY KEY (call_operator_id,logged_operator_id,freq_mhz,contact_date)\n);\n\n--Antennas on radios\nCREATE TABLE IF NOT EXISTS gear.radio_antenna\n(radio_id int REFERENCES gear.radio,\nantenna_id int REFERENCES gear.antenna,\nPRIMARY KEY (radio_id,antenna_id));<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\" id=\"h-the-alter-table-command-in-postgresql\">These tables show how foreign keys are created on tables in PostgreSQL. You can also use&nbsp;<code>ALTER TABLE<\/code>&nbsp;to add a foreign key. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\" id=\"h-the-alter-table-command-in-postgresql\">Note that we&#8217;re able to cross schema for one of the foreign keys &#8211; <code>radio_id<\/code> -in the&nbsp;<code>people.contact<\/code>&nbsp;table, <em>as long as we use the appropriate schema<\/em>. We can also create compound primary keys. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\" id=\"h-the-alter-table-command-in-postgresql\">Then, for the last table, I included the very common&nbsp;<code>IF NOT EXISTS<\/code> command. This is way for you to validate that there isn&#8217;t already a table in existence when you attempt to create one.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-the-drop-table-command-in-postgresql\">The DROP TABLE command in PostgreSQL<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Just as we can <code>CREATE<\/code> and <code>ALTER<\/code> tables in PostgreSQL, we can <a href=\"https:\/\/www.postgresql.org\/docs\/current\/sql-droptable.html\" target=\"_blank\" rel=\"noreferrer noopener\">drop<\/a> them as well. <\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example, let&#8217;s test this script: <code>DROP TABLE IF EXISTS gear.radio;<\/code> (while keeping the tables we&#8217;ve already created).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Once run, you should receive this error:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">ERROR:  cannot drop table radio because other objects depend on it\nDETAIL:  constraint contact_radio_id_fkey on table contacts depends on table radio\nconstraint radio_antenna_radio_id_fkey on table radio_antenna depends on table radio\nHINT:  Use DROP ... CASCADE to drop the dependent objects too.<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\" id=\"h-the-drop-table-command-in-postgresql\"><a href=\"https:\/\/github.com\/ScaryDBA\/GettingStartedWithPostgreSQL\/blob\/main\/08.%20Creating%20Tables\/creatingtables.md#alter-table\"><\/a>PostgreSQL protects the data integrity of the database by ensuring you can&#8217;t simply drop objects with dependencies.&nbsp;<code>CASCADE<\/code>&nbsp;will drop dependent objects, so exercise extreme caution in its use. You&#8217;ll also note that I used&nbsp;<code>IF EXISTS<\/code>&nbsp;to validate that there is a table prior to running the&nbsp;<code>DROP<\/code>&nbsp;command.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">With that, we now have enough tables in place. Next up: adding data!<\/p>\n\n\n\n<section id=\"my-first-block-block_5c693f60a763e8d1f52b6cb7ad09196a\" class=\"my-first-block alignwide\">\n    <div class=\"bg-brand-600 text-base-white py-5xl px-4xl rounded-sm bg-gradient-to-r from-brand-600 to-brand-500 red\">\n        <div class=\"gap-4xl items-start md:items-center flex flex-col md:flex-row justify-between\">\n            <div class=\"flex-1 col-span-10 lg:col-span-7\">\n                <h3 class=\"mt-0 font-display mb-2 text-display-sm\">Simple Talk is brought to you by Redgate Software<\/h3>\n                <div class=\"child:last-of-type:mb-0\">\n                                            Take control of your databases with the trusted Database DevOps solutions provider. Automate with confidence, scale securely, and unlock growth through AI.                                    <\/div>\n            <\/div>\n                                            <a href=\"https:\/\/www.red-gate.com\/solutions\/overview\/\" class=\"btn btn--secondary btn--lg\" aria-label=\"Discover how Redgate can help you: Simple Talk is brought to you by Redgate Software\">Discover how Redgate can help you<\/a>\n                    <\/div>\n    <\/div>\n<\/section>\n\n\n<section id=\"faq\" class=\"faq-block my-5xl\">\n    <h2>FAQs<\/h2>\n\n                        <h3 class=\"mt-4xl\">1. Why shouldn&#039;t I create a PostgreSQL database as the superuser?<\/h3>\n            <div class=\"faq-answer\">\n                <p dir=\"ltr\">Using the <code>postgres<\/code> superuser to create databases violates the principle of least privilege and bypasses security constraints. It&#8217;s better to create a dedicated role (like <code>radiodev<\/code>) with <code>CREATEDB<\/code> permission so ownership and object permissions cascade cleanly and stay easy to audit.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">2. What&#039;s the difference between a role and a user in PostgreSQL?<\/h3>\n            <div class=\"faq-answer\">\n                <p dir=\"ltr\">Modern PostgreSQL uses roles for everything. Older versions used the term &#8220;users,&#8221; which is why <code>CREATE USER<\/code> still exists \u2014 it&#8217;s simply an alias for <code>CREATE ROLE<\/code>.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">3. How does the search_path work in PostgreSQL?<\/h3>\n            <div class=\"faq-answer\">\n                <p dir=\"ltr\">The <code>search_path<\/code> tells PostgreSQL which schemas to check, in order, when you reference an object without specifying its schema. If a schema isn&#8217;t included in the <code>search_path<\/code>, you won&#8217;t be able to find or query objects inside it unless you explicitly reference the schema name (e.g., <code>gear.radio<\/code>).<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">4. Can I change a role&#039;s search_path permanently?<\/h3>\n            <div class=\"faq-answer\">\n                <p dir=\"ltr\">Yes. Use <code>ALTER ROLE rolename SET search_path TO \"$user\", schema1, schema2, public;<\/code>. This takes effect on new connections. A plain <code>SET search_path<\/code> only lasts for the current session.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">5. Why did PostgreSQL block my DROP TABLE command?<\/h3>\n            <div class=\"faq-answer\">\n                <div role=\"feed\" aria-label=\"Chat messages\" aria-describedby=\"_r_7m_\">\n<div>\n<div>\n<div tabindex=\"0\" role=\"article\" aria-label=\"Message 2 of 2\">\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<p dir=\"ltr\">PostgreSQL prevents dropping tables that other objects depend on (e.g., foreign keys) to protect data integrity. You&#8217;ll need to remove the dependent objects first, or use <code>DROP TABLE ... CASCADE<\/code> \u2014 with caution, since it removes all dependent objects too.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n            <\/div>\n            <\/section>\n","protected":false},"excerpt":{"rendered":"<p>Learn how to create roles, databases, schemas, and tables in PostgreSQL using psql. Step-by-step commands, real output examples, and best practices included.&hellip;<\/p>\n","protected":false},"author":221792,"featured_media":105920,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143523,53,143534],"tags":[4168,4170,159410,158978],"coauthors":[6785],"class_list":["post-112449","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-databases","category-featured","category-postgresql","tag-database","tag-database-administration","tag-gettingstartedpostgresgrant","tag-postgresql"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/112449","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/221792"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=112449"}],"version-history":[{"count":15,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/112449\/revisions"}],"predecessor-version":[{"id":112607,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/112449\/revisions\/112607"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media\/105920"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=112449"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=112449"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=112449"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=112449"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}