<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Judy</title>
    <description>The latest articles on DEV Community by Judy (@esproc_spl).</description>
    <link>https://dev.to/esproc_spl</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1191782%2Fdac3272f-c56a-41d9-914e-8f8fba86506b.jpg</url>
      <title>DEV Community: Judy</title>
      <link>https://dev.to/esproc_spl</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/esproc_spl"/>
    <language>en</language>
    <item>
      <title>No More Rewriting the Same Logic for Every Database</title>
      <dc:creator>Judy</dc:creator>
      <pubDate>Wed, 02 Sep 2026 06:16:31 +0000</pubDate>
      <link>https://dev.to/esproc_spl/no-more-rewriting-the-same-logic-for-every-database-59b9</link>
      <guid>https://dev.to/esproc_spl/no-more-rewriting-the-same-logic-for-every-database-59b9</guid>
      <description>&lt;p&gt;First, let me ask you: how many databases does your team use?&lt;/p&gt;

&lt;p&gt;MySQL for OLTP, PostgreSQL for OLAP, Snowflake for the data warehouse, and occasionally, running a report on some legacy Oracle system – that’s practically standard for every data team today.&lt;/p&gt;

&lt;p&gt;Now comes the question: How many times do you need to implement the same business logic?&lt;/p&gt;

&lt;h2&gt;
  
  
  The “dialect tax” every database imposes
&lt;/h2&gt;

&lt;p&gt;SQL has a subtle concept called “dialect.” Standard SQL is one thing, but every database has its own “accent”:&lt;/p&gt;

&lt;p&gt;MySQL has LIMIT. Oracle has ROWNUM. SQL Server has TOP.&lt;/p&gt;

&lt;p&gt;Date functions: MySQL has DATE_ADD. PostgreSQL has INTERVAL. Oracle has ADD_MONTHS.&lt;/p&gt;

&lt;p&gt;String concatenation: MySQL has CONCAT. SQL Server has +. PostgreSQL has ||.&lt;/p&gt;

&lt;p&gt;Window function support, recursive CTE syntax, and GROUP BY strictness — every database has its own way of doing things.&lt;/p&gt;

&lt;p&gt;You spend a whole afternoon getting a complex analytical SQL query working on MySQL. It runs beautifully. Then comes the requirement: run the same logic on Snowflake. You open the editor, copy the SQL over, hit run — and it throws an error.&lt;/p&gt;

&lt;p&gt;Not because the logic is wrong, but because you are using the wrong dialect.&lt;/p&gt;

&lt;p&gt;So, you start fixing it: replace LIMIT with QUALIFY, rewrite every date function, change the string concatenation syntax… Eventually, it works. But what happens when the requirements change again? Rewrite everything all over again? And what if the next project uses a different database?&lt;/p&gt;

&lt;p&gt;You spend more time translating between dialects than thinking through the logic.&lt;/p&gt;

&lt;p&gt;This isn’t an isolated case. According to a Stack Overflow survey, developers spend a significant amount of their coding time dealing with environment setup and compatibility issues. And SQL dialect differences are one of the most subtle yet costly forms of “compatibility tax”.&lt;/p&gt;

&lt;p&gt;Worse still, the cost compounds. Every new database you support means one more SQL code version to maintain. Three databases = three SQL code versions. The change of one business rule means three places to update. Miss one update = production data don’t match = a midnight page to fix bugs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why can’t we “write once, run everywhere”?
&lt;/h2&gt;

&lt;p&gt;You might think: “Then why not just use standard SQL?”&lt;/p&gt;

&lt;p&gt;In theory, yes. But in practice, standard SQL cannot cover real-world business requirements. Dialect differences in window function support, the many different approaches to date and time handling, and different implementations of string operations – these are either undefined or loosely defined in the standard SQL, leaving each database with its own implementation.&lt;/p&gt;

&lt;p&gt;Think about it differently: you don’t need “one SQL running everywhere”; you need “one logic generating SQL everywhere.”&lt;/p&gt;

&lt;p&gt;These are two very different concepts.&lt;/p&gt;

&lt;p&gt;“One SQL running everywhere” means sending the same SQL text to every database – and this approach simply doesn’t work.&lt;/p&gt;

&lt;p&gt;“One logic generating SQL for every database” means writing the logic once and letting a tool compile it into each database’s dialect – and this is a path that works.&lt;/p&gt;

&lt;h2&gt;
  
  
  SQLazy’s approach: Write logic once, adapt to every dialect automatically
&lt;/h2&gt;

&lt;p&gt;SQLazy takes exactly the second approach.&lt;/p&gt;

&lt;p&gt;You express your logic as a workflow – a readable, step-by-step sequence of operations. The compiler then translates it into native SQL for the target database.&lt;/p&gt;

&lt;p&gt;For example, the logic of “calculating the longest streak of consecutive up days of a stock” can be expressed as the following workflow:&lt;/p&gt;

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

&lt;p&gt;This workflow is database-independent. It describes the logic, not SQL syntax.&lt;/p&gt;

&lt;p&gt;Then you tell the compiler the target database – MySQL, PostgreSQL, Oracle, Snowflake, or BigQuery, and it automatically generates native SQL for that database.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faq0crar04o0omgz9sl05.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faq0crar04o0omgz9sl05.png" alt="database" width="800" height="383"&gt;&lt;/a&gt;&lt;br&gt;
With the same workflow, simply switch the database option to generate SQL in a different dialect. You don’t need to rewrite anything.&lt;/p&gt;

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

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

&lt;p&gt;The workflow stays the same because the logic stays the same. What changes is only the “accent” output by the compiler.&lt;/p&gt;

&lt;p&gt;What does this mean?&lt;/p&gt;

&lt;p&gt;First, you only need to maintain one version of the logic. When business requirements change, only update the workflow. The compiler regenerates native SQL for every database – no more “updating MySQL but forgetting Oracle”.&lt;/p&gt;

&lt;p&gt;Second, it’s easier for new developers to get started. They don’t need to learn database dialect differences – they only need to understand the workflow, while the compiler handles the dialect translation. The workflow is for humans to read; SQL is for databases to execute.&lt;/p&gt;

&lt;p&gt;Third, migration costs are nearly zero. Moving from MySQL to PostgreSQL? Just switch the database option, recompile, and the compiler automatically adapts to every SQL dialect. No need to modify code line by line, and no more dialect pitfalls.&lt;/p&gt;

&lt;p&gt;Fourth, auditing becomes simpler. You audit the workflow – a readable representation of the logic, instead of hundreds of lines of SQL code versions scattered across different databases.&lt;/p&gt;

&lt;p&gt;SQL dialect differences are not a minor inconvenience. They are the hidden tax data teams pay every day. Every additional database means more maintenance costs and a higher risk of errors.&lt;/p&gt;

&lt;p&gt;SQLazy’s solution is simple: let compilers, instead of humans, translate dialects.&lt;/p&gt;

&lt;p&gt;Your job is to write clear logic, and leave the rest to the compiler. You can also use an LLM to help write the logic. Natural language descriptions can then be standardized into SQLazy statements.&lt;/p&gt;

&lt;p&gt;AI writes the logic. A compiler writes the SQL.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>devops</category>
      <category>sql</category>
      <category>sqlazy</category>
    </item>
    <item>
      <title>SQLazy: Search for Adjacent Records at a Specified Offset Within Groups</title>
      <dc:creator>Judy</dc:creator>
      <pubDate>Wed, 26 Aug 2026 08:14:33 +0000</pubDate>
      <link>https://dev.to/esproc_spl/sqlazy-search-for-adjacent-records-at-a-specified-offset-within-groups-2m3i</link>
      <guid>https://dev.to/esproc_spl/sqlazy-search-for-adjacent-records-at-a-specified-offset-within-groups-2m3i</guid>
      <description>&lt;h2&gt;
  
  
  Problem Description
&lt;/h2&gt;

&lt;p&gt;In a table, ProductionLine_Number is the grouping field, and within each group records are sorted by date_Time. The task is to search, within each group, all records whose Cardboard_Number equals a specified string, then take the records within a specified offset before and after each matched record, merge and remove duplicates before outputting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Source Data&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Expected Result&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;In the ProductionLine_Number=1 group, sorted by time, the ids are 2,4,5,6,7,8, where the row matching spL1ml82N4o is id=4. Taking 2 rows before and after id=4 gives ids 2,4,5,6.&lt;/p&gt;

&lt;p&gt;In the ProductionLine_Number=2 group, sorted by time, the ids are 9,10,11,12, where the row matching spL1ml82N4o is id=11. Taking 2 rows before and after id=11 gives ids 9,10,11,12.&lt;/p&gt;

&lt;p&gt;Merging and deduplicating both groups gives 2,4,5,6,9,10,11,12. Note that ids 7 and 8 are more than 2 rows away from the matched row id=4, so they are excluded.&lt;/p&gt;

&lt;h2&gt;
  
  
  SQLazy Step-by-Step Implementation
&lt;/h2&gt;

&lt;p&gt;Core idea: After sorting by grouping field and time within groups, use compute to flag the rows matching the target value within each group, then use the relative-position range syntax flag[-2:2] to take a window of 2 rows before and after the current row, and check whether the window contains a matched row. If so, the current row falls within the result range. Finally filter and deduplicate.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6kmokstt22i5ex2m42uz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6kmokstt22i5ex2m42uz.png" alt="Implementation" width="800" height="270"&gt;&lt;/a&gt;&lt;br&gt;
[&lt;a href="https://www.sqlazy.com/?2Qk" rel="noopener noreferrer"&gt;Click to run this example online&lt;/a&gt;]&lt;/p&gt;

&lt;p&gt;The steps are explained below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Sort by grouping field and time within groups&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;sort ProductionLine_Number, date_Time asc&lt;/p&gt;

&lt;p&gt;Sort the data by ProductionLine_Number to group records, and by date_Time ascending within each group, ensuring subsequent relative-position calculations follow the correct time order.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Step 2: Flag the rows matching the target value&lt;/strong&gt;&lt;br&gt;
compute (if (Cardboard_Number = “spL1ml82N4o” then 1)) , as flag; partition ProductionLine_Number&lt;br&gt;
Within each ProductionLine_Number group, mark rows where Cardboard_Number equals the target string spL1ml82N4o as 1, leaving others empty. partition confines the flagging to each group independently.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Step 3: Use range syntax to check whether the window contains a match (core)&lt;/strong&gt;&lt;br&gt;
compute flag[-2:2] , max , as in_range; partition ProductionLine_Number&lt;br&gt;
This is the core step, using SQLazy’s relative-position range syntax. flag[-2:2] takes the flag values in the window from 2 rows before to 2 rows after the current row, then aggregates with max: as long as the window contains a matched row (flag=1), the current row’s in_range is 1. Thus every record within the offset range of each matched row is covered.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Step 4: Filter records within the range&lt;/strong&gt;&lt;br&gt;
filter in_range = 1&lt;br&gt;
Keep only rows where in_range is 1, i.e., records falling within the offset range before or after a matched row.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Step 5: Remove duplicate records&lt;/strong&gt;&lt;br&gt;
distinct id&lt;br&gt;
When the offset ranges of multiple matched rows overlap, the same row may be selected more than once. Use distinct id to deduplicate and output the final result.&lt;/p&gt;

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

