<?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>DEV Community: VisuaLeaf</title>
    <description>The latest articles on DEV Community by VisuaLeaf (@visualeaf).</description>
    <link>https://dev.to/visualeaf</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3818324%2F6cb54fe1-a36c-4f1a-a7b5-fa360f5daf8a.png</url>
      <title>DEV Community: VisuaLeaf</title>
      <link>https://dev.to/visualeaf</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/visualeaf"/>
    <language>en</language>
    <item>
      <title>Best Database Design Tools for 2027: Windows, macOS &amp; Linux</title>
      <dc:creator>VisuaLeaf</dc:creator>
      <pubDate>Mon, 26 Oct 2026 13:15:24 +0000</pubDate>
      <link>https://dev.to/visualeaf/best-database-design-tools-for-2027-windows-macos-linux-2m6j</link>
      <guid>https://dev.to/visualeaf/best-database-design-tools-for-2027-windows-macos-linux-2m6j</guid>
      <description>&lt;p&gt;Most database trouble starts with a schema nobody ever drew. A handful of tables turn into fifty, someone adds a foreign key late on a Friday, and a few months later nobody can explain how orders connect to customers without reading raw DDL.&lt;/p&gt;

&lt;p&gt;A visual design tool fixes that. You see the tables, you see the relationships, and you can show them to a teammate who wasn’t in the room. The catch is that “database tool” covers a lot of ground. Some let you design a schema before the database exists. Others are SQL clients that draw a diagram once you connect to a live server.&lt;/p&gt;

&lt;p&gt;This article compares eight popular tools, focusing on schema design and documentation. For each one, you get what it does well, where it falls short, how many databases it connects to, what it costs, and how painful the free trial is to start.&lt;/p&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;Short on time?&lt;/em&gt;&lt;/strong&gt;* Jump to the strengths and weaknesses overview at the end for a quick side-by-side of all eight tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I looked at
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Can you design a schema visually, or only view one that already exists?&lt;/li&gt;
&lt;li&gt;Can it reverse engineer a live database into a diagram?&lt;/li&gt;
&lt;li&gt;Can it generate SQL from the diagram (forward engineering)?&lt;/li&gt;
&lt;li&gt;Does it help with schema comparison, documentation export, or team workflows?&lt;/li&gt;
&lt;li&gt;How many databases can it connect to?&lt;/li&gt;
&lt;li&gt;What is free, what is paid, and how easy is it to start a trial?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The eight tools side by side
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Built around&lt;/th&gt;
&lt;th&gt;Runs on&lt;/th&gt;
&lt;th&gt;Databases&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;Free trial&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;VisuaLeaf&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;One GUI for MongoDB and SQL&lt;/td&gt;
&lt;td&gt;Windows, macOS, Linux&lt;/td&gt;
&lt;td&gt;37+&lt;/td&gt;
&lt;td&gt;Free Community Edition. Basic from $10/month or $79. Professional from $16/month or $149&lt;/td&gt;
&lt;td&gt;14 days, email only; can be extended&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DataGrip&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;SQL editing&lt;/td&gt;
&lt;td&gt;Windows, macOS, Linux&lt;/td&gt;
&lt;td&gt;50+&lt;/td&gt;
&lt;td&gt;Free for non-commercial use. From $10.90/month or $109/year. Organizations from $259/user/year&lt;/td&gt;
&lt;td&gt;30 days, starts straight away&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DbSchema&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;An offline model file&lt;/td&gt;
&lt;td&gt;Windows, macOS, Linux&lt;/td&gt;
&lt;td&gt;100+&lt;/td&gt;
&lt;td&gt;Free Community. Pro from $196 personal / $294 business. Architect from $313.60 personal / $470.40 business&lt;/td&gt;
&lt;td&gt;15 days of Architect, one click; can be extended&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DBeaver&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Live database work&lt;/td&gt;
&lt;td&gt;Windows, macOS, Linux&lt;/td&gt;
&lt;td&gt;100+&lt;/td&gt;
&lt;td&gt;Free Community. Lite $113/year, Enterprise $255/year, Ultimate $510/year&lt;/td&gt;
&lt;td&gt;14 days; request and import a licence key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Navicat&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Live database work&lt;/td&gt;
&lt;td&gt;Windows, macOS, Linux&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Free Premium Lite. Premium perpetual licence from 75$/month or $1,499 perpetual; subscriptions also offered&lt;/td&gt;
&lt;td&gt;14 days, starts automatically&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DbVisualizer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Live database work&lt;/td&gt;
&lt;td&gt;Windows, macOS, Linux&lt;/td&gt;
&lt;td&gt;40+ plus JDBC&lt;/td&gt;
&lt;td&gt;Free edition. Pro $199 the first year, then $89/year&lt;/td&gt;
&lt;td&gt;21 days, no account or card needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TablePlus&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A fast native client&lt;/td&gt;
&lt;td&gt;macOS, Windows, Linux&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;Free edition with limits. Basic $99 one-time, Standard $129 one-time&lt;/td&gt;
&lt;td&gt;Free forever, no signup needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Beekeeper Studio&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A clean SQL client&lt;/td&gt;
&lt;td&gt;Windows, macOS, Linux&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;Free Community. Paid from $18/month or $168 billed yearly&lt;/td&gt;
&lt;td&gt;14 days, no email or card&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Prices are USD list prices before tax, checked in October 2026. Where a vendor splits business and personal plans, the business price is shown. Plans change often, so confirm on each vendor’s site before you buy.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Tested the 8 GUIs
&lt;/h2&gt;

&lt;p&gt;All screenshots come from the same &lt;a href="https://www.mysql.com/" rel="noopener noreferrer"&gt;MySQL database&lt;/a&gt;, so the diagrams are comparable. For this comparison, I connected every tool to the same MySQL 8.4.11 database: &lt;code&gt;super_bowl_ticketing&lt;/code&gt;, with 19 tables and 21 foreign-key relationships.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://visualeaf.com/" rel="noopener noreferrer"&gt;1. VisuaLeaf&lt;/a&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Platform:&lt;/em&gt;&lt;/strong&gt;* Windows, macOS, Linux&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Databases:&lt;/em&gt;&lt;/strong&gt;* MySQL, MongoDB, PostgreSQL, and 34 more&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Price:&lt;/em&gt;&lt;/strong&gt;* free Community Edition. Basic from $10/month or $79 one-time; Professional $149 one-time (each includes 1 year of updates)&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Free trial:&lt;/em&gt;&lt;/strong&gt;* 14 days of Pro features. Add your email in the app or on the dashboard and the trial starts, with the option to extend it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;VisuaLeaf treats MongoDB and relational databases as equals, which is still rare. For schema work, you can create a diagram from scratch or reverse engineer an existing database into one, then stay in the same app for the rest of the job.&lt;/p&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;Key features&lt;/em&gt;&lt;/strong&gt;*&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Schema diagrams:&lt;/em&gt;&lt;/strong&gt;* create one from scratch or reverse engineer an existing database. ER diagrams for SQL databases are in the free Community Edition&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;SQL editor:&lt;/em&gt;&lt;/strong&gt;* with inline editing and a visual query builder&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Index manager:&lt;/em&gt;&lt;/strong&gt;* create, change, and drop indexes&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Query profiler:&lt;/em&gt;&lt;/strong&gt;* find and diagnose slow queries (from Basic)&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Data compare and sync:&lt;/em&gt;&lt;/strong&gt;* across MongoDB and SQL databases (Professional)&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Data masking:&lt;/em&gt;&lt;/strong&gt;* hide sensitive fields in exports (from Basic)&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Task automation:&lt;/em&gt;&lt;/strong&gt;* import, export, and migration tasks, including scheduled transfers&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Charts and dashboards:&lt;/em&gt;&lt;/strong&gt;* built from your queries (Professional)&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;AI Assistant&lt;/em&gt;&lt;/strong&gt;* in the Basic plan or MCP in the Pro one.&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Database Documentation:&lt;/em&gt;&lt;/strong&gt;* interactive HTML5, PDF, JSON, DDL (Pro)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuk2k1is5nh4x7igp31e8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuk2k1is5nh4x7igp31e8.png" alt="VisuaLeaf visual schema designer connected to the sample MySQL event-ticketing database." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;VisuaLeaf visual schema designer showing relational MySQL tables alongside the expanded structure of a JSON column.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4bz5ak57sfe5dn002ki1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4bz5ak57sfe5dn002ki1.png" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fns1m7k4lv0l25ih2wskg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fns1m7k4lv0l25ih2wskg.png" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;VisuaLeaf’s visual query builder and chart workspace: build a joined MySQL query on the left, or turn an aggregated SQL result into a payment chart on the right.&lt;/p&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;What I like&lt;/em&gt;&lt;/strong&gt;*&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One app for MongoDB and 36 other databases, which helps if your stack mixes both&lt;/li&gt;
&lt;li&gt;Diagrams you can create and reverse engineer, free for SQL databases&lt;/li&gt;
&lt;li&gt;Compare, sync, and masking sit next to the diagram, so you don’t need a second tool&lt;/li&gt;
&lt;li&gt;JSON fields can be expanded inside the diagram, which makes mixed relational and nested data easier to understand&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;Disadvantages&lt;/em&gt;&lt;/strong&gt;*&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It’s a newer product, so tutorials and community answers are thinner than for DBeaver or DataGrip&lt;/li&gt;
&lt;li&gt;The heavier features, such as data compare and sync or dashboards, need the Professional plan&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://www.jetbrains.com/datagrip/" rel="noopener noreferrer"&gt;2. DataGrip&lt;/a&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Platform:&lt;/em&gt;&lt;/strong&gt;* Windows, macOS, Linux&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Databases:&lt;/em&gt;&lt;/strong&gt;* MySQL, MongoDB, PostgreSQL, and dozens more (JetBrains doesn’t publish an exact number)&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Price:&lt;/em&gt;&lt;/strong&gt;* from $10.90/month or $109/year for individuals, $259/user/year for organizations. Free for non-commercial use&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Free trial:&lt;/em&gt;&lt;/strong&gt;* 30 days, starts directly in the app&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;DataGrip is JetBrains’ database IDE, and it’s the pick if you spend your day writing and refactoring SQL. The free license covers learning, hobby projects, open-source work, and content creation.&lt;/p&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;Key features&lt;/em&gt;&lt;/strong&gt;*&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;ER diagrams:&lt;/em&gt;&lt;/strong&gt;* generated automatically, showing tables, relationships, and foreign keys for a connected database&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Query console:&lt;/em&gt;&lt;/strong&gt;* run queries in different modes, with local history so you don’t lose work&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Code completion:&lt;/em&gt;&lt;/strong&gt;* aware of table structure, foreign keys, and the objects in the code you’re editing&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;On-the-fly analysis:&lt;/em&gt;&lt;/strong&gt;* flags probable bugs and suggests quick fixes&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Import and export:&lt;/em&gt;&lt;/strong&gt;* CSV, JSON, HTML, Markdown, Excel, and more&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Version control integration&lt;/em&gt;&lt;/strong&gt;* and query history&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;AI assistance:&lt;/em&gt;&lt;/strong&gt;* schema-aware help with queries, errors, and SQL dialect conversion&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Cloud discovery:&lt;/em&gt;&lt;/strong&gt;* finds databases in your AWS, Google Cloud, or Azure account&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frabrvzx5tf5xvswxztxu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frabrvzx5tf5xvswxztxu.png" alt="Dark-themed DataGrip database diagram showing ticketing tables, including events, ticket inventory, seats, customers, payments, and reservations, connected by foreign-key relationships." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;DataGrip database diagram generated from the live &lt;code&gt;super_bowl_ticketing&lt;/code&gt; MySQL schema.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F32vklobdpi0v9hlncme1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F32vklobdpi0v9hlncme1.png" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffhbh2f8e2m5pymp5s80e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffhbh2f8e2m5pymp5s80e.png" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;DataGrip generates DDL for existing MySQL tables and compares schema changes before creating a migration script.&lt;/p&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;What I like&lt;/em&gt;&lt;/strong&gt;*&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Smart completion, refactoring, and inspections for SQL&lt;/li&gt;
&lt;li&gt;ER diagrams without leaving the IDE&lt;/li&gt;
&lt;li&gt;The free non-commercial license is a real option for students and side projects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;Disadvantages&lt;/em&gt;&lt;/strong&gt;*&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Commercial work needs a paid subscription&lt;/li&gt;
&lt;li&gt;It’s built for working with existing databases, not for designing new ones from a blank page&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://dbschema.com/" rel="noopener noreferrer"&gt;3. DbSchema&lt;/a&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Platform:&lt;/em&gt;&lt;/strong&gt;* Windows, macOS, Linux&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Databases:&lt;/em&gt;&lt;/strong&gt;* MySQL, MongoDB, PostgreSQL, and 70+ more&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Price:&lt;/em&gt;&lt;/strong&gt;* free Community Edition. Pro $294 one-time ($196 for personal use), Architect $470.40 one-time. Both include the first year of updates, then $75/year (Pro) or $120/year (Architect) to keep getting new versions. Monthly plans are also available&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Free trial:&lt;/em&gt;&lt;/strong&gt;* 15 days of the Architect edition, one click, no card.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;DbSchema is a good design-first tool from the group. The diagram is the model, and you can save it to a file and work on it without a database connection. Community is free forever and covers connecting, reverse engineering, interactive diagrams, creating tables and columns, and a SQL editor. Pro and Architect add the design workflow on top.&lt;/p&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;Key features&lt;/em&gt;&lt;/strong&gt;*&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Interactive diagrams:&lt;/em&gt;&lt;/strong&gt;* reverse engineer a live database, or create and edit tables and columns visually. Free in Community&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;SQL editor:&lt;/em&gt;&lt;/strong&gt;* with autocompletion, free in Community&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Offline model file:&lt;/em&gt;&lt;/strong&gt;* save the design to a file and work without a connection (Pro). The file works well with Git&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Schema synchronization:&lt;/em&gt;&lt;/strong&gt;* compare, diff, and generate migration scripts (Pro)&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Documentation:&lt;/em&gt;&lt;/strong&gt;* interactive HTML5, PDF, and Markdown (Pro)&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Visual query builder and relational data browse&lt;/em&gt;&lt;/strong&gt;* (Pro)&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Data generator and importer:&lt;/em&gt;&lt;/strong&gt;* fill tables with test data, load CSV, XML and XLS files (Pro)&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Logical and conceptual design:&lt;/em&gt;&lt;/strong&gt;* database-independent models that you can migrate to a physical schema later (Architect)&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;AI assistant:&lt;/em&gt;&lt;/strong&gt;* a separate $9/month add-on for any edition, or your own key in Architect&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgrm240hbo2sgbgzhlxcy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgrm240hbo2sgbgzhlxcy.png" alt="DbSchema offline MySQL schema diagram." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;DbSchema keeps an offline visual model of the MySQL schema, including grouped tables, relationships, and expanded JSON fields.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhl2g7ak46te8h096ay0t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhl2g7ak46te8h096ay0t.png" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgmmiseci190chgx5ec9q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgmmiseci190chgx5ec9q.png" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;DbSchema’s Relational Data Editor follows connected rows across linked panes, from a venue to its events, orders, and reservations.&lt;/p&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;What I like&lt;/em&gt;&lt;/strong&gt;*&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can model before the database exists, which most tools here can't do&lt;/li&gt;
&lt;li&gt;Diagrams, reverse engineering, and the SQL editor are free for every supported database&lt;/li&gt;
&lt;li&gt;Git-friendly model file and generated documentation make it good for teams&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;Disavantages&lt;/em&gt;&lt;/strong&gt;*&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Saving the model, documentation, and schema sync need a paid edition. When the trial ends, Community can't open the project files you saved&lt;/li&gt;
&lt;li&gt;Steeper learning curve: its model-first workflow and many features can feel less straightforward for beginners than a simpler live database client.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://dbeaver.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;5. DBeaver&lt;/strong&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Platform:&lt;/em&gt;&lt;/strong&gt;* Windows, macOS, Linux&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Databases:&lt;/em&gt;&lt;/strong&gt;* MySQL, MongoDB (paid editions), PostgreSQL, and 100+ more&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Price:&lt;/em&gt;&lt;/strong&gt;* free Community Edition. Lite $113/year ($12/month), Enterprise $255/year ($26/month), Ultimate $510/year&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Free trial:&lt;/em&gt;&lt;/strong&gt;* 15 days. Create an account, then import the license key into the app (the most involved sign-up of the eight)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;DBeaver is the free all-rounder. The Community edition is open source and works with 100+ databases, and the paid editions add schema editing, comparison tools, and NoSQL support. It’s a general database tool first and a design tool second.&lt;/p&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;Key features&lt;/em&gt;&lt;/strong&gt;*&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;ER diagrams:&lt;/em&gt;&lt;/strong&gt;* for a whole schema or a single table, in every edition. Editing the schema from the diagram (ERD edit mode) starts at Enterprise&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;SQL editor and data editor:&lt;/em&gt;&lt;/strong&gt;* with query history and inline editing&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Visual query builder:&lt;/em&gt;&lt;/strong&gt;* build queries by clicking tables and columns (paid editions)&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Import, export, and charts:&lt;/em&gt;&lt;/strong&gt;* move data between databases and files, and draw analytical charts&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Schema comparison and table data comparison:&lt;/em&gt;&lt;/strong&gt;* (Enterprise)&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Mock data generation:&lt;/em&gt;&lt;/strong&gt;* fill tables with test data (Enterprise)&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Git sync and task scheduler:&lt;/em&gt;&lt;/strong&gt;* (Enterprise)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl68y5e889j1n8ilrz0r8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl68y5e889j1n8ilrz0r8.png" alt="DBeaver ER diagram showing MySQL ticketing tables such as customers, ticket orders, events, payments, reservations, and seats linked by foreign-key relationships." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;DBeaver turns a live MySQL schema into a navigable ER map; it's useful when you need to understand an existing database rather than design one from scratch.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fghuqtat08zfdtlysfv50.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fghuqtat08zfdtlysfv50.png" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fczyitr3fd8rro6s6co62.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fczyitr3fd8rro6s6co62.png" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;DBeaver goes beyond the diagram: compare a changed schema, then build a joined query visually and inspect the SQL it produces.&lt;/p&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;What I like&lt;/em&gt;&lt;/strong&gt;*&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A genuinely useful free edition, ER diagrams included&lt;/li&gt;
&lt;li&gt;Huge database coverage and a solid SQL editor&lt;/li&gt;
&lt;li&gt;Easy to browse and edit real data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;Disadvantages&lt;/em&gt;&lt;/strong&gt;*&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Diagrams exist to explore a database, and editing the schema from them is an Enterprise feature&lt;/li&gt;
&lt;li&gt;Starting the trial is the most involved of the eight: account, email, then a license key to import&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://www.navicat.com/en/products/navicat-premium" rel="noopener noreferrer"&gt;5. Navicat&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Navicat is the polished, long-established name in this group. It is a live-connection tool first, but the Enterprise edition adds a proper modelling workspace. Instead of only generating a diagram from an existing database, you can start with a conceptual, logical, physical, or hybrid design and take it forward to MySQL.&lt;/p&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;Key features&lt;/em&gt;&lt;/strong&gt;*&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Model-first design (Enterprise):&lt;/em&gt;&lt;/strong&gt;* start with a conceptual, logical, physical, or hybrid model. For physical models, choose the target database and version before adding tables.&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Naming standards and documentation:&lt;/em&gt;&lt;/strong&gt;* create naming rules, a shared glossary, and a data dictionary alongside the model.&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Forward and reverse engineering (Enterprise):&lt;/em&gt;&lt;/strong&gt;* reverse-engineer a live database or SQL file into a model, then generate SQL or synchronize the finished model back to a database.&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Live ER diagram:&lt;/em&gt;&lt;/strong&gt;* Standard and Enterprise can also generate a quick diagram from an existing schema. It supports MySQL, Oracle, PostgreSQL, SQLite, SQL Server, and MariaDB.&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Structure and data synchronization:&lt;/em&gt;&lt;/strong&gt;* compare two databases and generate the scripts needed to bring them back in sync.&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Navicat Collaboration:&lt;/em&gt;&lt;/strong&gt;* share connections, queries, snippets&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Import, export, backup, and SSH tunneling:&lt;/em&gt;&lt;/strong&gt;* covers the everyday work around a live database connection.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh6tfcgjs333v175v0024.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh6tfcgjs333v175v0024.png" alt="Navicat ER diagram of a MySQL ticketing database." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Navicat generates a physical ER diagram from the MySQL ticketing schema, with fields, keys, and foreign-key links visible in one view.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhqbajbad6wwbl6phx2g9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhqbajbad6wwbl6phx2g9.png" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuz2oxg4sjbp6dt7z01jv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuz2oxg4sjbp6dt7z01jv.png" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Navicat Enterprise pairs a proper modelling workspace with BI dashboards, so you can plan a database before building it and present data through charts afterward.&lt;/p&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;What I like&lt;/em&gt;&lt;/strong&gt;*&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A mature, polished interface with a long track record&lt;/li&gt;
&lt;li&gt;Sync and collaboration tools are strong for teams working across several databases&lt;/li&gt;
&lt;li&gt;The Premium Lite edition is free for commercial use, up to 5 users per organization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;Disadvantages&lt;/em&gt;&lt;/strong&gt;*&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It has the highest price on the list, and the free Premium Lite has no ER diagram view&lt;/li&gt;
&lt;li&gt;The full modelling workspace may feel like overkill if you only need a quick, simple schema diagram.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://dbvis.com/" rel="noopener noreferrer"&gt;6. DbVisualizer&lt;/a&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Platform:&lt;/em&gt;&lt;/strong&gt;* Windows, macOS, Linux&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Databases:&lt;/em&gt;&lt;/strong&gt;* MySQL, PostgreSQL, and 60+ others via JDBC&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Price:&lt;/em&gt;&lt;/strong&gt;* Free edition. Pro is $199 for the first year, then $89/year. Premium Support is $229 for the first year, then $119/year. A paid term includes a perpetual license for versions released during that period.&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Free trial:&lt;/em&gt;&lt;/strong&gt;* 21 days of Pro. Create an account, then start the trial from the app.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;DbVisualizer is a mature JDBC database client built for databases that already exist. It is good for exploring a live schema, writing SQL, browsing data, and following relationships, but it is not really a design-from-scratch tool. The Free edition covers the essentials, while Pro adds data editing, visual querying, and analysis tools.&lt;/p&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;Key features&lt;/em&gt;&lt;/strong&gt;*&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;References graph (Free):&lt;/em&gt;&lt;/strong&gt;* automatically maps primary and foreign keys, offers several layouts, and exports to PNG, SVG, or PDF.&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;SQL editor and query history (Free):&lt;/em&gt;&lt;/strong&gt;* write queries, revisit previous work, and reuse it when needed.&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Connection management and SSH tunnelling (Free):&lt;/em&gt;&lt;/strong&gt;* manage multiple databases and connect securely to remote servers.&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Table and data editor (Pro):&lt;/em&gt;&lt;/strong&gt;* edit table structures and data directly in the app.&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Import and export tools (Pro):&lt;/em&gt;&lt;/strong&gt;* move data between files and databases.&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Visual Query Builder (Pro):&lt;/em&gt;&lt;/strong&gt;* build &lt;code&gt;SELECT&lt;/code&gt; queries visually instead of writing every join and filter by hand.&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Explain plan and charts (Pro):&lt;/em&gt;&lt;/strong&gt;* inspect query performance and turn result sets into charts.&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;CLI and AI Assistant (Pro):&lt;/em&gt;&lt;/strong&gt;* run DbVisualizer from the command line or use its built-in AI help.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj64qzqmjr820648zw22k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj64qzqmjr820648zw22k.png" alt="DbVisualizer references graph of a MySQL ticketing schema." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;DbVisualizer’s References graph automatically maps the foreign-key structure of the live MySQL ticketing database.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdvsubz1znw57822dcfri.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdvsubz1znw57822dcfri.png" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffs7raoazldl0ujwr00sh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffs7raoazldl0ujwr00sh.png" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;DbVisualizer Pro can load a multi-table SQL query into its visual builder, then turn an aggregated result into a chart directly in SQL Commander.&lt;/p&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;What I like&lt;/em&gt;&lt;/strong&gt;*&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The references graph is included in the free edition&lt;/li&gt;
&lt;li&gt;Works with any JDBC database, with extra support for the common ones&lt;/li&gt;
&lt;li&gt;Pro is cheap to renew, and your license is perpetual for the versions released during your term&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;Disavantages&lt;/em&gt;&lt;/strong&gt;*&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The graph is built from existing foreign keys, so it is a viewer, not a modeling tool. There is no offline model file or design-first workflow&lt;/li&gt;
&lt;li&gt;Table management, the data editor, and the query builder are all Pro. The trial needs an account before you can start it&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://tableplus.com/" rel="noopener noreferrer"&gt;7. TablePlus&lt;/a&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Platform:&lt;/em&gt;&lt;/strong&gt;* macOS, Windows, Linux (plus iOS)&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Databases:&lt;/em&gt;&lt;/strong&gt;* MySQL, PostgreSQL, SQLite, SQL Server, MariaDB, Redis, MongoDB, and more (13 in total)&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Price:&lt;/em&gt;&lt;/strong&gt;* free tier with limits. Basic $99 one-time (1 device), Standard $129 one-time (2 devices), Team $79 per seat from 3 seats. Each includes 1 year of updates, then an optional $59/device renewal&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Free trial:&lt;/em&gt;&lt;/strong&gt;* 14 days, no sign-up needed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;TablePlus is the fast, native client of the group. It is built for people who spend their day in the data grid and want a quick, clean interface with a one-time price. It focuses on everyday database work rather than design, so schema diagrams are an add-on, not a core feature.&lt;/p&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;Key features&lt;/em&gt;&lt;/strong&gt;*&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Native interface:&lt;/em&gt;&lt;/strong&gt;* fast to open and responsive on large tables&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Data editor:&lt;/em&gt;&lt;/strong&gt;* edit rows inline and review changes before you commit them&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Advanced filters and query editor:&lt;/em&gt;&lt;/strong&gt;* with autocompletion&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Safe mode:&lt;/em&gt;&lt;/strong&gt;* helps prevent accidental changes on production databases&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Built-in ER diagram:&lt;/em&gt;&lt;/strong&gt;* generate a diagram directly from a live database or schema, then move tables around, zoom in, search for objects, and edit tables, columns, indexes, or foreign keys from the diagram.&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Plugin manager:&lt;/em&gt;&lt;/strong&gt;* install extra tools such as a SQL formatter or table dump&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;iOS app:&lt;/em&gt;&lt;/strong&gt;* included for free with a valid license&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feczynatlm59wzu07e3uu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feczynatlm59wzu07e3uu.png" alt="TablePlus ER diagram of a MySQL ticketing schema." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;TablePlus generates a clean ER diagram from the live MySQL ticketing database, with related tables and foreign-key links visible at a glance.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy0j75o0p5drtuv9evcga.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy0j75o0p5drtuv9evcga.png" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F30cqs3hxv42nh8k0wh5v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F30cqs3hxv42nh8k0wh5v.png" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;TablePlus combines a small SQL-powered Metrics Board with a fast spreadsheet-style grid for inspecting individual ticket orders.&lt;/p&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;What I like&lt;/em&gt;&lt;/strong&gt;*&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fast, native, and pleasant to use every day&lt;/li&gt;
&lt;li&gt;A one-time price, and the app keeps working after the first year of updates&lt;/li&gt;
&lt;li&gt;Opens the free tier without any sign-up&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;Disavantages&lt;/em&gt;&lt;/strong&gt;*&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The free tier is limited to 2 tabs, 2 windows, and 2 advanced filters at a time&lt;/li&gt;
&lt;li&gt;Supports fewer databases than other tools&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://www.beekeeperstudio.io/" rel="noopener noreferrer"&gt;8. Beekeeper Studio&lt;/a&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Platform:&lt;/em&gt;&lt;/strong&gt;* Windows, macOS, Linux&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Databases:&lt;/em&gt;&lt;/strong&gt;* MySQL, PostgreSQL, SQLite, SQL Server, and 22 more (26 in total). Some engines are paid-only: Oracle from Indie, MongoDB from Professional&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Price:&lt;/em&gt;&lt;/strong&gt;* free Community Edition (open source). Indie $9/month, Professional $14/month, Business $18/month, all billed yearly. Monthly billing costs about double. Every paid plan includes a lifetime license for the versions released during your subscription&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Free trial:&lt;/em&gt;&lt;/strong&gt;* 14 days of all paid features. Install the app, click "start free trial," and you're in, no email, account, or card needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Beekeeper Studio is a clean SQL client with an open-source Community edition. The ER diagram (called the Visual Schema Explorer) is built in, but it's a paid feature: it isn't included in the Community edition, and the trial unlocks it.&lt;/p&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;Key features&lt;/em&gt;&lt;/strong&gt;*&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;ER diagrams (paid):&lt;/em&gt;&lt;/strong&gt;* turn a live database schema into a visual map of its tables and foreign-key links. You can move tables around, use auto-layout when it gets messy, then export or copy the result.&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Table structure editor:&lt;/em&gt;&lt;/strong&gt;* create or change tables, relations, triggers, and indexes without writing every statement by hand.&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;SQL editor and data grid:&lt;/em&gt;&lt;/strong&gt;* write SQL with autocomplete, then browse and edit rows in a clean spreadsheet-style view.&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;Import, export, backup, and restore (paid):&lt;/em&gt;&lt;/strong&gt;* useful for moving data around and keeping database backups close.&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;JSON Sidebar (paid):&lt;/em&gt;&lt;/strong&gt;* opens a selected row as formatted JSON, which is much easier to read than a long JSON value in one cell.&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;AI Shell and Cloud Workspaces (Professional+):&lt;/em&gt;&lt;/strong&gt;* use your own AI provider to explore the schema and help write SQL, or sync connections and saved queries across devices and with a team.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdsbtee47nwwm66s3gr2u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdsbtee47nwwm66s3gr2u.png" alt="Beekeeper Studio ER diagram of a MySQL ticketing database." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Beekeeper Studio’s trial maps the foreign-key relationships in the MySQL ticketing database, keeping the overview focused on how tables connect.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Folwmznbtts8ijcsds90o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Folwmznbtts8ijcsds90o.png" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmucp46x3ja6ro50lrez7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmucp46x3ja6ro50lrez7.png" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Beekeeper Studio makes everyday database work straightforward: filter live payment records in the grid, then save and run a multi-table query from the SQL editor.&lt;/p&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;What I liked:&lt;/em&gt;&lt;/strong&gt;* Beekeeper Studio is one of the easiest tools here to pick up. The interface is clean, browsing and editing data feels quick, and the SQL editor does not bury basic work behind lots of menus.&lt;/p&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;Disadvantages:&lt;/em&gt;&lt;/strong&gt;* The limitation comes after the trial: the free Community Edition does not include ER diagrams, import/export, or the JSON Sidebar. The ER diagram is also mainly built for following relationships; the full-database view keeps only primary and foreign keys visible, while a focused selection shows more detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  So which one should you pick?
&lt;/h2&gt;

