<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Slava Rozhnev</title>
    <description>The latest articles on Forem by Slava Rozhnev (@rozhnev).</description>
    <link>https://forem.com/rozhnev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F980305%2F419de9d3-4790-4b03-9eca-596e6070dc1d.jpg</url>
      <title>Forem: Slava Rozhnev</title>
      <link>https://forem.com/rozhnev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/rozhnev"/>
    <language>en</language>
    <item>
      <title>Is SQLZoo good for learning SQL</title>
      <dc:creator>Slava Rozhnev</dc:creator>
      <pubDate>Sun, 24 May 2026 14:19:54 +0000</pubDate>
      <link>https://forem.com/rozhnev/is-sqlzoo-good-for-learning-sql-1206</link>
      <guid>https://forem.com/rozhnev/is-sqlzoo-good-for-learning-sql-1206</guid>
      <description>&lt;p&gt;&lt;strong&gt;Table of Contents&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is SQLZoo Good for Learning SQL? A Quick Answer&lt;/li&gt;
&lt;li&gt;How SQLZoo Works: Inside the Interactive Learning Engine&lt;/li&gt;
&lt;li&gt;Where SQLZoo Falls Short: Why Learners Often Get Stuck&lt;/li&gt;
&lt;li&gt;The Step-by-Step Approach to Mastering SQL&lt;/li&gt;
&lt;li&gt;Common Mistakes to Avoid When Practicing SQL&lt;/li&gt;
&lt;li&gt;What the Data Says About SQL Learning Tools&lt;/li&gt;
&lt;li&gt;How We Approach This at SQLtest.online&lt;/li&gt;
&lt;li&gt;Frequently Asked Questions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Is SQLZoo good for learning SQL?&lt;/strong&gt; Yes, for building hands-on syntax intuition through immediate feedback, but it leaves major gaps in database theory and interview readiness that you'll need a dedicated platform to fill.&lt;/p&gt;

&lt;p&gt;At SQLtest.online, we see learners arrive comfortable with SELECT and JOINs but unable to explain basic normalization. That's the SQLZoo effect: fast syntax, weak theory. Let's walk through what the platform does well and where you should look next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is SQLZoo Good for Learning SQL? A Quick Answer
&lt;/h2&gt;

&lt;p&gt;Our students often ask: &lt;strong&gt;Is SQLZoo good for learning SQL?&lt;/strong&gt; The short answer is yes, with a big "but."&lt;/p&gt;

&lt;p&gt;SQLZoo is a free, interactive platform where you write live queries against real databases. A Kaggle resource roundup, &lt;a href="https://www.kaggle.com/getting-started/357845" rel="noopener noreferrer"&gt;7 best free resources for learning SQL&lt;/a&gt;, lists it as a beginner-friendly, wiki-based tutorial with lessons you work through in the browser. That makes it a fantastic starting point. You learn by doing, and the feedback is instant.&lt;/p&gt;

&lt;p&gt;But SQLZoo isn't a complete training system. It's light on theoretical depth, progress tracking, and interview simulation. Think of it as the driving range for learning golf. You can groove your swing, but you never play a round under tournament conditions.&lt;/p&gt;

&lt;p&gt;It answers the question "Is SQLZoo good for learning SQL?" with a qualified yes for beginners and a clear no for interview prep.&lt;/p&gt;

&lt;h2&gt;
  
  
  How SQLZoo Works: Inside the Interactive Learning Engine
&lt;/h2&gt;

&lt;p&gt;SQLZoo's format is simple. Wiki-based tutorials introduce a topic. Interactive exercises run against a live database. Immediate feedback tells you whether your query is correct.&lt;/p&gt;

&lt;p&gt;The platform covers a wide range of topics. You move from basic SELECT statements to JOINs, subqueries, and aggregate functions, and the exercises support engines like &lt;a href="https://www.postgresql.org" rel="noopener noreferrer"&gt;PostgreSQL&lt;/a&gt; and &lt;a href="https://dev.mysql.com/doc/" rel="noopener noreferrer"&gt;MySQL&lt;/a&gt;. The hands-on modules are what SQLZoo is best known for.&lt;/p&gt;

&lt;p&gt;Lobnig et al. (2026), in their overview of narrative online SQL learning tools, highlight how platforms like SQLZoo make database practice accessible. Anyone with an internet connection can start writing SQL right away. No installs, no configuration.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does SQLZoo handle advanced topics like window functions?
&lt;/h3&gt;

&lt;p&gt;SQLZoo includes a section for window functions. It introduces ROW_NUMBER, RANK, and aggregate windowing, and it's a decent first exposure.&lt;/p&gt;

&lt;p&gt;Still, the explanations are brief. Learners often need to supplement this section with outside reading or a structured course to really understand partitioning and framing.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are the benefits of using SQLZoo?
&lt;/h3&gt;

&lt;p&gt;The primary benefit is the lack of setup. You open a browser and start writing SQL. The variety of non-trivial datasets (a world database, a movie database, and more) gives you realistic practice data.&lt;/p&gt;

&lt;p&gt;Another benefit is the price. SQLZoo is completely free. There are no paywalls and no premium tiers, which makes it one of the most accessible tools for new learners exploring a career in data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where SQLZoo Falls Short: Why Learners Often Get Stuck
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What are the main weaknesses of SQLZoo?
&lt;/h3&gt;

&lt;p&gt;The most common complaint is the lack of theoretical grounding. Learners master puzzles but struggle with the concepts behind them. There are no performance benchmarks either, so a correct query is scored the same as an efficient one.&lt;/p&gt;

&lt;p&gt;The interface also hasn't seen major updates in years. It feels like a tool from the early 2010s, and navigation between sections can confuse absolute beginners.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can SQLZoo prepare you for technical interviews?
&lt;/h3&gt;

&lt;p&gt;Not really. Interview preparation is almost nonexistent. SQLZoo teaches you to write SQL. It doesn't teach you to &lt;em&gt;think&lt;/em&gt; about SQL under pressure. That's why learners often stall after finishing the tutorials. They don't know their skill level or what to practice next.&lt;/p&gt;

&lt;p&gt;The platform offers window function exercises, but many people skip them because the explanations are thin. In a real interview, you need to explain your reasoning and optimize your query, not just produce a passing result.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Step-by-Step Approach to Mastering SQL
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is SQLZoo good for learning SQL without a mentor?
&lt;/h3&gt;

&lt;p&gt;It depends on your learning style. Some people thrive on a puzzle-like format. Others need structured guidance. Here's the approach we recommend.&lt;/p&gt;

&lt;p&gt;First, use SQLZoo for initial syntax. Work through SELECT, JOIN, and subquery tutorials, and type every query yourself. Don't copy-paste the answers.&lt;/p&gt;

&lt;p&gt;Next, pair that practice with theory. A theory checkpoint like &lt;a href="https://sqltest.online/en/question/db-theory/what-is-dbms" rel="noopener noreferrer"&gt;SQL Interview Questions #2: What is DBMS?&lt;/a&gt; explains the "why" behind the syntax so the patterns actually stick.&lt;/p&gt;

&lt;p&gt;Then practice multi-table queries on a task that challenges you. &lt;a href="https://sqltest.online/en/question/normal/find-all-the-actors-in-the-film" rel="noopener noreferrer"&gt;SQL Practice #6: Find all the actors in the film&lt;/a&gt; asks for a real JOIN against related tables, with immediate feedback.&lt;/p&gt;

&lt;p&gt;Finally, test yourself with interview-style questions. &lt;a href="https://sqltest.online/en/question/db-theory/what-are-dql-commands" rel="noopener noreferrer"&gt;SQL Interview Questions #9: What are DQL commands?&lt;/a&gt; bridges the syntax-to-theory gap that SQLZoo leaves open.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Mistakes to Avoid When Practicing SQL
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why do learners stop after using SQLZoo?
&lt;/h3&gt;

&lt;p&gt;We see three habits that lead to stagnation.&lt;/p&gt;

&lt;p&gt;The most common one is skipping the theory. SQLZoo lets you guess syntax until an exercise passes. Without theory, you don't understand &lt;em&gt;why&lt;/em&gt; the query works, so when the problem changes slightly, you're lost.&lt;/p&gt;

&lt;p&gt;A subtler mistake is dodging window functions. SQLZoo covers them, but learners often find the logic confusing, skip ahead, and miss a skill that shows up in many junior data interviews.&lt;/p&gt;

&lt;p&gt;The most expensive mistake is ignoring query performance. In the real world, a slow query is a broken query. SQLZoo doesn't measure execution time, so you learn to produce correct output rather than efficient output.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the role of theory in SQL practice?
&lt;/h3&gt;

&lt;p&gt;Theory explains the "why" behind the query. Without it, you're memorizing patterns instead of understanding logic. Try &lt;a href="https://sqltest.online/en/question/normal/find-all-films-of-an-actor" rel="noopener noreferrer"&gt;SQL Practice #7: Find all films of an actor&lt;/a&gt; for an exercise that tests your understanding of JOINs and subqueries alongside the underlying database design.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Data Says About SQL Learning Tools
&lt;/h2&gt;

&lt;p&gt;Let's look at the bigger picture. Lobnig et al. (2026) praise tools like SQLZoo for lowering the barrier to entry, but they note that higher-level skills need platforms with more scaffolding.&lt;/p&gt;

&lt;p&gt;This connects directly to the theory gap. You can write JOINs without understanding normal forms, but you'll struggle with interview questions on database design. If you want to see how the relational model fits together, the &lt;a href="https://en.wikipedia.org/wiki/SQL" rel="noopener noreferrer"&gt;SQL standard overview on Wikipedia&lt;/a&gt; is a solid, neutral primer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is SQLZoo good for learning SQL compared to modern platforms?
&lt;/h3&gt;

&lt;p&gt;Structured learning providers like &lt;a href="https://www.coursera.org" rel="noopener noreferrer"&gt;Coursera&lt;/a&gt; emphasize that courses with graded assessments and a clear path tend to keep learners engaged longer than open-ended exercises alone. SQLZoo is beginner-friendly, but it isn't built to be your sole resource on the way to a professional role.&lt;/p&gt;

&lt;p&gt;To fill the gaps, you want progress tracking, categorized difficulty, and interview-style practice. Syntax drills are only one piece of the puzzle.&lt;/p&gt;

&lt;h2&gt;
  
  
  How We Approach This at SQLtest.online
&lt;/h2&gt;

&lt;p&gt;At SQLtest.online, we built the platform to close the gaps SQLZoo leaves open. We offer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Interactive SQL exercises with immediate feedback.&lt;/li&gt;
&lt;li&gt;Tasks grouped by complexity, category, and database.&lt;/li&gt;
&lt;li&gt;Theory questions covering SQL fundamentals.&lt;/li&gt;
&lt;li&gt;Interview preparation with realistic scenarios.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You get immediate feedback, progress tracking, and a clear path from beginner to job-ready. We combine the hands-on practice of a tool like SQLZoo with the theoretical depth of a textbook and the pressure of a technical interview.&lt;/p&gt;

&lt;p&gt;Take &lt;a href="https://sqltest.online/en/question/not-rated/release-strategy" rel="noopener noreferrer"&gt;SQL Interview Questions #5: The Release Strategy&lt;/a&gt; to see how we frame real interview logic. Then try &lt;a href="https://sqltest.online/en/question/normal/find-the-films-never-been-rented" rel="noopener noreferrer"&gt;SQL Practice #22: Find the films never been rented&lt;/a&gt;, a common interview ask that needs a multi-table JOIN and a subquery. It tests the exact skills SQLZoo leaves underdeveloped. We believe learning SQL means mastering both syntax and theory, so our students leave ready for their first data role, not just the next tutorial.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is SQLZoo free to use?
&lt;/h3&gt;

&lt;p&gt;Yes. SQLZoo is completely free, with no paywalls or premium tiers. You can start writing queries in your browser without creating an account.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is SQLZoo good for SQL interview preparation?
&lt;/h3&gt;

&lt;p&gt;On its own, no. SQLZoo builds syntax fluency but doesn't simulate interviews, track progress, or push you on query performance. Pair it with interview-style practice to close that gap.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does SQLZoo cover advanced SQL topics?
&lt;/h3&gt;

&lt;p&gt;Partly. It introduces window functions, subqueries, and aggregates, but the explanations are brief, so advanced topics usually need extra reading or a structured course.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is SQLZoo good for complete beginners?
&lt;/h3&gt;

&lt;p&gt;Yes, for syntax. The instant-feedback exercises are a great first step. Beginners should add a theory resource early so they understand why each query works, not just that it passes.&lt;/p&gt;

&lt;h3&gt;
  
  
  What should I use alongside SQLZoo?
&lt;/h3&gt;

&lt;p&gt;Combine SQLZoo's syntax drills with a platform that adds theory questions, difficulty grouping, and interview simulation so your skills transfer to real work and interviews.&lt;/p&gt;

</description>
      <category>issqlzoogoodforlearn</category>
    </item>
    <item>
      <title>Learn SQL Online Free for Beginners: Complete 2026 Guide</title>
      <dc:creator>Slava Rozhnev</dc:creator>
      <pubDate>Sun, 24 May 2026 13:03:55 +0000</pubDate>
      <link>https://forem.com/rozhnev/learn-sql-online-free-for-beginners-complete-2026-guide-4mkk</link>
      <guid>https://forem.com/rozhnev/learn-sql-online-free-for-beginners-complete-2026-guide-4mkk</guid>
      <description>&lt;p&gt;&lt;strong&gt;Learning SQL online for free as a beginner means using no-cost, browser-based platforms and tutorials to master the Structured Query Language, the standard tool for managing and querying relational databases, without needing prior programming experience or paid subscriptions.&lt;/strong&gt; It is an entirely achievable goal with today's wealth of free resources.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Table of Contents&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What Does It Mean to Learn SQL Online Free for Beginners?&lt;/li&gt;