&lt;p&gt;After confirming the above steps, the SQLazy compiler automatically generates native SQL (MySQL syntax):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WITH t2 AS (
    SELECT id, Cardboard_Number, date_Time, ProductionLine_Number
        , CASE
            WHEN Cardboard_Number = 'spL1ml82N4o' THEN 1
            ELSE NULL
        END AS flag
    FROM table1
),
t3 AS (
    SELECT id, Cardboard_Number, date_Time, ProductionLine_Number, flag
        , MAX(flag) OVER (PARTITION BY ProductionLine_Number ORDER BY CASE
            WHEN ProductionLine_Number IS NULL THEN 1
            ELSE 0
        END, ProductionLine_Number ASC, CASE
            WHEN date_Time IS NULL THEN 1
            ELSE 0
        END, date_Time ASC ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING) AS in_range
    FROM t2
),
t4 AS (
    SELECT id, Cardboard_Number, date_Time, ProductionLine_Number, flag
        , in_range
    FROM t3
    WHERE in_range = 1
)
SELECT id, Cardboard_Number, date_Time, ProductionLine_Number, flag
    , in_range
FROM t4
GROUP BY id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SQLazy lets you describe logic in business language instead of writing nested SQL queries. In this example of searching adjacent records at a specified offset within groups, the highlight is the relative-position range syntax: flag[-2:2] expresses a 2-row window before and after with a simple index range, corresponding to the verbose ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING in SQL. Combined with compute's max aggregation, one line determines whether the window contains a match. partition keeps all calculations independent within each group, and step-by-step computation lets every intermediate result be verified.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Official Links&lt;/strong&gt;&lt;br&gt;
SQLazy Online Experience: sqlazy.com (free, no registration required)&lt;br&gt;
SQLazy Repository: github.com/SPLWare/SQLazy&lt;/p&gt;

</description>
      <category>sql</category>
      <category>devops</category>
      <category>discuss</category>
      <category>sqlazy</category>
    </item>
    <item>
      <title>Trae + SQLazy: A Practical Guide to Writing Complex SQL</title>
      <dc:creator>Judy</dc:creator>
      <pubDate>Mon, 24 Aug 2026 08:05:27 +0000</pubDate>
      <link>https://dev.to/esproc_spl/trae-sqlazy-a-practical-guide-to-writing-complex-sql-1gi6</link>
      <guid>https://dev.to/esproc_spl/trae-sqlazy-a-practical-guide-to-writing-complex-sql-1gi6</guid>
      <description>&lt;ol&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;AI-assisted data development is reshaping the way SQL is traditionally written. Today’s mainstream approaches can be broadly divided into two categories: one is the “end-to-end” approach, which directly generates native SQL; the other is the “layered” approach, which generates structured intermediate representations and then compiles them into SQL. The former is easy to get started with but difficult to handle complex business scenarios; the latter adds an abstraction layer, which enables engineering-grade guarantees of auditability, debuggability, and reproducibility.&lt;/p&gt;

&lt;p&gt;The approach adopted in this article is a combination of Trae (ByteDance’s AI programming tool) as the “brain”, responsible for understanding requirements, clarifying ambiguities, and generating SQLazy step-by-step scripts (.nspl); and SQLazy’s dedicated IDE as the “execution layer”, responsible for syntax validation, step-by-step debugging, and cross-database compilation. Together, they form a closed loop of “AI planning + human review + deterministic engine execution”.&lt;/p&gt;

&lt;p&gt;We selected four real-world cases, ranging from simple to complex, covering typical scenarios such as statistical aggregation, multi-table merging, cross-subgroup data filling, and amount allocation. The complete process – from requirement input to validation completion– is demonstrated, with emphasis on documenting the errors encountered and the reasoning behind corrections.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Tool Collaboration Model&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;2.1 Trae’s Roles and Capabilities&lt;/p&gt;

&lt;p&gt;Trae plays three key roles in this workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Automatically Load the Project Knowledge Base&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Deploy the global knowledge base specification file (.md) described in Section 3.1 – Environment Setupto the project and register it as a centralized specification:&lt;/p&gt;

&lt;p&gt;SQLazy script output format specifications (three tab-separated columns, one function per step)&lt;/p&gt;

&lt;p&gt;Hard constraints (reserved word handling, cross-step reference rules, etc.)&lt;/p&gt;

&lt;p&gt;Loading paths for function and feature documentation&lt;/p&gt;

&lt;p&gt;When the user enters /sqlazy-plan followed by business requirements in the chat box, Trae automatically inherits all the rules above without needing to restate them.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Structured Four-Step Output&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Trae is constrained to generate solutions following the four steps below instead of directly providing a final answer:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Capability Overview: List the functions and features involved in the task.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Requirement Decomposition: Break down business requirements into a sequence of data processing steps.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Feature Matching: Match each step with the appropriate SQLazy feature.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Code Generation: Generate the final step-by-step script (.nspl).&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This enforced output process ensures the auditability of the solution – the rationale behind each step is clearly visible.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Proactive Requirement Clarification&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Facing complex requirements, Trae proactively raises key questions, such as: How should the date range be defined? How should null values be handled? Is the grouping key unique? This prevents the AI from making unsupported assumptions about unstated conditions.&lt;/p&gt;

&lt;p&gt;2.2 SQLazy's Core Value&lt;/p&gt;

&lt;p&gt;SQLazy provides a dedicated IDE for writing and executing .nspl scripts. Its core value lies in three areas:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Clear Step Semantics, Low Audit Complexity&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Taking "longest streak of consecutive up days for a stock" as an example, the .nspl script requires only 5 steps: filter → sort → segment →count → find the maximum. Reading the entire script feels like reading a business operation checklist, rather than parsing complex nested SQL. Each line represents one step; the output of each step becomes the input for the next, with the entire logic laid out explicitly.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Step-by-Step Execution, Quick Problem Identification&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the IDE, you can execute each step individually and inspect intermediate results in real time. Once a step's output doesn't meet expectations, you can immediately pinpoint the specific logic error without having to dig through dozens of lines of nested SQL.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;One Script, Compiled Everywhere&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After validation passes, one click compiles to native SQL for MySQL, PostgreSQL, Oracle and other mainstream databases. No rewriting per database.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Workflow&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;3.1 Environment Setup&lt;/p&gt;

&lt;p&gt;The project adopts the following standard directory structure:&lt;/p&gt;

&lt;p&gt;project_root/&lt;br&gt;
├── plan.md        # Global specification: format conventions, loading paths&lt;br&gt;
├── sqlazy-plan.md  # Command entry: /sqlazy-plan trigger, inherits all rules from plan.md&lt;br&gt;
├── nspl/           # Delivery directory: .nspl scripts stored here&lt;br&gt;
├── function/       # Function reference documentation (auto-loaded)&lt;br&gt;
└── action/         # Action reference documentation (auto-loaded)&lt;/p&gt;

&lt;p&gt;After creating a new project in Trae, copy sqlazy-plan.md, plan.md files, function/ and action/ directories from the SQLazy installation directory's LLM folder to the project root.&lt;/p&gt;

&lt;p&gt;3.2 How to Trigger&lt;/p&gt;

&lt;p&gt;Use the /sqlazy-plan command in the Trae chat box to trigger the task, followed by a complete business requirement description. It is best to specify the tables, fields, join relationships, grouping dimensions, time range, and output requirements – all in one go.&lt;/p&gt;

&lt;p&gt;3.3 Validation and Correction&lt;/p&gt;

&lt;p&gt;This is the most critical step in the entire workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Construct a small set of representative test data and manually calculate the expected results.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run the script step by step in the SQLazy IDE, comparing intermediate results against expected values.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When issues are found, modify the script directly or report them to Trae for regeneration.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;After validation passes, compile to native SQL for the target database.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Hands-on Case Studies&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The following four cases, from easy to hard, fully document the problem-solving process.&lt;/p&gt;

&lt;p&gt;Case 1: Longest Streak of Consecutive Up Days for a Stock&lt;/p&gt;

&lt;p&gt;Requirement:&lt;/p&gt;

&lt;p&gt;/sqlazy-plan Stock price table stock contains three columns – CODE,DT (date), and CL (closing price). Calculate the maximum number of consecutive days the stock with code 100046 has been rising (i.e., each day’s closing price is higher than the previous day’s).&lt;/p&gt;

&lt;p&gt;Analysis and Implementation:&lt;/p&gt;

&lt;p&gt;This is the simplest type of statistical requirement. Trae outputs the following script following the four-step process:&lt;/p&gt;

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

&lt;p&gt;Simple statistical requirement, generated correctly by AI in one attempt. Run and validate directly in the SQLazy IDE, then compile to native SQL for the target database.&lt;/p&gt;

&lt;p&gt;Case 2: Merging Multiple Tables by ID into Single Rows&lt;/p&gt;

&lt;p&gt;Requirement:&lt;/p&gt;

&lt;p&gt;/sqlazy-plan There are four data tables, T1, T2, T3, and T4, with similar structures. Each table has two fields: the first field is an ID (named id, id2, id3, and id4 respectively), and the second field is named colA, colB, colC, and colD respectively. The goal is to merge these four tables by their ID values into a single result table with 9 columns: the first column, ID_main, stores the ID value, and the remaining 8 columns contain the fields from the four tables (i.e., all fields from T1, T2, T3, and T4). Each distinct ID appears as exactly one row in the merged table. If an ID is missing from any of the original tables, the corresponding columns from that table are set to NULL.&lt;/p&gt;

&lt;p&gt;Analysis and Implementation:&lt;/p&gt;

&lt;p&gt;The core challenge of this requirement is that the four tables have different ID field names (id, id2, id3, id4), and a method is needed to join them while ensuring no IDs are lost.&lt;/p&gt;

&lt;p&gt;Trae designed a dual-track strategy of "full join + ID coalescing," outputting an 8-step script:&lt;/p&gt;

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

&lt;p&gt;Same as Case 1, correct on first attempt.&lt;/p&gt;

&lt;p&gt;Case 3: Cross-Group Sequential Field Value Filling&lt;/p&gt;

&lt;p&gt;Requirement:&lt;/p&gt;

&lt;p&gt;/sqlazy-plan Given a data table lines, where the first two columns, Group1 and Group2, are grouping columns, the third column, LineID, is a unique row identifier, and the fourth field, TargetField, is a numeric target column. After sorting by Group1, Group2, and LineID, within the same Group1, every Group2 has the same number of records; only the last Group2 (in sort order) has non-NULL values in TargetField, while all other Group2 groups are NULLs. The goal is to copy the TargetField values from the last Group2 group to the other groups within the same Group1, in the same sequential row order (i.e., matching by row positions in sorted order). The final output should be a table containing only the columns Group1, Group2, LineID, and TargetField, sorted in ascending order by the first three columns.&lt;/p&gt;

&lt;p&gt;Analysis and Implementation (corrected after one iteration):&lt;/p&gt;

&lt;p&gt;The core difficulty of this requirement is that LineID is unique across all rows and cannot be directly used as a cross-group join key. Trae's first version incorrectly used Group1 + LineID as the join key, causing mapping failures. Below is the complete iterative correction process.&lt;/p&gt;