&lt;p&gt;There’s no single winner, because these tools are built around different jobs. Start with whether you’re designing a schema from scratch or exploring one that already exists.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;You work with more than one kind of database:&lt;/em&gt;&lt;/strong&gt;* &lt;a href="https://visualeaf.com/database/" rel="noopener noreferrer"&gt;VisuaLeaf&lt;/a&gt;. It has ER diagrams you can create and reverse engineer, free for SQL databases. The same app also works with MongoDB, DuckDB, ClickHouse, and more (37 in total). It’s the newest tool here, so expect fewer community answers.&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;You want to design a schema before the database exists:&lt;/em&gt;&lt;/strong&gt;* DbSchema. Diagrams are free, but saving the model file and generating docs need Pro.&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;You want free ER diagrams:&lt;/em&gt;&lt;/strong&gt;* DBeaver Community, or the DbVisualizer free edition. Editing the schema from the diagram is paid in DBeaver.&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;You write SQL all day:&lt;/em&gt;&lt;/strong&gt;* DataGrip, which is free for non-commercial use.&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;You want a fast native app with a one-time price:&lt;/em&gt;&lt;/strong&gt;* TablePlus, if you’re fine with a diagram plugin and the free tier limits.&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;You want the cleanest interface and an open-source core:&lt;/em&gt;&lt;/strong&gt;* Beekeeper Studio. Its ER diagram is in the paid editions, and the trial is the easiest to start.&lt;/li&gt;
&lt;li&gt;*&lt;strong&gt;&lt;em&gt;You manage several databases as a team and have the budget:&lt;/em&gt;&lt;/strong&gt;* Navicat. It’s the most expensive option here, and the diagram view isn’t in the free Lite edition.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All eight have a free edition or a trial, so the quickest way to decide is to point two or three of them at the same database and see which diagram your team can read.&lt;/p&gt;

&lt;h2&gt;
  
  
  Strengths and weaknesses
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Visual ER diagrams&lt;/th&gt;
&lt;th&gt;Biggest strength&lt;/th&gt;
&lt;th&gt;Biggest weakness&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;VisuaLeaf&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Create from scratch or reverse-engineer an existing SQL database; free for SQL databases&lt;/td&gt;
&lt;td&gt;MongoDB plus 36 other databases in one app, with compare, sync, and data masking&lt;/td&gt;
&lt;td&gt;Newer product, with a smaller community than DBeaver or DataGrip&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DataGrip&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Generated from a connected database&lt;/td&gt;
&lt;td&gt;The strongest SQL editor and refactoring help in this group&lt;/td&gt;
&lt;td&gt;Paid for commercial use and aimed at databases that already exist&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DbSchema&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Design-first, offline model file&lt;/td&gt;
&lt;td&gt;Model before a database exists, with Git support and interactive HTML documentation&lt;/td&gt;
&lt;td&gt;Its model-first workflow can feel heavier than a simple live database client&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DBeaver&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Generated from a connection in every edition; diagram editing is Enterprise-only&lt;/td&gt;
&lt;td&gt;A capable free Community edition with 100+ drivers&lt;/td&gt;
&lt;td&gt;Visual schema editing is paid, and starting the trial is more involved than the others&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Navicat&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Live ER diagrams in Premium; full data modelling in Enterprise&lt;/td&gt;
&lt;td&gt;Polished sync, collaboration, and modelling tools&lt;/td&gt;
&lt;td&gt;The most expensive option here, while Premium Lite has no diagrams&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DbVisualizer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;References graph, including in the free edition&lt;/td&gt;
&lt;td&gt;Export a clear relationship graph as PNG, SVG, or PDF&lt;/td&gt;
&lt;td&gt;It reads existing foreign keys; it is not a design-from-scratch tool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TablePlus&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Built-in ER diagram in paid editions, generated from a live database or schema&lt;/td&gt;
&lt;td&gt;Fast native client with a one-time license option&lt;/td&gt;
&lt;td&gt;The free tier limits workspaces and locks several advanced features&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Beekeeper Studio&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Auto-generated in paid editions and during the trial; the main view focuses on keys and relationships&lt;/td&gt;
&lt;td&gt;One of the cleanest, easiest interfaces, with an open-source Community edition&lt;/td&gt;
&lt;td&gt;Community does not include ER diagrams, import/export, or the JSON Sidebar&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h4&gt;
  
  
  Which database design tool is best?
&lt;/h4&gt;

&lt;p&gt;It depends on the job: VisuaLeaf or DBeaver for broader everyday database work; DbSchema is strong for planning; and DataGrip for SQL.  &lt;/p&gt;

&lt;h4&gt;
  
  
  Are there free ER diagram tools?
&lt;/h4&gt;

&lt;p&gt;Yes. DBeaver Community, DbVisualizer Free, DbSchema Community, and VisuaLeaf for SQL databases all include diagram features.&lt;/p&gt;

&lt;h4&gt;
  
  
  Can I generate an ER diagram from an existing MySQL database?
&lt;/h4&gt;

&lt;p&gt;Yes. Connect the database and reverse engineer its tables, primary keys, and foreign keys into a diagram.&lt;/p&gt;

&lt;h4&gt;
  
  
  Can I design a schema before creating the database?
&lt;/h4&gt;

&lt;p&gt;Yes. VisuaLeaf and DbSchema let you plan the structure first, then generate the SQL later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Try two or three tools with the same database before choosing one. The best option is the one whose diagram, SQL editor, and everyday workflow make sense to you and your team.&lt;/p&gt;

&lt;p&gt;If you want to try the workflow used in this article, &lt;a href="https://visualeaf.com/download" rel="noopener noreferrer"&gt;download VisuaLeaf&lt;/a&gt; for free and connect it to your &lt;a href="https://visualeaf.com/database/mysql/" rel="noopener noreferrer"&gt;MySQL&lt;/a&gt;, &lt;a href="https://visualeaf.com/database/postgresql/" rel="noopener noreferrer"&gt;PostgreSQL&lt;/a&gt;, &lt;a href="https://visualeaf.com/database/mongodb-gui-tool/" rel="noopener noreferrer"&gt;MongoDB&lt;/a&gt;, or another supported database.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>database</category>
      <category>software</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Best MySQL Database Design Tools in 2026</title>
      <dc:creator>VisuaLeaf</dc:creator>
      <pubDate>Mon, 26 Oct 2026 12:57:02 +0000</pubDate>
      <link>https://dev.to/visualeaf/best-mysql-database-design-tools-in-2026-1po1</link>
      <guid>https://dev.to/visualeaf/best-mysql-database-design-tools-in-2026-1po1</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/roxana_haidiner/best-mysql-database-design-tools-in-2025-4f01" class="crayons-story__hidden-navigation-link"&gt;Best MySQL Database Design Tools in 2026&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/roxana_haidiner" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2224346%2Ff71cca7a-b282-4793-a3ae-b04d89957d47.png" alt="roxana_haidiner profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/roxana_haidiner" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Roxana-Maria Haidiner
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Roxana-Maria Haidiner
                
                
              
              &lt;div id="story-author-preview-content-2586545" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/roxana_haidiner" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2224346%2Ff71cca7a-b282-4793-a3ae-b04d89957d47.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Roxana-Maria Haidiner&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/roxana_haidiner/best-mysql-database-design-tools-in-2025-4f01" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jun 12 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/roxana_haidiner/best-mysql-database-design-tools-in-2025-4f01" id="article-link-2586545"&gt;
          Best MySQL Database Design Tools in 2026
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/mysql"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;mysql&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/database"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;database&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/designtools"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;designtools&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/databasemanagement"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;databasemanagement&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/roxana_haidiner/best-mysql-database-design-tools-in-2025-4f01" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;14&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/roxana_haidiner/best-mysql-database-design-tools-in-2025-4f01#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              3&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            9 min read
          &lt;/small&gt;
          
          &lt;div class="only-sidebar-menu-item trusted-visible-block print-hidden align-center"&gt;
                
                    
                
          &lt;/div&gt;

        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>Top Free ER Diagram Tools for PostgreSQL in 2026</title>
      <dc:creator>VisuaLeaf</dc:creator>
      <pubDate>Mon, 26 Oct 2026 12:47:31 +0000</pubDate>
      <link>https://dev.to/visualeaf/top-free-er-diagram-tools-for-postgresql-in-2026-5337</link>
      <guid>https://dev.to/visualeaf/top-free-er-diagram-tools-for-postgresql-in-2026-5337</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/roxana_haidiner/top-free-er-diagram-tools-for-postgresql-in-2025-22ga" class="crayons-story__hidden-navigation-link"&gt;Top Free ER Diagram Tools for PostgreSQL in 2026&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/roxana_haidiner" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2224346%2Ff71cca7a-b282-4793-a3ae-b04d89957d47.png" alt="roxana_haidiner profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/roxana_haidiner" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Roxana-Maria Haidiner
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Roxana-Maria Haidiner
                
                
              
              &lt;div id="story-author-preview-content-2686997" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/roxana_haidiner" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2224346%2Ff71cca7a-b282-4793-a3ae-b04d89957d47.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Roxana-Maria Haidiner&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/roxana_haidiner/top-free-er-diagram-tools-for-postgresql-in-2025-22ga" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jul 14 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/roxana_haidiner/top-free-er-diagram-tools-for-postgresql-in-2025-22ga" id="article-link-2686997"&gt;
          Top Free ER Diagram Tools for PostgreSQL in 2026
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/postgres"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;postgres&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/postgressql"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;postgressql&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/sql"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;sql&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/database"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;database&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/roxana_haidiner/top-free-er-diagram-tools-for-postgresql-in-2025-22ga" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;13&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/roxana_haidiner/top-free-er-diagram-tools-for-postgresql-in-2025-22ga#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              2&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            9 min read
          &lt;/small&gt;
          
          &lt;div class="only-sidebar-menu-item trusted-visible-block print-hidden align-center"&gt;
                
                    
                
          &lt;/div&gt;

        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>ER Diagram vs Database Schema: What’s the Difference?</title>
      <dc:creator>VisuaLeaf</dc:creator>
      <pubDate>Wed, 21 Oct 2026 13:35:54 +0000</pubDate>
      <link>https://dev.to/visualeaf/er-diagram-vs-database-schema-whats-the-difference-1g1k</link>
      <guid>https://dev.to/visualeaf/er-diagram-vs-database-schema-whats-the-difference-1g1k</guid>
      <description>&lt;p&gt;An &lt;a href="https://visualeaf.com/features/visual-schema/" rel="noopener noreferrer"&gt;ER diagram&lt;/a&gt; and a &lt;a href="https://visualeaf.com/blog/what-is-a-sql-database-schema-and-how-do-you-design-one/" rel="noopener noreferrer"&gt;database schema&lt;/a&gt; can look like the same thing at first. They are related, but they are used at different moments: first, you plan how the data should connect; then you create the real tables, columns, keys, and rules in the database.&lt;/p&gt;