&lt;li&gt;What Exactly Is SQL and Why Should Beginners Learn It?&lt;/li&gt;
&lt;li&gt;How Free Online SQL Learning Actually Works&lt;/li&gt;
&lt;li&gt;The Best Free Path to Learn SQL: A Beginner's Step-by-Step Plan&lt;/li&gt;
&lt;li&gt;How to Choose the Right Free SQL Learning Platform: Key Evaluation Dimensions&lt;/li&gt;
&lt;li&gt;Common Mistakes Beginners Make When Learning SQL Online for Free&lt;/li&gt;
&lt;li&gt;When Free Online SQL Learning Is Right for You, and When It Isn't&lt;/li&gt;
&lt;li&gt;How SQLTest.online Fits Into Your Free Learning Journey&lt;/li&gt;
&lt;li&gt;Frequently Asked Questions About Learning SQL Online for Free&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This guide will help you learn SQL online free for beginners with clarity and confidence. Let's get started.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Does It Mean to Learn SQL Online Free for Beginners?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is it really possible to learn SQL online for free?
&lt;/h3&gt;

&lt;p&gt;Yes, and it happens more often than you might think. Plenty of people have moved into data roles using only free materials. You don't need a degree or a paid bootcamp. The resources exist and they work.&lt;/p&gt;

&lt;h3&gt;
  
  
  What does a typical free SQL learning path look like?
&lt;/h3&gt;

&lt;p&gt;It starts with understanding what a database is and how SQL fits in. Then you learn to retrieve data with SELECT, filter with WHERE, and sort with ORDER BY. After that comes grouping and aggregation. JOINs follow, then subqueries and set operations. Finally, you tackle window functions and CTEs.&lt;/p&gt;

&lt;p&gt;Many learners progress through these milestones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Early focus:&lt;/strong&gt; Basic SELECT, WHERE, ORDER BY&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Next focus:&lt;/strong&gt; GROUP BY, HAVING, basic JOINs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Then:&lt;/strong&gt; More JOINs, subqueries, UNION&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Later:&lt;/strong&gt; Window functions, common table expressions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The whole journey can take a couple of months of daily work. The key is writing queries every day, not just reading about them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Exactly Is SQL and Why Should Beginners Learn It?
&lt;/h2&gt;

&lt;p&gt;SQL stands for Structured Query Language. It is the universal language for managing relational databases. Data analysts, software developers, and database administrators all use it regularly. According to &lt;a href="https://en.wikipedia.org/wiki/SQL" rel="noopener noreferrer"&gt;Wikipedia&lt;/a&gt;, SQL is designed for managing data held in a relational database management system.&lt;/p&gt;

&lt;p&gt;The demand is real, too. The &lt;a href="https://www.bls.gov/" rel="noopener noreferrer"&gt;U.S. Bureau of Labor Statistics&lt;/a&gt; tracks strong projected growth for database and data-related roles, and SQL is the skill those jobs assume you already have.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why learning SQL for free is a smart choice
&lt;/h3&gt;

&lt;p&gt;Effective SQL practice for beginners is hands-on. By choosing free resources, you remove financial risk. You can explore the language without any upfront investment, decide whether data work suits you, and only later pay for a course if you want a formal credential.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does SQL compare to other programming languages?
&lt;/h3&gt;

&lt;p&gt;SQL is declarative. You describe the result you want, not the steps to get it. This makes it easier to learn than Python or Java for many people. Beginners often write useful queries within their first hour of practice.&lt;/p&gt;

&lt;p&gt;Common uses for SQL:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data analysts extract insights from company databases.&lt;/li&gt;
&lt;li&gt;Developers manage application data with SQL.&lt;/li&gt;
&lt;li&gt;Business intelligence professionals build reports and dashboards.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How Free Online SQL Learning Actually Works
&lt;/h2&gt;

&lt;p&gt;Free online SQL platforms use a learn-by-doing model. You read a short lesson and then write real queries against a live database. The system checks your output and gives immediate feedback.&lt;/p&gt;

&lt;p&gt;This active approach is the reason interactive practice tends to stick. When you type a query and immediately see whether the result is right or wrong, each attempt becomes a small experiment. That feedback loop builds understanding far faster than watching a video or reading a chapter and hoping it lands.&lt;/p&gt;

&lt;h3&gt;
  
  
  What makes free online SQL learning effective?
&lt;/h3&gt;

&lt;p&gt;Active practice is the key. Instead of passively watching videos, you type code. This builds retention and understanding. Mistakes become immediate learning opportunities because you see the results.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do interactive platforms teach SQL?
&lt;/h3&gt;

&lt;p&gt;Platforms like &lt;a href="https://www.khanacademy.org/computing/computer-programming/sql" rel="noopener noreferrer"&gt;Khan Academy&lt;/a&gt; provide structured lessons with built-in editors. You write queries in the browser and see results instantly. This removes setup friction and lets you focus on learning. &lt;a href="https://www.w3schools.com/sql/" rel="noopener noreferrer"&gt;W3Schools&lt;/a&gt; offers a similar try-it editor alongside a reference you can return to whenever syntax slips your mind.&lt;/p&gt;

&lt;p&gt;Key features that make interactive learning work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Immediate feedback on each query.&lt;/li&gt;
&lt;li&gt;Ability to learn at your own pace.&lt;/li&gt;
&lt;li&gt;No software installation needed.&lt;/li&gt;
&lt;li&gt;Access to realistic sample databases.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Best Free Path to Learn SQL: A Beginner's Step-by-Step Plan
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Follow this step-by-step plan to learn SQL online free for beginners
&lt;/h3&gt;

&lt;p&gt;This path takes you from beginner to advanced topics, and you can complete every stage below for free.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Start with the fundamentals.&lt;/strong&gt; Master SELECT, FROM, WHERE, and basic filtering. Use a resource like &lt;a href="https://www.khanacademy.org/computing/computer-programming/sql" rel="noopener noreferrer"&gt;Khan Academy's Intro to SQL&lt;/a&gt;. Write your first queries and get comfortable with simple data retrieval.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Practice daily.&lt;/strong&gt; Use an interactive platform and set a timer for 20 minutes. Solve a few exercises each day. Consistent practice builds confidence faster than long weekend sessions. A short daily habit beats a four-hour cram once a week.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Learn JOINs and aggregation.&lt;/strong&gt; Master INNER JOIN, LEFT JOIN, and GROUP BY. These skills appear in almost every real-world query. Combining tables is where SQL starts to feel powerful, so spend extra time here until multi-table queries feel natural.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tackle subqueries and set operations.&lt;/strong&gt; Learn how to nest queries and combine result sets with UNION. These techniques appear frequently in reporting and analytics.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Work on analytical problems.&lt;/strong&gt; Explore window functions, common table expressions (CTEs), and recursive queries. Our &lt;a href="https://sqltest.online/en/question/adventureworks/products-pairs" rel="noopener noreferrer"&gt;SQL Practice #31: Frequently Purchased Product Pairs&lt;/a&gt; is a good challenge for this stage.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prepare for interviews.&lt;/strong&gt; Practice theory alongside queries. Questions like &lt;a href="https://sqltest.online/en/question/db-theory/what-are-dql-commands" rel="noopener noreferrer"&gt;SQL Interview Questions #9: What are DQL commands?&lt;/a&gt; and &lt;a href="https://sqltest.online/en/question/db-theory/what-is-dbms" rel="noopener noreferrer"&gt;What is DBMS?&lt;/a&gt; test the fundamentals interviewers ask about most.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each stage builds on the previous one. Do not rush. Mastery comes from repetition.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Choose the Right Free SQL Learning Platform: Key Evaluation Dimensions
&lt;/h2&gt;

&lt;p&gt;When picking a platform to learn SQL online free for beginners, consider these factors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Interactivity:&lt;/strong&gt; Does the platform let you write real queries or just watch videos? Active learning beats passive watching.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database variety:&lt;/strong&gt; Does it support MySQL, PostgreSQL, SQL Server, or a custom dialect? Broad exposure helps you adapt later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Progression structure:&lt;/strong&gt; Does it have a clear beginner-to-advanced path or is it random exercises? Structure keeps you moving forward.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feedback quality:&lt;/strong&gt; Does it show expected output and explain mistakes, or just mark right or wrong? Good feedback accelerates learning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Community and support:&lt;/strong&gt; Are there forums, comments, or mentors? Getting stuck is easier with help available.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost:&lt;/strong&gt; Is it truly free or freemium with paywalled content? Read the fine print before committing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mobile-friendliness:&lt;/strong&gt; Can you practice on a phone? Some platforms offer responsive designs for on-the-go practice.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Test a few platforms before deciding. The one that feels right is the one you will stick with.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Mistakes Beginners Make When Learning SQL Online for Free
&lt;/h2&gt;

&lt;p&gt;The most common mistake is skipping the basics. Beginners often jump to complex JOINs before they truly understand WHERE clauses and data types, and the result is frustration and slow progress. A few hours spent getting filtering right pays off across every query you write afterward.&lt;/p&gt;

&lt;p&gt;A subtler trap is assuming all SQL is identical. Dialects differ: MySQL, PostgreSQL, and SQL Server vary in functions, date handling, and syntax. If you only ever practice on one system, a different dialect in a job or interview can throw you. Expose yourself to more than one early.&lt;/p&gt;

&lt;p&gt;Another expensive habit is reading without writing. Tutorials feel productive, but passive reading creates false confidence. You only learn SQL by typing queries, running them, and fixing what breaks. Treat every error message as a clue rather than a wall, and debug systematically instead of guessing.&lt;/p&gt;

&lt;p&gt;Finally, many beginners practice inconsistently. Twenty focused minutes a day will outpace an occasional marathon session, because the daily habit keeps concepts fresh while you layer new ones on top.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Free Online SQL Learning Is Right for You, and When It Isn't
&lt;/h2&gt;

&lt;p&gt;Free online learning fits most beginners beautifully. It suits career changers testing whether they enjoy data work, students supplementing a university course, professionals who need to query customer or product data for their actual job, and hobbyists who simply want to understand their own datasets. If that describes you, free resources will carry you a long way.&lt;/p&gt;

&lt;p&gt;It is a weaker fit in a few situations. If you need a formal certification for a regulated field, if you learn best with deadlines and an instructor holding you accountable, or if you are targeting a niche system with little free practice material, structured paid options make more sense. Once you have worked through the free path, a credential-bearing course can be a reasonable next step.&lt;/p&gt;

&lt;h2&gt;
  
  
  How SQLTest.online Fits Into Your Free Learning Journey
&lt;/h2&gt;

&lt;p&gt;We built SQLTest.online as a free, interactive practice companion rather than a replacement for any tutorial you enjoy. Our exercises are grouped by complexity, category, and database, so you can move from your first SELECT toward analytical challenges at a pace that matches your progress. We cover MySQL, PostgreSQL, SQL Server, SQLite, and Firebird, which means you practice across dialects instead of getting locked into one.&lt;/p&gt;

&lt;p&gt;We are especially useful once you have finished an introductory course and need structured repetition to make skills permanent. You can copy query results to your clipboard, work through interview-style theory questions, and tackle real-world problems drawn from realistic databases. If you want background reading, our &lt;a href="https://sqltest.online/en/about" rel="noopener noreferrer"&gt;about page&lt;/a&gt; explains the approach and our &lt;a href="https://sqltest.online/en/books" rel="noopener noreferrer"&gt;recommended SQL books&lt;/a&gt; point you toward deeper study. Think of us as the place you go to practice what you have learned until it sticks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions About Learning SQL Online for Free
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is it possible to learn SQL for free?
&lt;/h3&gt;

&lt;p&gt;Yes. Free, interactive platforms and tutorials cover everything from basic SELECT statements to window functions, and many people reach a working level without ever paying for a course.&lt;/p&gt;

&lt;h3&gt;
  
  
  How long does it take a beginner to learn SQL?
&lt;/h3&gt;

&lt;p&gt;With consistent daily practice, most beginners write basic queries within a few weeks and reach intermediate topics like JOINs and aggregation within two to three months. Consistency matters more than speed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need to know programming before learning SQL?
&lt;/h3&gt;

&lt;p&gt;No. SQL is declarative and designed to be readable, so you describe the result you want rather than coding step-by-step logic. Many people learn SQL as their first technical skill.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I learn SQL on my phone?
&lt;/h3&gt;

&lt;p&gt;You can read lessons and review concepts on a phone, and some platforms are mobile-friendly. Writing and testing real queries is easier on a desktop or tablet where you have a proper keyboard.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the best free way to practice SQL for beginners?
&lt;/h3&gt;

&lt;p&gt;The best approach combines a structured tutorial for concepts with an interactive platform for daily practice. Learn a topic, then immediately write queries against a real database until it feels natural.&lt;/p&gt;

</description>
      <category>learnsqlonlinefreefo</category>
    </item>
    <item>
      <title>Practice SQL JOINs Online Free: Master Multi-Table Queries</title>
      <dc:creator>Slava Rozhnev</dc:creator>
      <pubDate>Sat, 23 May 2026 13:16:53 +0000</pubDate>
      <link>https://forem.com/rozhnev/practice-sql-joins-online-free-master-multi-table-queries-213</link>
      <guid>https://forem.com/rozhnev/practice-sql-joins-online-free-master-multi-table-queries-213</guid>
      <description>&lt;p&gt;&lt;strong&gt;Practicing SQL JOINs online free means using interactive platforms that provide sample databases and real-time query feedback without any cost.&lt;/strong&gt; This hands-on approach lets you write INNER, LEFT, RIGHT, and FULL OUTER JOINs against actual data tables, building muscle memory for multi-table queries that hiring managers test in technical interviews.&lt;/p&gt;

&lt;p&gt;Unlike reading a syntax reference, active practice forces you to think through key relationships, handle NULLs, and debug unexpected row counts. When you practice JOINs online for free, you get immediate feedback on your syntax and logic. This turns passive learning into active skill building.&lt;/p&gt;