&lt;p&gt;First Version (Incorrect)&lt;/p&gt;

&lt;p&gt;Trae's first attempt used Group1 + LineID as the join key to fill the TargetField from the last subgroup back to the whole table:&lt;/p&gt;

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

&lt;p&gt;Problem: LineID is unique across all rows (e.g., 101, 105, 201, 205, 301, 305).&lt;/p&gt;

&lt;p&gt;Different Group2 groups share no LineID values. Therefore, when using Group1 + LineID as the join key, only the last subgroup’s own rows can match; all other subgroups’ rows fail to match, TargetField remains NULL, and the fill logic completely fails.&lt;/p&gt;

&lt;p&gt;Corrected Version: Using Row Number as Mapping Bridge&lt;/p&gt;

&lt;p&gt;Instead of using LineID for joining, the “row number” (positional sequence 1, 2, 3, ... obtained by ranking within each Group2 by LineID) serves as the mapping bridge. Since every Group2 within the same Group1 has the same row count, the “Nth row” naturally corresponds across different Group2 groups.&lt;/p&gt;

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

&lt;p&gt;Validation Example:&lt;/p&gt;

&lt;p&gt;Assume the data is as follows (all LineID values are unique):&lt;/p&gt;

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

&lt;p&gt;After t2 ranking, a row sequence number row_idx is generated: G2-1(101→1, 105→2), G2-2(201→1, 205→2), G2-3(301→1, 305→2). t3 takes the two records of G2-3. t4 creates a mapping table (A,1→100), (A,2→200). After t5’s backfill, the first row of every subgroup gets 100, and the second row gets 200.&lt;/p&gt;

&lt;p&gt;When LineID is unique across the entire table, it cannot be directly used as a cross-group join key. You must first rank within each subgroup to obtain a row sequence number, and use the row number as the mapping bridge. The maximum-rank filter can directly select the last subgroup without requiring the additional aggregation and backfill steps.&lt;/p&gt;

&lt;p&gt;Case 4: Invoice Amount Split by Account, with Total Preserved&lt;/p&gt;

&lt;p&gt;Requirement:&lt;/p&gt;

&lt;p&gt;/sqlazy-plan For the invoice table i (containing fields invoiceid, amount, projectid) and the project table p (containing fields id, projectid, accountcode), join them on projectid. For the joined result, add a new allocation field, splitamount, to implement a splitting logic that distributes the amount by the number of accounts under each project, while ensuring the total sum remains preserved. Within each group, sort by accountcode in ascending order. For the 2nd through the Nth account, calculate splitamount using amount/ total_number_of_accounts and round the result to 2 decimal places. The first account absorbs the rounding remainder; its splitamount equals the invoice’s original amount minus the sum of all other accounts’ splitamount values, so that the allocated amounts exactly match the original invoice amount.&lt;/p&gt;

&lt;p&gt;Analysis and Implementation (finalized after two iterations):&lt;/p&gt;

&lt;p&gt;This is the most complex of the four cases. Although Trae’s first version correctly identified the partitioned aggregation approach, there were issues with syntax details and step organization. It took two iterations to finalize.&lt;/p&gt;

&lt;p&gt;First Iteration: Partitioned Aggregation (partially corrected)&lt;/p&gt;

&lt;p&gt;Trae used a mathematically equivalent approach – partitioned aggregation to calculate the remainder – convert “first row’s remainder = total amount – sum of all the other rows’ allocated values” within the partition to amount - sum_temp_split + temp_split, where sum_temp_split is the sum of base allocated values across all rows in the partition.&lt;/p&gt;

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

&lt;p&gt;Two issues were found after running:&lt;/p&gt;

&lt;p&gt;Issue 1: Line 4 used * as the count formula, causing the SQLazy parser to report the error “logic error near [*]”. The current version does not support * as a count formula.&lt;/p&gt;

&lt;p&gt;Issue 2: User feedback: “Complete the calculation directly in one step based on whether rank=1, without intermediate temporary columns” – they want to merge the base allocation calculation and the final conditional calculation into a single step, reducing the number of intermediate steps.&lt;/p&gt;

&lt;p&gt;Second Iteration: Final Version&lt;/p&gt;

&lt;p&gt;Two separate corrections were applied:&lt;/p&gt;

&lt;p&gt;Changed * to the specific field projectid (counting projectid within a partition is equivalent to counting rows)&lt;/p&gt;

&lt;p&gt;Combine the original t5 and t6 steps into one – within a single computed-column statement, create three derived columns separated by semicolons.&lt;/p&gt;

&lt;p&gt;Below is the final script:&lt;/p&gt;

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

&lt;p&gt;Validation Example:&lt;/p&gt;

&lt;p&gt;projectid=1, amount=100.00, 3 accounts (accountcode=1, 2, 3)&lt;/p&gt;

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

&lt;p&gt;The splitting logic is correct: the first account absorbs the rounding difference of 0.01, and the total is preserved. Three key lessons from this case: ① * is not supported in the current SQLazy environment; specific field names must be used; ② In computed-column statements, aggregate parameters and cross-row parameters are mutually exclusive and cannot be used simultaneously; ③ Reserved words (such as sum) used as names must be enclosed in single quotes.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The core value of the Trae + SQLazy combination does not lie in “letting AI write SQL automatically”, but rather in constraining AI’s uncertainty within an auditable, debuggable intermediate layer, then letting a deterministic engine handle the final execution.&lt;/p&gt;

&lt;p&gt;In this workflow, the three parties have clear division of labor:&lt;/p&gt;

&lt;p&gt;Trae handles understanding requirements, clarifying ambiguities, and generating the structured initial nspl draft – this is what AI does best: “structuring fuzzy problems”.&lt;/p&gt;

&lt;p&gt;SQLazy IDE handles syntax validation, step-by-step debugging, and cross-database compilation — this is the reliable execution by a deterministic engine.&lt;/p&gt;

&lt;p&gt;Humans are responsible for verifying business rules, validating test results, and correcting logic deviations – this is irreplaceable business judgment.&lt;/p&gt;

&lt;p&gt;Reviewing the four cases: from the simple statistics that passed on first attempt, to the multi-table merge generated correctly in one pass, to the cross-group filling that required correcting the join key, to the invoice split finalized after two iterations – each step confirms the feasibility of the “AI assistance + human review + small-sample validation” approach. In practice, this workflow can be standardized to make AI a true productivity amplifier rather than a source of risk.&lt;/p&gt;

</description>
      <category>development</category>
      <category>sql</category>
      <category>discuss</category>
      <category>sqlazy</category>
    </item>
    <item>
      <title>SQLazy: Fill Field Values by Sequence Across Sub-Groups</title>
      <dc:creator>Judy</dc:creator>
      <pubDate>Wed, 19 Aug 2026 09:00:53 +0000</pubDate>
      <link>https://dev.to/esproc_spl/sqlazy-fill-field-values-by-sequence-across-sub-groups-3576</link>
      <guid>https://dev.to/esproc_spl/sqlazy-fill-field-values-by-sequence-across-sub-groups-3576</guid>
      <description>&lt;h2&gt;
  
  
  Problem Description
&lt;/h2&gt;

&lt;p&gt;A table contains Group1 and Group2 as grouping fields, LineID as the sequence number within each group, and TargetField as the target field to fill. After sorting by Group1, Group2, and LineID, within the same Group1, each Group2 has the same number of records; only the last Group2 has TargetField values, while the others are empty. The goal is to copy the values from the last sub-group within each big group to fill the same-row positions of other sub-groups.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Source Data&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Expected Result&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;Group1=2 has 2 sub-groups (Group2=4,5), each with 3 records. The last sub-group Group2=5 has TargetField [5,3,4]; these values are copied to the other sub-group Group2=4.&lt;/p&gt;

&lt;h2&gt;
  
  
  SQLazy Step-by-Step Implementation
&lt;/h2&gt;

&lt;p&gt;Core idea: First use rank to generate row numbers rn within each (Group1, Group2), then leverage the fact that only the last sub-group has values. Use compute to summarize TargetField by (Group1, rn). Each (Group1, rn) group has only one non-null row, so sum returns that value, naturally copying the last sub-group's values to the same-row positions of other sub-groups.&lt;/p&gt;

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

&lt;p&gt;[&lt;a href="https://www.sqlazy.com/?4Mz" rel="noopener noreferrer"&gt;Click to run this example online&lt;/a&gt;]&lt;/p&gt;

&lt;p&gt;The steps are explained below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Sort by grouping fields and sequence&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;sort Group1, Group2, LineID&lt;/p&gt;

&lt;p&gt;Sort the data by Group1, Group2, LineID in ascending order to ensure records within each big group are processed in sub-group and row number order.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Step 2: Generate row numbers within each sub-group&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;rank as rn; partition Group1, Group2&lt;/p&gt;

&lt;p&gt;Use rank within each (Group1, Group2) partition to generate row numbers rn. Since each sub-group has the same number of records, within the same big group, rn=1 rows come from each sub-group's first record, rn=2 from the second, and so on.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Step 3: Aggregate by row number to fill values (core)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;compute TargetField sum as tempTarget; partition Group1, rn&lt;/p&gt;

&lt;p&gt;This is the core step. Group by (Group1, rn) and use sum to aggregate TargetField. Since only the last sub-group has non-null TargetField values (others are NULL), sum ignores NULL. Each (Group1, rn) group has only one valid value, so sum returns that value, naturally copying the last sub-group's values to the same-row positions of other sub-groups.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq39w3juf5u4s8ax1jjwp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq39w3juf5u4s8ax1jjwp.png" alt="sub-groups" width="800" height="355"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Step 4: Select the final result columns&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;derive Group1, Group2, LineID, tempTarget as TargetField&lt;/p&gt;
&lt;h2&gt;
  
  
  Generated SQL
&lt;/h2&gt;

&lt;p&gt;After confirming the above steps, the SQLazy compiler automatically generates native SQL (PostgreSQL syntax):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WITH t1 AS (
  SELECT Group1, Group2, LineID, TargetField
  FROM lines
),
sub__6 AS (
  SELECT
    sub__5.*,
    RANK() OVER (PARTITION BY Group1, Group2 ORDER BY Group1, Group2, LineID) AS rn
  FROM t1 sub__5
),
t3 AS (
  SELECT
    Group1, Group2, LineID, TargetField, rn,
    SUM(TargetField) OVER (PARTITION BY Group1, rn) AS tempTarget
  FROM sub__6
)
SELECT Group1, Group2, LineID, tempTarget AS TargetField
FROM t3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SQLazy lets you describe logic in business language instead of writing nested SQL queries.&lt;/p&gt;

&lt;p&gt;The solution process is divided into 4 steps, each of which can independently verify intermediate results, reducing the probability of errors in complex logic. SQLazy's rank function generates row numbers directly within partitions, more concise than SQL's RANK() OVER. The compute function combined with sum naturally aggregates unique non-null values from the same row position across sub-groups, replacing explicit cross-row references.&lt;/p&gt;