&lt;p&gt;Think of a clinic named HyperClinic. This clinic needs to store patients, doctors, specializations, and appointments.&lt;/p&gt;

&lt;p&gt;A quick way to do this is to store everything in one table, called &lt;code&gt;appointments&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;appointment_id&lt;/td&gt;
&lt;td&gt;patient_name&lt;/td&gt;
&lt;td&gt;patient_phone&lt;/td&gt;
&lt;td&gt;doctor_name&lt;/td&gt;
&lt;td&gt;specialization&lt;/td&gt;
&lt;td&gt;appointment_time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;Marit Solheim&lt;/td&gt;
&lt;td&gt;555-0147&lt;/td&gt;
&lt;td&gt;Owen Wilson&lt;/td&gt;
&lt;td&gt;Cardiology&lt;/td&gt;
&lt;td&gt;2026-10-20 10:00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This approach works for one row, but it causes problems when you add another appointment for Marit, change Dr. Wilson's specialization, or have two patients with the same name.&lt;/p&gt;

&lt;p&gt;The patient information is repeated. The doctor information is repeated. A typo can lead to inconsistency. And nothing prevents creating an appointment for a doctor who does not exist.&lt;/p&gt;

&lt;p&gt;To avoid these problems, the team needs to plan the database before creating tables.&lt;/p&gt;

&lt;h2&gt;
  
  
  ER Diagram vs Database Schema
&lt;/h2&gt;

&lt;p&gt;The key point of differentiation is not the shape of the boxes and arrows. It is the stage of development.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Stage&lt;/td&gt;
&lt;td&gt;Key question&lt;/td&gt;
&lt;td&gt;Objects defined&lt;/td&gt;
&lt;td&gt;What the diagram shows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Conceptual ER diagram&lt;/td&gt;
&lt;td&gt;What does the company need to store?&lt;/td&gt;
&lt;td&gt;Entities and relationships&lt;/td&gt;
&lt;td&gt;Boxes and lines, no fields&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Logical data model&lt;/td&gt;
&lt;td&gt;How should the data be structured?&lt;/td&gt;
&lt;td&gt;Attributes, keys, normalized relationships&lt;/td&gt;
&lt;td&gt;Fields with key icons and relationship lines, no data types&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Physical database schema&lt;/td&gt;
&lt;td&gt;How will PostgreSQL implement it?&lt;/td&gt;
&lt;td&gt;Tables, columns, data types, constraints, indexes, and DDL statements&lt;/td&gt;
&lt;td&gt;Fields with data types&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A *&lt;strong&gt;&lt;em&gt;schema diagram&lt;/em&gt;&lt;/strong&gt;* is a graphic representation of the database schema. Such diagrams are also referred to as &lt;a href="https://www.geeksforgeeks.org/dbms/introduction-of-er-model/" rel="noopener noreferrer"&gt;physical ER diagrams.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.geeksforgeeks.org/dbms/database-schemas/" rel="noopener noreferrer"&gt;A *&lt;strong&gt;&lt;em&gt;database schema&lt;/em&gt;&lt;/strong&gt;*&lt;/a&gt; is the actual structure stored in PostgreSQL and comprises objects such as tables, columns, primary and foreign keys, constraints, and indexes.&lt;/p&gt;

&lt;p&gt;Thus, this is not a binary choice. A database design is typically developed from an ER diagram through the logical data model to the database schema.&lt;/p&gt;

&lt;p&gt;In this article, we will build HyperClinic in the same order: Step 1 is the conceptual ER diagram, Steps 2 to 4 build the logical model, and Step 5 creates the physical PostgreSQL schema.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the Requirements
&lt;/h2&gt;

&lt;p&gt;Nobody will start writing &lt;code&gt;CREATE TABLE&lt;/code&gt; before the team figures out the task.&lt;/p&gt;

&lt;p&gt;In our case, HyperClinic must be able to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;save patient information&lt;/li&gt;
&lt;li&gt;save doctors and their specializations&lt;/li&gt;
&lt;li&gt;create appointments&lt;/li&gt;
&lt;li&gt;show which patient sees which doctor&lt;/li&gt;
&lt;li&gt;ensure appointments refer to existing patients and doctors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The job names depend on the company. Requirements may be gathered by a business analyst and designed by a database architect, data modeler, backend developer, or DBA. In a small company, one person may do several of these jobs.&lt;/p&gt;

&lt;p&gt;What is important is the workflow, not the name.&lt;/p&gt;

&lt;p&gt;The team should ask itself such questions as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is it possible for one patient to have several appointments?&lt;/li&gt;
&lt;li&gt;Is it possible for one doctor to have many patients?&lt;/li&gt;
&lt;li&gt;Do all doctors have only one specialization?&lt;/li&gt;
&lt;li&gt;Is it possible to create an appointment without a doctor?&lt;/li&gt;
&lt;li&gt;Is it possible to create two appointments with the same doctor for the same time?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are the business rules that PostgreSQL won't be able to guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Create the Conceptual ER Diagram
&lt;/h2&gt;

&lt;p&gt;Before deciding on columns, data types, or SQL, first identify what HyperClinic needs to store:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Patient&lt;/li&gt;
&lt;li&gt;Doctor&lt;/li&gt;
&lt;li&gt;Appointment&lt;/li&gt;
&lt;li&gt;Specialization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An *&lt;strong&gt;&lt;em&gt;entity&lt;/em&gt;&lt;/strong&gt;* is simply something the system needs to store.&lt;/p&gt;

&lt;p&gt;This is a *&lt;strong&gt;&lt;em&gt;conceptual ER diagram&lt;/em&gt;&lt;/strong&gt;*. It helps the team see the big picture without getting into implementation details. The boxes show the entities. The lines show their relationships. There are no columns, primary keys, foreign keys, or PostgreSQL data types yet.&lt;/p&gt;

&lt;p&gt;The relationships are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A patient has many appointments.&lt;/li&gt;
&lt;li&gt;A doctor has many appointments.&lt;/li&gt;
&lt;li&gt;An appointment is made by one patient and one doctor.&lt;/li&gt;
&lt;li&gt;A specialization has many doctors.&lt;/li&gt;
&lt;li&gt;Each doctor has one specialization.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh5u0oiknufw8i86j8zhq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh5u0oiknufw8i86j8zhq.png" alt=" " width="800" height="499"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Add Attributes and Primary Keys
&lt;/h2&gt;

&lt;p&gt;This is the point where the model becomes *&lt;strong&gt;&lt;em&gt;logical&lt;/em&gt;&lt;/strong&gt;*.&lt;/p&gt;

&lt;p&gt;The conceptual diagram tells us that HyperClinic has patients, doctors, appointments, and specializations. The logical model answers the next question: *&lt;strong&gt;&lt;em&gt;what information belongs to each one?&lt;/em&gt;&lt;/strong&gt;*&lt;/p&gt;

&lt;p&gt;You now add attributes, choose identifiers, and organize the data so the same fact is not stored in several places. You are still designing the structure. You are not choosing PostgreSQL types such as &lt;code&gt;VARCHAR(100)&lt;/code&gt; or &lt;code&gt;TIMESTAMP&lt;/code&gt; yet.&lt;/p&gt;

&lt;p&gt;Think of it like planning a house:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the conceptual diagram says the house needs a kitchen, bedrooms, and a bathroom;&lt;/li&gt;
&lt;li&gt;the logical model decides what belongs in each room;&lt;/li&gt;
&lt;li&gt;the physical schema gives the builder the exact materials and measurements.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The logical model takes three steps: attributes and primary keys (this step), normalization (Step 3), and foreign keys (Step 4).&lt;/p&gt;

&lt;p&gt;Start with the attributes. For each entity, write down what the system needs to know about it:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Entity&lt;/td&gt;
&lt;td&gt;Attributes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Patient&lt;/td&gt;
&lt;td&gt;first name, last name, date of birth, phone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Doctor&lt;/td&gt;
&lt;td&gt;first name, last name, specialization&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Specialization&lt;/td&gt;
&lt;td&gt;name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Appointment&lt;/td&gt;
&lt;td&gt;patient, doctor, date and time, reason, status&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Then give every entity a *&lt;strong&gt;&lt;em&gt;primary key&lt;/em&gt;&lt;/strong&gt;*. A primary key is a column, or a set of columns, that uniquely identifies a row.&lt;/p&gt;

&lt;p&gt;For instance, &lt;code&gt;patient_id = 1&lt;/code&gt; must identify only one patient. A name cannot be used as a primary key, as patients can have the same name and names change over time.&lt;/p&gt;

&lt;p&gt;The same holds for doctors, specializations, and appointments: &lt;code&gt;doctor_id&lt;/code&gt;, &lt;code&gt;specialization_id&lt;/code&gt;, and &lt;code&gt;appointment_id&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Normalize the Logical Model
&lt;/h2&gt;

&lt;p&gt;Normalization means organizing data so each fact is stored in the right place, instead of being repeated in several places.&lt;/p&gt;

&lt;p&gt;You do not finish the ER diagram and normalize it later as a separate task. Usually, you draw the entities, test the structure, notice repeated data, and improve the model.&lt;/p&gt;

&lt;p&gt;The examples below start from a flat table. The services are only for illustration: the final HyperClinic model does not include them.&lt;/p&gt;

&lt;h3&gt;
  
  
  First Normal Form: One Value per Field
&lt;/h3&gt;

&lt;p&gt;First normal form, or *&lt;strong&gt;&lt;em&gt;1NF&lt;/em&gt;&lt;/strong&gt;*, means that one field should contain one value, not a list.&lt;/p&gt;

&lt;p&gt;× &lt;strong&gt;Not in 1NF:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;appointment_id&lt;/td&gt;
&lt;td&gt;patient_name&lt;/td&gt;
&lt;td&gt;doctor_name&lt;/td&gt;
&lt;td&gt;services&lt;/td&gt;
&lt;td&gt;appointment_at&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;Marit Solheim&lt;/td&gt;
&lt;td&gt;Owen Wilson&lt;/td&gt;
&lt;td&gt;Consultation, ECG&lt;/td&gt;
&lt;td&gt;2026-10-20 10:00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;services&lt;/code&gt; field contains two values. This makes the data harder to filter, validate, or update.&lt;/p&gt;

&lt;p&gt;✓ &lt;strong&gt;In 1NF:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;appointment_id&lt;/td&gt;
&lt;td&gt;patient_name&lt;/td&gt;
&lt;td&gt;doctor_name&lt;/td&gt;
&lt;td&gt;service&lt;/td&gt;
&lt;td&gt;appointment_at&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;Marit Solheim&lt;/td&gt;
&lt;td&gt;Owen Wilson&lt;/td&gt;
&lt;td&gt;Consultation&lt;/td&gt;
&lt;td&gt;2026-10-20 10:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;Marit Solheim&lt;/td&gt;
&lt;td&gt;Owen Wilson&lt;/td&gt;
&lt;td&gt;ECG&lt;/td&gt;
&lt;td&gt;2026-10-20 10:00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each row now contains one service. However, the appointment, patient, and doctor information is repeated. The next step fixes that.&lt;/p&gt;

&lt;h3&gt;
  
  
  Second Normal Form: Each Field Depends on the Whole Key
&lt;/h3&gt;

&lt;p&gt;Second normal form, or &lt;strong&gt;2NF&lt;/strong&gt;, applies when a table uses a combined key.&lt;/p&gt;

&lt;p&gt;Here, one appointment can have more than one service. The combination of &lt;code&gt;appointment_id&lt;/code&gt; and &lt;code&gt;service&lt;/code&gt; identifies one row.&lt;/p&gt;

&lt;p&gt;× &lt;strong&gt;Not in 2NF:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;appointment_id&lt;/td&gt;
&lt;td&gt;service&lt;/td&gt;
&lt;td&gt;patient_name&lt;/td&gt;
&lt;td&gt;doctor_name&lt;/td&gt;
&lt;td&gt;appointment_at&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;Consultation&lt;/td&gt;
&lt;td&gt;Marit Solheim&lt;/td&gt;
&lt;td&gt;Owen Wilson&lt;/td&gt;
&lt;td&gt;2026-10-20 10:00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;ECG&lt;/td&gt;
&lt;td&gt;Marit Solheim&lt;/td&gt;
&lt;td&gt;Owen Wilson&lt;/td&gt;
&lt;td&gt;2026-10-20 10:00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;patient_name&lt;/code&gt;, &lt;code&gt;doctor_name&lt;/code&gt;, and &lt;code&gt;appointment_at&lt;/code&gt; values depend only on &lt;code&gt;appointment_id&lt;/code&gt;. They do not depend on the full combination of &lt;code&gt;appointment_id&lt;/code&gt; and &lt;code&gt;service&lt;/code&gt;, so they are repeated.&lt;/p&gt;

&lt;p&gt;✓ &lt;strong&gt;In 2NF:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;appointments&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;appointment_id&lt;/td&gt;
&lt;td&gt;patient_name&lt;/td&gt;
&lt;td&gt;doctor_name&lt;/td&gt;
&lt;td&gt;appointment_at&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;Marit Solheim&lt;/td&gt;
&lt;td&gt;Owen Wilson&lt;/td&gt;
&lt;td&gt;2026-10-20 10:00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;appointment_services&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;appointment_id&lt;/td&gt;
&lt;td&gt;service&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;Consultation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;ECG&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Now the appointment information is stored once. The &lt;code&gt;appointment_services&lt;/code&gt; table only stores the services linked to that appointment. The patient and doctor names are still text here. Step 4 replaces them with IDs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Third Normal Form: Do Not Repeat Details That Belong Elsewhere
&lt;/h3&gt;

&lt;p&gt;Third normal form, or &lt;strong&gt;3NF&lt;/strong&gt;, removes indirect repetition.&lt;/p&gt;

&lt;p&gt;× &lt;strong&gt;Not in 3NF:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;doctor_id&lt;/td&gt;
&lt;td&gt;first_name&lt;/td&gt;
&lt;td&gt;last_name&lt;/td&gt;
&lt;td&gt;specialization_id&lt;/td&gt;
&lt;td&gt;specialization_name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Owen&lt;/td&gt;
&lt;td&gt;Wilson&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Cardiology&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Odile&lt;/td&gt;
&lt;td&gt;Marchetti&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Cardiology&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The specialization name depends on &lt;code&gt;specialization_id&lt;/code&gt;, not on the doctor. Storing it in &lt;code&gt;doctors&lt;/code&gt; means it will be repeated for every cardiologist.&lt;/p&gt;

&lt;p&gt;✓ &lt;strong&gt;In 3NF:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;doctors&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;doctor_id&lt;/td&gt;
&lt;td&gt;first_name&lt;/td&gt;
&lt;td&gt;last_name&lt;/td&gt;
&lt;td&gt;specialization_id&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Owen&lt;/td&gt;
&lt;td&gt;Wilson&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Odile&lt;/td&gt;
&lt;td&gt;Marchetti&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;specializations&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;specialization_id&lt;/td&gt;
&lt;td&gt;specialization_name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Cardiology&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Now &lt;code&gt;Cardiology&lt;/code&gt; is stored once. If the clinic changes its name, the team updates one row in &lt;code&gt;specializations&lt;/code&gt;, not every doctor.&lt;/p&gt;

&lt;p&gt;After normalization, each table holds only its own facts:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Table&lt;/td&gt;
&lt;td&gt;Example Columns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;patients&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;patient_id&lt;/code&gt;, &lt;code&gt;first_name&lt;/code&gt;, &lt;code&gt;last_name&lt;/code&gt;, &lt;code&gt;date_of_birth&lt;/code&gt;, &lt;code&gt;phone&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;doctors&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;doctor_id&lt;/code&gt;, &lt;code&gt;first_name&lt;/code&gt;, &lt;code&gt;last_name&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;specializations&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;specialization_id&lt;/code&gt;, &lt;code&gt;specialization_name&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;appointments&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;appointment_id&lt;/code&gt;, &lt;code&gt;appointment_at&lt;/code&gt;, &lt;code&gt;reason&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The lines show which tables are related, but the link columns, such as &lt;code&gt;specialization_id&lt;/code&gt; in &lt;code&gt;doctors&lt;/code&gt;, are not here yet. They come in the next step.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1cbucz79blqydqvzwt53.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1cbucz79blqydqvzwt53.png" alt=" " width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Link Related Tables with Foreign Keys
&lt;/h2&gt;

&lt;p&gt;To connect two tables, you add a column to one of them. That column is a &lt;strong&gt;foreign key&lt;/strong&gt;: it stores the ID of a row in the other table. This is how a relationship is made.&lt;/p&gt;

&lt;p&gt;An appointment should not store redundant information like a patient's name, phone, and date of birth.&lt;/p&gt;

&lt;p&gt;Instead, it stores the ID of the linked patient:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;appointment_id: 16
patient_id: 1
doctor_id: 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The field &lt;code&gt;appointments.patient_id&lt;/code&gt; is a &lt;strong&gt;foreign key&lt;/strong&gt; that references &lt;code&gt;patients.patient_id&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The field &lt;code&gt;appointments.doctor_id&lt;/code&gt; links to &lt;code&gt;doctors.doctor_id&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The field &lt;code&gt;doctors.specialization_id&lt;/code&gt; links to &lt;code&gt;specializations.specialization_id&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A foreign key performs two functions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It describes the relationship between tables.&lt;/li&gt;
&lt;li&gt;It prevents invalid references.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Without a foreign key, you can insert an appointment with &lt;code&gt;patient_id = 9999&lt;/code&gt;, even if patient 9999 does not exist.&lt;/p&gt;

&lt;p&gt;If there is a foreign key, PostgreSQL rejects this record.&lt;/p&gt;

&lt;p&gt;A column named &lt;code&gt;patient_id&lt;/code&gt; alone does not create a foreign key. The &lt;code&gt;REFERENCES&lt;/code&gt; constraint creates it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7b1uoqrcndm3jvnvew5r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7b1uoqrcndm3jvnvew5r.png" alt=" " width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The diagram now shows the foreign key columns, marked with a link icon, and an arrow for each relationship. There are still no data types. Types depend on the database engine, so they come in the physical stage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Define the Physical PostgreSQL Model
&lt;/h2&gt;