&lt;p&gt;Many beginners read about JOINs but never write one until an interview. That gap costs confidence. The best way to close it is to practice SQL JOINs online free with real datasets that mimic production environments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Table of Contents&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What Does It Mean to Practice SQL JOINs Online Free?&lt;/li&gt;
&lt;li&gt;Why Traditional SQL JOIN Tutorials Fall Short for Real-World Skills&lt;/li&gt;
&lt;li&gt;A Three-Step Framework to Master SQL JOINs Through Practice&lt;/li&gt;
&lt;li&gt;How to Evaluate a Free SQL JOIN Practice Platform: Key Dimensions&lt;/li&gt;
&lt;li&gt;Three Mistakes That Sabotage Your SQL JOIN Practice and How to Fix Them&lt;/li&gt;
&lt;li&gt;Why We Built SQLtest.online for Hands-On JOIN Practice&lt;/li&gt;
&lt;li&gt;When to Commit to a Structured JOIN Practice Routine: Three Signals&lt;/li&gt;
&lt;li&gt;How SQL JOIN Practice Prepares You for Technical Interviews&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What Does It Mean to Practice SQL JOINs Online Free?
&lt;/h2&gt;

&lt;p&gt;An SQL JOIN is a clause that combines rows from two or more tables based on a related column. The &lt;a href="https://en.wikipedia.org/wiki/Join_(SQL)" rel="noopener noreferrer"&gt;Wikipedia entry on SQL joins&lt;/a&gt; defines a join as an operation that combines columns from two or more tables into a single result set based on matching values. Without JOINs, you cannot answer questions that span multiple tables.&lt;/p&gt;

&lt;p&gt;When you practice SQL JOINs online free, you write queries against live databases. You see results instantly. You compare your output against expected results. This feedback loop is what builds real skill.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is an SQL JOIN and why does it matter?
&lt;/h3&gt;

&lt;p&gt;JOINs are the backbone of relational database queries. Every data professional, from analyst to backend developer, uses them daily. The &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/join.html" rel="noopener noreferrer"&gt;MySQL JOIN reference&lt;/a&gt; shows how an INNER JOIN matches rows across two tables on a shared key, merging the matched rows into a single result set.&lt;/p&gt;

&lt;p&gt;Knowing how to write accurate JOINs separates a beginner from someone who can work with real data. Interviewers test JOIN skills repeatedly because incorrect JOINs produce wrong answers that are hard to catch.&lt;/p&gt;

&lt;h3&gt;
  
  
  How can I practice SQL JOINs online free with sample databases?
&lt;/h3&gt;

&lt;p&gt;You can practice SQL JOINs online free by signing up for platforms that offer interactive SQL editors pre-loaded with sample databases. These platforms let you write queries against tables like customers, orders, products, and employees without setting up anything locally.&lt;/p&gt;

&lt;p&gt;The key is finding a platform that gives you immediate result feedback. When you practice against sample databases, you should see your output instantly and compare it to expected results. This rapid feedback loop accelerates learning dramatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Traditional SQL JOIN Tutorials Fall Short for Real-World Skills
&lt;/h2&gt;

&lt;p&gt;Reading a syntax reference or watching a video gives you passive knowledge. JOINs require active recall. When you face a real database with NULLs, duplicate keys, and unexpected row counts, static examples do not prepare you.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.w3schools.com/Sql/sql_join.asp" rel="noopener noreferrer"&gt;W3Schools&lt;/a&gt; has a reference page that lists INNER, LEFT OUTER, RIGHT OUTER, and FULL OUTER JOIN as the main join types. That reference is useful for syntax lookup. It is not useful for building the debugging instinct you need in a real job.&lt;/p&gt;

&lt;p&gt;Most tutorials show clean, sanitized data. Real databases are messy. NULL values appear in join columns. Duplicate keys create unexpected row multiplication. The only way to learn how to handle these cases is through interactive SQL JOIN exercises that throw real-world scenarios at you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why do static tutorials fail to build practical JOIN skills?
&lt;/h3&gt;

&lt;p&gt;Static tutorials present one correct answer and move on. They do not force you to reason about why a JOIN returned 50 rows instead of 100. They do not ask you to debug a query that runs but produces wrong results.&lt;/p&gt;

&lt;p&gt;When you practice on a platform with feedback, you learn to reason about row counts. You predict how many rows each JOIN type will return given the data. That predictive skill is what interviewers test and what jobs require.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does interactive SQL JOIN practice fill the gap?
&lt;/h3&gt;

&lt;p&gt;Interactive platforms let you experiment. You write a LEFT JOIN, see the result, then rewrite it as an INNER JOIN and compare. That cause-and-effect observation builds intuition faster than any book.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://datalemur.com/sql-tutorial/sql-joins-inner-outer-left-right" rel="noopener noreferrer"&gt;DataLemur&lt;/a&gt;'s SQL JOIN tutorial states that an INNER JOIN returns only rows with matching values from both tables. A LEFT JOIN returns all rows from the left table plus matching rows from the right table. Reading that definition is step one. Writing both JOINs on the same tables and comparing row counts is where the learning sticks.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Three-Step Framework to Master SQL JOINs Through Practice
&lt;/h2&gt;

&lt;p&gt;This framework builds from simple to complex. Each step depends on the previous one, so follow them in order.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Start with single-table queries.&lt;/strong&gt; Confirm you understand the base tables, their columns, row counts, and key relationships. Before you can join two tables, you need to know what each table contains and which columns link them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Write INNER JOINs on two tables using a foreign key.&lt;/strong&gt; Verify row counts match expectations. An INNER JOIN should return fewer rows than the larger table unless every row has a match. Compare your result count against a known correct count.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Progress to LEFT JOINs, then multi-table JOINs, then self-joins and full outer joins.&lt;/strong&gt; Each step adds complexity. Multi-table JOINs with three or more tables test your ability to chain relationships. Self-joins and full outer joins appear in advanced scenarios and interview questions.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 1: Understand your base tables first
&lt;/h3&gt;

&lt;p&gt;Many beginners try to JOIN before they understand the source tables. This leads to confusion when the result set does not make sense. Spend time exploring each table's columns, data types, and row counts before writing a JOIN.&lt;/p&gt;

&lt;p&gt;Start by running SELECT * queries on each table. Note the primary key and any foreign key columns. This groundwork makes JOIN logic obvious later, and the &lt;a href="https://www.postgresql.org/docs/current/tutorial-join.html" rel="noopener noreferrer"&gt;PostgreSQL join tutorial&lt;/a&gt; walks through the same idea of qualifying columns by their table before combining them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Write INNER JOINs and verify row counts
&lt;/h3&gt;

&lt;p&gt;An INNER JOIN returns only rows with matching values in both tables. If table A has 100 rows and table B has 200 rows, a well-written INNER JOIN on a foreign key should return at most 100 rows when the key is unique in A.&lt;/p&gt;

&lt;p&gt;After you run your query, compare the row count against what you predicted. A mismatch means you need to debug your logic or check the data for NULLs. This verification habit separates competent SQL users from guessers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Progress to LEFT JOINs, multi-table queries, and self-joins
&lt;/h3&gt;

&lt;p&gt;LEFT JOINs return all rows from the left table and matching rows from the right. This is essential for finding unmatched records. Multi-table JOINs chain multiple ON clauses to connect three or more tables.&lt;/p&gt;

&lt;p&gt;Self-joins are especially tricky. In the "Using Self Joins" lesson, &lt;a href="https://doi.org/10.1007/978-1-4842-4905-5_8" rel="noopener noreferrer"&gt;Deardurff (2019)&lt;/a&gt; explains that a self-join treats a single table as two separate instances with different aliases. This pattern is common in employee-manager hierarchies and duplicate-record detection.&lt;/p&gt;

&lt;p&gt;Full outer joins combine both tables completely. As &lt;a href="https://en.wikipedia.org/wiki/Join_(SQL)" rel="noopener noreferrer"&gt;Wikipedia&lt;/a&gt; describes, a FULL OUTER JOIN returns all rows from both tables, with NULLs filled in where matches do not exist. This is useful for data reconciliation tasks.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;JOIN Type&lt;/th&gt;
&lt;th&gt;Returns&lt;/th&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;th&gt;Expected Row Count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;INNER JOIN&lt;/td&gt;
&lt;td&gt;Only matching rows from both tables&lt;/td&gt;
&lt;td&gt;Finding records that exist in both datasets&lt;/td&gt;
&lt;td&gt;Fewer than or equal to the smaller table&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LEFT JOIN&lt;/td&gt;
&lt;td&gt;All rows from left table plus matching rows from right&lt;/td&gt;
&lt;td&gt;Finding records with optional related data&lt;/td&gt;
&lt;td&gt;Equal to left table row count&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RIGHT JOIN&lt;/td&gt;
&lt;td&gt;All rows from right table plus matching rows from left&lt;/td&gt;
&lt;td&gt;Same as LEFT JOIN but reversed&lt;/td&gt;
&lt;td&gt;Equal to right table row count&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FULL OUTER JOIN&lt;/td&gt;
&lt;td&gt;All rows from both tables&lt;/td&gt;
&lt;td&gt;Data reconciliation and comparing two datasets&lt;/td&gt;
&lt;td&gt;Sum of both tables minus matches&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CROSS JOIN&lt;/td&gt;
&lt;td&gt;Every possible combination of rows&lt;/td&gt;
&lt;td&gt;Generating all combinations&lt;/td&gt;
&lt;td&gt;Row count of table A times row count of table B&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How to Evaluate a Free SQL JOIN Practice Platform: Key Dimensions
&lt;/h2&gt;

&lt;p&gt;Not all practice platforms are equal. Here are five dimensions to evaluate when choosing where to practice SQL JOINs online free.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dataset variety:&lt;/strong&gt; Does the platform offer multiple sample databases like e-commerce, HR, and library schemas so you practice JOINs in different contexts?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Query feedback:&lt;/strong&gt; Does it show the result set immediately and highlight errors or unexpected row counts?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Progression structure:&lt;/strong&gt; Are exercises grouped by complexity or thrown at you randomly?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interview relevance:&lt;/strong&gt; Do exercises mirror real interview questions like finding unmatched records or aggregating across joined tables?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost and access:&lt;/strong&gt; Is the core JOIN practice truly free or are advanced exercises behind a paywall?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Dataset variety and real-world relevance
&lt;/h3&gt;

&lt;p&gt;A platform with one sample database limits your exposure. Real jobs involve many different schemas. The more databases you practice against, the more flexible your JOIN skills become.&lt;/p&gt;

&lt;p&gt;Look for platforms that offer e-commerce schemas with customers, orders, and products, plus library schemas with authors, books, and borrowers. Each schema tests JOINs in a different context.&lt;/p&gt;

&lt;h3&gt;
  
  
  Query feedback and error visibility
&lt;/h3&gt;

&lt;p&gt;The best platforms show your result set immediately. They also make it easy to compare your output with the expected output. Some platforms highlight which rows differ or reveal the correct query after multiple attempts.&lt;/p&gt;

&lt;p&gt;When you work through interactive SQL JOIN exercises, error visibility is crucial. A platform that simply says wrong answer without context teaches less than one that shows you where your result diverged.&lt;/p&gt;

&lt;h3&gt;
  
  
  Progression structure and interview readiness
&lt;/h3&gt;

&lt;p&gt;The best platforms group exercises by complexity. You start with simple two-table INNER JOINs and progress to multi-table queries, self-joins, and analytic JOINs. This scaffolded approach builds confidence gradually.&lt;/p&gt;

&lt;p&gt;Interview questions often require multi-table JOINs with aggregation and filtering. A good platform includes exercises that combine JOINs with GROUP BY, HAVING, and subqueries. For example, &lt;a href="https://sqltest.online/en/question/difficult/find-all-films-where-henry-berry-did-not-participate" rel="noopener noreferrer"&gt;finding all films where a specific actor did not participate&lt;/a&gt; is a multi-table JOIN challenge that tests set-based thinking.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;Exercise Types&lt;/th&gt;
&lt;th&gt;Sample Databases&lt;/th&gt;
&lt;th&gt;Query Feedback&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SQLtest.online&lt;/td&gt;
&lt;td&gt;Interactive SQL exercises grouped by complexity&lt;/td&gt;
&lt;td&gt;Multiple databases&lt;/td&gt;
&lt;td&gt;Immediate result set display&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQLZoo&lt;/td&gt;
&lt;td&gt;Step-by-step tutorial exercises&lt;/td&gt;
&lt;td&gt;World, Nobel databases&lt;/td&gt;
&lt;td&gt;Shows result set&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQLBolt&lt;/td&gt;
&lt;td&gt;Lesson-based interactive exercises&lt;/td&gt;
&lt;td&gt;Single movies database&lt;/td&gt;
&lt;td&gt;Shows result set&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DataLemur&lt;/td&gt;
&lt;td&gt;Interview-style practice questions&lt;/td&gt;
&lt;td&gt;Multiple databases&lt;/td&gt;
&lt;td&gt;Expected vs actual output&lt;/td&gt;
&lt;td&gt;Free tier available&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HackerRank&lt;/td&gt;
&lt;td&gt;Coding challenge problems&lt;/td&gt;
&lt;td&gt;Multiple domains&lt;/td&gt;
&lt;td&gt;Pass or fail with test cases&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Three Mistakes That Sabotage Your SQL JOIN Practice and How to Fix Them
&lt;/h2&gt;

&lt;p&gt;These mistakes are common among beginners. Recognizing them early saves hours of debugging frustration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does INNER JOIN drop rows unexpectedly?
&lt;/h3&gt;

&lt;p&gt;The most common mistake is forgetting that INNER JOIN drops non-matching rows. Beginners see missing data and think the query is broken. In reality, the INNER JOIN is working correctly by only returning rows where the join condition succeeds.&lt;/p&gt;

&lt;p&gt;The fix is to check your data. Do the join columns contain NULLs? Are there rows in one table without corresponding rows in the other? If you need all rows from one side, use a LEFT JOIN instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  When should you use LEFT JOIN instead of INNER JOIN?
&lt;/h3&gt;

&lt;p&gt;The subtler trap is using LEFT JOIN when INNER JOIN is correct, or vice versa. A LEFT JOIN introduces NULLs in the right-table columns for rows without matches. Those NULLs can cause downstream calculations to produce wrong answers.&lt;/p&gt;