&lt;p&gt;Official Links&lt;br&gt;
SQLazy Online Experience:&lt;a href="https://sqlazy.com/" rel="noopener noreferrer"&gt;sqlazy.com&lt;/a&gt;(free, no registration required)&lt;br&gt;
SQLazy Repository:&lt;a href="https://github.com/SPLWare/SQLazy" rel="noopener noreferrer"&gt;github.com/SPLWare/SQLazy&lt;/a&gt;&lt;/p&gt;

</description>
      <category>sql</category>
      <category>devops</category>
      <category>development</category>
      <category>sqlazy</category>
    </item>
    <item>
      <title>SQLazy: Identify Whether Differences Within Groups Come from Brand or Type Problem Description</title>
      <dc:creator>Judy</dc:creator>
      <pubDate>Fri, 07 Aug 2026 06:53:47 +0000</pubDate>
      <link>https://dev.to/esproc_spl/sqlazy-identify-whether-differences-within-groups-come-from-brand-or-type-problem-description-4hj8</link>
      <guid>https://dev.to/esproc_spl/sqlazy-identify-whether-differences-within-groups-come-from-brand-or-type-problem-description-4hj8</guid>
      <description>&lt;p&gt;The ID field of table tbl represents car categories, with each category further divided into Brand and Type. The task is to group by ID and determine the source of differences within each group: if a group has multiple brands, Difference is "Brand"; if a group has multiple types, Difference is "Type". The same ID may produce multiple records.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Source Data&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Expected Result&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;ID=1 has both Honda and Jeep as brands, as well as Coupe and SUV as types, so Difference produces both Brand and Type.&lt;/p&gt;

&lt;p&gt;ID=2 only has Ford as a brand, but has Sedan and Crossover as types, so only Type is produced.&lt;/p&gt;

&lt;h2&gt;
  
  
  SQLazy Step-by-Step Implementation
&lt;/h2&gt;

&lt;p&gt;Core idea: First use summarize to group by ID and count distinct brands (cntBrand) and types (cntType), then use expand to cross-join the results with the dimensions ("Brand" and "Type"), and finally use filter to keep only the rows that satisfy the conditions.&lt;/p&gt;

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

&lt;p&gt;[&lt;a href="https://www.sqlazy.com/?4fJ" rel="noopener noreferrer"&gt;Click to run this example online&lt;/a&gt;]&lt;/p&gt;

&lt;p&gt;The steps are explained below.&lt;/p&gt;

&lt;p&gt;Step 1: Group by ID and count distinct brands and types&lt;/p&gt;

&lt;p&gt;summarize Brand icount as cntBrand, Type icount as cntType; group ID&lt;/p&gt;

&lt;p&gt;Use summarize to group by ID. The icount function counts distinct Brand and Type values within each group, recorded as cntBrand and cntType respectively. This step compresses each row of detail data into one row per ID, containing distinct counts.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Step 2: Expand the dimension list into rows&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;expand ["Brand","Type"] as Difference&lt;/p&gt;

&lt;p&gt;The expand function unfolds the constant list ["Brand","Type"] into rows, cross-joining with each upstream row to generate the new column Difference. Each ID gets two rows: Difference="Brand" and Difference="Type", while retaining cntBrand and cntType fields.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Step 3: Filter dimensions that meet the conditions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;filter (if ((Difference = "Brand") then cntBrand&amp;gt;1; (Difference = "Type") then cntType&amp;gt;1))&lt;/p&gt;

&lt;p&gt;Use filter with conditional branching syntax: for rows where Difference="Brand", check cntBrand&amp;gt;1; for rows where Difference="Type", check cntType&amp;gt;1. Only rows with counts greater than 1 are kept. A single filter statement expresses different conditions for different branches, much more intuitive than SQL's nested CASE WHEN.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Step 4: Select the required columns from the result&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;derive ID, Difference&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Generated SQL&lt;/strong&gt;&lt;br&gt;
After confirming the above steps, the SQLazy compiler automatically generates native SQL (PostgreSQL syntax):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WITH Value1 AS (
  SELECT
    ID,
    COUNT(DISTINCT Brand) AS cntBrand,
    COUNT(DISTINCT Type) AS cntType
  FROM (
    SELECT ID, Brand, Type FROM tbl
  ) tbl
  GROUP BY ID
),
Value2 AS (
  SELECT
    Value1.ID, Value1.cntBrand, Value1.cntType, Difference
  FROM Value1
  CROSS JOIN (
    SELECT 'Brand' AS Difference
    UNION ALL
    SELECT 'Type'
  ) T_1
)
SELECT ID, Difference FROM Value2
WHERE CASE
  WHEN (Difference = 'Brand') THEN cntBrand &amp;gt; 1
  WHEN (Difference = 'Type') THEN cntType &amp;gt; 1
  ELSE NULL
END
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SQLazy lets you describe logic in business language instead of writing nested SQL queries. The solution process is divided into 4 steps, each can independently verify intermediate results, reducing the probability of errors in complex logic. The icount function of summarize can directly count distinct values, replacing SQL's COUNT(DISTINCT ...). The expand function expands a constant list into rows, completing in one line what requires CROSS JOIN with UNION ALL in SQL. The filter function's conditional branching syntax (if ... then ...; ... then ...) makes multi-condition filtering logic clear at a glance, clearer than SQL's nested CASE WHEN.&lt;/p&gt;

&lt;p&gt;Official Links&lt;br&gt;
SQLazy Online Experience: &lt;a href="https://sqlazy.com/" rel="noopener noreferrer"&gt;sqlazy.com&lt;/a&gt; (free, no registration required)&lt;br&gt;
SQLazy Repository: &lt;a href="https://github.com/SPLWare/SQLazy" rel="noopener noreferrer"&gt;github.com/SPLWare/SQLazy&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>SQLazy：Merge Multiple Tables into Single Rows by Common ID</title>
      <dc:creator>Judy</dc:creator>
      <pubDate>Tue, 04 Aug 2026 06:57:22 +0000</pubDate>
      <link>https://dev.to/esproc_spl/sqlazymerge-multiple-tables-into-single-rows-by-common-id-266e</link>
      <guid>https://dev.to/esproc_spl/sqlazymerge-multiple-tables-into-single-rows-by-common-id-266e</guid>
      <description>&lt;h2&gt;
  
  
  Problem Description
&lt;/h2&gt;

&lt;p&gt;Merge multiple structurally similar tables with different column names into a wide table using full outer joins by common ID. Four tables have similar structures, each with two fields. The fields have the same meaning but different names (id, id2, id3, id4 all represent ID). The goal is to merge the four tables into single rows by ID, with each ID appearing in exactly one row. When an ID is absent in a table, the corresponding columns take NULL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Source Data&lt;/strong&gt;&lt;br&gt;
T1 table:&lt;/p&gt;

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

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

&lt;p&gt;T3 table:&lt;/p&gt;

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

&lt;p&gt;T4 table:&lt;/p&gt;

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

&lt;p&gt;*&lt;em&gt;Expected Result&lt;br&gt;
*&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;For example, ID=555 appears in both T1 and T2 but not in T3 or T4, so id, colA, id2, colB have values, while id3/colC/id4/colD are NULL.&lt;/p&gt;

&lt;p&gt;ID=222 only appears in T3, so only id3 and colC have values; all other columns are NULL.&lt;/p&gt;

&lt;p&gt;ID=10 appears in T2 and T4 but not in T1 or T3, so id2, colB, id4, colD have values; all other columns are NULL.&lt;/p&gt;
&lt;h2&gt;
  
  
  SQLazy Step-by-Step Implementation
&lt;/h2&gt;

&lt;p&gt;Core Idea: First use derive to unify the ID column names of each table to ID_main, making subsequent merging easier. Then start from the first table and perform full outer joins one by one: use join to full outer join the current result with the next table on ID_main, then use derive and nvl to merge the new ID into the ID_main column, appending tables one by one to get the final result.&lt;/p&gt;

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

&lt;p&gt;[&lt;a href="https://www.sqlazy.com/?3q4" rel="noopener noreferrer"&gt;Click to run this example online&lt;/a&gt;]&lt;/p&gt;

&lt;p&gt;The steps are explained below.&lt;/p&gt;

&lt;p&gt;Steps 1-4: Unify ID Column Names Across Tables&lt;/p&gt;

&lt;p&gt;derive id as ID_main, id, colA&lt;/p&gt;

&lt;p&gt;Use derive on T1-T4 to rename their respective ID column names (id/id2/id3/id4) uniformly to ID_main.&lt;/p&gt;

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

&lt;p&gt;Step 5: Full Outer Join T1 and T2&lt;/p&gt;

&lt;p&gt;join ID_main; with t2; ID_main; take id2, colB; full&lt;/p&gt;

&lt;p&gt;Use the join function to full outer join t1 and t2 on ID_main.&lt;/p&gt;

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

&lt;p&gt;Step 6: Merge NULLs in ID Column&lt;/p&gt;

&lt;p&gt;derive nvl(ID_main, id2) as ID_main, id, colA, id2, colB&lt;/p&gt;

&lt;p&gt;If a record comes from t2 but is not present in t1, its ID_main is NULL. This step assigns t2.id2 to ID_main in such records, ensuring the ID_main column always has a value. Different SQL implementations use different syntax for this step - for example, Oracle uses nvl and SQL Server uses COALESCE - while NSPL uniformly uses the nvl function, which is automatically translated into the corresponding dialect during compilation based on the database type.&lt;/p&gt;

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

&lt;p&gt;Steps 7-8: Append T3&lt;/p&gt;

&lt;p&gt;join ID_main; with t3; ID_main; take id3, colC; full&lt;/p&gt;

&lt;p&gt;derive nvl(ID_main, id3) as ID_main, id, colA, id2, colB, id3, colC&lt;/p&gt;

&lt;p&gt;Repeat the join + derive pattern to full outer join T3 into the current result. After appending each table, use ifn to merge the new ID into the unified ID_main column.&lt;/p&gt;

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

&lt;p&gt;Steps 9-10: Append T4 to Get Final Result&lt;/p&gt;

&lt;p&gt;join ID_main; with t4; ID_main; take id4, colD; full&lt;/p&gt;

&lt;p&gt;derive nvl(ID_main, id4) as ID_main, id, colA, id2, colB, id3, colC, id4, colD&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Generated SQL&lt;/strong&gt;&lt;br&gt;
After confirming the above steps, the SQLazy compiler automatically generates native SQL (SQL Server syntax):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WITH t1 AS (
        SELECT id AS ID_main, id, colA
        FROM T1
    ),
    t2 AS (
        SELECT id2 AS ID_main, id2, colB
        FROM T2
    ),
    r1 AS (
        SELECT t1.ID_main, t1.id, t1.colA, t2.id2, t2.colB
        FROM t1
            FULL JOIN t2 ON ID_main = t2.ID_main
    ),
    r1b AS (
        SELECT COALESCE(NULLIF(CAST(ID_main AS VARCHAR(10)), ''), NULLIF(CAST(id2 AS VARCHAR(10)), '')) AS ID_main
            , id, colA, id2, colB
        FROM r1
    ),
    t3 AS (
        SELECT id3 AS ID_main, id3, colC
        FROM T3
    ),
    r2 AS (
        SELECT r1b.ID_main, r1b.id, r1b.colA, r1b.id2, r1b.colB
            , t3.id3, t3.colC
        FROM r1b
            FULL JOIN t3 ON ID_main = t3.ID_main
    ),
    r2b AS (
        SELECT COALESCE(NULLIF(CAST(ID_main AS VARCHAR(10)), ''), NULLIF(CAST(id3 AS VARCHAR(10)), '')) AS ID_main
            , id, colA, id2, colB, id3
            , colC
        FROM r2
    ),
    t4 AS (
        SELECT id4 AS ID_main, id4, colD
        FROM T4
    ),
    r3 AS (
        SELECT r2b.ID_main, r2b.id, r2b.colA, r2b.id2, r2b.colB
            , r2b.id3, r2b.colC, t4.id4, t4.colD
        FROM r2b
            FULL JOIN t4 ON ID_main = t4.ID_main
    )