&lt;p&gt;The physical model describes the PostgreSQL tables, columns, data types, and rules that you want to create.&lt;/p&gt;

&lt;p&gt;In this example, &lt;code&gt;INT&lt;/code&gt; is used for IDs (the diagram shows it as &lt;code&gt;INTEGER&lt;/code&gt;, which is the same type), &lt;code&gt;VARCHAR&lt;/code&gt; for text, &lt;code&gt;DATE&lt;/code&gt; for a date, and &lt;code&gt;TIMESTAMP&lt;/code&gt; for a date and time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9rl4t4co7gujryfap3cr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9rl4t4co7gujryfap3cr.png" alt=" " width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Create the Database Schema
&lt;/h3&gt;

&lt;p&gt;The physical model is ready. Now you need to create it on the PostgreSQL server.&lt;/p&gt;

&lt;p&gt;You can do this in either of two ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run the &lt;code&gt;CREATE SCHEMA&lt;/code&gt; and &lt;code&gt;CREATE TABLE&lt;/code&gt; statements in the &lt;a href="https://visualeaf.com/docs/sql-editor" rel="noopener noreferrer"&gt;SQL Editor.&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The following statements are DDL, short for Data Definition Language. DDL is the SQL used to create the database structure.&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;SCHEMA&lt;/span&gt; &lt;span class="n"&gt;clinic&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;clinic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;specializations&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;specialization_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;GENERATED&lt;/span&gt; &lt;span class="n"&gt;ALWAYS&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;IDENTITY&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;specialization_name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&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;UNIQUE&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;clinic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctors&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;doctor_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;GENERATED&lt;/span&gt; &lt;span class="n"&gt;ALWAYS&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;IDENTITY&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;specialization_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;clinic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;specializations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;specialization_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;first_name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&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;span class="n"&gt;last_name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&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;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;clinic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patients&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;patient_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;GENERATED&lt;/span&gt; &lt;span class="n"&gt;ALWAYS&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;IDENTITY&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;first_name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&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;span class="n"&gt;last_name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&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;span class="n"&gt;date_of_birth&lt;/span&gt; &lt;span class="nb"&gt;DATE&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;span class="n"&gt;phone&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&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="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;clinic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;appointments&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;appointment_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;GENERATED&lt;/span&gt; &lt;span class="n"&gt;ALWAYS&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;IDENTITY&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;patient_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;clinic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patients&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;patient_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;doctor_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;clinic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doctors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doctor_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;appointment_at&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;span class="n"&gt;reason&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&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;span class="n"&gt;status&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&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="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="s1"&gt;'scheduled'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;appointments_status_check&lt;/span&gt;
    &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'scheduled'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'completed'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'cancelled'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is how you can create it in VisuaLeaf's SQL Editor:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fphi5sfln1xcp637a3az7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fphi5sfln1xcp637a3az7.png" alt=" " width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You can also use &lt;a href="https://visualeaf.com/docs/sql-visual-schema" rel="noopener noreferrer"&gt;&lt;strong&gt;Materialize&lt;/strong&gt;&lt;/a&gt; instead of running the SQL yourself. Starting with an empty database, VisuaLeaf creates the &lt;code&gt;clinic&lt;/code&gt; schema and its tables directly from the physical model.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Materialize can also apply later changes. In this example, the four original clinic tables already exist, so they are marked as up to date while the new &lt;code&gt;lab_results&lt;/code&gt; table is created.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffqie5j56am7tach4ewtp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffqie5j56am7tach4ewtp.png" alt=" " width="800" height="590"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Use one method to create the original schema. If you run the same &lt;code&gt;CREATE TABLE&lt;/code&gt; statements after the tables already exist, PostgreSQL returns an “already exists” error.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: View the Created Database Schema
&lt;/h2&gt;

&lt;p&gt;After either option succeeds, open Schema Manager and select the &lt;code&gt;clinic&lt;/code&gt; schema.&lt;/p&gt;

&lt;p&gt;You can now see the &lt;code&gt;patients&lt;/code&gt;, &lt;code&gt;appointments&lt;/code&gt;, &lt;code&gt;doctors&lt;/code&gt;, and &lt;code&gt;specializations&lt;/code&gt; tables on the PostgreSQL server. This is the implemented database schema: it is no longer only a model or a plan.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdsw3qhodtuislo5epu04.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdsw3qhodtuislo5epu04.png" alt=" " width="800" height="550"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This schema contains several constraints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;NOT NULL&lt;/code&gt; requires a value.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PRIMARY KEY&lt;/code&gt; makes each ID unique.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;REFERENCES&lt;/code&gt; creates foreign-key relationships.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;UNIQUE&lt;/code&gt; prevents duplicate specialization names.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DEFAULT 'scheduled'&lt;/code&gt; gives new appointments an initial status.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CHECK&lt;/code&gt; prevents unsupported status values.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, PostgreSQL will reject this appointment if patient 9999 does not exist:&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;clinic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;appointments&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;patient_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;doctor_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;appointment_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;reason&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;9999&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'2026-11-20 10:00'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'Annual check-up'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PostgreSQL returns an error like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ERROR:  insert or update on table "appointments" violates foreign key constraint "appointments_patient_id_fkey"
DETAIL:  Key (patient_id)=(9999) is not present in table "patients".
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the difference between drawing a relationship in a diagram and enforcing it in the database.&lt;/p&gt;

&lt;h2&gt;
  
  
  ER Diagram vs Database Schema: The Difference
&lt;/h2&gt;

&lt;p&gt;The conceptual, logical, and physical models are the steps between the first idea and the finished database.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;ER diagram&lt;/th&gt;
&lt;th&gt;Database schema&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Main job&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Plans what data the system needs and how it connects&lt;/td&gt;
&lt;td&gt;Stores the real database structure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Includes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Entities, attributes, and relationships&lt;/td&gt;
&lt;td&gt;Tables, columns, data types, keys, constraints, and indexes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;In this example&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The HyperClinic models before the schema is created&lt;/td&gt;
&lt;td&gt;The &lt;code&gt;clinic&lt;/code&gt; schema after the SQL is run or the model is materialized&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Where you see it&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;In the diagram designer&lt;/td&gt;
&lt;td&gt;In PostgreSQL and Schema Manager&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;An ER diagram helps the team decide what to build. The database schema is the structure PostgreSQL creates from that plan.&lt;/p&gt;

&lt;p&gt;The tool used for this demonstration is &lt;a href="https://visualeaf.com/download" rel="noopener noreferrer"&gt;VisuaLeaf&lt;/a&gt;. You can download it for free and follow the same steps: design the model, create the schema, and view the tables and relationships in one place.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>database</category>
      <category>software</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>What changed in MongoDB 9.0, and how can it help in everyday database work? Practical examples in VisuaLeaf show the new behavior and compare results with MongoDB 8.2.5.</title>
      <dc:creator>VisuaLeaf</dc:creator>
      <pubDate>Wed, 14 Oct 2026 17:43:06 +0000</pubDate>
      <link>https://dev.to/visualeaf/what-changed-in-mongodb-90-and-how-can-it-help-in-everyday-database-work-practical-examples-in-40ih</link>
      <guid>https://dev.to/visualeaf/what-changed-in-mongodb-90-and-how-can-it-help-in-everyday-database-work-practical-examples-in-40ih</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/roxana_haidiner/whats-new-in-mongodb-90-query-settings-validation-and-resource-limits-h1k" class="crayons-story__hidden-navigation-link"&gt;What's New in MongoDB 9.0: Query Settings, Validation, and Resource Limits&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/roxana_haidiner" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2224346%2Ff71cca7a-b282-4793-a3ae-b04d89957d47.png" alt="roxana_haidiner profile" class="crayons-avatar__image" width="512" height="512"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/roxana_haidiner" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Roxana-Maria Haidiner
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Roxana-Maria Haidiner
                
                
              
              &lt;div id="story-author-preview-content-4851574" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/roxana_haidiner" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2224346%2Ff71cca7a-b282-4793-a3ae-b04d89957d47.png" class="crayons-avatar__image" alt="" width="512" height="512"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Roxana-Maria Haidiner&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/roxana_haidiner/whats-new-in-mongodb-90-query-settings-validation-and-resource-limits-h1k" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Oct 14&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/roxana_haidiner/whats-new-in-mongodb-90-query-settings-validation-and-resource-limits-h1k" id="article-link-4851574"&gt;
          What's New in MongoDB 9.0: Query Settings, Validation, and Resource Limits
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/mongodb"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;mongodb&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/nosql"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;nosql&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/database"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;database&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/softwaredevelopment"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;softwaredevelopment&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/roxana_haidiner/whats-new-in-mongodb-90-query-settings-validation-and-resource-limits-h1k" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;4&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/roxana_haidiner/whats-new-in-mongodb-90-query-settings-validation-and-resource-limits-h1k#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            10 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>How to Open a SQLite Database: Query and Explore Your Data</title>
      <dc:creator>VisuaLeaf</dc:creator>
      <pubDate>Wed, 14 Oct 2026 17:36:15 +0000</pubDate>
      <link>https://dev.to/visualeaf/how-to-open-a-sqlite-database-query-and-explore-your-data-275d</link>
      <guid>https://dev.to/visualeaf/how-to-open-a-sqlite-database-query-and-explore-your-data-275d</guid>
      <description>&lt;p&gt;You have a SQLite file and want to see the information inside it. To open a SQLite database, select the file in software that can read SQLite, such as &lt;a href="https://visualeaf.com" rel="noopener noreferrer"&gt;VisuaLeaf&lt;/a&gt;. You can then browse the tables and use SQL to find the information you need.&lt;/p&gt;

&lt;p&gt;SQL is the language you use to tell a database what you want. A query is one of those requests. It might ask for ten inspection records, the name of a technician, or the number of checks that failed.&lt;/p&gt;

&lt;p&gt;This guide uses an example inspection database. You’ll open the file, browse its tables, join related records, read JSON values, export results to Excel, and create a chart. If you follow along with your own database, replace the example table and column names with yours.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, understand what is inside the file
&lt;/h2&gt;

&lt;p&gt;SQLite stores data in a database file. You don't need to set up a separate database server to open it.&lt;/p&gt;

&lt;p&gt;Inside the file, the data is organised into *&lt;strong&gt;&lt;em&gt;tables&lt;/em&gt;&lt;/strong&gt;*. A table looks a little like a spreadsheet: it has rows and columns.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5u5x1phg182i35aajvbu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5u5x1phg182i35aajvbu.png" alt="VisuaLeaf Split View showing the SQLite inspections table structure beside a data grid with five rows." width="800" height="388"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;inspections&lt;/code&gt; table’s columns on the left and five inspection records on the right.&lt;/p&gt;

&lt;p&gt;The information is split across several tables:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;sites&lt;/code&gt; holds details about the places being inspected,
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;technicians&lt;/code&gt; holds details about the people doing the inspections,
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;inspection_items&lt;/code&gt; holds the results of individual checks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An inspection can therefore have one row describing the visit and several other rows describing its checklist items.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open your SQLite database in VisuaLeaf
&lt;/h2&gt;

&lt;p&gt;SQLite files often end in &lt;code&gt;.db&lt;/code&gt;, &lt;code&gt;.sqlite&lt;/code&gt;, or &lt;code&gt;.sqlite3&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://visualeaf.com/database/sqlite/" rel="noopener noreferrer"&gt;VisuaLeaf&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a connection and choose SQLite.&lt;/li&gt;
&lt;li&gt;Select your existing database file.&lt;/li&gt;
&lt;li&gt;Connect, then expand &lt;code&gt;main&lt;/code&gt; and *&lt;strong&gt;&lt;em&gt;Tables&lt;/em&gt;&lt;/strong&gt;* in the sidebar.&lt;/li&gt;
&lt;li&gt;Open a table to see its records.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You select a file instead of entering a server address, port, or database username. If your database is already connected, go straight to its tables.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;main&lt;/code&gt; is SQLite's name for the primary database opened by that connection. You don't need to create another database with that name.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fulczsn7mq6pfiorlzcha.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fulczsn7mq6pfiorlzcha.png" alt="VisuaLeaf SQLite connection form showing the database file path and a successful connection test." width="800" height="713"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Connecting to the local &lt;code&gt;field_inspections.sqlite&lt;/code&gt; database in VisuaLeaf.&lt;/p&gt;

&lt;p&gt;If you don’t have a SQLite database yet, click *&lt;strong&gt;&lt;em&gt;New DB&lt;/em&gt;&lt;/strong&gt;* in the connection form to create one. It starts empty, so you can add your own tables and data. The examples below use our existing inspection database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read a few inspections with SQL
&lt;/h2&gt;

&lt;p&gt;Open &lt;code&gt;inspections&lt;/code&gt; in the data view to browse its rows. You can also choose exactly which columns to display using the SQL Editor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT id, status, risk_score
FROM inspections
ORDER BY id
LIMIT 10;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read this query one line at a time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SELECT id, status, risk_score&lt;/code&gt; chooses the three columns to show.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;FROM inspections&lt;/code&gt; tells SQLite which table to read.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ORDER BY id&lt;/code&gt; sorts the records by their identifier, from smallest to largest.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;LIMIT 10&lt;/code&gt; asks for at most ten rows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This query reads data. It doesn't change the records.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd9iq6miyj0gif7fvleq6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd9iq6miyj0gif7fvleq6.png" alt="VisuaLeaf SQLite SQL Editor showing a SELECT query above ten inspection rows with id, status, and risk_score columns." width="800" height="616"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Reading ten inspection records, ordered by ID, with their statuses and risk scores.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;id&lt;/code&gt; column is the inspection's *&lt;strong&gt;&lt;em&gt;primary key&lt;/em&gt;&lt;/strong&gt;*: the value that identifies that row. Keep it in your results so you can find the same inspection again.&lt;/p&gt;

&lt;p&gt;Look at the values before writing a filter. A column called &lt;code&gt;status&lt;/code&gt; doesn't tell you which words the application stores there. Check the records rather than guessing whether a status is called &lt;code&gt;complete&lt;/code&gt;, &lt;code&gt;completed&lt;/code&gt;, or something else.&lt;/p&gt;

&lt;h2&gt;
  
  
  What do the column types mean?
&lt;/h2&gt;

&lt;p&gt;Open Table Management for &lt;code&gt;sites&lt;/code&gt;. It shows how each column is defined.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F732jo46emivb5oiednsj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F732jo46emivb5oiednsj.png" alt="VisuaLeaf Table Management showing the SQLite sites table with INTEGER, TEXT, and REAL columns, primary key id, and a foreign key linking client_id to clients.id." width="800" height="662"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;sites&lt;/code&gt; table in VisuaLeaf, showing each column’s data type, whether it allows missing values (&lt;code&gt;NULL&lt;/code&gt;), and its default value.&lt;/p&gt;

&lt;p&gt;The screenshot includes three common SQLite types:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Data type&lt;/th&gt;
&lt;th&gt;What it stores&lt;/th&gt;
&lt;th&gt;Example columns&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;INTEGER&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Whole numbers, such as 1, 2, or 3&lt;/td&gt;
&lt;td&gt;&lt;code&gt;id&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TEXT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Text, such as names and cities&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;name&lt;/code&gt;, &lt;code&gt;city&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;REAL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Numbers with decimals, such as 44.43&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;latitude&lt;/code&gt;, &lt;code&gt;longitude&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;active&lt;/code&gt; column has a *&lt;strong&gt;&lt;em&gt;default&lt;/em&gt;&lt;/strong&gt;* of &lt;code&gt;1&lt;/code&gt;. That means SQLite supplies &lt;code&gt;1&lt;/code&gt; when a new row leaves this column out of the insert.&lt;/p&gt;

&lt;p&gt;A default alone also allows someone to enter another value. To restrict &lt;code&gt;active&lt;/code&gt; to &lt;code&gt;0&lt;/code&gt; and &lt;code&gt;1&lt;/code&gt;, the table would need a *&lt;strong&gt;&lt;em&gt;constraint&lt;/em&gt;&lt;/strong&gt;*, which is a rule that data must follow. For example, &lt;code&gt;CHECK (active IN (0, 1))&lt;/code&gt; checks that the value is one of those two numbers. A separate &lt;code&gt;NOT NULL&lt;/code&gt; rule rejects a missing value.&lt;/p&gt;

&lt;p&gt;Click *&lt;strong&gt;&lt;em&gt;Show DDL&lt;/em&gt;&lt;/strong&gt;* to see the SQL that defines the &lt;code&gt;sites&lt;/code&gt; table. DDL means &lt;strong&gt;Data Definition Language&lt;/strong&gt;. This definition shows the column types and rules, such as primary keys, foreign keys, &lt;code&gt;NOT NULL&lt;/code&gt;, and default values.&lt;/p&gt;

&lt;p&gt;You can also inspect the table's *&lt;strong&gt;&lt;em&gt;indexes&lt;/em&gt;&lt;/strong&gt;*. An index is a lookup structure, a little like the index at the back of a book. It can help SQLite find certain values without reading every row. Having an index on a column does not automatically connect it to another table.&lt;/p&gt;

&lt;p&gt;SQLite is flexible with data types. In a regular table, a column marked &lt;code&gt;INTEGER&lt;/code&gt; may still accept text. The declared type tells you what the column is intended to store, but it does not always enforce that type.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understand foreign keys and join related tables
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;inspections&lt;/code&gt; table stores &lt;code&gt;site_id&lt;/code&gt; and &lt;code&gt;technician_id&lt;/code&gt; to identify where each inspection took place and who performed it. The site and technician details live in their own tables.&lt;/p&gt;

&lt;p&gt;A *&lt;strong&gt;&lt;em&gt;primary key&lt;/em&gt;&lt;/strong&gt;* identifies a specific row. For example, &lt;code&gt;sites.id&lt;/code&gt; identifies each site.&lt;/p&gt;

&lt;p&gt;A *&lt;strong&gt;&lt;em&gt;foreign key&lt;/em&gt;&lt;/strong&gt;* is a rule that connects a value in one table to a matching key in another. Here, &lt;code&gt;inspections.site_id&lt;/code&gt; refers to &lt;code&gt;sites.id&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If an inspection has &lt;code&gt;site_id = 7&lt;/code&gt;, it belongs to the site whose &lt;code&gt;id&lt;/code&gt; is &lt;code&gt;7&lt;/code&gt;. You can look up that site’s name and city in &lt;code&gt;sites&lt;/code&gt;, without copying those details into every inspection row. Several inspections can have the same &lt;code&gt;site_id&lt;/code&gt; because you can inspect the same site more than once.&lt;/p&gt;

&lt;p&gt;The same idea applies to &lt;code&gt;technician_id&lt;/code&gt;: it refers to the technician’s &lt;code&gt;id&lt;/code&gt; in &lt;code&gt;technicians&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://visualeaf.com/features/visual-schema/" rel="noopener noreferrer"&gt;ER diagram&lt;/a&gt; shows these connections as lines between the tables.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn61o13tu4w7s8xiqfk27.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn61o13tu4w7s8xiqfk27.png" alt="VisuaLeaf ER diagram linking inspections.site_id to sites.id and inspections.technician_id to technicians.id, with foreign key settings and an open delete-action menu." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Foreign keys connect each inspection to its site and technician. The diagram shows these relationships and the available rules for handling deletions.&lt;/p&gt;

&lt;p&gt;A *&lt;strong&gt;&lt;em&gt;join&lt;/em&gt;&lt;/strong&gt;* uses those matching values to bring information from different tables into one result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT
    inspections.id,
    sites.name AS site,
    technicians.full_name AS technician,
    inspections.risk_score
FROM inspections
LEFT JOIN sites
    ON inspections.site_id = sites.id
LEFT JOIN technicians
    ON inspections.technician_id = technicians.id