&lt;p&gt;Ask yourself: do I need every row from the left table even if there is no match in the right? If yes, use LEFT JOIN. If you only want rows that exist in both tables, use INNER JOIN. This distinction is tested frequently in interviews.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does a missing ON clause create a Cartesian product?
&lt;/h3&gt;

&lt;p&gt;The most expensive mistake is omitting the ON clause entirely. This produces a CROSS JOIN where every row from table A pairs with every row from table B. The result set multiplies, so 100 rows against 200 rows returns 20,000 rows.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/join.html" rel="noopener noreferrer"&gt;MySQL JOIN reference&lt;/a&gt; notes that CROSS JOIN and INNER JOIN are syntactically equivalent in MySQL, which is exactly why an accidental missing ON clause silently produces a Cartesian product. If your query returns far more rows than expected, check your JOIN syntax first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why We Built SQLtest.online for Hands-On JOIN Practice
&lt;/h2&gt;

&lt;p&gt;We built SQLtest.online because we saw learners stuck between theory and interview readiness. They could recite JOIN syntax but froze when asked to write a three-table query in an interview.&lt;/p&gt;

&lt;p&gt;Our platform offers interactive SQL exercises where you write real JOIN queries against multiple sample databases. You get immediate result-set feedback. We group tasks by complexity, category, and database so you can start with simple two-table INNER JOINs and progress to multi-table and self-join exercises.&lt;/p&gt;

&lt;p&gt;The platform is free. We believe everyone should be able to practice SQL JOINs against a real sample database without financial barriers. We also support copying SQL code to the clipboard so you can save your work for later review.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does SQLtest.online help you practice SQL JOINs online free?
&lt;/h3&gt;

&lt;p&gt;SQLtest.online gives you a browser-based SQL environment with pre-loaded sample databases. You do not install anything. You do not configure anything. You just write queries and see results.&lt;/p&gt;

&lt;p&gt;Our interactive SQL JOIN exercises range from beginner to advanced. We include theory questions that reinforce fundamentals covering what a database is, what DBMS means, and what RDBMS means. This foundation makes JOIN logic easier to understand.&lt;/p&gt;

&lt;p&gt;We also prepare you for technical interviews. Our exercises mirror real interview patterns such as multi-table queries, finding unmatched records, aggregating across joined tables, and handling NULLs.&lt;/p&gt;

&lt;h3&gt;
  
  
  What types of JOIN exercises does SQLtest.online offer?
&lt;/h3&gt;

&lt;p&gt;We offer exercises across multiple databases, including an AdventureWorks schema and a rental database. Each database has exercises grouped by difficulty.&lt;/p&gt;

&lt;p&gt;For example, &lt;a href="https://sqltest.online/en/question/adventureworks/get-product-category-colors" rel="noopener noreferrer"&gt;getting product category color counts&lt;/a&gt; requires joining product and category tables then aggregating results. This kind of multi-table grouping is a common interview question.&lt;/p&gt;

&lt;p&gt;We also have exercises that test data manipulation with JOINs, such as &lt;a href="https://sqltest.online/en/question/data-manipulation-queries/create-customer-addresses-view" rel="noopener noreferrer"&gt;creating a customer addresses view&lt;/a&gt;. These teach you that JOINs work in CREATE VIEW, UPDATE, and DELETE statements, not just SELECT queries.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Commit to a Structured JOIN Practice Routine: Three Signals
&lt;/h2&gt;

&lt;p&gt;Knowing when to start, pivot, or change your approach is as important as the practice itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  The build signal: start with two-table INNER JOINs
&lt;/h3&gt;

&lt;p&gt;If you can write a basic SELECT but freeze when someone says join the orders and customers tables, that is your build signal. Start with two-table INNER JOINs immediately. Do not wait until you feel ready. Write your first JOIN query today.&lt;/p&gt;

&lt;p&gt;Spend 15 minutes daily practicing. Consistency beats intensity. A focused 15-minute session where you write and debug JOIN queries is worth more than a three-hour tutorial binge once a week.&lt;/p&gt;

&lt;h3&gt;
  
  
  The pivot signal: switch to platforms that verify row counts
&lt;/h3&gt;

&lt;p&gt;If you have done a few JOIN exercises but still cannot predict row counts before running the query, you need to pivot. Switch to a platform that shows expected versus actual row counts and forces you to reason before executing.&lt;/p&gt;

&lt;p&gt;At SQLtest.online, we design exercises that help you build this predictive skill.&lt;/p&gt;

&lt;h3&gt;
  
  
  The abandon signal: stop passive learning
&lt;/h3&gt;

&lt;p&gt;If you have been practicing by reading syntax references for weeks without writing a single JOIN query, drop that approach entirely. Passive learning creates the illusion of progress without building real skill.&lt;/p&gt;

&lt;p&gt;Commit to active query writing. Open a browser, go to a free SQL JOIN practice platform, and write queries. The first one will be slow. By the tenth one, you will start to see patterns. By the fiftieth, JOINs will feel natural.&lt;/p&gt;

&lt;h2&gt;
  
  
  How SQL JOIN Practice Prepares You for Technical Interviews
&lt;/h2&gt;

&lt;p&gt;Technical interviews for data roles almost always include JOIN questions. Interviewers want to see that you can combine &lt;a href="https://www.postgresql.org/docs/current/tutorial-join.html" rel="noopener noreferrer"&gt;data from multiple tables&lt;/a&gt; accurately and efficiently.&lt;/p&gt;

&lt;h3&gt;
  
  
  What interview questions test your JOIN skills?
&lt;/h3&gt;

&lt;p&gt;Common interview JOIN patterns include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Finding records in one table that do not exist in another using LEFT JOIN with IS NULL&lt;/li&gt;
&lt;li&gt;Aggregating data across related tables using JOIN with GROUP BY&lt;/li&gt;
&lt;li&gt;Comparing rows within the same table using self-joins&lt;/li&gt;
&lt;li&gt;Handling NULLs in join columns using COALESCE with JOIN&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, &lt;a href="https://sqltest.online/en/question/normal/find-duplicate-actor-names" rel="noopener noreferrer"&gt;finding duplicate actor names&lt;/a&gt; tests a self-join pattern that appears in real interviews. You join a table to itself to find rows with matching values in certain columns.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do multi-table JOINs appear in real interviews?
&lt;/h3&gt;

&lt;p&gt;Interviewers often present a scenario: we have customers, orders, and products tables. Write a query to find the top 10 customers by total spending. This requires joining all three tables and using aggregation.&lt;/p&gt;

&lt;p&gt;Another common pattern is finding employees who have never submitted an expense report. This tests LEFT JOIN with NULL filtering. Our &lt;a href="https://sqltest.online/en/question/bookings/bookings-total-amount" rel="noopener noreferrer"&gt;total bookings amount exercise&lt;/a&gt; uses a similar pattern, joining transaction tables to calculate totals.&lt;/p&gt;

&lt;p&gt;For more practice, try our &lt;a href="https://sqltest.online/en/question/aggregation-functions/find-the-average-cost-of-renting-a-movie-by-category" rel="noopener noreferrer"&gt;average cost of renting a movie by category&lt;/a&gt; exercise. It combines JOIN operations with aggregate functions in a realistic context.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How can I practice SQL JOINs online for free?
&lt;/h3&gt;

&lt;p&gt;Use browser-based platforms like SQLtest.online, SQLZoo, or DataLemur that offer free interactive exercises with pre-loaded sample databases. You can write and run queries instantly without installing any software.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the best way to learn SQL JOINs?
&lt;/h3&gt;

&lt;p&gt;Write queries daily against real datasets. Start with INNER JOINs and progress to LEFT, RIGHT, FULL OUTER, and self-joins. Focus on predicting row counts before running each query and compare actual results against your predictions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Are there SQL JOIN practice questions with answers?
&lt;/h3&gt;

&lt;p&gt;Yes. Platforms like HackerRank and SQLtest.online provide exercises with expected result sets so you can verify your output. Many platforms also show the correct query after you submit your attempt.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I practice SQL JOINs for interviews?
&lt;/h3&gt;

&lt;p&gt;Focus on multi-table queries that find unmatched records, aggregate across joined tables, and handle NULLs. Patterns like LEFT JOIN with IS NULL and JOIN with GROUP BY appear frequently in technical interviews.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I practice SQL JOINs without installing software?
&lt;/h3&gt;

&lt;p&gt;Yes. Browser-based platforms like SQLtest.online and SQLBolt let you write and run queries instantly without local setup. You only need a web browser and an internet connection to start.&lt;/p&gt;

&lt;h3&gt;
  
  
  How much time should I spend practicing SQL JOINs each day?
&lt;/h3&gt;

&lt;p&gt;Fifteen minutes of focused daily practice is more effective than several hours once a week. Consistency builds the muscle memory you need for interviews and real-world query writing.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between INNER and LEFT JOIN in practice?
&lt;/h3&gt;

&lt;p&gt;An INNER JOIN drops rows that lack a match in the other table. A LEFT JOIN keeps all rows from the left table and fills in NULLs where no match exists. Use INNER JOIN when you only want matched records and LEFT JOIN when you need complete results from one side.&lt;/p&gt;

</description>
      <category>practicesqljoinsonli</category>
    </item>
    <item>
      <title>PostgreSQL Upgraded to Latest Minor Versions on SQLize.online 🐘🚀</title>
      <dc:creator>Slava Rozhnev</dc:creator>
      <pubDate>Sat, 16 May 2026 09:34:17 +0000</pubDate>
      <link>https://forem.com/rozhnev/postgresql-upgraded-to-latest-minor-versions-on-sqlizeonline-9np</link>
      <guid>https://forem.com/rozhnev/postgresql-upgraded-to-latest-minor-versions-on-sqlizeonline-9np</guid>
      <description>&lt;p&gt;As the owner of &lt;a href="https://sqlize.online" rel="noopener noreferrer"&gt;SQLize.online&lt;/a&gt;, I’m committed to providing a playground that doesn’t just let you write code, but lets you test against the most stable, secure, and "production-ready" environments possible.&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.amazonaws.com%2Fuploads%2Farticles%2Fhxp1wflg3mzcljkgnmoj.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.amazonaws.com%2Fuploads%2Farticles%2Fhxp1wflg3mzcljkgnmoj.png" alt=" " width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That’s why I’ve just finished upgrading our PostgreSQL stack to the latest minor versions across the board!&lt;/p&gt;

&lt;p&gt;🛠 What’s New?&lt;br&gt;
The PostgreSQL Global Development Group recently pushed out a series of critical maintenance updates. These are now live and ready for you on the site:&lt;/p&gt;

&lt;p&gt;PostgreSQL 18.4 (The latest stable)&lt;/p&gt;

&lt;p&gt;PostgreSQL 17.10&lt;/p&gt;

&lt;p&gt;PostgreSQL 16.14&lt;/p&gt;

&lt;p&gt;PostgreSQL 15.18&lt;/p&gt;

&lt;p&gt;🔒 Why Minor Versions Matter&lt;br&gt;
In the world of databases, "minor" doesn't mean "unimportant." These updates address:&lt;/p&gt;

&lt;p&gt;Security Patches: Protecting against recently discovered vulnerabilities.&lt;/p&gt;

&lt;p&gt;Stability Fixes: Squashing edge-case bugs that could cause unexpected query behavior.&lt;/p&gt;

&lt;p&gt;Performance Improvements: Minor tweaks to the query planner and indexing engine to ensure your tests reflect real-world efficiency.&lt;/p&gt;

&lt;p&gt;🐘 Test Your Code Today&lt;br&gt;
Whether you are practicing complex joins, testing out PostgreSQL 18 features, or verifying that your legacy queries still work on older versions before a migration, SQLize is updated and ready.&lt;/p&gt;

&lt;p&gt;Fun Fact: PostgreSQL 14 is reaching its End of Life (EOL) in November 2026. If you're still on v14, now is the perfect time to use the SQLize sandbox to test your logic on v17 or v18!&lt;/p&gt;

&lt;p&gt;Try it out now: &lt;a href="https://sqlize.online" rel="noopener noreferrer"&gt;sqlize.online&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy querying!&lt;/p&gt;

</description>
      <category>postgressql</category>
      <category>sandbox</category>
      <category>database</category>
      <category>sql</category>
    </item>
    <item>
      <title>Why `SUM() OVER (ORDER BY ...)` Sometimes Feels Wrong: A Practical Guide to SQL Window Frames</title>
      <dc:creator>Slava Rozhnev</dc:creator>
      <pubDate>Wed, 11 Mar 2026 19:06:35 +0000</pubDate>
      <link>https://forem.com/rozhnev/why-sum-over-order-by-sometimes-feels-wrong-a-practical-guide-to-sql-window-frames-1iga</link>
      <guid>https://forem.com/rozhnev/why-sum-over-order-by-sometimes-feels-wrong-a-practical-guide-to-sql-window-frames-1iga</guid>
      <description>&lt;p&gt;Window functions in SQL can make you feel productive very quickly. You learn &lt;code&gt;PARTITION BY&lt;/code&gt;, add &lt;code&gt;ORDER BY&lt;/code&gt;, use &lt;code&gt;ROW_NUMBER()&lt;/code&gt;, &lt;code&gt;RANK()&lt;/code&gt;, and running totals, and it feels like you already have the mental model.&lt;/p&gt;

&lt;p&gt;That was exactly my mistake.&lt;/p&gt;

&lt;p&gt;For a while, I thought I understood window functions well enough because my queries were working and the results looked plausible. The confusion only started later, when I began getting results that were syntactically correct but did not match what I expected logically.&lt;/p&gt;

&lt;p&gt;A classic example looks like this:&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;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You expect a normal running total. Instead, the result suddenly jumps by multiple rows at once. No SQL error. No broken query. The database is doing exactly what you asked.&lt;/p&gt;

&lt;p&gt;The missing piece is usually the same: &lt;strong&gt;the window frame&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The frame determines &lt;strong&gt;which rows around the current row are actually included in the calculation&lt;/strong&gt;. Until that part clicks, window functions are easy to copy from memory but hard to control precisely.&lt;/p&gt;