SELECT COALESCE(NULLIF(CAST(ID_main AS VARCHAR(10)), ''), NULLIF(CAST(id4 AS VARCHAR(10)), '')) AS ID_main
    , id, colA, id2, colB, id3
    , colC, id4, colD
FROM r3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SQLazy lets you describe logic in business language instead of writing nested SQL queries. SQLazy's step-by-step computation breaks multi-table merging into independent steps such as unifying column names, performing full outer joins one table at a time, and merging IDs - each step can be independently verified for intermediate results. The join function, combined with derive and nvl, flexibly handles post-merge operations such as renaming columns and merging NULL values. This strategy of appending tables one by one is much clearer and more maintainable than writing deeply nested FULL JOIN + COALESCE queries in SQL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Official Links&lt;/strong&gt;&lt;br&gt;
SQLazy Online Experience: &lt;a href="https://sqlazy.com/" rel="noopener noreferrer"&gt;sqlazy.com&lt;/a&gt; (free, no registration required)&lt;br&gt;
SQLazy Repository: &lt;a href="https://github.com/SPLWare/SQLazy" rel="noopener noreferrer"&gt;github.com/SPLWare/SQLazy&lt;/a&gt;&lt;/p&gt;

</description>
      <category>sql</category>
      <category>development</category>
      <category>programmers</category>
      <category>sqlazy</category>
    </item>
    <item>
      <title>SQLazy：Forward Fill NULL Values</title>
      <dc:creator>Judy</dc:creator>
      <pubDate>Fri, 31 Jul 2026 06:58:11 +0000</pubDate>
      <link>https://dev.to/esproc_spl/sqlazyforward-fill-null-values-1b0b</link>
      <guid>https://dev.to/esproc_spl/sqlazyforward-fill-null-values-1b0b</guid>
      <description>&lt;h2&gt;
  
  
  Problem Description
&lt;/h2&gt;

&lt;p&gt;A table records employee information with three fields: id (sort key), name, and dept (department). The dept column contains NULL values that need to be forward filled—each NULL should be replaced with the most recent non-NULL value in the same column.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Source Data&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Expected Result&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnc5ypiykuvdrhr33cr4h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnc5ypiykuvdrhr33cr4h.png" alt="Expected Result" width="800" height="304"&gt;&lt;/a&gt;&lt;br&gt;
For example, id=4–5 (dept=NULL): take the dept value Sales from id=3.&lt;/p&gt;
&lt;h2&gt;
  
  
  SQLazy Step-by-Step Implementation
&lt;/h2&gt;

&lt;p&gt;Core idea: First create a logical grouping marker (grp) that increments each time dept is NOT NULL, grouping consecutive NULL rows with their preceding non-NULL row into the same partition. Then use grp as the partition key, taking the max dept value within each partition to forward fill the NULLs. This two-step strategy—create grouping marker first, then aggregate by partition—is SQLazy’s classic pattern for forward-fill problems.&lt;/p&gt;

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

&lt;p&gt;[&lt;a href="https://www.sqlazy.com/?39E" rel="noopener noreferrer"&gt;Click to run this example online&lt;/a&gt;]&lt;/p&gt;

&lt;p&gt;The steps are explained below.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Step 1: Sort by id in ascending order&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
sort id asc&lt;/p&gt;

&lt;p&gt;Sort by id in ascending order to ensure records are processed in sequence; this is the prerequisite for subsequent grouping and filling.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Step 2: Create a logical grouping marker&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;compute if ((dept notnull) then 1 else 0) cum as grp&lt;/p&gt;

&lt;p&gt;This is the most critical step. Use the computed column with the cum (running total) argument to cumulatively sum the condition if ((dept notnull) then 1 else 0). When dept is NOT NULL, it contributes 1 (starting a new group); when NULL, it contributes 0 (continuing the current group). The cumulative result grp increments by 1 each time dept is non-NULL, dividing the data into partitions.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Step 3: Forward fill by partition&lt;/strong&gt;&lt;br&gt;
compute dept max as filled_dept; partition grp&lt;br&gt;
Within the grp partition, use the max aggregation to get the dept value. Each partition has only the first row’s dept as non-NULL, the rest are NULL. The MAX function automatically picks the non-NULL value, achieving forward fill.&lt;br&gt;
The partition grp ensures that fills in different partitions do not interfere with each other. This replaces NULL values with the preceding non-NULL value in id order.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Step 4: Use derive to select the output fields&lt;br&gt;
**&lt;br&gt;
**Generated SQL&lt;/strong&gt;&lt;br&gt;
After confirming the logic of the above 4 steps, the SQLazy compiler automatically generates native SQL (MySQL syntax used here):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WITH t2 AS (
        SELECT id, name, dept
            , SUM(CASE
                WHEN dept IS NOT NULL THEN 1
                ELSE 0
            END) OVER (ORDER BY CASE
                WHEN id IS NULL THEN 1
                ELSE 0
            END, id ASC ROWS UNBOUNDED PRECEDING) AS grp
        FROM forwardFill
    )
    t3 AS (
        SELECT id, name, dept, grp
            , MAX(dept) OVER (PARTITION BY grp) AS filled_dept
        FROM t2
    )
SELECT id, name, filled_dept AS dept
FROM t3
ORDER BY CASE
    WHEN id IS NULL THEN 1
    ELSE 0
END, id ASC
;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SQLazy lets you describe logic in business language instead of writing nested SQL queries. Step-by-step calculation breaks forward fill into independent steps, each verifiable independently. The cum (conditional running total) automatically generates group numbers—this is cleaner than SQL’s window function approach for forward-fill problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Official Links
&lt;/h2&gt;

&lt;p&gt;SQLazy Online Experience: &lt;a href="https://sqlazy.com/" rel="noopener noreferrer"&gt;sqlazy.com&lt;/a&gt; (free, no registration required)&lt;br&gt;
SQLazy Repository: &lt;a href="https://sqlazy.com/" rel="noopener noreferrer"&gt;github.com/SPLWare/SQLazy&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Code-as-Documentation: An SQL Development Paradigm for the AI Era</title>
      <dc:creator>Judy</dc:creator>
      <pubDate>Wed, 29 Jul 2026 06:11:47 +0000</pubDate>
      <link>https://dev.to/esproc_spl/code-as-documentation-an-sql-development-paradigm-for-the-ai-era-ff1</link>
      <guid>https://dev.to/esproc_spl/code-as-documentation-an-sql-development-paradigm-for-the-ai-era-ff1</guid>
      <description>&lt;h2&gt;
  
  
  A “runnable but unmaintainable” SQL query
&lt;/h2&gt;

&lt;p&gt;Let’s first look at the following SQL used in a production environment. The query aims to “merge all overlapping time intervals within each account”:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT account_id, MIN(start_date) AS start_date, MAX(end_date) AS end_date
FROM (
  SELECT account_id, start_date, end_date, prev_max,
    SUM(CASE WHEN start_date &amp;gt; prev_max OR prev_max IS NULL THEN 1 ELSE 0 END) 
      OVER (PARTITION BY account_id ORDER BY start_date) AS gid
  FROM (
    SELECT account_id, start_date, end_date,
      MAX(end_date) OVER (
        PARTITION BY account_id 
        ORDER BY CASE WHEN account_id IS NOT NULL THEN 1 END, start_date
        ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
      ) AS prev_max
    FROM acc
  ) t1
) t2
GROUP BY account_id, gid
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This SQL snippet uses window functions MAX()OVER and SUM() OVER for their cumulative-sum technique, along with ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING – syntax most developers would probably need to look up in the documentation. The syntax is completely correct, and the query runs and produces the expected output.&lt;/p&gt;

&lt;p&gt;Six months later, the requirement changes – the merging condition shifts from “overlapping alone” to “intervals of no more than 3 days”. Modifying the code to fit this new rule is extremely difficult, basically amounting to a rewrite. You’d need to work out the logic from scratch, mentally re-running the window functions’ computations, just to figure out what to change and how. The code runs, but the cost of understanding it is about the same as rewriting it from scratch.&lt;/p&gt;

&lt;p&gt;This is the reality of SQL development: code is written for machines to execute, not for humans to read.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why self-documentation is particularly hard for SQL
&lt;/h2&gt;

&lt;p&gt;SQL is a declarative language: it tells the database what to do, not how to do. This creates a problem –** business logic is encoded into the syntactic structure instead of being expressed explicitly.&lt;br&gt;
**&lt;br&gt;
A window function like LAG(amount, 1) OVER (PARTITION BY customer_id ORDER BY month) may correspond to the business concept of “calculating last month’s transaction amount”.&lt;/p&gt;

&lt;p&gt;A cumulative-sum snippet like SUM(CASE WHEN ... THEN 1 ELSE 0 END) OVER (...) may correspond to the logic of “conditional segmentation”.&lt;/p&gt;

&lt;p&gt;But these business semantics are implicit in SQL – readers need to infer for themselves what each snippet is actually doing.&lt;/p&gt;

&lt;p&gt;Even worse, comments cannot solve this problem. Code changes, but comments don’t always follow. Over time, comments become less reliable than the code itself. And SQL’s nested structure is inherently resistant to comments. Wedged between multiple layers of parentheses, they only make readability worse.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI-assisted coding doesn’t solve this problem either.&lt;/strong&gt; You generate a SQL snippet with AI, execute and commit it – but you don’t put the prompts in the repository. All that’s left is the final SQL. When the requirement changes, you are back to the same SQL, and have to re-decode its logic from scratch. Even if the original spec written for AI is kept, there’s no guarantee the regeneration produces the same SQL – LLMs aren’t deterministic. Update the spec and regenerate, and you might get a completely different query, which means a re-audit is involved. That’s not a modification, that amounts to a rewrite.&lt;/p&gt;

&lt;p&gt;Therefore, AI-assisted coding doesn’t solve the root problem: documentation and code falling out of sync.&lt;/p&gt;

&lt;p&gt;Unless, spec and SQL are fused into one – permanently paired, so a spec change automatically updates the SQL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“Code as documentation” is an ideal. SQL makes it further out of reach.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One misconception needs clearing up here: “code as documentation” doesn’t mean “writing lots of comments in the code”. Comments are a supplement. True “code as documentation” means three things:&lt;/p&gt;

&lt;p&gt;The code structure itself expresses the logic. Reading the code is as clear as reading documentation – no extra explanation needed.&lt;/p&gt;

&lt;p&gt;Code and documentation are the same thing. There’s no such problem as “the code changed but the documentation didn’t”.&lt;/p&gt;