ORDER BY inspections.id
LIMIT 10;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A name such as &lt;code&gt;sites.name&lt;/code&gt; means “the &lt;code&gt;name&lt;/code&gt; column in the &lt;code&gt;sites&lt;/code&gt; table.” Writing the table name first makes it clear which table we mean.&lt;/p&gt;

&lt;p&gt;The query starts with inspections, matches each one to its site, and then matches it to its technician. The result puts the inspection ID, both names, and the risk score together.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;LEFT JOIN&lt;/code&gt; keeps the inspection in the result even if a matching site or technician is missing. SQLite shows &lt;code&gt;NULL&lt;/code&gt; for the missing information. &lt;code&gt;NULL&lt;/code&gt; means no value. A normal inner &lt;code&gt;JOIN&lt;/code&gt; would leave out inspections without a match.&lt;/p&gt;

&lt;p&gt;A null name alone doesn't prove a record is missing: the related row could exist but have no name. Check the matching IDs before deciding what is wrong.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fae12h6mfildizaxw6qpm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fae12h6mfildizaxw6qpm.png" alt="VisuaLeaf displaying a LEFT JOIN query across inspections, sites, and technicians, with inspection IDs, site names, technician names, and risk scores in the results." width="800" height="635"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Joining three SQLite tables to show each inspection’s site, technician, and risk score.&lt;/p&gt;

&lt;p&gt;You can also create this query visually with VisuaLeaf’s &lt;a href="https://visualeaf.com/features/visual-query-builder/" rel="noopener noreferrer"&gt;Query Builder&lt;/a&gt;, without writing SQL. Select the tables, connect their matching columns, and choose which fields to include in the result.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftd11cxynttu8w01bdnlf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftd11cxynttu8w01bdnlf.png" alt="VisuaLeaf Query Builder showing LEFT JOINs from inspections to sites and technicians, a risk_score greater than 2 filter, ascending ID sorting, and query results below." width="800" height="598"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Joining tables visually in VisuaLeaf’s Query Builder, filtering for risk scores above 2, and sorting inspections by ID.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is SQLite checking the relationships?
&lt;/h3&gt;

&lt;p&gt;SQLite can have foreign keys defined while their enforcement is turned off. To check the current connection’s setting, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PRAGMA foreign_keys;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A result of &lt;code&gt;1&lt;/code&gt; means checking is on; &lt;code&gt;0&lt;/code&gt; means it is off. To enable it, run this before making changes, outside an active transaction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PRAGMA foreign_keys = ON;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This setting applies to the current connection. It helps prevent new invalid references, but it does not fix existing ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Query JSON data in SQLite
&lt;/h2&gt;

&lt;p&gt;One database column can hold several related details as JSON. In our &lt;code&gt;inspections&lt;/code&gt; table, &lt;code&gt;device_info&lt;/code&gt; stores the device platform, app version, battery percentage, and whether the inspection was recorded offline.&lt;/p&gt;

&lt;p&gt;Start by viewing the original values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT id, device_info
FROM inspections
LIMIT 3;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see the result on the left of the screenshot. Each &lt;code&gt;device_info&lt;/code&gt; cell contains named values, such as &lt;code&gt;"platform":"android"&lt;/code&gt;. Here, &lt;code&gt;platform&lt;/code&gt; is the name and &lt;code&gt;android&lt;/code&gt; is its value.&lt;/p&gt;

&lt;p&gt;To display the platform separately, use &lt;code&gt;json_extract()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT
    i.id AS inspection_id,
    s.name AS site,
    json_extract(i.device_info, '$.platform') AS platform,
    json_extract(i.device_info, '$.app_version') AS app_version,
    json_extract(i.device_info, '$.battery_percent') AS battery,
    i.risk_score
FROM inspections AS i
JOIN sites AS s ON s.id = i.site_id
WHERE json_extract(i.device_info, '$.offline') = 1
ORDER BY i.risk_score DESC
LIMIT 12;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read that expression in three parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;device_info&lt;/code&gt; is the column containing the JSON.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;'$.platform'&lt;/code&gt; tells SQLite to find the value named &lt;code&gt;platform&lt;/code&gt; inside it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AS platform&lt;/code&gt; names the result column.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This creates a column in your query results. It does not add a new column to the database or change the stored JSON.&lt;/p&gt;

&lt;p&gt;The longer query on the right extracts the platform, app version, and battery percentage. It also adds the site name using a join.&lt;/p&gt;

&lt;p&gt;Its &lt;code&gt;WHERE&lt;/code&gt; condition keeps inspections where &lt;code&gt;offline&lt;/code&gt; is &lt;code&gt;true&lt;/code&gt;. SQLite returns that JSON value as &lt;code&gt;1&lt;/code&gt;. Finally, &lt;code&gt;ORDER BY ... DESC&lt;/code&gt; puts the highest risk scores first, and &lt;code&gt;LIMIT 12&lt;/code&gt; returns at most twelve rows.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F16ts0rmr56l71608cbde.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F16ts0rmr56l71608cbde.png" alt="VisuaLeaf split view showing device_info JSON beside a SQLite query extracting platform, app version, and battery values for offline inspections." width="800" height="572"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Original device JSON on the left; extracted details for offline inspections on the right.&lt;/p&gt;

&lt;h2&gt;
  
  
  Export SQLite query results to Excel
&lt;/h2&gt;

&lt;p&gt;Once your query returns the information you need, you &lt;a href="https://visualeaf.com/blog/automate-mongodb-exports-imports-sync-jobs/" rel="noopener noreferrer"&gt;can export it to an Excel&lt;/a&gt; workbook. This lets you share the results with someone who doesn’t use a database tool.&lt;/p&gt;

&lt;p&gt;Open the export form for your query, then:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set *&lt;strong&gt;&lt;em&gt;Source Type&lt;/em&gt;&lt;/strong&gt;* to *&lt;strong&gt;&lt;em&gt;Query&lt;/em&gt;&lt;/strong&gt;*.&lt;/li&gt;
&lt;li&gt;Select your SQLite connection and the &lt;code&gt;main&lt;/code&gt; database.&lt;/li&gt;
&lt;li&gt;Check the SQL in the *&lt;strong&gt;&lt;em&gt;Query&lt;/em&gt;&lt;/strong&gt;* field.&lt;/li&gt;
&lt;li&gt;Set *&lt;strong&gt;&lt;em&gt;Target Type&lt;/em&gt;&lt;/strong&gt;* to *&lt;strong&gt;&lt;em&gt;Excel Workbook&lt;/em&gt;&lt;/strong&gt;*.&lt;/li&gt;
&lt;li&gt;Choose a file location and name, such as &lt;code&gt;inspections.xlsx&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Click *&lt;strong&gt;&lt;em&gt;Export Now&lt;/em&gt;&lt;/strong&gt;*.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For our JSON query, the exported columns contain the inspection ID, site, platform, app version, battery percentage, and risk score.&lt;/p&gt;

&lt;p&gt;The export follows your query’s filters and limits. If you keep &lt;code&gt;LIMIT 12&lt;/code&gt;, it exports at most twelve rows. Remove that limit if you want all matching inspections.&lt;/p&gt;

&lt;p&gt;The Excel file contains a copy of the query results. Editing it does not update your SQLite database.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6jkawm43x3ryw8l7hgcl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6jkawm43x3ryw8l7hgcl.png" alt="VisuaLeaf export form with a SQLite query as the source and inspections.xlsx as the Excel workbook destination." width="800" height="572"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Exporting SQLite query results to an Excel workbook.&lt;/p&gt;

&lt;h2&gt;
  
  
  Count and chart failed checks by category
&lt;/h2&gt;

&lt;p&gt;Use &lt;code&gt;GROUP BY&lt;/code&gt; to group failed checklist items by category, then &lt;code&gt;COUNT(*)&lt;/code&gt; to count them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT
    issue_category,
    COUNT(*) AS failed_checks
FROM inspection_items
WHERE result = 'fail'
GROUP BY issue_category
ORDER BY failed_checks DESC
LIMIT 8;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The query returns up to eight categories with the most failed checks.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://visualeaf.com/features/chart-builder/" rel="noopener noreferrer"&gt;VisuaLeaf’s Chart Builder&lt;/a&gt;, choose *&lt;strong&gt;&lt;em&gt;Pie&lt;/em&gt;&lt;/strong&gt;&lt;em&gt;, set *&lt;/em&gt;&lt;strong&gt;Category&lt;/strong&gt;** to &lt;code&gt;issue_category&lt;/code&gt;, and *&lt;strong&gt;&lt;em&gt;Value&lt;/em&gt;&lt;/strong&gt;* to &lt;code&gt;failed_checks&lt;/code&gt;. Each slice shows that category’s share of the failed checks returned by the query.&lt;/p&gt;

&lt;p&gt;These totals count checklist items, not inspections. One inspection can contain several failed items. Remove &lt;code&gt;LIMIT 8&lt;/code&gt; if you want to include every category with failed checks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7est0hg824wgwa626rmh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7est0hg824wgwa626rmh.png" alt="VisuaLeaf pie chart of failed checks across eight categories, with documentation at 22.62% and electrical at 20.44%, the two largest shares." width="800" height="574"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each slice shows a category’s share of the failed checks included in the chart.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Start by opening your SQLite file and checking its tables and columns. Then use a small &lt;code&gt;SELECT&lt;/code&gt; query to read a few records before adding filters, joins, or calculations.&lt;/p&gt;

&lt;p&gt;Once the results contain what you need, you can export them to Excel or turn them into a chart. Check your filters and row limits first—they determine which data appears in the final result.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>sqlite</category>
      <category>database</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>VisuaLeaf</dc:creator>
      <pubDate>Fri, 18 Sep 2026 08:52:14 +0000</pubDate>
      <link>https://dev.to/visualeaf/-449c</link>
      <guid>https://dev.to/visualeaf/-449c</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/roxana_haidiner/mongodb-sharding-manage-a-sharded-cluster-visually-1nkc" class="crayons-story__hidden-navigation-link"&gt;MongoDB Sharding: Manage a Sharded Cluster Visually&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/roxana_haidiner" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2224346%2Ff71cca7a-b282-4793-a3ae-b04d89957d47.png" alt="roxana_haidiner profile" class="crayons-avatar__image" width="512" height="512"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/roxana_haidiner" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Roxana-Maria Haidiner
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Roxana-Maria Haidiner
                
                
              
              &lt;div id="story-author-preview-content-4681989" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/roxana_haidiner" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2224346%2Ff71cca7a-b282-4793-a3ae-b04d89957d47.png" class="crayons-avatar__image" alt="" width="512" height="512"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Roxana-Maria Haidiner&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/roxana_haidiner/mongodb-sharding-manage-a-sharded-cluster-visually-1nkc" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Sep 18&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/roxana_haidiner/mongodb-sharding-manage-a-sharded-cluster-visually-1nkc" id="article-link-4681989"&gt;
          MongoDB Sharding: Manage a Sharded Cluster Visually
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/mongodb"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;mongodb&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/database"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;database&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/software"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;software&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/softwaredevelopment"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;softwaredevelopment&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/roxana_haidiner/mongodb-sharding-manage-a-sharded-cluster-visually-1nkc" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;5&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/roxana_haidiner/mongodb-sharding-manage-a-sharded-cluster-visually-1nkc#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            5 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>Learn how to use DuckDB for local analysis: open a database, query CSV and Parquet files, inspect relationships, edit data, and create charts.</title>
      <dc:creator>VisuaLeaf</dc:creator>
      <pubDate>Thu, 03 Sep 2026 07:21:04 +0000</pubDate>
      <link>https://dev.to/visualeaf/learn-how-to-use-duckdb-for-local-analysis-open-a-database-query-csv-and-parquet-files-inspect-1ceo</link>
      <guid>https://dev.to/visualeaf/learn-how-to-use-duckdb-for-local-analysis-open-a-database-query-csv-and-parquet-files-inspect-1ceo</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/roxana_haidiner/how-to-use-duckdb-for-local-data-analysis-1koe" class="crayons-story__hidden-navigation-link"&gt;How to Use DuckDB for Local Data Analysis&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/roxana_haidiner" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2224346%2Ff71cca7a-b282-4793-a3ae-b04d89957d47.png" alt="roxana_haidiner profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/roxana_haidiner" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Roxana-Maria Haidiner
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Roxana-Maria Haidiner
                
                
              
              &lt;div id="story-author-preview-content-4562903" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/roxana_haidiner" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2224346%2Ff71cca7a-b282-4793-a3ae-b04d89957d47.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Roxana-Maria Haidiner&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/roxana_haidiner/how-to-use-duckdb-for-local-data-analysis-1koe" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Sep 3&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/roxana_haidiner/how-to-use-duckdb-for-local-data-analysis-1koe" id="article-link-4562903"&gt;
          How to Use DuckDB for Local Data Analysis
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/duckdb"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;duckdb&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/sql"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;sql&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/database"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;database&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/softwaredevelopment"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;softwaredevelopment&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/roxana_haidiner/how-to-use-duckdb-for-local-data-analysis-1koe" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;4&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/roxana_haidiner/how-to-use-duckdb-for-local-data-analysis-1koe#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            8 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>mongodump Tasks Automate and Schedule MongoDB Backups</title>
      <dc:creator>VisuaLeaf</dc:creator>
      <pubDate>Mon, 31 Aug 2026 11:00:28 +0000</pubDate>
      <link>https://dev.to/visualeaf/mongodump-tasks-automate-and-schedule-mongodb-backups-1emf</link>
      <guid>https://dev.to/visualeaf/mongodump-tasks-automate-and-schedule-mongodb-backups-1emf</guid>
      <description>&lt;p&gt;Running a MongoDB backup once is not difficult. With &lt;a href="https://visualeaf.com/database/mongodb-gui-tool/" rel="noopener noreferrer"&gt;MongoDB Database Tools&lt;/a&gt; installed, the complete PowerShell command can be as short as this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:\Program Files\MongoDB\Tools\100\bin\mongodump.exe"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;--uri&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"mongodb://127.0.0.1:27017"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;--db&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"gui_test_db"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;--gzip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;--out&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"C:\MongoDBBackups\gui_test_db"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example uses an unauthenticated local MongoDB deployment. Authenticated or remote deployments also require the appropriate credentials, authentication database, TLS settings, and connection options.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;mongodump&lt;/code&gt; is MongoDB's command-line utility for creating a binary export of a database or collection. The problem is rarely the first run. It is repeating the same backup every day or week, keeping the settings consistent, using a fresh destination, and checking whether the operation actually completed.&lt;/p&gt;

&lt;p&gt;That is where a saved task becomes useful. In VisuaLeaf, the same binary dump can be configured once, run manually when needed, scheduled, and checked later from its execution history.&lt;/p&gt;

&lt;h2&gt;
  
  
  Save the Same mongodump as a VisuaLeaf Task
&lt;/h2&gt;

&lt;p&gt;Open &lt;a href="https://visualeaf.com/features/task-manager/" rel="noopener noreferrer"&gt;*&lt;strong&gt;&lt;em&gt;Task Manager&lt;/em&gt;&lt;/strong&gt;*&lt;/a&gt;, create a new job, and choose the binary dump/restore operation. Select *&lt;strong&gt;&lt;em&gt;Export to Dump&lt;/em&gt;&lt;/strong&gt;* for the operation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6jb72mohjnolhhspbz5x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6jb72mohjnolhhspbz5x.png" alt="VisuaLeaf Task Manager showing a completed mongodump task for the gui_test_db database and its four collections." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The full &lt;code&gt;gui_test_db&lt;/code&gt; database saved as a reusable mongodump task in VisuaLeaf.&lt;/p&gt;

&lt;p&gt;The task should have a name that still makes sense when you return to it later. I used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Full Database mongodump – 4.6 GB (Gzip)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is more useful than a generic name such as &lt;code&gt;Backup&lt;/code&gt; or &lt;code&gt;New Job&lt;/code&gt;. It tells me the scope, approximate source size, and whether compression is enabled.&lt;/p&gt;

&lt;p&gt;For the source, I selected the complete &lt;code&gt;gui_test_db&lt;/code&gt; database running locally on port &lt;code&gt;27017&lt;/code&gt;. Its uncompressed data size was approximately 4.6 GB, spread across four collections:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;dataset_30000&lt;/code&gt;  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;large_collection_4GB&lt;/code&gt;  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;large_docs_10mb&lt;/code&gt;  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;medium_docs_1mb&lt;/code&gt;  &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The source panel displayed the database statistics before the job ran. This gave me a quick check that I had selected the full database before creating several gigabytes of output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Back Up a Database or One Collection
&lt;/h2&gt;

&lt;p&gt;The correct scope depends on why you need the backup.&lt;/p&gt;

&lt;p&gt;Choose a *&lt;strong&gt;&lt;em&gt;collection dump&lt;/em&gt;&lt;/strong&gt;* when you only need one dataset, want to move a collection between environments, or need a smaller restore unit. Choose a *&lt;strong&gt;&lt;em&gt;database dump&lt;/em&gt;&lt;/strong&gt;* when the job should include all collections and their metadata.&lt;/p&gt;

&lt;p&gt;Be deliberate here. A successful task using the wrong source is still the wrong backup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose the Output Folder and Enable Gzip
&lt;/h2&gt;

&lt;p&gt;I saved the VisuaLeaf output in its own folder and enabled *&lt;strong&gt;&lt;em&gt;Compress with Gzip&lt;/em&gt;&lt;/strong&gt;*. A separate folder for every run keeps completed backups distinct and prevents CLI files, task files, or partial attempts from being mixed.&lt;/p&gt;

&lt;p&gt;The job summary also showed *&lt;strong&gt;&lt;em&gt;auto threads&lt;/em&gt;&lt;/strong&gt;* and *&lt;strong&gt;&lt;em&gt;no timeout&lt;/em&gt;&lt;/strong&gt;*. Auto threads leaves the worker configuration to VisuaLeaf. The gear icon opens the advanced settings if the task needs a specific thread limit or timeout. I left both settings at their defaults for this workflow.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwg4lh49hkffdzq9b5mxz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwg4lh49hkffdzq9b5mxz.png" alt="VisuaLeaf Binary Dump task with gui_test_db selected as the source MongoDB database." width="800" height="683"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A full &lt;code&gt;gui_test_db&lt;/code&gt; backup configured as a binary dump task in VisuaLeaf.&lt;/p&gt;

&lt;p&gt;After choosing the source and destination, save the job. The connection, database, folder, compression, and operation settings remain attached to the task, so the next backup does not require rebuilding the command.&lt;/p&gt;

&lt;h2&gt;
  
  
  Schedule and Monitor the MongoDB Backup
&lt;/h2&gt;

&lt;p&gt;You can still start a saved job manually, but scheduling is the main reason to turn this workflow into a task. VisuaLeaf's &lt;a href="https://visualeaf.com/docs/task-manager" rel="noopener noreferrer"&gt;Task Manager documentation&lt;/a&gt; lists one-time, hourly, daily, weekly, monthly, and custom cron schedules.&lt;/p&gt;

&lt;p&gt;Choose the frequency based on how much recent data the application could afford to lose. A weekly backup is easy to manage, but it may be inadequate for a database that changes throughout the day.&lt;/p&gt;

&lt;p&gt;Use a dated destination for recurring runs and decide how many older backups to keep. Otherwise, the task may work perfectly while the backup drive quietly runs out of space.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6c4xqo0xovbbkjc0o4xs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6c4xqo0xovbbkjc0o4xs.png" alt="VisuaLeaf Schedule Job window with a recurring daily 6 AM MongoDB backup selected." width="800" height="694"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The full database dump is scheduled to run every day at 6:00 AM.&lt;/p&gt;