&lt;p&gt;For me, understanding frames was the point where window functions stopped feeling like a bag of handy tricks and started feeling like a consistent system.&lt;/p&gt;

&lt;p&gt;When I want to test behavior like this quickly, I usually run the queries in a live environment. For quick experiments I use &lt;a href="https://sqlize.online" rel="noopener noreferrer"&gt;sqlize.online&lt;/a&gt;, and for more structured SQL practice and lessons I publish material on &lt;a href="https://sqltest.online" rel="noopener noreferrer"&gt;sqltest.online&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this article, I want to walk through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what a window frame actually is;&lt;/li&gt;
&lt;li&gt;the difference between &lt;code&gt;ROWS&lt;/code&gt;, &lt;code&gt;RANGE&lt;/code&gt;, and &lt;code&gt;GROUPS&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;how boundaries like &lt;code&gt;UNBOUNDED PRECEDING&lt;/code&gt;, &lt;code&gt;CURRENT ROW&lt;/code&gt;, and &lt;code&gt;n FOLLOWING&lt;/code&gt; work;&lt;/li&gt;
&lt;li&gt;why the default frame can surprise you;&lt;/li&gt;
&lt;li&gt;how to write running totals and moving averages without hidden assumptions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What a window frame is
&lt;/h2&gt;

&lt;p&gt;When we write a window function, we usually see something like this:&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;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;
    &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;payment_date&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When I first started using queries like this, I mentally translated them as: “the function is calculated over the whole &lt;code&gt;customer_id&lt;/code&gt; partition in &lt;code&gt;payment_date&lt;/code&gt; order.”&lt;/p&gt;

&lt;p&gt;That is not quite right.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;PARTITION BY&lt;/code&gt; defines &lt;strong&gt;which partition the window works inside&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ORDER BY&lt;/code&gt; defines &lt;strong&gt;the order of rows inside that partition&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;window frame&lt;/strong&gt; defines &lt;strong&gt;which subset of rows from that partition is used for the current row’s calculation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The full shape looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;function_name&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="p"&gt;...]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="p"&gt;...]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;RANGE&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;GROUPS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="k"&gt;AND&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;So the mental model is really three layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Choose the partition.&lt;/li&gt;
&lt;li&gt;Define the ordering inside that partition.&lt;/li&gt;
&lt;li&gt;For each row, define the frame: where it starts and where it ends.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why this matters
&lt;/h2&gt;

&lt;p&gt;The same &lt;code&gt;SUM()&lt;/code&gt; can mean completely different things depending on the frame:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a running total from the start of the partition to the current row;&lt;/li&gt;
&lt;li&gt;a 3-row sliding window;&lt;/li&gt;
&lt;li&gt;an average across the current row and future rows;&lt;/li&gt;
&lt;li&gt;a full-partition aggregate without collapsing rows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From the outside, these queries can look very similar. Their behavior is not similar at all. That is why window functions can seem simple right up until you hit the first result that looks weird in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frame boundaries
&lt;/h2&gt;

&lt;p&gt;A frame is usually defined with &lt;code&gt;BETWEEN ... AND ...&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The available boundaries are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;UNBOUNDED PRECEDING&lt;/code&gt; — start from the first row in the partition;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;n PRECEDING&lt;/code&gt; — go &lt;code&gt;n&lt;/code&gt; rows or logical steps backward;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CURRENT ROW&lt;/code&gt; — the current row;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;n FOLLOWING&lt;/code&gt; — go &lt;code&gt;n&lt;/code&gt; rows or logical steps forward;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;UNBOUNDED FOLLOWING&lt;/code&gt; — continue to the last row in the partition.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example:&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;ROWS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="n"&gt;UNBOUNDED&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CURRENT&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the classic running-total frame: from the start of the partition up to the current row.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;ROWS&lt;/code&gt;, &lt;code&gt;RANGE&lt;/code&gt;, and &lt;code&gt;GROUPS&lt;/code&gt;: the real difference
&lt;/h2&gt;

&lt;p&gt;This is where things usually become interesting.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;ROWS&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;ROWS&lt;/code&gt; works with &lt;strong&gt;physical rows&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you write:&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;ROWS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CURRENT&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;that always means exactly three rows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the current row;&lt;/li&gt;
&lt;li&gt;the two previous rows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It does not matter whether those rows have the same &lt;code&gt;ORDER BY&lt;/code&gt; value or not. The count is based on row positions.&lt;/p&gt;

&lt;p&gt;This is the most predictable mode. In most practical analytics work, especially when I need a fixed-width sliding window, &lt;code&gt;ROWS&lt;/code&gt; is usually where I start.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;RANGE&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;RANGE&lt;/code&gt; works with a &lt;strong&gt;logical value range&lt;/strong&gt;, not physical rows.&lt;/p&gt;

&lt;p&gt;If multiple rows share the same &lt;code&gt;ORDER BY&lt;/code&gt; value, they can enter the frame together as one logical group.&lt;/p&gt;

&lt;p&gt;That is why &lt;code&gt;RANGE&lt;/code&gt; often surprises people.&lt;/p&gt;

&lt;p&gt;The most important detail is this: if you specify &lt;code&gt;ORDER BY&lt;/code&gt; but do &lt;strong&gt;not&lt;/strong&gt; define a frame explicitly, many databases use this default:&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;RANGE&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="n"&gt;UNBOUNDED&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CURRENT&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That means the calculation includes not only all rows before the current row, but also &lt;strong&gt;all rows that share the same ordering value as the current row&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If your &lt;code&gt;ORDER BY&lt;/code&gt; column contains duplicates, the result can jump more than you expect.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;GROUPS&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;GROUPS&lt;/code&gt; works with &lt;strong&gt;peer groups&lt;/strong&gt; of equal &lt;code&gt;ORDER BY&lt;/code&gt; values.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;ROWS&lt;/code&gt; counts rows and &lt;code&gt;RANGE&lt;/code&gt; thinks in logical value ranges, &lt;code&gt;GROUPS&lt;/code&gt; counts groups of equal values.&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 sql"&gt;&lt;code&gt;&lt;span class="n"&gt;GROUPS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CURRENT&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;means: take the current peer group and the previous peer group as whole units.&lt;/p&gt;

&lt;p&gt;This is useful when your mental model is based on equal-value groups rather than individual rows. PostgreSQL supports it. Support in MySQL and MariaDB is more limited depending on version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 1: running total
&lt;/h2&gt;

&lt;p&gt;Let’s start with the most common case.&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;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;payment_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;
        &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;payment_date&lt;/span&gt;
        &lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="n"&gt;UNBOUNDED&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CURRENT&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;running_total&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;payment&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;payment_date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What happens here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rows are partitioned by &lt;code&gt;customer_id&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;rows inside the partition are ordered by &lt;code&gt;payment_date&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;for each row, the sum runs from the first row in the partition up to the current row.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the explicit, correct running-total pattern.&lt;/p&gt;

&lt;p&gt;In my own queries, I almost always write this frame explicitly, even if the database would happen to return the expected result without it. Writing the frame makes the behavior obvious and protects you from subtle surprises later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 2: moving average
&lt;/h2&gt;

&lt;p&gt;Now let’s use a fixed-width window:&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;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;payment_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;
            &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;payment_date&lt;/span&gt;
            &lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CURRENT&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;moving_avg_3&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;payment&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;payment_date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For each row, the frame includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the current row;&lt;/li&gt;
&lt;li&gt;the two previous rows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the maximum frame width is three rows.&lt;/p&gt;

&lt;p&gt;This is a typical case where &lt;code&gt;ROWS&lt;/code&gt; is the right choice and &lt;code&gt;RANGE&lt;/code&gt; is not. The goal is a fixed number of rows, not a logical expansion around equal values.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 3: looking forward
&lt;/h2&gt;

&lt;p&gt;Window functions can look ahead as well:&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;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;payment_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;
            &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;payment_date&lt;/span&gt;
            &lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="k"&gt;CURRENT&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;FOLLOWING&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;forward_avg&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;payment&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;payment_date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This kind of frame is useful for things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;smoothing;&lt;/li&gt;
&lt;li&gt;local trend analysis;&lt;/li&gt;
&lt;li&gt;short forward-looking comparisons.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Near the end of the partition, the frame naturally shrinks because there are no future rows left.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 4: full-partition aggregate without &lt;code&gt;GROUP BY&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Sometimes you want to keep row-level detail and still show a partition-level aggregate next to each row:&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;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;payment_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;
            &lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="n"&gt;UNBOUNDED&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;UNBOUNDED&lt;/span&gt; &lt;span class="k"&gt;FOLLOWING&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;customer_avg&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;payment&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="k"&gt;IN&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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payment_date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This behaves a bit like &lt;code&gt;GROUP BY customer_id&lt;/code&gt;, except rows are not collapsed. You still see every row, with the partition average attached to each one.&lt;/p&gt;

&lt;p&gt;That pattern is useful when you want to compare a row against its wider context:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;deviation from average;&lt;/li&gt;
&lt;li&gt;share of total;&lt;/li&gt;
&lt;li&gt;comparison with a partition maximum or minimum.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The main trap: &lt;code&gt;ROWS&lt;/code&gt; and &lt;code&gt;RANGE&lt;/code&gt; can produce different running totals
&lt;/h2&gt;

&lt;p&gt;Suppose you have multiple rows with the same &lt;code&gt;amount&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Compare these two expressions:&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;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;
        &lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="n"&gt;UNBOUNDED&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CURRENT&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;sum_rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;
        &lt;span class="k"&gt;RANGE&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="n"&gt;UNBOUNDED&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CURRENT&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;sum_range&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;payment&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="k"&gt;IN&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="mi"&gt;2&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="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;amount = 11.99&lt;/code&gt; appears multiple times, the behavior changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ROWS&lt;/code&gt; counts one physical row at a time;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RANGE&lt;/code&gt; includes all rows with the same &lt;code&gt;amount&lt;/code&gt; together.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is why a running total based on &lt;code&gt;RANGE&lt;/code&gt; can jump several rows at once when there are duplicates in the ordering column.&lt;/p&gt;

&lt;p&gt;This is one of the most common sources of confusion I see with window functions. The query is valid. The database is right. The expectation was wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  When I use &lt;code&gt;ROWS&lt;/code&gt; vs &lt;code&gt;RANGE&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;My rule of thumb is simple.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;ROWS&lt;/code&gt; when you want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;row-by-row running totals;&lt;/li&gt;
&lt;li&gt;moving averages based on a fixed number of rows;&lt;/li&gt;
&lt;li&gt;predictable incremental behavior;&lt;/li&gt;
&lt;li&gt;analytics where each physical row matters separately.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use &lt;code&gt;RANGE&lt;/code&gt; when you want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;calculations based on logical value ranges;&lt;/li&gt;
&lt;li&gt;tied &lt;code&gt;ORDER BY&lt;/code&gt; values to be treated together;&lt;/li&gt;
&lt;li&gt;behavior tied to the ordering value itself rather than row count.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use &lt;code&gt;GROUPS&lt;/code&gt; when the right mental model is “peer groups as units.”&lt;/p&gt;

&lt;p&gt;If I am not sure, I almost always start with &lt;code&gt;ROWS&lt;/code&gt;. It is the most predictable option.&lt;/p&gt;

&lt;h2&gt;
  
  
  Named windows
&lt;/h2&gt;

&lt;p&gt;When the same window definition is reused several times in one query, things get noisy fast. That is where the &lt;code&gt;WINDOW&lt;/code&gt; clause helps:&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;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;payment_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;running_total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;running_avg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;payment_count&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;payment&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;WINDOW&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;
    &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;payment_date&lt;/span&gt;
    &lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="n"&gt;UNBOUNDED&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CURRENT&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;payment_date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why I like this approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;less duplication;&lt;/li&gt;
&lt;li&gt;lower chance of mistakes;&lt;/li&gt;
&lt;li&gt;easier maintenance;&lt;/li&gt;
&lt;li&gt;the logic of the window lives in one place.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a query contains several window functions, named windows usually make it much easier to read.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical reporting pattern: daily sales
&lt;/h2&gt;

&lt;p&gt;One of the most useful patterns for reporting and dashboards looks like this:&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="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payment_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;payment_day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;daily_total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payment_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="n"&gt;UNBOUNDED&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CURRENT&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;cumulative_total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payment_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CURRENT&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;rolling_7day_avg&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;payment&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payment_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;payment_day&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you two very useful metrics immediately:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;cumulative_total&lt;/code&gt; for the running total;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rolling_7day_avg&lt;/code&gt; for a 7-day moving average.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notice the &lt;code&gt;SUM(SUM(amount)) OVER (...)&lt;/code&gt; pattern: first we aggregate by day, then we apply a window function over the grouped result.&lt;/p&gt;

&lt;p&gt;I like this example because it shows the practical value of frames very quickly. In one query, you get accumulation, smoothing, and a solid base for a chart or dashboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this topic kept breaking for me
&lt;/h2&gt;

&lt;p&gt;If I reduce my own mistakes here to a short list, they usually came from three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;mentally substituting “the whole partition” where only the current frame was actually used;&lt;/li&gt;
&lt;li&gt;forgetting that &lt;code&gt;RANGE&lt;/code&gt; does not behave like &lt;code&gt;ROWS&lt;/code&gt; when &lt;code&gt;ORDER BY&lt;/code&gt; values are duplicated;&lt;/li&gt;
&lt;li&gt;relying too long on defaults instead of writing the frame explicitly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once I started asking one extra question for every window query, most of the confusion went away:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which exact rows should be included in the calculation for the current row?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That question alone removes a lot of the magic.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is worth remembering
&lt;/h2&gt;