&lt;p&gt;When new hires take over, reading the code is enough to understand the business logic – no extra training and verbal handoffs from the last person needed.&lt;/p&gt;

&lt;p&gt;In the SQL world, this means SQL itself needs to be self-descriptive. Every line of code should tell the reader clearly “what this step is doing”, not “what this syntax does”. But native SQL can’t do this. Multi-layer nesting is everywhere, and readability suffers badly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So, the solution is this: make the spec both readable and executable. Spec is documentation; spec is code.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  SQLazy’s solution: workflow as documentation
&lt;/h2&gt;

&lt;p&gt;Here’s what SQLazy does: skip writing SQL directly. Instead, describe the logic as a workflow of steps. Each step is an atomic action. The steps combined are the complete business logic.&lt;/p&gt;

&lt;p&gt;Take the example of “merging overlapping intervals” again. Here’s how SQLazy’s workflow expresses it:&lt;/p&gt;

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

&lt;p&gt;These lines of workflow read almost like natural language: sort, calculate the maximum end date so far, segment rows by condition, aggregate to get the earliest and latest dates. You can guess what most actions mean without ever having to learn them. Only “segment” needs a bit of explanation – it means “group the data by condition”. But paired with the visual step-by-step execution, one glance at the intermediate result makes it click instantly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fckio3fxfjpe7vg1uswhi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fckio3fxfjpe7vg1uswhi.png" alt="intermediate result" width="799" height="428"&gt;&lt;/a&gt;&lt;br&gt;
Run this example online: &lt;a href="https://www.sqlazy.com/?4Bs" rel="noopener noreferrer"&gt;https://www.sqlazy.com/?4Bs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This compilable workflow is itself the documentation:&lt;/p&gt;

&lt;p&gt;Every step has clear business meaning: “sort” sorts, “compute” calculates helper values, “segment” groups the data by condition, and “summarize” aggregates.&lt;/p&gt;

&lt;p&gt;The order of steps is the order of thinking. First sort, then compute, then segment, then aggregate – exactly how you’d work through this problem manually.&lt;/p&gt;

&lt;p&gt;No extra comments are needed to explain “what this SQL is doing”. Every line of the workflow already says it clearly.&lt;/p&gt;

&lt;p&gt;Six months later, when the requirement changes, just open the workflow and edit the corresponding step directly.&lt;/p&gt;

&lt;p&gt;Code and documentation are one thing, not two. This is “code-as-documentation” actually made real.&lt;/p&gt;

&lt;p&gt;Here’s another example: group by account, and reset the sequence number whenever the interval between activities exceeds one hour. Here’s how SQLazy’s workflow expresses it:&lt;/p&gt;

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

&lt;p&gt;Three lines of workflow, business logic clear at a glance: “reset the sequence number whenever the interval exceeds one hour”. The equivalent hand-written SQL needs at least two layers of nested window functions, plus LAG-based time-gap calculations. With the workflow, you don’t need to take care of how the SQL is written – the compiler handles that for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The compiler guarantees: documentation and code will never get out of sync
&lt;/h2&gt;

&lt;p&gt;Under the traditional model, documentation and code are separate, kept in sync only by hand – and manual sync is error-prone. Documentation gets written, then the code changes without the docs catching up; or the code changes but the documentation isn’t updated.&lt;/p&gt;

&lt;p&gt;SQLazy’s model is this: workflow (documentation) → compiler → SQL (code).&lt;/p&gt;

&lt;p&gt;Compile the workflow above into SQL (switchable across different databases):&lt;/p&gt;

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

&lt;p&gt;Documentation is the source. Code is the compiled output. As long as the workflow stays the same, the generated SQL stays the same; when the workflow changes, the SQL regenerates automatically. There’s no more “documentation changed but code didn’t” or “code changed but documentation didn’t” – because they’re two sides of the same thing.&lt;/p&gt;

&lt;p&gt;When you switch databases, just change the target option – from MySQL to Oracle or from PostgreSQL to Snowflake – and the compiler automatically generates SQL in the matching dialect. No need to modify the workflow. No need to rewrite the documentation.&lt;/p&gt;

&lt;p&gt;In the SQL world, “Code-as-documentation” has always been a hard-to-reach ideal. Without a workflow mechanism like SQLazy’s, code and documentation stay two separate things. You write the SQL, then have to write separate documentation explaining what it does. Syncing the two falls entirely on manual work – and manual work means errors.&lt;/p&gt;

&lt;p&gt;SQLazy changes that. It breaks complex SQL into clear steps, which themselves are the documentation, and the compiler guarantees code and documentation stay in sync, permanently.&lt;/p&gt;

&lt;p&gt;This isn’t wrapping a shell around SQL. It’s changing the development paradigm from “writing code for machines to read” to “writing logic for humans to read and letting the machine translate it”. The workflow is for humans. The SQL is for the database to run. The compiler keeps the two in sync – no manual work required.&lt;/p&gt;

&lt;p&gt;Next time you open a SQL file written three months ago and can’t make sense of it, ask yourself: if you’d written it as a workflow instead, would you still be in this much pain?&lt;/p&gt;

</description>
      <category>code</category>
      <category>sql</category>
      <category>development</category>
      <category>sqlazy</category>
    </item>
    <item>
      <title>SQLazy：Conditional Running Total with Reset</title>
      <dc:creator>Judy</dc:creator>
      <pubDate>Mon, 27 Jul 2026 07:32:56 +0000</pubDate>
      <link>https://dev.to/esproc_spl/sqlazyconditional-running-total-with-reset-47oo</link>
      <guid>https://dev.to/esproc_spl/sqlazyconditional-running-total-with-reset-47oo</guid>
      <description>&lt;h2&gt;
  
  
  Problem Description
&lt;/h2&gt;

&lt;p&gt;Conditional running total with reset: restart accumulation when logic is 't'. An event table table_t1 records event sequences with three fields: id (sort key), logic (condition flag, values 't' or 'f'), and val (numeric value for accumulation). A computed column output needs to be added: when logic equals 't', output is set to 1; otherwise output equals the previous row's output plus the current row's val. This is a conditional running total that resets and restarts accumulation when logic is 't'.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Source Data&lt;/strong&gt;&lt;/p&gt;

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

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

&lt;p&gt;id=1 (logic=t): Start of a new segment, output = 1&lt;/p&gt;

&lt;p&gt;id=2 to id=4 (logic=f): Accumulating row by row within the same segment, output = previous output + current val, yielding 3, 6, 10&lt;/p&gt;

&lt;p&gt;id=5 (logic=t): Reset condition met, start a new segment, output = 1&lt;/p&gt;
&lt;h2&gt;
  
  
  SQLazy Step-by-Step Implementation
&lt;/h2&gt;

&lt;p&gt;Core idea: First create a grouping marker, then accumulate by partition. Generate a logical grouping marker (logic_run) via a computed column; this marker increments each time logic is 't', dividing the data into independent segments. Then use logic_run as the partition key, applying the cum function for conditional accumulation within each partition. This two-step strategy—create grouping marker first, then accumulate by partition—is SQLazy's classic pattern for conditional reset accumulation problems.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://www.sqlazy.com/?2Kw" rel="noopener noreferrer"&gt;[Click to run this example online]&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The steps are explained below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Sort by id in ascending order&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;sort id asc&lt;/p&gt;

&lt;p&gt;Sort the data by id in ascending order to ensure records are processed in chronological order; this is the prerequisite for subsequent segmentation and accumulation.&lt;/p&gt;

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

&lt;p&gt;compute if (logic = 't' then 1 else 0) cum as logic_run&lt;/p&gt;

&lt;p&gt;This is the most critical step. Use the computed column with the cum (running total) argument to cumulatively sum the conditional expression if (logic=‘t’ then 1 else 0). When logic is ‘t’, it contributes 1 (indicating the start of a new segment); when logic is ‘f’, it contributes 0 (continuing the current segment). The cumulative result logic_run increments by 1 each time logic is ‘t’, dividing the data into segments: the first 4 records (id=1-4) have logic_run=1, the last 3 records (id=5-7) have logic_run=2.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcamecogef9249vk8k97l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcamecogef9249vk8k97l.png" alt=" " width="799" height="317"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Step 3: Calculate running total by partition&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;compute if (logic = 't' then 1 else val) cum as op partition logic_run&lt;/p&gt;

&lt;p&gt;Within the logic_run partition, use the cum (running total) function to cumulatively sum the conditional expression if (logic=‘t’ then 1 else val). Each partition is calculated independently: when logic is ‘t’, the cumulative value resets to 1; when logic is ‘f’, the cumulative value is the previous row’s accumulation plus the current row’s val. The partition logic_run ensures that accumulations in different partitions do not interfere with each other. This achieves the conditional running total with reset.&lt;/p&gt;

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

&lt;p&gt;Unnecessary fields can be removed later using the derive function.&lt;/p&gt;

&lt;p&gt;Generated SQL&lt;br&gt;
After confirming the logic of the above 3 steps, the SQLazy compiler automatically generates native SQL (MySQL syntax used here):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WITH t3 AS (
        SELECT id, logic, val, SUM(CASE 
                WHEN logic = 't' THEN 1
                ELSE 0
            END) OVER (ORDER BY CASE 
                WHEN id IS NULL THEN 1
                ELSE 0
            END, id ASC ROWS UNBOUNDED PRECEDING) AS logic_run
        FROM t2
    )
SELECT id, logic, val, logic_run
    , SUM(CASE 
        WHEN logic = 't' THEN 1
        ELSE val
    END) OVER (PARTITION BY logic_run ORDER BY CASE 
        WHEN id IS NULL THEN 1
        ELSE 0
    END, id ASC ROWS UNBOUNDED PRECEDING) AS oput
FROM t3
ORDER BY CASE 
    WHEN id IS NULL THEN 1
    ELSE 0
END, id ASC
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SQLazy lets you describe logic in business language instead of writing nested SQL queries. This example demonstrates two major features of SQLazy: first, step-by-step calculation that breaks conditional reset accumulation into three independent steps—sort, create grouping marker, accumulate by partition—each verifiable independently; second, the partition + cum syntax for partitioned running totals, which is more concise and intuitive than SQL’s window function syntax, directly expressing the business semantics of “reset on ‘t’, otherwise accumulate”.&lt;/p&gt;

&lt;p&gt;Official Links&lt;br&gt;
SQLazy Online Experience: &lt;a href="https://sqlazy.com/" rel="noopener noreferrer"&gt;sqlazy.com&lt;/a&gt; (free, no registration required)&lt;br&gt;
SQLazy Repository: &lt;a href="https://github.com/SPLWare/SQLazy" rel="noopener noreferrer"&gt;github.com/SPLWare/SQLazy&lt;/a&gt;&lt;/p&gt;

</description>
      <category>sql</category>
      <category>dataengineering</category>
      <category>llm</category>
      <category>sqlazy</category>
    </item>
    <item>
      <title>Compilable and Executable Pseudocode (spec) Solves AI Coding Hallucinations</title>
      <dc:creator>Judy</dc:creator>
      <pubDate>Fri, 24 Jul 2026 03:56:21 +0000</pubDate>
      <link>https://dev.to/esproc_spl/compilable-and-executable-pseudocode-spec-solves-ai-coding-hallucinations-160f</link>
      <guid>https://dev.to/esproc_spl/compilable-and-executable-pseudocode-spec-solves-ai-coding-hallucinations-160f</guid>
      <description>&lt;p&gt;The AI-generated SQL – no one dares to say they fully “understand” it&lt;br&gt;