&lt;p&gt;After a run, *&lt;strong&gt;&lt;em&gt;Execution History&lt;/em&gt;&lt;/strong&gt;* provides a detailed dump summary. In my test, it recorded:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Total duration, collections, documents, and database size.&lt;/li&gt;
&lt;li&gt;The thread used for each collection.&lt;/li&gt;
&lt;li&gt;Document count and size per collection.&lt;/li&gt;
&lt;li&gt;Individual collection duration and processing speed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frelq1v97ehhhcjntxjjt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frelq1v97ehhhcjntxjjt.png" alt="VisuaLeaf dump summary showing four MongoDB collections with document counts, sizes, threads, and export durations." width="800" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The completed dump included four collections, 30,449 documents, and 4.72 GB of source data.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;*&lt;strong&gt;&lt;em&gt;Performance note:&lt;/em&gt;&lt;/strong&gt;* On the same Windows machine, with Gzip enabled and the same 4.72 GB database, the VisuaLeaf task completed in 4 minutes 40 seconds. The equivalent direct &lt;code&gt;mongodump&lt;/code&gt; CLI run took 6 minutes 12 seconds, so VisuaLeaf finished about 25% faster in this test. Results may vary depending on the hardware, storage, data, and task settings.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn1y92olt2czobscn0e1u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn1y92olt2czobscn0e1u.png" alt="PowerShell result for a direct mongodump backup showing Completed, 372.31 seconds, and 3.1 GB of compressed output." width="800" height="738"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The equivalent direct &lt;code&gt;mongodump&lt;/code&gt; CLI run completed in 6 minutes and 12 seconds and produced 3.1 GB of compressed output.&lt;/p&gt;

&lt;p&gt;Before treating the exported files as a valid backup, check that the execution status is *&lt;strong&gt;&lt;em&gt;Completed&lt;/em&gt;&lt;/strong&gt;*. A cancelled or failed run may still leave partial files in the destination.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Files Does the Backup Create?
&lt;/h2&gt;

&lt;p&gt;A compressed directory dump creates one &lt;code&gt;.bson.gz&lt;/code&gt; data file and one &lt;code&gt;.metadata.json.gz&lt;/code&gt; file for each collection. The VisuaLeaf output for &lt;code&gt;gui_test_db&lt;/code&gt; contained:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dataset_30000.bson.gz
dataset_30000.metadata.json.gz
large_collection_4GB.bson.gz
large_collection_4GB.metadata.json.gz
large_docs_10mb.bson.gz
large_docs_10mb.metadata.json.gz
medium_docs_1mb.bson.gz
medium_docs_1mb.metadata.json.gz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The BSON files contain the documents. The metadata files contain collection options and index definitions used during restoration. MongoDB describes this directory structure in its official &lt;code&gt;[mongodump](https://www.mongodb.com/docs/database-tools/mongodump/)&lt;/code&gt; &lt;a href="https://www.mongodb.com/docs/database-tools/mongodump/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The 4.6 GB test database produced approximately 3.18 GB of compressed output in VisuaLeaf. That result is specific to this data. A similarly sized database containing images, encrypted values, or already compressed content may shrink much less.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr2yr84z3a4vk90hei0ec.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr2yr84z3a4vk90hei0ec.png" alt="Windows Explorer showing four bson.gz files and four metadata.json.gz files from gui_test_db, totaling 3.18 GB." width="800" height="907"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The compressed &lt;code&gt;gui_test_db&lt;/code&gt; backup contains eight BSON and metadata files totaling 3.18 GB.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can You Run mongodump in MongoDB Compass?
&lt;/h2&gt;

&lt;p&gt;No. MongoDB Compass can export an individual collection as JSON or CSV, but MongoDB explicitly says Compass is not a backup tool. It does not create a full binary database dump containing compressed BSON files and collection metadata. For that, MongoDB directs users to its separate Database Tools. See the official &lt;a href="https://www.mongodb.com/docs/compass/import-export/" rel="noopener noreferrer"&gt;Compass import and export documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftg7czp28z6f1wmfwta1o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftg7czp28z6f1wmfwta1o.png" alt="MongoDB Compass Export Data window showing JSON and CSV as the available collection export formats." width="800" height="553"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;MongoDB Compass exports collection data as JSON or CSV; it does not create a binary database backup.&lt;/p&gt;

&lt;p&gt;This is an important difference between exporting data and creating a backup. JSON can preserve MongoDB types when the correct Extended JSON format is used, while CSV can lose type information. Neither Compass export is the same workflow as a BSON database dump intended for &lt;code&gt;mongorestore&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Restore the BSON Backup in VisuaLeaf
&lt;/h2&gt;

&lt;p&gt;Creating compressed BSON files is only half of a backup test. You also need to confirm that MongoDB can restore them.&lt;/p&gt;

&lt;p&gt;To avoid changing the original &lt;code&gt;gui_test_db&lt;/code&gt; database, I created a separate VisuaLeaf restore job. Under *&lt;strong&gt;&lt;em&gt;Source Configuration&lt;/em&gt;&lt;/strong&gt;&lt;em&gt;, I selected *&lt;/em&gt;&lt;strong&gt;Restore (BSON)&lt;/strong&gt;** and chose the folder containing the &lt;code&gt;.bson.gz&lt;/code&gt; and &lt;code&gt;.metadata.json.gz&lt;/code&gt; files.&lt;/p&gt;

&lt;p&gt;For the target, I selected the same MongoDB connection and entered a new database name: &lt;code&gt;gui_test_db_restore&lt;/code&gt;. Because that database did not exist, &lt;a href="https://visualeaf.com" rel="noopener noreferrer"&gt;VisuaLeaf&lt;/a&gt; indicated that it would create it when the restore job ran.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjelmskcuwpwvddmpkyws.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjelmskcuwpwvddmpkyws.png" alt="VisuaLeaf Restore BSON job using a compressed MongoDB dump folder and the new gui_test_db_restore database." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;VisuaLeaf restore job configured to import a compressed BSON dump into a new MongoDB database.&lt;/p&gt;

&lt;p&gt;This operation uses the binary dump-and-restore workflow. It reads the BSON data and collection metadata without converting the documents to JSON or CSV.&lt;/p&gt;

&lt;p&gt;When restoring to a different deployment, check version compatibility first. MongoDB requires the source and destination to use the same major version or feature compatibility version. See the official &lt;a href="https://www.mongodb.com/docs/database-tools/mongodump/mongodump-behavior/" rel="noopener noreferrer"&gt;mongodump behavior documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After the job finishes, check the restored database rather than relying only on the Completed status. The restore took 7 minutes and 34 seconds and recreated all four collections with 30,449 documents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Four collections.&lt;/li&gt;
&lt;li&gt;30,449 documents in total.&lt;/li&gt;
&lt;li&gt;30,000 documents in &lt;code&gt;dataset_30000&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;292 documents in &lt;code&gt;large_collection_4GB&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;57 documents in &lt;code&gt;large_docs_10mb&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;100 documents in &lt;code&gt;medium_docs_1mb&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The expected collection indexes and options.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fix2a93c1lfxdl27dyjiq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fix2a93c1lfxdl27dyjiq.png" alt="VisuaLeaf BSON restore summary showing four restored MongoDB collections, 30,449 documents, and a total size of 3.19 GB." width="800" height="571"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;VisuaLeaf restored all four collections and 30,449 documents from the compressed BSON dump.&lt;/p&gt;

&lt;p&gt;The document counts matched the source, confirming that the &lt;a href="https://visualeaf.com/blog/postgresql-jsonb-vs-mongodb-bson-architectural-tradeoffs/" rel="noopener noreferrer"&gt;BSON&lt;/a&gt; files could be restored successfully. A production restore test should also verify indexes, collection options, and several real application queries.&lt;/p&gt;

&lt;p&gt;Do not restore a test dump over the only copy of your original database. Use a new database name or a separate development environment.&lt;/p&gt;

&lt;p&gt;A backup is not verified until the restore finishes successfully and the restored data matches the source.&lt;/p&gt;

&lt;h2&gt;
  
  
  If mongodump Does Not Run on Windows
&lt;/h2&gt;

&lt;p&gt;Two small Windows problems appeared during this test.&lt;/p&gt;

&lt;p&gt;If PowerShell says that &lt;code&gt;mongodump&lt;/code&gt; is not recognized, MongoDB Database Tools may be installed without its &lt;code&gt;bin&lt;/code&gt; directory being added to PATH. Using the complete path to &lt;code&gt;mongodump.exe&lt;/code&gt;, as shown in the opening command, avoids that problem.&lt;/p&gt;

&lt;p&gt;A connection error can also mean that the command is using the wrong host or port. One attempt used &lt;code&gt;localhost:27019&lt;/code&gt;, while the current MongoDB instance was listening on &lt;code&gt;27017&lt;/code&gt;. Check the connection used by VisuaLeaf or MongoDB Compass before retrying.&lt;/p&gt;

&lt;p&gt;Cancelling a dump may leave partial files in the destination. Delete those incomplete files or use a new output folder before starting another backup.&lt;/p&gt;

&lt;h2&gt;
  
  
  MongoDB Backup Limitations to Plan For
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;mongodump&lt;/code&gt; backup still needs consistency planning, safe storage, and restore testing.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Limitation&lt;/th&gt;
&lt;th&gt;Why it matters&lt;/th&gt;
&lt;th&gt;What to do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Writes continue during the dump&lt;/td&gt;
&lt;td&gt;A database-scoped dump may not represent one exact point in time.&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;--oplog&lt;/code&gt; cannot be combined with this &lt;code&gt;--db&lt;/code&gt; command; it requires a full replica-set member dump. Use another backup method when exact point-in-time consistency is required.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Collection names differ only by capitalization&lt;/td&gt;
&lt;td&gt;Directory dumps may conflict on case-insensitive Windows and macOS file systems.&lt;/td&gt;
&lt;td&gt;Avoid ambiguous collection names or use archive output with the CLI.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The backup stays on the MongoDB machine&lt;/td&gt;
&lt;td&gt;Disk failure, device loss, or ransomware could affect the database and backup together.&lt;/td&gt;
&lt;td&gt;Keep another copy on separate or off-machine storage.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The backup is never restored&lt;/td&gt;
&lt;td&gt;A Completed status and BSON files do not prove that the backup is usable.&lt;/td&gt;
&lt;td&gt;Restore into a separate database and verify document counts, indexes, and collection options.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Old backups are never removed&lt;/td&gt;
&lt;td&gt;Scheduled dumps can eventually fill the destination disk.&lt;/td&gt;
&lt;td&gt;Define a retention policy and monitor available storage space.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;MongoDB positions &lt;code&gt;mongodump&lt;/code&gt; and &lt;code&gt;mongorestore&lt;/code&gt; mainly for smaller deployments; larger or business-critical systems may require a different backup strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;A reliable MongoDB backup workflow does not end when files appear in a folder. It ends when those files can be restored, and the resulting database has been verified.&lt;/p&gt;

&lt;p&gt;The command line remains useful for one-off dumps and scripts. In VisuaLeaf, you can &lt;a href="https://visualeaf.com/blog/automate-mongodb-exports-imports-sync-jobs/" rel="noopener noreferrer"&gt;save both export and restore jobs&lt;/a&gt;, schedule repeated backups, and review their status, duration, history, and logs. You still need off-machine copies, retention rules, consistency planning, and restore tests, but the recurring steps are easier to manage from one place.&lt;/p&gt;

</description>
      <category>mongodb</category>
      <category>nosql</category>
      <category>database</category>
      <category>sql</category>
    </item>
    <item>
      <title>PostgreSQL JSONB: Query, Update, and Index JSON Data</title>
      <dc:creator>VisuaLeaf</dc:creator>
      <pubDate>Tue, 18 Aug 2026 11:20:25 +0000</pubDate>
      <link>https://dev.to/visualeaf/postgresql-jsonb-query-update-and-index-json-data-1h21</link>
      <guid>https://dev.to/visualeaf/postgresql-jsonb-query-update-and-index-json-data-1h21</guid>
      <description>&lt;p&gt;JSONB allows you to store JSON data within a PostgreSQL table row. It is suitable when certain columns remain unchanged, while the remaining columns may vary across rows.&lt;/p&gt;

&lt;p&gt;Let us take the support tickets table, where the status, priority, and the date a ticket was created cannot change. However, the client name, environment, tags, and even error details may be optional and have different formats. That is why the optional content may be stored in the &lt;a href="https://visualeaf.com/blog/postgresql-jsonb-vs-mongodb-bson-architectural-tradeoffs/" rel="noopener noreferrer"&gt;JSONB field&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This storage process is easy enough. However, you need answers to questions such as how to find a nested value, update a single field, or create an effective index for your database engine.&lt;/p&gt;

&lt;p&gt;This article explores how to achieve that goal using a support_tickets table and the corresponding details column.&lt;/p&gt;

&lt;h2&gt;
  
  
  PostgreSQL JSONB operations used in this guide
&lt;/h2&gt;

&lt;p&gt;We’ll use each of these against the same &lt;code&gt;support_tickets.details&lt;/code&gt; column, so you can see how querying, updating, and indexing work together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create the support tickets table
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE TABLE support_tickets (
    ticket_id  BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    status     TEXT NOT NULL,
    priority   TEXT NOT NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    details    JSONB NOT NULL
        CHECK (jsonb_typeof(details) = 'object')
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I kept &lt;code&gt;status&lt;/code&gt;, &lt;code&gt;priority&lt;/code&gt;, and &lt;code&gt;created_at&lt;/code&gt; outside JSONB because they have stable types and are useful for filtering and sorting. The less predictable ticket context goes into &lt;code&gt;details&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here is one record used in the test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INSERT INTO support_tickets (status, priority, details)
VALUES (
    'open',
    'high',
    '{
      "customer": {
        "name": "Trevor Lisbon",
        "plan": "Professional"
      },
      "environment": {
        "browser": "Chrome",
        "os": "Windows 11",
        "appVersion": "4.8.2"
      },
      "tags": ["sync", "postgresql"],
      "error": {
        "code": "SYNC_TIMEOUT",
        "retryable": true
      }
    }'::jsonb
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;CHECK&lt;/code&gt; constraint confirms that &lt;code&gt;details&lt;/code&gt; contains a JSON object. It does not guarantee that &lt;code&gt;customer.plan&lt;/code&gt; exists or that &lt;code&gt;tags&lt;/code&gt; is always an array. JSONB validates the JSON format, not your complete application schema.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5r6vnci4oj9i1yjtyrrj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5r6vnci4oj9i1yjtyrrj.png" alt="VisuaLeaf displaying a PostgreSQL support_tickets table with expanded customer and tags fields from a JSONB column." width="800" height="376"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;PostgreSQL table with regular columns and nested JSONB data displayed in VisuaLeaf.&lt;/p&gt;

&lt;h2&gt;
  
  
  Query nested JSONB values
&lt;/h2&gt;

&lt;p&gt;PostgreSQL provides two operators that look similar but return different data types:&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;-&amp;gt;&lt;/code&gt; when you want an object or array to remain JSONB.&lt;br&gt;&lt;br&gt;
Use &lt;code&gt;-&amp;gt;&amp;gt;&lt;/code&gt; when you need a scalar value for an ordinary SQL comparison.&lt;/p&gt;

&lt;p&gt;This query returns the complete customer object and extracts the plan as text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT
    ticket_id,
    details -&amp;gt; 'customer' AS customer,
    details -&amp;gt; 'customer' -&amp;gt;&amp;gt; 'plan' AS plan
FROM support_tickets
WHERE ticket_id &amp;lt;= 4
ORDER BY ticket_id;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftkimn4ihws1k3zhnv7yw.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftkimn4ihws1k3zhnv7yw.gif" alt="VisuaLeaf showing an expandable customer JSONB object and the customer plan returned as text in PostgreSQL." width="800" height="535"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Expanding the &lt;code&gt;customer&lt;/code&gt; JSONB object while &lt;code&gt;plan&lt;/code&gt; is returned as text.&lt;/p&gt;

&lt;p&gt;A common mistake is to use &lt;code&gt;-&amp;gt;&lt;/code&gt; for the final value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WHERE details -&amp;gt; 'customer' -&amp;gt; 'plan' = 'Professional'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The left side is JSONB, while &lt;code&gt;Professional&lt;/code&gt; is being treated as a SQL string. PostgreSQL may return:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;invalid input syntax for type json
Token "Professional" is invalid.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The clearer fix is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WHERE details -&amp;gt; 'customer' -&amp;gt;&amp;gt; 'plan' = 'Professional'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To filter by a nested value, use &lt;code&gt;-&amp;gt;&amp;gt;&lt;/code&gt; for the final part of the path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT
    ticket_id,
    status,
    details -&amp;gt; 'customer' -&amp;gt;&amp;gt; 'name' AS customer_name
FROM support_tickets
WHERE details -&amp;gt; 'error' -&amp;gt;&amp;gt; 'code' = 'SYNC_TIMEOUT';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnmgp4il1nssvm2uadivi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnmgp4il1nssvm2uadivi.png" alt="VisuaLeaf SQL editor querying nested PostgreSQL JSONB fields with arrow operators and returning matching customer names." width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Filtering tickets by a nested JSONB error code and returning the customer name as text.&lt;/p&gt;

&lt;p&gt;If a row has no &lt;code&gt;error&lt;/code&gt; object, PostgreSQL returns SQL &lt;code&gt;NULL&lt;/code&gt; for that expression. It does not fail.&lt;/p&gt;

&lt;p&gt;Search inside a JSONB array&lt;/p&gt;

&lt;p&gt;You can search the &lt;code&gt;tags&lt;/code&gt; array with the &lt;code&gt;?&lt;/code&gt; operator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT ticket_id, status, details -&amp;gt; 'tags' AS tags
FROM support_tickets
WHERE details -&amp;gt; 'tags' ? 'postgresql';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This assumes that &lt;code&gt;tags&lt;/code&gt; contains an array of strings. If some rows store &lt;code&gt;"tags": "postgresql"&lt;/code&gt; instead, the data is valid JSONB but has the wrong shape for this query.&lt;/p&gt;

&lt;h2&gt;
  
  
  Match part of a document with &lt;code&gt;@&amp;gt;&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The containment operator checks whether one JSONB value contains another:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT ticket_id, status, details -&amp;gt; 'error' AS error
FROM support_tickets
WHERE details @&amp;gt; '{
  "error": {
    "code": "SYNC_TIMEOUT"
  }
}'::jsonb;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwd1e6puk0exx6iwo0jvf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwd1e6puk0exx6iwo0jvf.png" alt="VisuaLeaf running a PostgreSQL JSONB containment query and displaying two matching support tickets with expanded error details." width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Using &lt;code&gt;@&amp;gt;&lt;/code&gt; to find tickets containing the &lt;code&gt;SYNC_TIMEOUT&lt;/code&gt; error object.&lt;/p&gt;

&lt;p&gt;The structure must match the document. This does not work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WHERE details @&amp;gt; '{"code": "SYNC_TIMEOUT"}'::jsonb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;code&lt;/code&gt; is nested under &lt;code&gt;error&lt;/code&gt;; it is not a top-level key. This matters when you add a GIN index, because containment queries can use that index directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Update a nested value with &lt;code&gt;jsonb_set&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Ticket 1 was reproduced in version &lt;code&gt;4.8.4&lt;/code&gt;. Instead of replacing the entire &lt;code&gt;details&lt;/code&gt; document, I updated only &lt;code&gt;environment.appVersion&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;UPDATE&lt;/span&gt; &lt;span class="n"&gt;support_tickets&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;details&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jsonb_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;details&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'{environment,appVersion}'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;to_jsonb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'4.8.4'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;text&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="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;ticket_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;RETURNING&lt;/span&gt;
    &lt;span class="n"&gt;ticket_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;details&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'environment'&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result shows &lt;code&gt;appVersion&lt;/code&gt; as &lt;code&gt;4.8.4&lt;/code&gt;, while &lt;code&gt;browser&lt;/code&gt; and &lt;code&gt;os&lt;/code&gt; remain unchanged.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhtaz3o7ae29s2aa7d2gq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhtaz3o7ae29s2aa7d2gq.png" alt="VisuaLeaf running jsonb_set to update a nested PostgreSQL JSONB value from version 4.8.2 to 4.8.4." width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Updating &lt;code&gt;environment.appVersion&lt;/code&gt; while preserving the other JSONB fields.&lt;/p&gt;

&lt;p&gt;The final &lt;code&gt;false&lt;/code&gt; means the key must already exist. The &lt;code&gt;::text&lt;/code&gt; cast is also important; without it, PostgreSQL may not know which type to convert:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;could not determine polymorphic type because input has type unknown
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  When the parent object is missing
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;jsonb_set&lt;/code&gt; can create the final key, but not a missing parent object. Ticket 3 has no &lt;code&gt;error&lt;/code&gt; object, so updating &lt;code&gt;error.firstSeenAt&lt;/code&gt; would run without an error but change nothing.&lt;/p&gt;

&lt;p&gt;This version creates the parent when needed:&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;support_tickets&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;details&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jsonb_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;details&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'{error}'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;COALESCE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;details&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'error'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'{}'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;jsonb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;jsonb_build_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;'firstSeenAt'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'2026-08-03T11:05:00Z'&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="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;ticket_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="n"&gt;RETURNING&lt;/span&gt; &lt;span class="n"&gt;details&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'error'&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;RETURNING&lt;/code&gt; lets you confirm that the field was actually added.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fudm1j9vsulslb1oudb1l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fudm1j9vsulslb1oudb1l.png" alt="JSONB error object with the new firstSeenAt field." width="799" height="180"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Creating the missing &lt;code&gt;error&lt;/code&gt; object.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add a GIN index for containment searches
&lt;/h2&gt;

&lt;p&gt;For the indexing test, I added 40,000 generated tickets. The table contained 40,004 rows in total, while the original four remained available for the earlier examples.&lt;/p&gt;

&lt;p&gt;Because the query uses &lt;code&gt;@&amp;gt;&lt;/code&gt; against &lt;code&gt;details&lt;/code&gt;, I created a containment-focused GIN index:&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;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_support_tickets_details_gin&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;support_tickets&lt;/span&gt;
&lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;GIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;details&lt;/span&gt; &lt;span class="n"&gt;jsonb_path_ops&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;ANALYZE&lt;/span&gt; &lt;span class="n"&gt;support_tickets&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;jsonb_path_ops&lt;/code&gt; works well for containment searches, but it does not support every JSONB operator. For example, it cannot support the &lt;code&gt;?&lt;/code&gt; operator used in the tags query.&lt;/p&gt;

&lt;p&gt;I then opened the same query in VisuaLeaf’s Explain view:&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;ticket_id&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;support_tickets&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;details&lt;/span&gt; &lt;span class="o"&gt;@&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'{
  "error": {
    "code": "SYNC_TIMEOUT"
  }
}'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;jsonb&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PostgreSQL found 402 matching tickets and used &lt;code&gt;idx_support_tickets_details_gin&lt;/code&gt; through a Bitmap Index Scan.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F93iz2oa3p9jho2gfgrvn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F93iz2oa3p9jho2gfgrvn.png" alt="VisuaLeaf Explain showing a PostgreSQL Bitmap Index Scan using a JSONB GIN index." width="800" height="605"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The JSONB query uses the GIN index instead of scanning the full table.&lt;/p&gt;