&lt;p&gt;A window frame is not about the partition itself and not about ordering by itself. It is specifically about &lt;strong&gt;the boundaries of rows included in the current calculation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In short:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;PARTITION BY&lt;/code&gt; splits data into partitions;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ORDER BY&lt;/code&gt; defines order inside a partition;&lt;/li&gt;
&lt;li&gt;the frame defines which rows around the current row are included in the calculation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The three most useful patterns to remember are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;running total:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="n"&gt;UNBOUNDED&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CURRENT&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;full partition:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="n"&gt;UNBOUNDED&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;UNBOUNDED&lt;/span&gt; &lt;span class="k"&gt;FOLLOWING&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;3-row sliding window:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;PRECEDING&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;CURRENT&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the most important trap is this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;if you have &lt;code&gt;ORDER BY&lt;/code&gt; but no explicit frame, many databases will use &lt;code&gt;RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which means duplicate ordering values can change the result in ways that are easy to miss.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;Window functions only became intuitive for me once I stopped thinking in terms of just partitions and ordering and started thinking in terms of &lt;strong&gt;frames&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you want to write analytical SQL with confidence, it is worth building one habit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;do not rely on the default frame;&lt;/li&gt;
&lt;li&gt;write it explicitly;&lt;/li&gt;
&lt;li&gt;choose between &lt;code&gt;ROWS&lt;/code&gt; and &lt;code&gt;RANGE&lt;/code&gt; on purpose;&lt;/li&gt;
&lt;li&gt;remember that ties in &lt;code&gt;ORDER BY&lt;/code&gt; change frame behavior.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If I had to leave one practical takeaway from this entire article, it would be this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When you write a window function, do not think only about &lt;code&gt;PARTITION BY&lt;/code&gt; and &lt;code&gt;ORDER BY&lt;/code&gt;. Ask one more question: which exact rows should participate in the calculation for the current row?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once that answer is explicit, window queries become much more reliable and much easier to reason about.&lt;/p&gt;

&lt;p&gt;If you want to experiment with &lt;code&gt;ROWS&lt;/code&gt;, &lt;code&gt;RANGE&lt;/code&gt;, and &lt;code&gt;GROUPS&lt;/code&gt; directly, &lt;a href="https://sqlize.online" rel="noopener noreferrer"&gt;sqlize.online&lt;/a&gt; is the easiest place to test queries quickly. If you want a more structured way to study SQL through lessons and practice, I publish that work on &lt;a href="https://sqltest.online" rel="noopener noreferrer"&gt;sqltest.online&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>database</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Make Your Technical Tutorials Interactive with SQLize Embed</title>
      <dc:creator>Slava Rozhnev</dc:creator>
      <pubDate>Tue, 20 Jan 2026 13:19:48 +0000</pubDate>
      <link>https://forem.com/rozhnev/make-your-technical-tutorials-interactive-with-sqlize-embed-1eo3</link>
      <guid>https://forem.com/rozhnev/make-your-technical-tutorials-interactive-with-sqlize-embed-1eo3</guid>
      <description>&lt;p&gt;If you've ever written a SQL tutorial or database documentation, you know the struggle. You provide a beautiful code snippet, but for your readers to actually &lt;em&gt;see&lt;/em&gt; it in action, they have to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Copy the code.&lt;/li&gt;
&lt;li&gt;Open their local terminal or a heavy IDE.&lt;/li&gt;
&lt;li&gt;Set up a database schema.&lt;/li&gt;
&lt;li&gt;Run the code.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most readers won't do it. They just keep scrolling.&lt;/p&gt;

&lt;p&gt;Today, I'm excited to introduce &lt;strong&gt;SQLize Embed&lt;/strong&gt;—a lightweight, responsive, and powerful way to embed live SQL sandboxes directly into your blog, documentation, or educational site.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is SQLize Embed?
&lt;/h2&gt;

&lt;p&gt;SQLize Embed is a client-side library that transforms static &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; elements into fully functional SQL editors. Powered by the &lt;a href="https://sqlize.online" rel="noopener noreferrer"&gt;SQLize.online&lt;/a&gt; engine, it allows users to write and execute SQL against real database instances without leaving your page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;20+ Database Engines:&lt;/strong&gt; Supports everything from the classics like &lt;strong&gt;MySQL 8.0/9.3&lt;/strong&gt;, &lt;strong&gt;PostgreSQL (14-18)&lt;/strong&gt;, and &lt;strong&gt;SQLite 3&lt;/strong&gt;, to enterprise giants like &lt;strong&gt;MS SQL Server (2017-2025)&lt;/strong&gt; and &lt;strong&gt;Oracle 23ai&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Ready-to-Use Datasets:&lt;/strong&gt; Want to demo a JOIN? Use preloaded databases like &lt;strong&gt;Sakila (MySQL/MariaDB)&lt;/strong&gt;, &lt;strong&gt;OpenFlights&lt;/strong&gt;, or &lt;strong&gt;AdventureWorks (MS SQL)&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Modern Editor Experience:&lt;/strong&gt; Powered by the &lt;strong&gt;Ace Editor&lt;/strong&gt;, providing syntax highlighting, auto-indentation, and a professional coding feel.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Responsive &amp;amp; Lightweight:&lt;/strong&gt; Works seamlessly on mobile and desktop.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Read-Only Mode:&lt;/strong&gt; Perfect for strictly showing examples that you want users to run but not modify.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started in 30 Seconds
&lt;/h2&gt;

&lt;p&gt;Adding a live SQL sandbox to your site is as easy as adding a YouTube video.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Include the Script
&lt;/h3&gt;

&lt;p&gt;Add this script tag to your site's &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; or before the closing &lt;code&gt;&amp;lt;/body&amp;gt;&lt;/code&gt; tag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://sqlize.online/js/sqlize-embed.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Add Your Sandbox
&lt;/h3&gt;

&lt;p&gt;Create a &lt;code&gt;div&lt;/code&gt; with the &lt;code&gt;data-sqlize-editor&lt;/code&gt; attribute. Specify your preferred database version and initial code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;data-sqlize-editor&lt;/span&gt; &lt;span class="na"&gt;data-sql-version=&lt;/span&gt;&lt;span class="s"&gt;"mysql80"&lt;/span&gt; &lt;span class="na"&gt;code-rows=&lt;/span&gt;&lt;span class="s"&gt;"10"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
-- Create a sample table
CREATE TABLE dev_to_fans (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(100)
);

-- Insert data
INSERT INTO dev_to_fans (username) VALUES ('awesome_dev'), ('sql_ninja');

-- Run it!
SELECT * FROM dev_to_fans;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Advanced Configuration
&lt;/h2&gt;