First, look at the following SQL query, which aims to find credit card customers whose transaction amount has increased for three consecutive months:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WITH ranked AS (
  SELECT customer_id, month, amount,
    LAG(amount,1) OVER(PARTITION BY customer_id ORDER BY month) as prev_1,
    LAG(amount,2) OVER(PARTITION BY customer_id ORDER BY month) as prev_2
  FROM transactions
)
SELECT customer_id, COUNT(*) FROM ranked
WHERE amount &amp;gt; prev_1 AND prev_1 &amp;gt; prev_2
AND prev_1 IS NOT NULL AND prev_2 IS NOT NULL
GROUP BY customer_id HAVING COUNT(*) &amp;gt;= 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The syntax is correct, and it runs. But is the logic sound? No one can say for sure right away.&lt;/p&gt;

&lt;p&gt;This is the daily routine of data analysts and developers today: they give a task to an AI, get dozens of lines of SQL in seconds, copy and paste the code, and it runs. But can they really trust the results? Modern AI can generate runnable SQL, but it is often not fully reliable.&lt;/p&gt;

&lt;p&gt;AI-generated SQL is a black box. The code may look correct, run without errors, and return a clean result, but what it actually does may have nothing to do with what you intended. Worse, you only realize it’s wrong after it has already executed. During the review phase, you have to mentally execute the SQL yourself, effectively redoing the work that the AI was supposed to handle.&lt;/p&gt;

&lt;p&gt;Even worse, even if the accuracy of AI-generated SQL reaches 90%, how can you know that this time they aren’t falling into the remaining 10%? Data-driven decisions should not become a game of chance. You get a SQL query that executes successfully and returns a clean result, but what it actually does is not what the user intended. This type of error cannot be caught by syntax checking. And the verification cost during the code review phase is extremely high: to confirm whether the logic is correct, you have to break down CTEs layer by layer, run them step by step, and compare the intermediate results.&lt;/p&gt;

&lt;p&gt;What is the real problem?&lt;br&gt;
In AI-assisted programming, there is a basic workflow: the user writes a clear specification and AI generates runnable code.&lt;/p&gt;

&lt;p&gt;This is not a question of whether AI is “capable” or not, but a hard constraint under current technical conditions. If the user only sketches out a couple of lines of specification, no AI can generate runnable engineering-grade code out of thin air – unless the task is a common, well-defined one such as bubble sort. The actual AI-assisted programming workflow is: the user writes a spec → the AI generates code → the user reviews it → spec the refined → the AI regenerates the code. The spec is both the starting point and a constraint throughout the entire process.&lt;/p&gt;

&lt;p&gt;But even with a perfectly clear specification, AI can still make mistakes – it’s a matter of probability. A poorly defined specification increases the chance that the AI will guess wrong, while a well-defined specification reduces that probability but can never eliminate it. This is because, at its core, AI is making probabilistic predictions. From input to output, it searches the space of possible answers for the most likely one, rather than logically deriving the only correct result.&lt;/p&gt;

&lt;p&gt;Then what can we do? Is there any way to turn a “likely answer” to a “definitive one”?&lt;/p&gt;

&lt;p&gt;The answer is: make the spec compliant enough to compile. Once the spec can be parsed and executed by a compiler, AI no longer needs to “guess” the code. The compiler is deterministic – the same input always produces the same output. No more “works this time, breaks the next”. This isn’t about “reducing the hallucination rate”; it’s about bypassing the stage where hallucinations can occur.&lt;/p&gt;

&lt;p&gt;So, the overall logic chain is clear: AI-assisted programming requires a spec → a well-defined spec can reduce hallucination rate → but it cannot eliminate hallucinations → make the spec compliant enough to compile → use a compiler instead of AI to generate code →hallucinations disappear.&lt;/p&gt;

&lt;p&gt;Compilable pseudocode: an advanced form of the specification&lt;br&gt;
Traditionally, the requirements document that programmers create is intended for humans. It contains natural language, charts and diagrams, and pseudocode. In the era of AI, the spec can evolve into a new form with two characteristics:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Human-readable: No need to be familiar with the target language (SQL, Java, Python), you can grasp the logic at a glance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Machine-compilable: The specification has a fixed syntactic structure and can be deterministically transformed into final code, without relying on AI guessing.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is “compilable pseudocode”, a more compliant spec. The difference between it and traditional pseudocode is this: traditional pseudocode won’t be compiled – it is intended only for humans to read; compilable pseudocode is both human-readable and machine-readable, so it can be compiled into target code.&lt;/p&gt;

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

&lt;p&gt;The value of this kind of spec is that it squeezes AI’s “creative space” down to a minimum. AI doesn't need to “understand” the business logic, “design” an implementation plan, and “guess” the correct code – it only needs to translate the uncompliant spec into a compliant one. Translation leaves a far narrower margin for error than “generating code directly from natural language”.&lt;/p&gt;

&lt;p&gt;More importantly, you can verify the correctness of the spec before it is compiled and executed. Each step of the logic can be verified independently, so errors are caught before execution rather than being debugged afterward.&lt;/p&gt;

&lt;p&gt;SQLazy’s workflow: The compilable pseudocode&lt;br&gt;
SQLazy brings this concept to the field of complex SQL development. Its workflow is essentially compilable pseudocode: developers describe data querying logic step by step in natural language, previewing the intermediate result at each step. Once the logic is confirmed correct, the compiler deterministically generates the SQL.&lt;/p&gt;

&lt;p&gt;Still on the same requirement as above: finding the credit card customers whose transaction amount has increased for three consecutive months. Let’s implement this using SQLazy’s workflow:&lt;/p&gt;

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

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

&lt;p&gt;You don’t need to know SQL – the four steps alone make the logic clear. At every step you can preview the intermediate result:&lt;/p&gt;

&lt;p&gt;sort: Sort rows by customer and month.&lt;/p&gt;

&lt;p&gt;segment: Mark records where the transaction amounts fall, and generate a number for each consecutively rising segment.&lt;/p&gt;

&lt;p&gt;summarize: Count the number of months covered by each segment.&lt;/p&gt;

&lt;p&gt;filter: Select customers whose transaction amounts increase for at least consecutive 3 months (&amp;gt;=3).&lt;/p&gt;

&lt;p&gt;Here is another real-world example. A table stores time intervals for multiple accounts. Each account corresponds to multiple records, and the intervals may overlap. The goal is to merge all overlapping intervals within each account. With SQLazy, this can be implemented in just four steps:&lt;/p&gt;

&lt;p&gt;sort account_id, start_date: Sort rows by account and start date.&lt;/p&gt;

&lt;p&gt;compute end_date[:-1] max as prev_max: Calculate the maximum end date among all rows prior to the current row.&lt;/p&gt;

&lt;p&gt;segment condition start_date &amp;gt; prev_max as gid: Segment rows based on the condition, and assign a group number to each segment.&lt;/p&gt;

&lt;p&gt;summarize start_date min as start_date, end_date max as end_date: Aggregate by account and group number.&lt;/p&gt;

&lt;p&gt;Once the above logic is confirmed correct, the SQLazy compiler automatically generates native SQL – 100% accurate, with zero hallucination risk. If you switch the target database from MySQL to Oracle, you only need to change one option and the compiler automatically adapts to the target SQL dialect.&lt;/p&gt;

&lt;p&gt;SQLazy’s workflow is the spec, and the compiler is the last gate guaranteeing the code quality.&lt;/p&gt;

&lt;p&gt;In this process, the role of AI is also redefined. Conventionally, AI is responsible for generating the final SQL directly from the natural language – an extremely difficult task with a high hallucination rate. With SQLazy, AI is charged with only one thing: translating users’ colloquial, step-by-step descriptions into formal workflow syntax. The former is “decision-making” while the latter is “translation”. Even if the AI’s translation is slightly off, you can spot it at a glance at the workflow level, and the cost of correction is nearly zero.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsygvcmqw8z67gwjbr4i1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsygvcmqw8z67gwjbr4i1.png" alt="spec" width="800" height="428"&gt;&lt;/a&gt;&lt;br&gt;
Use LLM to convert the natural language into SQLazy syntax&lt;/p&gt;

&lt;p&gt;AI hallucinations in programming are a problem not because AI occasionally makes mistakes, but because of something more fundamental: under the conventional model, AI’s mistakes can only be discovered after execution. By the time you find the result is wrong, the code has already been generated – even executed – making the cost of correction extremely high.&lt;/p&gt;

&lt;p&gt;SQLazy’s compilable pseudocode shifts the moment of discovering errors from after execution to before execution. Humans confirm the logic at the spec level, and the compiler generates the final code. AI’s room for error is squeezed down to the “translation” step alone, where the result can be verified instantly by humans – solving the hallucination problem in AI programming.&lt;/p&gt;

&lt;p&gt;This does not mean programmers are going back to the era of “writing documentation”. Rather, it redivides the labor between humans and AI: humans handle design (describing “what to do” using structured spec); AI handles translation (converting colloquial spec into compliant spec); and the compiler handles execution (guaranteeing a deterministic output).&lt;/p&gt;

&lt;p&gt;The next phase of AI programming is probably not about making AI generate longer code, but about enabling humans to write clearer spec.&lt;/p&gt;

</description>
      <category>code</category>
      <category>sql</category>
      <category>devops</category>
      <category>software</category>
    </item>
    <item>
      <title>SQLazy: Querying the Start Timestamp of the Next Group from the Event Table</title>
      <dc:creator>Judy</dc:creator>
      <pubDate>Wed, 22 Jul 2026 07:24:00 +0000</pubDate>
      <link>https://dev.to/esproc_spl/sqlazy-querying-the-start-timestamp-of-the-next-group-from-the-event-table-4ja4</link>
      <guid>https://dev.to/esproc_spl/sqlazy-querying-the-start-timestamp-of-the-next-group-from-the-event-table-4ja4</guid>
      <description>&lt;p&gt;Problem description&lt;br&gt;
A table stores status values of an object at different timestamps, with one record per timestamp. Segment rows based on changes in the status value and output the effective start timestamp and the effective end timestamp for each status segment – that is, merge consecutive records with the same status into a single time interval.&lt;/p&gt;

&lt;p&gt;Source data:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvzybe3lhpudzmr4cz2g5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvzybe3lhpudzmr4cz2g5.png" alt="Source data" width="800" height="415"&gt;&lt;/a&gt;&lt;br&gt;
Expected output (intervals where each value remains constant):&lt;/p&gt;

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

&lt;p&gt;Take object 1 as an example:&lt;/p&gt;

&lt;p&gt;The first three records contain the same value A, so they can be merged into a single interval: 2024-01-01 – 2024-01-04 (the timestamp when the next record begins).&lt;/p&gt;

&lt;p&gt;Value B remains for record 4 and record 5 and they can be merged into a single interval: 2024-01-04 – 2024-01-06 (the timestamp when the next record begins).&lt;/p&gt;