&lt;p&gt;The index worked for this query and dataset. PostgreSQL may still choose a sequential scan for a small table or when many rows match.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to use JSONB
&lt;/h2&gt;

&lt;p&gt;JSONB is useful for flexible data, but stable fields usually belong in regular columns.&lt;/p&gt;

&lt;p&gt;In this example, &lt;code&gt;environment&lt;/code&gt; and &lt;code&gt;error&lt;/code&gt; fit inside JSONB. Fields such as &lt;code&gt;status&lt;/code&gt;, &lt;code&gt;priority&lt;/code&gt;, and &lt;code&gt;created_at&lt;/code&gt; are better as regular columns.&lt;/p&gt;

&lt;p&gt;I ran these examples in &lt;a href="https://visualeaf.com/database/postgresql/" rel="noopener noreferrer"&gt;VisuaLeaf&lt;/a&gt;, using its SQL editor, nested JSONB table view, and visual query plan to inspect the results.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.postgresql.org/docs/current/datatype-json.html" rel="noopener noreferrer"&gt;PostgreSQL: JSON Types&lt;/a&gt;  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.postgresql.org/docs/current/functions-json.html" rel="noopener noreferrer"&gt;PostgreSQL: JSON Functions and Operators&lt;/a&gt;  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.postgresql.org/docs/current/using-explain.html" rel="noopener noreferrer"&gt;PostgreSQL: Using EXPLAIN&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>postgres</category>
      <category>postgressql</category>
      <category>database</category>
      <category>sql</category>
    </item>
    <item>
      <title>MongoDB Schema Design: Build or Reverse Engineer Visually</title>
      <dc:creator>VisuaLeaf</dc:creator>
      <pubDate>Thu, 06 Aug 2026 11:45:16 +0000</pubDate>
      <link>https://dev.to/visualeaf/mongodb-schema-design-build-or-reverse-engineer-visually-5d28</link>
      <guid>https://dev.to/visualeaf/mongodb-schema-design-build-or-reverse-engineer-visually-5d28</guid>
      <description>&lt;p&gt;MongoDB collections can become difficult to understand once they contain nested objects, arrays, references, and documents with slightly different structures.&lt;/p&gt;

&lt;p&gt;Looking at one JSON document at a time shows you the data, but not the complete model. It is harder to see how collections connect, which fields use inconsistent BSON types, or whether a value is embedded or referenced elsewhere.&lt;/p&gt;

&lt;p&gt;The opposite problem happens when the database does not exist yet. You may know which collections and fields you need, but reviewing the design across separate JSON files or application models is not easy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://visualeaf.com/features/visual-schema/" rel="noopener noreferrer"&gt;VisuaLeaf’s Visual Schema&lt;/a&gt; supports both directions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generate a diagram from an existing MongoDB database.&lt;/li&gt;
&lt;li&gt;Design a new schema from an empty diagram.&lt;/li&gt;
&lt;li&gt;Keep the schema virtual or materialize it into real collections.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I tested both workflows using a travel-planning database containing collections such as &lt;code&gt;travelers&lt;/code&gt;, &lt;code&gt;places&lt;/code&gt;, &lt;code&gt;flights&lt;/code&gt;, &lt;code&gt;accommodations&lt;/code&gt;, &lt;code&gt;restaurants&lt;/code&gt;, &lt;code&gt;activities&lt;/code&gt;, &lt;code&gt;carRentals&lt;/code&gt;, and &lt;code&gt;vacationPlans&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3df2kfd2ac64dpf9s0rn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3df2kfd2ac64dpf9s0rn.png" alt="MongoDB Visual Schema in VisuaLeaf showing the central vacationPlans collection connected to travelers, flights, accommodations, restaurants, car rentals, activities, and places." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;VisuaLeaf generates a visual MongoDB schema showing collections, fields, BSON types, and relationships in the &lt;code&gt;TravelPlannerDemo&lt;/code&gt; database.&lt;/p&gt;

&lt;p&gt;Although this guide focuses on MongoDB, VisuaLeaf’s Visual Schema can also be used with &lt;a href="https://visualeaf.com/database/" rel="noopener noreferrer"&gt;SQL databases.&lt;/a&gt; If you’re deciding which database model fits your project or want to compare their structures, read &lt;a href="https://visualeaf.com/blog/mongodb-vs-postgresql-whats-the-difference/" rel="noopener noreferrer"&gt;MongoDB vs PostgreSQL: What’s the Difference?&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reverse engineer an existing MongoDB database
&lt;/h3&gt;

&lt;p&gt;If your database already contains data, you can generate a Visual Schema from its collections.&lt;/p&gt;

&lt;p&gt;Connect to MongoDB, open *&lt;strong&gt;&lt;em&gt;Visual Schema&lt;/em&gt;&lt;/strong&gt;*, and select the database you want to analyze. VisuaLeaf reads the existing documents and creates a diagram containing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;collections and fields;&lt;/li&gt;
&lt;li&gt;detected BSON types;&lt;/li&gt;
&lt;li&gt;embedded objects;&lt;/li&gt;
&lt;li&gt;arrays and their nested fields;&lt;/li&gt;
&lt;li&gt;detected relationships between collections.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In my &lt;code&gt;TravelPlannerDemo&lt;/code&gt; database, the generated diagram included the main travel collections and their nested structures.&lt;/p&gt;

&lt;p&gt;For example, &lt;code&gt;vacationPlans&lt;/code&gt; contains simple fields such as &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt;, and &lt;code&gt;createdAt&lt;/code&gt;, but it also includes more complex structures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;budget&lt;/code&gt; and &lt;code&gt;dates&lt;/code&gt; as embedded objects;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;itinerary&lt;/code&gt;, &lt;code&gt;checklist&lt;/code&gt;, and &lt;code&gt;documents&lt;/code&gt; as arrays;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;destinationIds&lt;/code&gt; and &lt;code&gt;travelerIds&lt;/code&gt; as arrays of references;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ownerTravelerId&lt;/code&gt; and &lt;code&gt;selectedAccommodationId&lt;/code&gt; as individual references.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of opening several documents to understand how these fields fit together, I could inspect the structure in one diagram.&lt;/p&gt;

&lt;p&gt;This is particularly useful when you are working with a database you did not design, or when the structure has changed, but its documentation has not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Review the detected structure
&lt;/h2&gt;

&lt;p&gt;Here’s the trap most of us fall into: reverse-engineering a database shows what’s currently stored, not what was actually intended.&lt;/p&gt;

&lt;p&gt;MongoDB allows documents in the same collection to have different fields and BSON types. For example, most documents might store a date correctly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ISODate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-07-18T09:30:00Z&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;while an older import stores it as a string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-07-18&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These values may look similar in a table, but one has the BSON type &lt;code&gt;date&lt;/code&gt; and the other has the type &lt;code&gt;string&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The same problem can occur with identifiers. A field such as &lt;code&gt;ownerTravelerId&lt;/code&gt; may contain an &lt;code&gt;ObjectId&lt;/code&gt; in most documents but a string in others. That difference matters when you try to connect it to &lt;code&gt;travelers._id&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The generated schema represents the data VisuaLeaf analyzed. Rare fields may also be absent if they do not appear in the sampled documents. Review unusual records and any fields with multiple detected types before using the diagram as your final model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Review and add relationships
&lt;/h2&gt;

&lt;p&gt;MongoDB does not store relationships as enforced foreign keys. It stores the reference value, but the meaning of that value often needs to be confirmed.&lt;/p&gt;

&lt;p&gt;In the travel database, relationships can connect fields such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vacationPlans.ownerTravelerId → travelers._id
vacationPlans.travelerIds[] → travelers._id
vacationPlans.destinationIds[] → places._id
vacationPlans.selectedAccommodationId → accommodations._id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Other collections use similar connections:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;accommodations.placeId → places._id
restaurants.placeId → places._id
activities.placeId → places._id
flights.passengers.travelerId → travelers._id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check both ends before accepting or adding a relationship. An &lt;code&gt;ObjectId&lt;/code&gt; does not match a string containing the same characters.&lt;/p&gt;

&lt;p&gt;For example, these two values use different BSON types:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nc"&gt;ObjectId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;66af9138ad73c91224345c10&lt;/span&gt;&lt;span class="dl"&gt;"&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 javascript"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;66af9138ad73c91224345c10&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The diagram can document a relationship, but it does not make MongoDB enforce it. Your application can still insert an &lt;code&gt;ownerTravelerId&lt;/code&gt; that does not exist in &lt;code&gt;travelers&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The generated diagram may also need some visual cleanup. In a database with many related collections, lines can overlap and detected connections may not represent the relationship you intended. Reposition the collections and review each relationship rather than accepting every connection without checking.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdlhmsx6fe1ms6dl1ndug.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdlhmsx6fe1ms6dl1ndug.png" alt="Close-up of a MongoDB schema diagram in VisuaLeaf showing nested fields, BSON types, arrays, and relationships between vacation plans, places, and accommodations." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A closer look at nested fields, BSON types, arrays, and relationships between&lt;/strong&gt; &lt;em&gt;`_vacationPlans&lt;/em&gt;&lt;code&gt;___,__ _&lt;/code&gt;&lt;em&gt;places&lt;/em&gt;&lt;code&gt;___, and__ _&lt;/code&gt;&lt;em&gt;accommodations&lt;/em&gt;`_ &lt;strong&gt;in VisuaLeaf.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Modify the generated schema
&lt;/h2&gt;

&lt;p&gt;A reverse-engineered schema does not have to remain a read-only picture of the current database.&lt;/p&gt;

&lt;p&gt;You can use it as a starting point and then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;add or remove fields;&lt;/li&gt;
&lt;li&gt;correct BSON types;&lt;/li&gt;
&lt;li&gt;define embedded objects and arrays;&lt;/li&gt;
&lt;li&gt;add missing relationships;&lt;/li&gt;
&lt;li&gt;remove incorrect relationships;&lt;/li&gt;
&lt;li&gt;reorganize the diagram.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this example, I added a new optional &lt;code&gt;travelStyle&lt;/code&gt; field to &lt;code&gt;vacationPlans&lt;/code&gt;. I also manually connected &lt;code&gt;destinationIds&lt;/code&gt; to &lt;code&gt;places._id&lt;/code&gt; to represent the reference between the two collections.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhvmgwyzbmksmydxhiroc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhvmgwyzbmksmydxhiroc.png" alt="Editing the vacationPlans MongoDB schema in VisuaLeaf by adding the optional travelStyle field with the String BSON type." width="800" height="523"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The same collection contains embedded data as well. The &lt;code&gt;budget&lt;/code&gt; object belongs to one vacation plan, so storing it inside the document keeps the related values together. &lt;code&gt;destinationIds&lt;/code&gt;, however, contains references to documents stored in the &lt;code&gt;places&lt;/code&gt; collection.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vacationPlans
├── budget
│   ├── currency
│   ├── estimatedTotal
│   └── paidSoFar
├── dates
├── itinerary[]
├── ownerTravelerId
├── travelerIds[]
└── destinationIds[]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This shows both modeling approaches in the same collection: embedded data for information owned by the vacation plan and references for information stored elsewhere and reused.&lt;/p&gt;

&lt;p&gt;Changing the virtual schema does not automatically migrate existing documents. If you rename a field, change its type, or move it into an embedded object, the records already stored in MongoDB remain unchanged.&lt;/p&gt;

&lt;p&gt;The diagram describes the proposed model. Data migration remains a separate operation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design a new MongoDB schema from scratch
&lt;/h2&gt;

&lt;p&gt;You can also use Visual Schema before the database exists.&lt;/p&gt;

&lt;p&gt;Start with an empty diagram and add the collections you need. You can then define their fields, BSON types, embedded structures, arrays, and relationships.&lt;/p&gt;

&lt;p&gt;For this example, I created a small tour-booking schema with two collections:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Collection&lt;/th&gt;
&lt;th&gt;Example fields&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;departures&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;_id&lt;/code&gt;, &lt;code&gt;packageId&lt;/code&gt;, &lt;code&gt;startDate&lt;/code&gt;, &lt;code&gt;capacity&lt;/code&gt;, &lt;code&gt;meetingPoint&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reservations&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;_id&lt;/code&gt;, &lt;code&gt;departureId&lt;/code&gt;, &lt;code&gt;bookingReference&lt;/code&gt;, &lt;code&gt;leadGuest&lt;/code&gt;, &lt;code&gt;totalAmount&lt;/code&gt;, &lt;code&gt;paymentStatus&lt;/code&gt;, &lt;code&gt;createdAt&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;leadGuest&lt;/code&gt; field is an embedded object containing fields such as &lt;code&gt;fullName&lt;/code&gt; and &lt;code&gt;email&lt;/code&gt;. The relationship connects:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;reservations.departureId → departures._id&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This represents a one-to-many relationship: one departure can have multiple reservations.&lt;/p&gt;

&lt;p&gt;Defining the schema visually makes several problems easier to notice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;references pointing to the wrong field;&lt;/li&gt;
&lt;li&gt;incorrect BSON types;&lt;/li&gt;
&lt;li&gt;fields that should be grouped inside an embedded object;&lt;/li&gt;
&lt;li&gt;missing or unnecessary relationships;&lt;/li&gt;
&lt;li&gt;inconsistent field names between collections.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhebkwgkol5au017x1dvf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhebkwgkol5au017x1dvf.png" alt="VisuaLeaf visual schema showing departures and reservations connected through departures._id and reservations.departureId, with BSON types and an embedded leadGuest object." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After completing the design, save the schema and bind it to the MongoDB connection where you want to create the collections. The diagram remains a virtual model until you materialize it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the schema virtual or materialize it
&lt;/h2&gt;

&lt;p&gt;The diagram can remain virtual if you only need it for documentation, planning, or discussing changes with your team.&lt;/p&gt;

&lt;p&gt;Nothing is created in MongoDB until you choose to materialize it.&lt;/p&gt;

&lt;p&gt;When the design is ready, save it and bind it to the MongoDB connection and target database. You can then click *&lt;strong&gt;&lt;em&gt;Materialize&lt;/em&gt;&lt;/strong&gt;* to review the collections and generated validators before applying the schema.&lt;/p&gt;

&lt;p&gt;This allows both workflows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Existing MongoDB database → Visual Schema
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Visual Schema → MongoDB database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can therefore reverse engineer an existing database such as &lt;code&gt;TravelPlannerDemo&lt;/code&gt;, modify its virtual schema, and materialize the result into a separate test database.  &lt;/p&gt;

&lt;p&gt;You can also begin with an empty diagram, as I did with &lt;code&gt;TourBookingSchema&lt;/code&gt;, and create a new database from that design.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fulny3425f5e17xilwyqe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fulny3425f5e17xilwyqe.png" alt="VisuaLeaf Materialize screen showing the reservations collection, its BSON fields, embedded leadGuest object, generated MongoDB validator, and Apply to Database button." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose whether to attach validators
&lt;/h2&gt;

&lt;p&gt;Materializing a schema does not require you to enforce every field and BSON type.&lt;/p&gt;

&lt;p&gt;The materialization screen lets you review the JSON Schema generated for each collection. You can then decide whether MongoDB should use it as a &lt;code&gt;$jsonSchema&lt;/code&gt; validator.&lt;/p&gt;

&lt;p&gt;If you want to create the collections without enforcing the schema, leave *&lt;strong&gt;&lt;em&gt;Attach validator&lt;/em&gt;&lt;/strong&gt;* disabled.&lt;/p&gt;

&lt;p&gt;These are separate decisions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The visual schema describes the intended structure.&lt;/li&gt;
&lt;li&gt;Materialization creates the collections in MongoDB.&lt;/li&gt;
&lt;li&gt;The generated JSON Schema describes the field and type rules.&lt;/li&gt;
&lt;li&gt;The attached validator makes MongoDB enforce those rules during writes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A field is mandatory only if it is included in the schema’s &lt;code&gt;required&lt;/code&gt; array. Otherwise, its BSON type is checked only when the field is present.&lt;/p&gt;

&lt;p&gt;This distinction matters when your application still writes inconsistent data.&lt;/p&gt;

&lt;p&gt;Suppose the generated schema expects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;bsonType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;date&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the application sends this instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-08-03&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MongoDB will reject the write when the validator is attached. A document validation failure commonly returns MongoDB error code &lt;code&gt;121&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The solution is not necessarily to remove validation. First check what your application actually sends. You may need to fix the value before inserting it or adjust the schema if both types are intentionally supported.&lt;/p&gt;

&lt;h2&gt;
  
  
  Review the materialization plan
&lt;/h2&gt;

&lt;p&gt;Before clicking *&lt;strong&gt;&lt;em&gt;Apply to Database&lt;/em&gt;&lt;/strong&gt;*, check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the selected connection and database;&lt;/li&gt;
&lt;li&gt;which collections will be created;&lt;/li&gt;
&lt;li&gt;which collections already exist;&lt;/li&gt;
&lt;li&gt;collection and field names;&lt;/li&gt;
&lt;li&gt;BSON types;&lt;/li&gt;
&lt;li&gt;embedded objects and arrays;&lt;/li&gt;
&lt;li&gt;relationships in the visual diagram;&lt;/li&gt;
&lt;li&gt;generated JSON Schemas;&lt;/li&gt;
&lt;li&gt;which collections have the *&lt;strong&gt;&lt;em&gt;Attach validator&lt;/em&gt;&lt;/strong&gt;* enabled.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the first test, use a new database instead of applying the model directly to an existing one.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TourBookingDemo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes the result easier to inspect and avoids mixing the materialized collections with the original &lt;code&gt;TravelPlannerDemo&lt;/code&gt; data.&lt;/p&gt;