&lt;p&gt;You can customize the appearance and behavior of the sandbox using simple HTML attributes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Attribute&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;data-sql-version&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The DB engine (e.g., &lt;code&gt;psql17&lt;/code&gt;, &lt;code&gt;mssql2025&lt;/code&gt;, &lt;code&gt;clickhouse&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;mysql80&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;code-rows&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The height of the editor in lines&lt;/td&gt;
&lt;td&gt;&lt;code&gt;12&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;result-rows&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The height of the result area&lt;/td&gt;
&lt;td&gt;&lt;code&gt;12&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;data-read-only&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Set to &lt;code&gt;true&lt;/code&gt; to disable editing&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Use Cases
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Interactive Learning:&lt;/strong&gt; Build a "SQL 101" course where users solve challenges directly in the browser.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Documentation:&lt;/strong&gt; Stop using screenshots of tables. Let users run &lt;code&gt;DESCRIBE table&lt;/code&gt; themselves.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Technical Blogs:&lt;/strong&gt; Show off complex PostgreSQL window functions or the new MariaDB Vector types with live examples.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try the Live Demo
&lt;/h2&gt;

&lt;p&gt;Check out the live documentation and various examples here: &lt;br&gt;
👉 &lt;a href="https://sqlize.online/embed-example" rel="noopener noreferrer"&gt;SQLize Embed Documentation&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  👉 &lt;a href="https://querynomic.one/#/resources/core" rel="noopener noreferrer"&gt;SQLize Embed Showcase&lt;/a&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Join the Community!
&lt;/h3&gt;

&lt;p&gt;We are constantly adding support for new database versions (we already have MS SQL Server 2025!). If you have a specific database or dataset you'd like to see, let us know in the comments!&lt;/p&gt;

&lt;p&gt;Happy coding! 💻&lt;/p&gt;

&lt;h1&gt;
  
  
  sql #database #webdev #tutorial #productivity #programming
&lt;/h1&gt;

</description>
      <category>sql</category>
      <category>documentation</category>
      <category>mariadb</category>
      <category>postgressql</category>
    </item>
    <item>
      <title>Debunking the Myth: Is JOIN Always Faster Than Correlated Subqueries?</title>
      <dc:creator>Slava Rozhnev</dc:creator>
      <pubDate>Tue, 11 Nov 2025 19:18:20 +0000</pubDate>
      <link>https://forem.com/rozhnev/debunking-the-myth-is-join-always-faster-than-correlated-subqueries-1m05</link>
      <guid>https://forem.com/rozhnev/debunking-the-myth-is-join-always-faster-than-correlated-subqueries-1m05</guid>
      <description>&lt;p&gt;Hey there, fellow developers! If you've ever dabbled in SQL, you've probably heard the golden rule: "Never use correlated subqueries in SELECT—they're a recipe for N+1 disasters!" Instead, we're told to always opt for JOINs because they're set-based, efficient, and lightning-fast.&lt;/p&gt;

&lt;p&gt;But is this rule set in stone? I decided to put it to the test across four popular database systems: MySQL 8.0, Oracle 23c, PostgreSQL 16, and SQLite 3.45. Spoiler alert: The results were eye-opening. Sometimes, the "bad" correlated subquery outperformed the "good" JOIN. Let's dive in and see why.&lt;/p&gt;

&lt;p&gt;The Test Setup: Customers and Orders&lt;br&gt;
To keep things fair, I used a simple schema with two tables:&lt;/p&gt;

&lt;p&gt;customers: A small table with 25 rows of customer data.&lt;br&gt;
orders: A larger table with 1,000 rows of orders, linked via a foreign key.&lt;br&gt;
The goal? Count the number of orders per customer, including those with zero orders.&lt;/p&gt;

&lt;p&gt;Here's the schema (using MySQL syntax for reference):&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="c1"&gt;-- Customers table&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;customers&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="nb"&gt;INT&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;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;255&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="c1"&gt;-- Orders table&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;orders&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;order_id&lt;/span&gt; &lt;span class="nb"&gt;INT&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;customer_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;order_date&lt;/span&gt; &lt;span class="nb"&gt;DATETIME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;FOREIGN&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;customer_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;CASCADE&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Data was populated with random values to simulate real-world scenarios.&lt;/p&gt;

&lt;p&gt;The Two Queries: JOIN vs. Correlated Subquery&lt;br&gt;
I compared two approaches to achieve the same result.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The "Good" Way – JOIN + GROUP BY
This is the set-based, relational approach everyone loves:
&lt;/li&gt;
&lt;/ol&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="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;orders_count&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; 
    &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; 
    &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; 
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Pros: Handles all customers, even those without orders.&lt;/li&gt;
&lt;li&gt;Theory: One optimized operation to join and aggregate.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;The "Bad" Way – Correlated Subquery
This is the row-by-row method we're warned against:
&lt;/li&gt;
&lt;/ol&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="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
     &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; 
     &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;orders_count&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; 
    &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Pros: Also includes customers with zero orders.&lt;/li&gt;
&lt;li&gt;Theory: Executes a subquery for each customer—classic N+1 problem.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Testing Across Databases: The Results&lt;br&gt;
I ran both queries on online SQL testers (links provided below) and analyzed execution times and plans using EXPLAIN. Here's what happened.&lt;/p&gt;

&lt;p&gt;MySQL 8.0: Subquery Wins!&lt;br&gt;
Execution Times: Subquery ~14 ms vs. JOIN ~16 ms.&lt;br&gt;
Why? The subquery triggered a Nested Loop plan with fast index lookups (25 quick searches). JOIN used Hash Join + Aggregate, which was overkill for small data.&lt;br&gt;
Key Insight: With an index on orders.customer_id, the subquery wasn't N+1—it was efficient Nested Loops.&lt;br&gt;
Test Link: &lt;a href="https://sqlize.online/s/1a" rel="noopener noreferrer"&gt;MySQL Tester&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Oracle 23c: Subquery Dominates!&lt;br&gt;
Execution Times: Subquery ~2.4 ms vs. JOIN ~15 ms.&lt;br&gt;
Why? Similar to MySQL—Nested Loop for subquery vs. Hash Join for JOIN. The subquery avoided heavy aggregation overhead.&lt;br&gt;
Key Insight: Indexes are crucial; without them, Oracle falls back to full scans.&lt;br&gt;
Test Link: &lt;a href="https://sqlize.online/s/va" rel="noopener noreferrer"&gt;Oracle Tester&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;PostgreSQL 16: JOIN Takes the Lead&lt;br&gt;
Execution Times: JOIN ~0.6 ms vs. Subquery ~1.9 ms.&lt;br&gt;
Why? PostgreSQL's optimizer rewrote the subquery into a JOIN-like plan, but the explicit JOIN was slightly faster. Subquery showed 25 sub-plan executions (mild N+1).&lt;br&gt;
Key Insight: PostgreSQL is smart—indexes level the playing field.&lt;br&gt;
Test Link: &lt;a href="https://sqlize.online/s/ua" rel="noopener noreferrer"&gt;PostgreSQL Tester&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;SQLite 3.45: A Tie!&lt;br&gt;
Execution Times: Both ~1 ms.&lt;br&gt;
Why? Plans were nearly identical: SCAN on customers + SEARCH on orders via index. No N+1 effect.&lt;br&gt;
Key Insight: SQLite's simplicity made both queries efficient; choose based on readability.&lt;br&gt;
Test Link: &lt;a href="https://sqlize.online/s/la" rel="noopener noreferrer"&gt;SQLite Tester&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Key Takeaways: No Silver Bullet&lt;br&gt;
The "JOIN is always faster" myth crumbles because performance depends on:&lt;/p&gt;

&lt;p&gt;Database Optimizer: PostgreSQL rewrites queries; MySQL/Oracle follow your syntax more literally.&lt;br&gt;
Data Size: Small outer tables (like our 25 customers) favor Nested Loops; large ones benefit from Hash Joins.&lt;br&gt;
Indexes: Without an index on orders.customer_id, subqueries tank. With it, they shine.&lt;br&gt;
Bottom Line: Don't blindly follow rules. Always run EXPLAIN (or EXPLAIN ANALYZE) to see the actual execution plan. Test with your data!&lt;/p&gt;

&lt;p&gt;What are your experiences with JOINs vs. subqueries? Drop a comment below!&lt;/p&gt;

&lt;p&gt;This article is based on real testing and analysis. Links to testers are provided for you to verify the results.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>performance</category>
      <category>database</category>
    </item>
    <item>
      <title>SQL Tricks: Generate Calendar Table</title>
      <dc:creator>Slava Rozhnev</dc:creator>
      <pubDate>Sat, 19 Jul 2025 19:34:49 +0000</pubDate>
      <link>https://forem.com/rozhnev/sql-tricks-generate-calendar-table-3lcn</link>
      <guid>https://forem.com/rozhnev/sql-tricks-generate-calendar-table-3lcn</guid>
      <description>&lt;p&gt;Creating a "calendar table" or "date dimension" is a common task in SQL, especially for reporting, data warehousing, or when you need to perform calculations based on dates that might not exist in your actual data (e.g., finding days with no sales). While a full-fledged calendar table usually contains many attributes (day of week, week number, quarter, holiday flags, etc.), sometimes you just need a simple list of dates for a specific period, like the current month.&lt;/p&gt;

&lt;p&gt;In this post, we'll explore how to dynamically generate a table containing all dates for the current month across different popular RDBMS dialects: MySQL, PostgreSQL, MS SQL Server, and Oracle. This approach avoids hardcoding dates and ensures your script always works for the current period.&lt;/p&gt;

&lt;p&gt;Why generate a calendar table?&lt;br&gt;
Before we dive into the code, let's briefly touch upon why this is useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Filling Gaps: If your transaction data only records days with activity, a calendar table can help you identify days with no activity (e.g., zero sales).&lt;/li&gt;
&lt;li&gt;Time-Series Analysis: Essential for analyses that require a continuous timeline, even when data is sparse.&lt;/li&gt;
&lt;li&gt;Simplifying Joins: You can join your data to a calendar table to easily group by specific date parts or filter by continuous date ranges.&lt;/li&gt;
&lt;li&gt;Reporting: Providing a complete date range for reports, even if some dates have no associated data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's look at the solutions for each database system.&lt;/p&gt;
&lt;h2&gt;
  
  
  MySQL
&lt;/h2&gt;

&lt;p&gt;MySQL offers a few ways to generate series. We'll use a common table expression (CTE) combined with a recursive CTE or a numbers table approach to generate our dates.&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;WITH&lt;/span&gt; &lt;span class="k"&gt;RECURSIVE&lt;/span&gt; &lt;span class="n"&gt;dates&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt;
        &lt;span class="n"&gt;DATE_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CURDATE&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="n"&gt;DAYOFMONTH&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CURDATE&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;dt&lt;/span&gt; &lt;span class="c1"&gt;-- First day of current month&lt;/span&gt;
    &lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt;
        &lt;span class="n"&gt;DATE_ADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt;
        &lt;span class="n"&gt;dates&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt;
        &lt;span class="n"&gt;dt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;LAST_DAY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CURDATE&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="c1"&gt;-- Last day of current month&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;dt&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;calendar_date&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
    &lt;span class="n"&gt;dates&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Explanation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WITH RECURSIVE dates AS (...): Defines a recursive CTE named dates.&lt;/li&gt;
&lt;li&gt;Anchor Member: SELECT DATE_SUB(CURDATE(), INTERVAL DAYOFMONTH(CURDATE()) - 1 DAY): This calculates the first day of the current month. CURDATE() gets the current date, DAYOFMONTH(CURDATE()) gets the day of the month (e.g., 15 for July 15th), and we subtract (day - 1) days to get to the 1st of the month.&lt;/li&gt;
&lt;li&gt;Recursive Member: SELECT DATE_ADD(dt, INTERVAL 1 DAY) FROM dates WHERE dt &amp;lt; LAST_DAY(CURDATE()): This part adds one day to the previous date (dt) until it reaches the last day of the current month, which is obtained using LAST_DAY(CURDATE()).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://sqlize.online/sql/mysql80/9adfdb9b62ed20c8ba374b1b20bd5dda/" rel="noopener noreferrer"&gt;Try this solution with SQLize.online&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  PostgreSQL
&lt;/h2&gt;

&lt;p&gt;PostgreSQL has a very convenient generate_series() function which is perfect for this task.&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;GENERATE_SERIES&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;DATE_TRUNC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'month'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;CURRENT_DATE&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;-- First day of current month&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DATE_TRUNC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'month'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;CURRENT_DATE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="s1"&gt;'1 month'&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="s1"&gt;'1 day'&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;-- Last day of current month&lt;/span&gt;
        &lt;span class="s1"&gt;'1 day'&lt;/span&gt;
    &lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;calendar_date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Explanation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GENERATE_SERIES(start, stop, step): Generates a series of values.&lt;/li&gt;
&lt;li&gt;DATE_TRUNC('month', CURRENT_DATE): Truncates the current date to the beginning of the month, giving us the first day.&lt;/li&gt;
&lt;li&gt;(DATE_TRUNC('month', CURRENT_DATE) + INTERVAL '1 month' - INTERVAL '1 day'):📅 This calculates the last day of the current month. We go to the beginning of the next month (+ INTERVAL '1 month') and then subtract one day (- INTERVAL '1 day') to get the last day of the current month.&lt;/li&gt;
&lt;li&gt;'1 day': Specifies that each step in the series should be one day.&lt;/li&gt;
&lt;li&gt;:📅 Casts the resulting timestamp to a date type for a cleaner output.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://sqlize.online/sql/psql17/f904728c3f9f4bbd0077b90046ad4674/" rel="noopener noreferrer"&gt;Test tris code on SQLize.online&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  MS SQL Server
&lt;/h2&gt;

&lt;p&gt;SQL Server offers multiple ways to generate sequences. Since SQL Server 2022, the GENERATE_SERIES function provides a straightforward method, similar to PostgreSQL. For earlier versions, or if you prefer, recursive CTEs are also a good option.&lt;/p&gt;

&lt;p&gt;Using GENERATE_SERIES (SQL Server 2022+)&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;DATEADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DATEADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DATEDIFF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GETDATE&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;calendar_date&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
    &lt;span class="n"&gt;GENERATE_SERIES&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EOMONTH&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GETDATE&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt; &lt;span class="o"&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;&lt;strong&gt;Explanation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GENERATE_SERIES(start, stop, step): This function generates a sequence of numbers from start to stop with increments of step. By default, step is 1 if omitted.&lt;/li&gt;
&lt;li&gt;DATEADD(month, DATEDIFF(month, 0, GETDATE()), 0): This calculates the first day of the current month. This will be our base date.&lt;/li&gt;
&lt;li&gt;DAY(EOMONTH(GETDATE())) - 1: EOMONTH(GETDATE()) returns the last day of the current month. DAY() extracts the day number (e.g., 31 for July 31st). Subtracting 1 gives us the maximum number to generate for our series (from 0 to days_in_month - 1). For example, if a month has 31 days, we want to generate numbers from 0 to 30.&lt;/li&gt;
&lt;li&gt;DATEADD(day, value, ...): For each value generated by GENERATE_SERIES (which are 0, 1, 2, ... up to days_in_month - 1), we add that number of days to our first day of current month base date. This effectively generates each date of the month.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://sqlize.online/sql/mssql2022/00ab057284fa01765d7941dee2097a3b/" rel="noopener noreferrer"&gt;Try this SQL online&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Using Recursive CTE (Older SQL Server Versions or Alternative)&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;WITH&lt;/span&gt; &lt;span class="n"&gt;Dates&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt;
        &lt;span class="n"&gt;DATEADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DATEDIFF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GETDATE&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;dt&lt;/span&gt; &lt;span class="c1"&gt;-- First day of current month&lt;/span&gt;
    &lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt;
        &lt;span class="n"&gt;DATEADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;day&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="n"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt;
        &lt;span class="n"&gt;Dates&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt;
        &lt;span class="n"&gt;DATEPART&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DATEADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;day&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="n"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DATEPART&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GETDATE&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;dt&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;calendar_date&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
    &lt;span class="n"&gt;Dates&lt;/span&gt;
&lt;span class="k"&gt;OPTION&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MAXRECURSION&lt;/span&gt; &lt;span class="mi"&gt;366&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;-- Set max recursion limit, 366 covers leap years&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Explanation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WITH Dates AS (...): Defines a recursive CTE named Dates.&lt;/li&gt;
&lt;li&gt;Anchor Member: SELECT DATEADD(month, DATEDIFF(month, 0, GETDATE()), 0) AS dt: This is a common SQL Server idiom to get the first day of the current month.&lt;/li&gt;
&lt;li&gt;DATEDIFF(month, 0, GETDATE()): Calculates the number of month boundaries crossed between 0 (which SQL Server treats as January 1, 1900) and GETDATE().&lt;/li&gt;
&lt;li&gt;DATEADD(month, ..., 0): Adds that number of months to 0, effectively landing on the first day of the current month.&lt;/li&gt;
&lt;li&gt;Recursive Member: SELECT DATEADD(day, 1, dt) FROM Dates WHERE DATEPART(month, DATEADD(day, 1, dt)) = DATEPART(month, GETDATE()): Adds one day to dt as long as the month of the next date is still the current month.&lt;/li&gt;
&lt;li&gt;OPTION (MAXRECURSION 366): Important for recursive CTEs in SQL Server. It sets the maximum number of times the recursive part can execute. 366 is a safe number to cover all possible days in a year (including leap years).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://sqlize.online/sql/mssql2017/70cfed98b5e5b58176222c740254868b/" rel="noopener noreferrer"&gt;Try legacy SQL Server code&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Oracle
&lt;/h2&gt;

&lt;p&gt;Oracle provides a powerful CONNECT BY LEVEL clause, often used for generating sequences.&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;TRUNC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SYSDATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'MM'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;LEVEL&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;calendar_date&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
    &lt;span class="n"&gt;dual&lt;/span&gt;
&lt;span class="k"&gt;CONNECT&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt;
    &lt;span class="n"&gt;TRUNC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SYSDATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'MM'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;LEVEL&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;LAST_DAY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SYSDATE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Explanation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TRUNC(SYSDATE, 'MM'): Truncates the current date (SYSDATE) to the first day of the current month.&lt;/li&gt;
&lt;li&gt;LEVEL: A pseudo-column in hierarchical queries that returns the current level in the hierarchy (starting from 1).&lt;/li&gt;
&lt;li&gt;TRUNC(SYSDATE, 'MM') + LEVEL - 1: Generates successive dates starting from the first day of the month.&lt;/li&gt;
&lt;li&gt;When LEVEL is 1, it's first_day_of_month + 1 - 1 = first_day_of_month.&lt;/li&gt;
&lt;li&gt;When LEVEL is 2, it's first_day_of_month + 2 - 1 = first_day_of_month + 1 day.&lt;/li&gt;
&lt;li&gt;FROM dual: dual is a dummy table in Oracle, often used for selecting pseudo-columns or evaluating expressions.&lt;/li&gt;
&lt;li&gt;CONNECT BY TRUNC(SYSDATE, 'MM') + LEVEL - 1 &amp;lt;= LAST_DAY(SYSDATE): This clause acts as the loop condition. It continues generating rows as long as the generated date is less than or equal to the last day of the current month (LAST_DAY(SYSDATE)).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://sqlize.online/sql/oracle23/06389d165705036f32f36b9ebc80e8f0/" rel="noopener noreferrer"&gt;Oracle SQL playground online&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;As you can see, while the syntax differs across RDBMS, the core concept of generating a series of dates remains similar. With the addition of GENERATE_SERIES in SQL Server 2022, modern SQL versions are increasingly standardizing on easier ways to achieve this. Understanding these techniques is crucial for effective data manipulation and reporting in SQL. Choose the method that best suits your specific database environment and version.&lt;/p&gt;

&lt;p&gt;I hope this was helpful! Feel free to leave a comment if you have any questions or alternative approaches.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>analytics</category>
      <category>mysql</category>
      <category>database</category>
    </item>
    <item>
      <title>SQLtest.online - the site where you can test your SQL skills</title>
      <dc:creator>Slava Rozhnev</dc:creator>
      <pubDate>Sat, 17 Feb 2024 12:00:35 +0000</pubDate>
      <link>https://forem.com/rozhnev/sqltestonline-the-site-where-you-can-test-your-sql-skills-5b0d</link>
      <guid>https://forem.com/rozhnev/sqltestonline-the-site-where-you-can-test-your-sql-skills-5b0d</guid>
      <description>&lt;p&gt;Hello friends! I'll tell you about my new project - &lt;a href="https://SQLTest.online"&gt;SQLTest.online&lt;/a&gt;. This is a platform for those who are learning SQL and want to test their skills by solving practical problems.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3w7envmj2gmrrsnjyd3p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3w7envmj2gmrrsnjyd3p.png" alt="Image description" width="800" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The using of the site is very simple - select a problem from the list on the left and try to solve it! So far there are more then 220 questions on MySQL, several questions on Firebird. For tests, public databases Sakila (MySQL), Bookings (PostgreSQL) and Employee (Firebird) are used. In the future, I plan to expand both the list of available databases and the number of questions for each of them.&lt;/p&gt;

&lt;p&gt;The table structure of the database relevant for each task is displayed on the right side of the screen and helps you in solving.&lt;/p&gt;

&lt;p&gt;If you are at a loss with a solution, you can use the hint or run a query and see the result. Validation of a solution is performed in two stages: a basic check using regular expressions and then checking the result against the correct solution.&lt;/p&gt;

&lt;p&gt;If you are interested in the project and decide to pass all the tests, you can log in to the site. Login is currently available via Google, Yandex and GitHub. When you log in to the site, no personal information is collected, only data about your progress in solving problems. However, even without registration, you get access to all functions of the site without restrictions!&lt;/p&gt;

&lt;p&gt;So, Happy testing with &lt;a href="https://SQLTest.online"&gt;SQLTest.online&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you find problems on the site, write about them below in the comments to the article.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Generate Date Series in popular Databases</title>
      <dc:creator>Slava Rozhnev</dc:creator>
      <pubDate>Wed, 23 Aug 2023 20:43:15 +0000</pubDate>
      <link>https://forem.com/rozhnev/generate-date-series-in-popular-databases-5cag</link>
      <guid>https://forem.com/rozhnev/generate-date-series-in-popular-databases-5cag</guid>
      <description>&lt;p&gt;In this post I want to answer to frequently asked question: How I can generate date series between to particular dates? &lt;/p&gt;

&lt;p&gt;Generating a date series between two particular dates can be done using different methods depending on the relational database management system (RDBMS) you are using. I'll provide examples for a few popular RDBMS systems: MySQL, PostgreSQL, and Microsoft SQL Server.&lt;/p&gt;

&lt;p&gt;Please note that the syntax might slightly differ based on the specific version of the RDBMS you're using, so you should consult the documentation for your specific version if you encounter any issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  MySQL
&lt;/h2&gt;

&lt;p&gt;Legacy MySQL (5.7.*)&lt;br&gt;
The old MySQL doesn't have built-in functions to generate a date series, so you might need to use a temporary table or a numbers table. Here's an example using a numbers table approach:&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;TEMPORARY&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;Numbers&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;-- Insert numbers up to the desired range&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;Numbers&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;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;...;&lt;/span&gt;  

&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;DATE_ADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'start_date'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;generated_date&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Numbers&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; 
    &lt;span class="n"&gt;DATE_ADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'start_date'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="s1"&gt;'end_date'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just replace 'start_date' and 'end_date' with your desired start and end dates and try it on &lt;a href="https://sqlize.online" rel="noopener noreferrer"&gt;SQLize.online&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In Modern MySQL 8.0.*, you can use a Common Table Expression (CTE) to generate a date series between two particular dates. Here's how you can do it:&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;SET&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;start_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2022-01-01'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;end_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2022-01-31'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="k"&gt;RECURSIVE&lt;/span&gt; &lt;span class="n"&gt;DateSeries&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;start_date&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;generated_date&lt;/span&gt;
    &lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;DATE_ADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;generated_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;DateSeries&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;generated_date&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;end_date&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;generated_date&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;DateSeries&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Explanation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The WITH RECURSIVE clause defines the CTE named DateSeries.&lt;/li&gt;
&lt;li&gt;In the initial SELECT statement within the CTE, we set the anchor value to the start date.&lt;/li&gt;
&lt;li&gt;In the recursive SELECT statement, we use the DATE_ADD function to increment the date by one day for each iteration.&lt;/li&gt;
&lt;li&gt;The WHERE clause in the recursive SELECT statement ensures that the recursion continues until the generated date is less than the end date.&lt;/li&gt;
&lt;li&gt;Finally, the outer SELECT statement selects all the generated dates from the CTE.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Remember that recursive queries can be resource-intensive, so use them cautiously and only when necessary. Try the query &lt;a href="https://sqlize.online/sql/mysql80/e4524eaa286ded621409bede4e41e52c/" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  PostgreSQL
&lt;/h2&gt;

&lt;p&gt;PostgreSQL has the generate_series function that makes this task easy:&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;generate_series&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2022-01-01'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2022-01-31'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'1 day'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;generated_date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace 'start_date' and 'end_date' with your desired start and end dates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Microsoft SQL Server
&lt;/h2&gt;

&lt;p&gt;SQL Server also has a similar approach using the sys.dates system table and the DATEADD function:&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;DECLARE&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;start_date&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2022-01-01'&lt;/span&gt;
&lt;span class="k"&gt;DECLARE&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;end_date&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2022-01-31'&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;TOP&lt;/span&gt; 
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DATEDIFF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;start_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;end_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;generated_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DATEADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ROW_NUMBER&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;object_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;start_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all_objects&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
&lt;span class="k"&gt;CROSS&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all_objects&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since SQL Server 2022 where implemented &lt;code&gt;GENERATE_SERIES&lt;/code&gt; function you can use it for generate dates series too in next way:&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;DATEADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2022-01-01'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;GENERATE_SERIES&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DATEDIFF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2022-01-01'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2022-01-31'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Oracle
&lt;/h2&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="nb"&gt;DATE&lt;/span&gt; &lt;span class="s1"&gt;'2022-01-01'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;LEVEL&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;generate_series&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;dual&lt;/span&gt;
&lt;span class="k"&gt;CONNECT&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;LEVEL&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt; &lt;span class="s1"&gt;'2022-01-31'&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt; &lt;span class="s1"&gt;'2022-01-01'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another cool method:&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;TRUNC&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;DATE&lt;/span&gt; &lt;span class="s1"&gt;'2023-01-01'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;ROWNUM&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;dt&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;DUAL&lt;/span&gt; &lt;span class="k"&gt;CONNECT&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;ROWNUM&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you know more methods to get date series in different RDBMS, please post in comments&lt;/p&gt;

</description>
      <category>sql</category>
      <category>postgres</category>
      <category>oracle</category>
      <category>sqlserver</category>
    </item>
    <item>
      <title>Exploring PostgreSQL's EXCLUDE Operator: Advanced Data Constraints</title>
      <dc:creator>Slava Rozhnev</dc:creator>
      <pubDate>Tue, 30 May 2023 22:05:45 +0000</pubDate>
      <link>https://forem.com/rozhnev/exploring-postgresqls-exclude-operator-advanced-data-constraints-2k60</link>
      <guid>https://forem.com/rozhnev/exploring-postgresqls-exclude-operator-advanced-data-constraints-2k60</guid>
      <description>&lt;p&gt;&lt;strong&gt;Introduction&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the process of designing a database, as described in my previous article, I decided to utilize the EXCLUDE constraint to maintain data integrity. While contemplating this, I realized that the EXCLUDE operator deserves a dedicated article.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Introduction to the EXCLUDE Operator&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;PostgreSQL is renowned for its numerous powerful features, and one of them is the EXCLUDE operator. This operator allows you to create advanced constraints on sets of values within table columns. In this article, I want to delve into the EXCLUDE operator, provide examples of its usage, and help you understand how to leverage it to build flexible and efficient databases.&lt;/p&gt;

&lt;p&gt;Similar to the UNIQUE constraint in PostgreSQL, the EXCLUDE operator is used to define constraints on sets of values within table columns. However, unlike UNIQUE, it enables you to specify rules that determine which values cannot coexist within a particular column or set of columns. The EXCLUDE operator is often used with GiST or SP-GiST index types to ensure query efficiency, although it can also be used with a regular B-Tree index.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Examples of Usage&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A common example of utilizing EXCLUDE is applying a constraint on overlapping time intervals, such as movie screenings in a cinema.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;serial&lt;/span&gt; &lt;span class="k"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;event_time&lt;/span&gt; &lt;span class="n"&gt;tstzrange&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;no_screening_time_overlap&lt;/span&gt; &lt;span class="n"&gt;exclude&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="n"&gt;gist&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;event_time&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'["2023-01-01 19:00:00", "2023-01-01 20:45:00"]'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above example, we create a table named "events" and insert a record with a time interval. You can check the SQL on &lt;a href="https://sqlize.online/sql/psql15/f560a464533bf811b6c8b902b9b509d8/" rel="noopener noreferrer"&gt;SQLize.online&lt;/a&gt;. Afterwards, you can try inserting another row with an interval that overlaps with an existing one in the table. Most likely, it will result in an error. If you manage to succeed, let me know in the comments!&lt;/p&gt;

&lt;p&gt;Similar to UNIQUE, the EXCLUDE constraint can be applied to a group of columns. For instance, you can use the "event_start" and "event_end" columns of type timestamp and restrict time overlaps. Here's an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;event_id&lt;/span&gt; &lt;span class="nb"&gt;serial&lt;/span&gt; &lt;span class="k"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;event_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;event_start&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&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;event_end&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&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;EXCLUDE&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;GIST&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_start&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event_end&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;Constraints on numeric ranges can also be imposed using EXCLUDE. Take a look at this example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;ranges&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;range_id&lt;/span&gt; &lt;span class="nb"&gt;serial&lt;/span&gt; &lt;span class="k"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;start_value&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&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;end_value&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&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;EXCLUDE&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;GIST&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;int4range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end_value&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="k"&gt;WITH&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;In this example, the "ranges" table is created, which contains numeric ranges. The EXCLUDE operator with a GiST index specifies that the numeric ranges in the "start_value" and "end_value" columns cannot overlap.&lt;/p&gt;

&lt;p&gt;Another significant application is constraining the intersection of geometric figures:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;polygons&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;polygon_id&lt;/span&gt; &lt;span class="nb"&gt;serial&lt;/span&gt; &lt;span class="k"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;polygon_data&lt;/span&gt; &lt;span class="n"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Polygon&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;EXCLUDE&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;GIST&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;polygon_data&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;Here, the "polygons" table is created, which stores information about polygons. The EXCLUDE operator with a GiST index ensures that geometric objects in the "polygon_data" column cannot intersect or be contained within each other.&lt;/p&gt;

&lt;p&gt;In all the above examples, we utilized the EXCLUDE constraint based on a GiST index. However, for completeness, let's provide an example using an R-Tree:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="nb"&gt;serial&lt;/span&gt; &lt;span class="k"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;email&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;255&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;EXCLUDE&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;btree&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="o"&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;In this example, we nearly replicated the functionality of the UNIQUE constraint with a slight modification. Our uniqueness is now case-insensitive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The EXCLUDE operator in PostgreSQL offers the ability to create advanced constraints on sets of values within table columns. It allows you to define rules that restrict combinations of values that cannot coexist. This is particularly useful for ensuring data integrity and performing complex checks at the database level.&lt;/p&gt;

&lt;p&gt;In this article, we explored several examples of using the EXCLUDE operator, including constraints on overlapping time intervals, prohibition of intersecting geometric objects, and constraints on non-overlapping numeric ranges. The EXCLUDE operator is a powerful tool that can be employed to build flexible and efficient databases in PostgreSQL.&lt;/p&gt;

&lt;p&gt;In your projects, utilize the EXCLUDE operator to create sophisticated constraints and ensure data integrity at the database level. This will help you maintain the structure and reliability of your database while optimizing its usage.&lt;/p&gt;

&lt;p&gt;If you found this article helpful, you can show your &lt;a href="https://ko-fi.com/phpize" rel="noopener noreferrer"&gt;support to the author&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>postgressql</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>MySQL 8.0: Invisible Columns</title>
      <dc:creator>Slava Rozhnev</dc:creator>
      <pubDate>Tue, 16 May 2023 12:03:39 +0000</pubDate>
      <link>https://forem.com/rozhnev/mysql-80-invisible-columns-19o8</link>
      <guid>https://forem.com/rozhnev/mysql-80-invisible-columns-19o8</guid>
      <description>&lt;p&gt;The Oracle company ported &lt;code&gt;INVISIBLE COLUMN&lt;/code&gt; feature to it's little brother MySQL. Since version 8.0.23 the invisible column feature available for MySQL users.&lt;/p&gt;

&lt;p&gt;Let's learn how it works!&lt;/p&gt;

&lt;p&gt;You can create invisible column as regular when you create a table using &lt;code&gt;INVISIBLE&lt;/code&gt; keyword:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE TABLE test_table (
  a INT,
  b DATE INVISIBLE
) ENGINE = InnoDB;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or add new column to an exists table by &lt;code&gt;ALTER TABLE&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ALTER TABLE test_table ADD COLUMN c INT INVISIBLE;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a column exists you can change its visibility using CHANGE, MODIFY or ALTER COLUMN command as you can see below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ALTER TABLE test_table CHANGE COLUMN b b DATE VISIBLE;
ALTER TABLE test_table MODIFY COLUMN b DATE INVISIBLE;
ALTER TABLE test_table ALTER COLUMN c SET VISIBLE;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you fetch table structure you will see all columns, but part of them will be marked by &lt;code&gt;INVISIBLE&lt;/code&gt; flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SHOW COLUMNS FROM test_table;
SHOW CREATE TABLE test_table;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So you see, the invisible column manipulation pretty simple. Now look how the visibility affects DML.&lt;/p&gt;

&lt;p&gt;First, when you try to select all columns using &lt;code&gt;SELECT *&lt;/code&gt;, the invisible columns are not appears. So if you need to see they, you must to know the column name and select it by its name. Look below SQL code examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TABLE test_table; SELECT * FROM test_table; -- column hidden

SELECT a, b, c FROM test_table; -- column visible
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same way, when you need to insert data to invisible column you must call it by name:&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 test_table VALUES (1, now(), 33); -- Error
INSERT INTO test_table () VALUES (1, now(), 33); -- Error too

INSERT INTO test_table VALUES (1, 22); -- NULL inserted
INSERT INTO test_table (a, b, c) VALUES (1, now(), 33); -- all values
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where you can use this feature? First - security - you can use invisible column for hide some column from DB users with low permissions. Second - when you use generated column you can prevent insert data to such column doing it invisible.&lt;br&gt;
Look next example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE TABLE test_table (
  a INT,
  b INT
) ENGINE = InnoDB;

INSERT INTO test_table VALUES (1, 2), (2, 3), (3, 4);

ALTER TABLE test_table ADD COLUMN a_b_sum INT AS (a + b);

SELECT * FROM test_table;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In above example we added generated column to our &lt;code&gt;test_table&lt;/code&gt; but this change can break our insert query. What we can do in this case? Right - use invisible column.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ALTER TABLE test_table ALTER COLUMN a_b_sum SET INVISIBLE;

INSERT INTO test_table VALUES (5, 7), (2, 9), (2, 3);

SELECT a, b, a_b_sum FROM test_table;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here you can &lt;a href="https://sqlize.online/s/0d"&gt;run SQL queries online&lt;/a&gt; and test this feature.&lt;/p&gt;

&lt;p&gt;So, this is all about this feature. If you think about more use-cases please share it in comments. &lt;/p&gt;

</description>
      <category>mysql</category>
      <category>tutorial</category>
      <category>sql</category>
    </item>
  </channel>
</rss>