&lt;p&gt;Record 6 and record 7 have value A again, the same as before. However, since value B intervenes between them, a new group starts: 2024-01-06 – infinity (the end date of the last interval is 9999-12-31).&lt;/p&gt;

&lt;p&gt;Step-by-step implementation with SQLazy&lt;br&gt;
Key approach: sort rows by timestamp, and check whether the value in the current row has changed relative to the previous row. If it has changed, start a new group; otherwise, put it in the current group. Finally, take the minimum timestamp in each group as the start timestamp and the minimum timestamp in the next group as its end timestamp.&lt;/p&gt;

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

&lt;p&gt;[Run this example online]&lt;/p&gt;

&lt;p&gt;Let me walk through each step below:&lt;/p&gt;

&lt;p&gt;Step 1: Sort rows in chronological order in ascending order&lt;/p&gt;

&lt;p&gt;sort timestamp&lt;/p&gt;

&lt;p&gt;Sort rows by timestamp in ascending order, ensuring intervals in each group are processed in chronological order.&lt;/p&gt;

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

&lt;p&gt;Step 2: Start a new partition when value changes&lt;/p&gt;

&lt;p&gt;segment value; change; as gid&lt;/p&gt;

&lt;p&gt;This is the core step. The “segment” statement segment rows by the “value” field: each time the value changes, a new group is started and assigned a group number (gid). Within each group, the value remains unchanged.&lt;/p&gt;

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

&lt;p&gt;Step 3: Aggregate by group number and value, and take the start timestamp&lt;/p&gt;

&lt;p&gt;summarize timestamp first as effective_from, id first as id; group gid, value&lt;/p&gt;

&lt;p&gt;Group rows by gid and value:&lt;/p&gt;

&lt;p&gt;Take the timestamp of the first record in each group and use it as the effective start timestamp for that status.&lt;/p&gt;

&lt;p&gt;id: Take the first value in each group.&lt;/p&gt;

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

&lt;p&gt;Step 4: Calculate the effective end timestamp&lt;/p&gt;

&lt;p&gt;&lt;em&gt;compute nvl(effective_from[1], datetime("9999-12-31 00:00:00")) as effective_to&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;effective_from[1] takes the effective_from value of the next row after the current group as the end timestamp for the current group. If the current group is the last group (i.e., effective_from[1] is empty), use nvl to set it to a maximum date (9999-12-31), which represents “to present”.&lt;/p&gt;

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

&lt;p&gt;Finally, remove the helper column gid and output the final result.&lt;/p&gt;

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

&lt;p&gt;Compile the steps into SQL&lt;/p&gt;

&lt;p&gt;Once the above steps are complete and verified, SQLazy’s compiler can automatically generate the equivalent native SQL (The Oracle syntax for this example):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WITH Value AS (
    SELECT
        id,
        value,
        timestamp
    FROM
        events
),
Value2 AS (
    SELECT
        gid,
        id AS id,
        value AS value,
        timestamp AS effective_from
    FROM
        (
            SELECT
                id,
                value,
                timestamp,
                SUM(
                    CASE
                        WHEN value &amp;lt;&amp;gt; col__5 THEN 1
                        ELSE 0
                    END
                ) OVER (
                    ORDER BY
                        CASE
                            WHEN timestamp IS NULL THEN 1
                            ELSE 0
                        END,
                        timestamp ASC
                ) + 1 AS gid
            FROM
                (
                    SELECT
                        Value.*,
                        LAG(value) OVER (
                            ORDER BY
                                CASE
                                    WHEN timestamp IS NULL THEN 1
                                    ELSE 0
                                END,
                                timestamp ASC
                        ) AS col__5
                    FROM
                        Value
                ) sub__6
        ) Value1
    GROUP BY
        gid
)
SELECT
    gid,
    id,
    value,
    effective_from,
    LEAD(
        effective_from,
        1,
        TO_DATE('9999-12-31 00:00:00', 'YYYY-MM-DD HH24:MI:SS')
    ) OVER (
        ORDER BY
            gid
    ) AS effective_to
FROM
    Value2
ORDER BY
gid;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You only need to verify the logic of each of the above four steps – no need to manually debug the SQL – and the compiler generates the production-ready code.&lt;/p&gt;

&lt;p&gt;SQLazy lets you describe logic using business language, rather than writing nested queries in SQL syntax. This example of “dividing time intervals based on value change” has one core statement: segment value; change, which directly corresponds to the business requirement of “starting a new group whenever the value changes”. To write SQL manually, you need to understand the window boundaries of LAG/LEAD, manually handle NULLs, and adjust for syntax differences across different databases. SQLazy, however, compresses this complex logic into 4 intuitive steps – sort, segment, aggregate, and take the the next row’s timestamp as the end timestamp. The intermediate result of each step can be verified independently, and the compiler generates the final runnable SQL. You just need to verify the business meaning of each step, and leave the rest to the compiler.&lt;/p&gt;

&lt;p&gt;Try SQLazy online: sqlazy.com (Free to use, signup not required)&lt;br&gt;
SQLazy project repository: github.com/SPLWare/SQLazy&lt;/p&gt;

</description>
      <category>devops</category>
      <category>sql</category>
      <category>programming</category>
      <category>sqlazy</category>
    </item>
    <item>
      <title>SQLazy: Merging Overlapping Time Intervals</title>
      <dc:creator>Judy</dc:creator>
      <pubDate>Thu, 16 Jul 2026 02:23:06 +0000</pubDate>
      <link>https://dev.to/esproc_spl/sqlazy-merging-overlapping-time-intervals-4fd0</link>
      <guid>https://dev.to/esproc_spl/sqlazy-merging-overlapping-time-intervals-4fd0</guid>
      <description>&lt;h2&gt;
  
  
  Problem description
&lt;/h2&gt;

&lt;p&gt;A table stores multiple time intervals for each account, and the intervals may overlap.&lt;/p&gt;

&lt;p&gt;Source data:&lt;/p&gt;

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

&lt;p&gt;Expected output (merge overlapping intervals for each account):&lt;/p&gt;

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

&lt;p&gt;The first three intervals (6/20-6/29, 6/25-7/25, 7/20-8/26) overlap and can be merged into 6/20 - 8/26;&lt;/p&gt;

&lt;p&gt;The nonoverlapping interval 12/25 - 1/25;&lt;/p&gt;

&lt;p&gt;The overlapping intervals (4/27-7/27, 6/25-7/14, 7/10-8/14) can be merged into 4/27-8/14;&lt;/p&gt;

&lt;p&gt;The nonoverlapping interval 9/10-11/12.&lt;/p&gt;

&lt;p&gt;Apply the same merging operations to account B: merge any intervals that overlap.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step-by-step implementation with SQLazy
&lt;/h2&gt;

&lt;p&gt;Key approach: Check whether the current interval’s start date is later than the maximum end date among all previous intervals. If it is, the current interval does not overlap with any of the previous intervals and a new group should be started; otherwise, merge it into the current group.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://www.sqlazy.com/?4Bs" rel="noopener noreferrer"&gt;[Run this example online]&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Step 1: Sort rows by account and the start date&lt;/p&gt;

&lt;p&gt;sort account_id, start_date&lt;/p&gt;

&lt;p&gt;Sort rows by account_id and start_date in ascending order, ensuring intervals within each account are processed in chronological order.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fge0yyqk6f7l9x297gtxu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fge0yyqk6f7l9x297gtxu.png" alt="Sort rows by account and the start date" width="800" height="438"&gt;&lt;/a&gt;&lt;br&gt;
Step 2: Calculate the maximum end date among all rows before the current row&lt;/p&gt;

&lt;p&gt;compute end_date[:-1] max as prev_max; partition account_id&lt;/p&gt;

&lt;p&gt;Within each account partition, calculate the maximum end_date value among all rows from the first row up to, but excluding, the current row, and record it as prev_max.&lt;/p&gt;

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

&lt;p&gt;Step 3: Segment rows based on the condition and assign group numbers&lt;/p&gt;

&lt;p&gt;segment condition start_date &amp;gt; prev_max as gid; partition account_id&lt;/p&gt;

&lt;p&gt;Check each row in sequence: if start_date &amp;gt; prev_max, the current interval does not overlap with any of the previous intervals and a new group is started (gid+1); otherwise, the row is assigned to the current group.&lt;/p&gt;

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

&lt;p&gt;Step 4: Aggregate by account and group number&lt;/p&gt;

&lt;p&gt;summarize start_date min as start_date, end_date max as end_date; group account_id, gid&lt;/p&gt;

&lt;p&gt;For each group, extract the earliest start date and the latest end date to form a merged interval.&lt;/p&gt;

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

&lt;p&gt;Finally, remove the helper column gid.&lt;/p&gt;

&lt;p&gt;Compile the steps into SQL&lt;/p&gt;

&lt;p&gt;Once the above steps are complete and verified, SQLazy’s compiler can automatically generate the equivalent native SQL (using MySQL as an example):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WITH t2 AS (
    SELECT
        account_id,
        start_date,
        end_date,
        MAX(end_date) OVER (
            PARTITION BY account_id
            ORDER BY
                CASE WHEN account_id IS NULL THEN 1 ELSE 0 END,
                account_id ASC,
                CASE WHEN start_date IS NULL THEN 1 ELSE 0 END,
                start_date ASC
            ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
        ) AS prev_max
    FROM
        acc
),
t3 AS (
    SELECT
        account_id,
        start_date,
        end_date,
        prev_max,
        1 + SUM(
            CASE
                WHEN start_date &amp;gt; prev_max THEN 1
                ELSE 0
            END
        ) OVER (
            PARTITION BY account_id
            ORDER BY
                CASE WHEN account_id IS NULL THEN 1 ELSE 0 END,
                account_id ASC,
                CASE WHEN start_date IS NULL THEN 1 ELSE 0 END,
                start_date ASC
        ) AS gid
    FROM
        t2
)
SELECT
    account_id,
    MIN(start_date) AS start_date,
    MAX(end_date) AS end_date
FROM
    t3
GROUP BY
    account_id,
    gid
ORDER BY
    account_id,
    start_date;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You only need to verify the logic of each of the four steps – no need to understand or debug the SQL – and the compiler will generate the production-ready code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why SQLazy is more efficient
&lt;/h2&gt;

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

&lt;p&gt;SQLazy lets you describe logic using business language, rather than writing nested queries in SQL syntax. This example of “merge overlapping intervals” is expressed clearly in just 4 steps – sort, calculate the maximum end date among the preceding rows, segment rows by condition, and aggregate. The compiler generates the final SQL for you, and you only need to verify the business meaning of each step.&lt;/p&gt;

&lt;p&gt;Try SQLazy online: &lt;a href="https://sqlazy.com/" rel="noopener noreferrer"&gt;sqlazy.com&lt;/a&gt; (Free to use, signup not required)&lt;br&gt;
SQLazy project repository: &lt;a href="https://github.com/SPLWare/SQLazy" rel="noopener noreferrer"&gt;github.com/SPLWare/SQLazy&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>programming</category>
      <category>discuss</category>
      <category>sqlazy</category>
    </item>
  </channel>
</rss>