&lt;p&gt;If you materialize changes against a database that already contains records, changing &lt;code&gt;reservations.departureId&lt;/code&gt; from &lt;code&gt;String&lt;/code&gt; to &lt;code&gt;ObjectId&lt;/code&gt; in the diagram will not convert the stored values. Data migration remains a separate operation.&lt;/p&gt;

&lt;p&gt;It also does not create enforced foreign keys. The relationship between &lt;code&gt;reservations.departureId&lt;/code&gt; and &lt;code&gt;departures._id&lt;/code&gt; remains part of the model, but your application must maintain valid references.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0sve8uozl6kk4og7t34z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0sve8uozl6kk4og7t34z.png" alt="VisuaLeaf showing the newly created departures and reservations collections in MongoDB." width="800" height="486"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify the result
&lt;/h2&gt;

&lt;p&gt;After applying the materialization plan, open the target database and confirm that the &lt;code&gt;departures&lt;/code&gt; and &lt;code&gt;reservations&lt;/code&gt; collections were created.&lt;/p&gt;

&lt;p&gt;If you enabled *&lt;strong&gt;&lt;em&gt;Attach validator&lt;/em&gt;&lt;/strong&gt;*, inspect the validation settings and test them with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one valid document that should be accepted;&lt;/li&gt;
&lt;li&gt;one invalid document that should be rejected.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frhcezatzy0lhnfk0b1zh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frhcezatzy0lhnfk0b1zh.png" alt="Split-screen MongoDB shell in VisuaLeaf showing a valid reservation inserted successfully and an invalid reservation rejected by JSON Schema validation because several BSON types do not match." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you left the option disabled, the collections should still exist, but MongoDB will continue accepting documents with different structures.&lt;/p&gt;

&lt;p&gt;Also remember that a validator does not replace indexes. Fields used for lookups and filtering may still need indexes, such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reservations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createIndex&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;departureId&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="na"&gt;createdAt&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="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;departures&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createIndex&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;startDate&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="na"&gt;status&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Validators control document structure. Indexes support query performance and can enforce uniqueness when created with the &lt;code&gt;unique&lt;/code&gt; option.&lt;br&gt;&lt;br&gt;
Relationships explain how collections connect, but MongoDB does not enforce them as foreign keys.&lt;/p&gt;

&lt;h2&gt;
  
  
  One schema, two directions
&lt;/h2&gt;

&lt;p&gt;Visual Schema is useful when you inherit an existing MongoDB database and need to understand its structure. It is also useful before you create the first collection.&lt;/p&gt;

&lt;p&gt;In my travel-planning example, I could generate a diagram containing &lt;code&gt;travelers&lt;/code&gt;, &lt;code&gt;places&lt;/code&gt;, &lt;code&gt;flights&lt;/code&gt;, &lt;code&gt;accommodations&lt;/code&gt;, &lt;code&gt;activities&lt;/code&gt;, &lt;code&gt;vacationPlans&lt;/code&gt;, and the other related collections. I could inspect nested structures such as &lt;code&gt;budget&lt;/code&gt; and &lt;code&gt;itinerary&lt;/code&gt;, review the references, and correct the model where necessary.&lt;/p&gt;

&lt;p&gt;The same workspace also supports the opposite workflow. You can start with an empty diagram, design the collections visually, and then decide whether to keep the model virtual or materialize it into MongoDB.&lt;/p&gt;

&lt;p&gt;When you materialize it, attaching the generated &lt;a href="https://visualeaf.com/blog/mongodb-json-schema-validation/" rel="noopener noreferrer"&gt;JSON Schema validators&lt;/a&gt; remains optional.&lt;/p&gt;

&lt;p&gt;*&lt;strong&gt;&lt;em&gt;Want to turn your MongoDB database into a diagram—or your diagram into a database?&lt;/em&gt;&lt;/strong&gt;*&lt;/p&gt;

&lt;p&gt;&lt;a href="https://visualeaf.com/download" rel="noopener noreferrer"&gt;Download VisuaLeaf&lt;/a&gt; and generate a schema from your existing MongoDB database, or design a new one from scratch.&lt;/p&gt;

</description>
      <category>mongodb</category>
      <category>database</category>
      <category>javascript</category>
      <category>software</category>
    </item>
    <item>
      <title>Which MongoDB Client Handles Large Collections Best: VisuaLeaf vs Compass vs Studio 3T</title>
      <dc:creator>VisuaLeaf</dc:creator>
      <pubDate>Tue, 04 Aug 2026 07:56:19 +0000</pubDate>
      <link>https://dev.to/visualeaf/which-mongodb-client-handles-large-collections-best-visualeaf-vs-compass-vs-studio-3t-1jam</link>
      <guid>https://dev.to/visualeaf/which-mongodb-client-handles-large-collections-best-visualeaf-vs-compass-vs-studio-3t-1jam</guid>
      <description>&lt;p&gt;A MongoDB client can feel fast with a few hundred documents and become frustrating as soon as the result grows.&lt;/p&gt;

&lt;p&gt;The database is not always the problem. MongoDB may finish the query while the desktop client is still fetching batches, converting BSON values, and building thousands of rows or JSON blocks.&lt;/p&gt;

&lt;p&gt;I tested VisuaLeaf, MongoDB Compass, and Studio 3T with the same collection of 50,844 documents. Then I tested three related workflows: comparing collections, automating repeated work, and investigating an unindexed query.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test setup
&lt;/h2&gt;

&lt;p&gt;I ran all three applications on the same HP laptop with 16 GB of RAM and Windows 11 Pro. Each application was updated to the latest version available when I ran the test. All three connected to the same MongoDB database through the same internet connection and opened the same &lt;code&gt;largeCollection&lt;/code&gt;. The collection contained nested objects and arrays, not only flat fields.&lt;/p&gt;

&lt;p&gt;For the first test, I opened the collection without a filter, projection, or custom limit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;db.largeCollection.find({})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is what happened:&lt;/p&gt;

&lt;h3&gt;
  
  
  Test 1: What the large-result test actually shows
&lt;/h3&gt;

&lt;p&gt;The three clients made different choices.&lt;/p&gt;

&lt;p&gt;VisuaLeaf allowed one continuous result. That is useful when you need to scroll through a large set, select many documents, inspect distant rows, or export the current result without moving through pages.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuaranufuy6h5x7sazkjw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuaranufuy6h5x7sazkjw.png" alt="VisuaLeaf displaying 50,000 MongoDB documents with a load time of 13.481 seconds." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;VisuaLeaf loaded 50,000 documents in one result in 13.481 seconds.&lt;/p&gt;

&lt;p&gt;Compass limited the amount of data visible at once. This protects the interface from an unexpectedly large query and works well when you only need to inspect a sample, edit one document, or confirm a filter. The tradeoff is that browsing the whole collection requires hundreds of pages.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwm1gf7v40vgutjfgfojd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwm1gf7v40vgutjfgfojd.png" alt="MongoDB Compass showing documents 1–100 of 50,844 with page-size options up to 100." width="799" height="481"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;MongoDB Compass displayed the collection in pages of up to 100 documents.&lt;/p&gt;

&lt;p&gt;Studio 3T did not finish this particular operation before I stopped it. That does not prove that every version, display mode, or computer will behave the same way. It does mean that an unrestricted result was not practical in this setup.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzojedptegz8loztlb68f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzojedptegz8loztlb68f.png" alt="Studio 3T showing a not responding message while reading documents from a MongoDB collection." width="800" height="479"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Studio 3T became unresponsive while attempting to load 50,000 documents.&lt;/p&gt;

&lt;p&gt;If a client struggles, reduce both the number of documents and the number of fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;db.largeCollection.find(
  {},
  {
    _id: 1,
    company: 1,
    registered: 1
  }
).limit(1000)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is usually the better daily workflow. A large result may be useful for inspection or export, but it still consumes database resources, network bandwidth, memory, and rendering time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test 2: Comparing two MongoDB collections
&lt;/h3&gt;

&lt;p&gt;For this test, I used two controlled collections in the &lt;code&gt;QA_Edit&lt;/code&gt; database: &lt;code&gt;orders_compare_source&lt;/code&gt; and &lt;code&gt;orders_compare_target&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Both collections started with 1,000 identical documents. I then modified 100 documents, removed 50, and added 50 in the target collection. I matched the documents by &lt;code&gt;_id&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;VisuaLeaf and Studio 3T both found 850 identical documents, 100 modified documents, 50 missing from the source, and 50 missing from the target.&lt;/p&gt;

&lt;p&gt;A useful comparison must answer three questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Which documents exist on only one side?  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Which fields changed inside matching documents?  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What will be inserted, updated, or deleted during synchronization?  &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  VisuaLeaf
&lt;/h3&gt;

&lt;p&gt;VisuaLeaf has a dedicated &lt;a href="https://visualeaf.com/features/collection-compare/" rel="noopener noreferrer"&gt;Collection Compare&lt;/a&gt; workflow for schema and data differences. It keeps the comparison next to the document, query, and data-operation tools.&lt;/p&gt;

&lt;p&gt;The important setup choice is the matching field. &lt;code&gt;_id&lt;/code&gt; works only if both collections preserve the same values. If an import regenerated them, use a stable key such as &lt;code&gt;customerId&lt;/code&gt;, &lt;code&gt;orderId&lt;/code&gt;, or &lt;code&gt;externalId&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiq7a0zy8xurbsmqnxhcl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiq7a0zy8xurbsmqnxhcl.png" alt="VisuaLeaf comparison results for two MongoDB collections showing 850 identical documents, 100 modified, 50 missing in source, and 50 missing in target." width="800" height="683"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;VisuaLeaf found 850 identical, 100 modified, and 50 documents missing from each collection.&lt;/p&gt;

&lt;h3&gt;
  
  
  Studio 3T
&lt;/h3&gt;

&lt;p&gt;Studio 3T provides the most detailed comparison controls of the three. Its &lt;a href="https://studio3t.com/knowledge-base/articles/compare-mongodb-collections/" rel="noopener noreferrer"&gt;Data Compare &amp;amp; Sync&lt;/a&gt; supports collections on different databases or servers, document- and field-level differences, result export, and synchronization in either direction.&lt;/p&gt;

&lt;p&gt;Bi-directional synchronization is useful, but it increases the cost of a wrong choice. Decide which collection is the source of truth before applying changes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsst1txnfatzh32wc7zwl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsst1txnfatzh32wc7zwl.png" alt="Studio 3T MongoDB collection comparison showing 850 identical documents, 100 different, 50 without a source match, and 50 without a target match." width="800" height="707"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Studio 3T returned the same differences using &lt;code&gt;_id&lt;/code&gt; as the matching field.&lt;/p&gt;

&lt;h3&gt;
  
  
  MongoDB Compass
&lt;/h3&gt;

&lt;p&gt;Compass does not provide the same dedicated collection-diff workflow. You can still build part of the comparison with an aggregation.&lt;/p&gt;

&lt;p&gt;For example, this pipeline finds source documents without a matching &lt;code&gt;_id&lt;/code&gt; in the target:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;db.orders_compare_source.aggregate([
  {
    $lookup: {
      from: "orders_compare_target",
      localField: "_id",
      foreignField: "_id",
      as: "target"
    }
  },
  {
    $match: {
      target: {
        $eq: []
      }
    }
  },
  {
  $count: "missingInTarget"
  }
  }
])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This finds missing matches, not every field-level change. It also assumes both collections are in the same database. Comparing data on different servers requires a script, a temporary copy, or another tool.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffrss7hzgk6ctcpa765ul.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffrss7hzgk6ctcpa765ul.png" alt="MongoDB Compass pipeline showing 50 documents missing from orders_compare_target." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Compass used an aggregation pipeline to find 50 source documents missing from the target.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test 3: Automating Daily Orders
&lt;/h2&gt;

&lt;p&gt;Large collections make small mistakes more expensive.&lt;/p&gt;

&lt;p&gt;For this test, I used a daily orders JSON file.&lt;/p&gt;

&lt;p&gt;In Studio 3T, I imported the file into a &lt;code&gt;daily_orders&lt;/code&gt; collection. Studio 3T can also save and schedule tasks, so it works well when you want to repeat imports, exports, or other database jobs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffpjf7d4nhpa03nrp24j5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffpjf7d4nhpa03nrp24j5.png" alt="Build a connected workflow: import to staging, then continue with the next task" width="800" height="570"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;JSON import task: import a file into a collection&lt;/p&gt;

&lt;p&gt;In VisuaLeaf, I used &lt;a href="https://visualeaf.com/features/task-manager/" rel="noopener noreferrer"&gt;Task Manager&lt;/a&gt; to connect more than one step:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Import Daily Orders JSON → Add Orders to Main Collection → Export Sales Report&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcte0fhqvveg4r2p5x5nl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcte0fhqvveg4r2p5x5nl.png" alt="VisuaLeaf can turn import, copy, and export steps into one connected workflow." width="800" height="526"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;VisuaLeaf lets you connect tasks into a workflow, not just run one import.&lt;/p&gt;

&lt;p&gt;The useful part is that tasks can have parent tasks. So the next task runs after the previous one is completed.&lt;/p&gt;

&lt;p&gt;This is helpful when the work is not just “import this file.” In a real workflow, you may need to import data, map fields, move it into the main collection, mask sensitive data, and export a report after that.&lt;/p&gt;

&lt;p&gt;For example, field mapping matters because &lt;code&gt;orderedAt&lt;/code&gt; should be mapped to &lt;code&gt;createdAt&lt;/code&gt; as a MongoDB Date, and values like email or phone numbers may need data masking before export.&lt;/p&gt;

&lt;p&gt;So for me, Studio 3T is strong for saving and scheduling database tasks. VisuaLeaf is easier when I want to see a connected workflow in one place and control the order of the steps.&lt;/p&gt;

&lt;p&gt;Compass is different. It is great for interactive work, but recurring workflows usually need something outside the desktop app, like a script, cron, Windows Task Scheduler, CI, or an Atlas service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test 4: Checking the Query Before Blaming the Client
&lt;/h2&gt;

&lt;p&gt;The first test measured how each tool handled a large result on screen.&lt;/p&gt;

&lt;p&gt;This test is different. Here, I wanted to check the work MongoDB was doing before the data even reached the client.&lt;/p&gt;

&lt;p&gt;I started with a query that had no supporting index:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;db.largeCollection.find({
  company: "ZENSOR"
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part was the explain plan.&lt;/p&gt;

&lt;p&gt;I checked:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;scan stage in the winning plan: &lt;code&gt;COLLSCAN&lt;/code&gt; or &lt;code&gt;IXSCAN&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;totalDocsExamined&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;totalKeysExamined&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nReturned&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;execution time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When the query uses &lt;code&gt;COLLSCAN&lt;/code&gt;, MongoDB has to scan the collection to find matching documents. That means the slow part is not always the GUI. Sometimes the database is simply doing too much work.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;COLLSCAN&lt;/code&gt; is not automatically a problem. But in this case, scanning 50,844 documents to return only 51 showed that the query could benefit from an index.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6tldf2vqff2bhxa8vown.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6tldf2vqff2bhxa8vown.png" alt="VisuaLeaf Query Profiler showing a collection scan with 50,844 documents examined and 51 returned." width="800" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;VisuaLeaf detected a &lt;code&gt;COLLSCAN&lt;/code&gt;, examining 50,844 documents to return 51.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiee01pq7102ob5althrg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiee01pq7102ob5althrg.png" alt="Studio 3T Visual Explain showing a collection scan with 50,844 documents examined and 51 returned." width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Studio 3T reported a collection scan across 50,844 documents for 51 results.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftsosejsdllswyi86leny.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftsosejsdllswyi86leny.png" alt="MongoDB Compass Explain Plan showing a collection scan with 50,844 documents examined and 51 returned." width="800" height="518"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Compass used a &lt;code&gt;COLLSCAN&lt;/code&gt;, examining all 50,844 documents to find 51 matches.&lt;/p&gt;

&lt;p&gt;After that, I created an index:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;db.largeCollection.createIndex({
  company: 1
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I ran the same explain operation again.&lt;/p&gt;

&lt;p&gt;A lower execution time is useful, but the stronger evidence is that the winning plan now contains an &lt;code&gt;IXSCAN&lt;/code&gt;. In this test, &lt;code&gt;totalDocsExamined&lt;/code&gt; dropped from 50,844 to 51, matching the 51 returned documents.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://visualeaf.com/features/query-profiler/" rel="noopener noreferrer"&gt;VisuaLeaf’s Query Profiler&lt;/a&gt; is useful here because it brings slow queries, execution plans, index usage, scanned documents, returned documents, and optimization hints into one place.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flnau13lp7bkxu91xst07.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flnau13lp7bkxu91xst07.png" alt="VisuaLeaf Query Profiler showing the company index with 51 documents and 51 index keys examined." width="800" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After adding the &lt;code&gt;company_1&lt;/code&gt; index, VisuaLeaf examined 51 documents and 51 index keys.&lt;/p&gt;

&lt;p&gt;Studio 3T has the deepest performance tools in this comparison, with Query Profiler, Visual Explain, and Index Manager.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgog4gnfsvko3tdj4gdag.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgog4gnfsvko3tdj4gdag.png" alt="Studio 3T Visual Explain showing an index scan using company_1 and examining 51 documents." width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the &lt;code&gt;company_1&lt;/code&gt; index, Studio 3T used an index scan and examined 51 documents.&lt;/p&gt;

&lt;p&gt;Compass also has Explain Plan, which works well when you already know which query you want to inspect.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsxogwnyk27lb4qg0j8tm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsxogwnyk27lb4qg0j8tm.png" alt="MongoDB Compass Explain Plan showing an index scan with 51 documents and 51 index keys examined." width="800" height="553"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After indexing &lt;code&gt;company&lt;/code&gt;, Compass changed to &lt;code&gt;IXSCAN&lt;/code&gt; and examined only 51 documents.&lt;/p&gt;

&lt;p&gt;For this test, the main point was simple: before blaming the client, check if MongoDB is scanning too much data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which client fits which workflow?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;If you mainly need to…&lt;/th&gt;
&lt;th&gt;Best fit&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Open and work with a large continuous result&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;VisuaLeaf&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;It loaded 50,000 documents in 13.481 seconds and remained usable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compare two collections visually&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;VisuaLeaf&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;It clearly separated identical, modified, and missing documents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automate multi-step data workflows&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;VisuaLeaf&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Import, copy, export, and scheduling can be managed in one workflow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Find inefficient queries in the same workspace&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;VisuaLeaf&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Query Profiler combines plans, index usage, scanned documents, and optimization hints&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browse documents in small pages with a free official client&lt;/td&gt;
&lt;td&gt;MongoDB Compass&lt;/td&gt;
&lt;td&gt;Pagination keeps browsing controlled and predictable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Access advanced synchronization and administration controls&lt;/td&gt;
&lt;td&gt;Studio 3T&lt;/td&gt;
&lt;td&gt;It provides more granular diff, sync, and performance options&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;There is no single client for every MongoDB job. Compass is a good option for controlled, paginated browsing, while Studio 3T offers advanced administration and synchronization controls. But for everyday work with large results, collection comparisons, automated tasks, and query analysis, &lt;a href="https://visualeaf.com" rel="noopener noreferrer"&gt;VisuaLeaf&lt;/a&gt; provides the most complete workflow in one place.&lt;/p&gt;

&lt;p&gt;Choose the client around the operation that costs you time, not the longest feature list.&lt;/p&gt;

</description>
      <category>mongodb</category>
      <category>javascript</category>
      <category>database</category>
      <category>software</category>
    </item>
  </channel>
</rss>
