<?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: Omer Hochman</title>
    <description>The latest articles on DEV Community by Omer Hochman (@omer_hochman).</description>
    <link>https://dev.to/omer_hochman</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%2F4020184%2F5259d759-5650-4fbd-b73e-93bf595a8914.jpg</url>
      <title>DEV Community: Omer Hochman</title>
      <link>https://dev.to/omer_hochman</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/omer_hochman"/>
    <language>en</language>
    <item>
      <title>Your LLM fused the two columns you asked for — and the eval marked it wrong</title>
      <dc:creator>Omer Hochman</dc:creator>
      <pubDate>Tue, 07 Jul 2026 20:43:35 +0000</pubDate>
      <link>https://dev.to/omer_hochman/your-llm-fused-the-two-columns-you-asked-for-and-the-eval-marked-it-wrong-5gge</link>
      <guid>https://dev.to/omer_hochman/your-llm-fused-the-two-columns-you-asked-for-and-the-eval-marked-it-wrong-5gge</guid>
      <description>&lt;p&gt;You ask a text-to-SQL model to "list the members' names". The benchmark's gold query returns &lt;code&gt;first_name, last_name&lt;/code&gt; — two columns. The model returns one: a helpfully assembled full name. Read side by side, the model's answer is arguably the better one. The scorer marks it wrong, every single time, and your engine number reads lower than the engine is.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- gold: two columns&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;first_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;last_name&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;...;&lt;/span&gt;

&lt;span class="c1"&gt;-- model: one column, same information&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;first_name&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;last_name&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;...;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why every execution-accuracy scorer does this
&lt;/h2&gt;

&lt;p&gt;Execution accuracy — the metric behind BIRD, Spider, and most private text-to-SQL evals — runs both queries and compares &lt;em&gt;positional value tuples&lt;/em&gt;: canonical BIRD literally compares &lt;code&gt;set(fetchall())&lt;/code&gt;. Column names are ignored on purpose (aliases shouldn't matter), which means the &lt;em&gt;shape&lt;/em&gt; of each row is all that's left. A one-column result can never equal a two-column gold, no matter how right the content is.&lt;/p&gt;

&lt;p&gt;This is the mirror image of the extra-columns failure everyone already knows about — where the model SELECTs a helpful extra field and the added column breaks tuple equality. Fusing two requested columns into one is exactly as fatal as adding one nobody asked for. The scorer has no notion of "close": the tuple matches or it doesn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  The loss is real, measurable, and lopsided
&lt;/h2&gt;

&lt;p&gt;We bucketed a full 500-question BIRD-dev run of our free-lane engine with a structural differ before touching the prompt. The concatenation signature was unambiguous:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;7 of 238 losses&lt;/strong&gt; concatenated columns where the gold query kept them separate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0 of 256 wins&lt;/strong&gt; used &lt;code&gt;||&lt;/code&gt; at all — the operator appeared only in losing answers.&lt;/li&gt;
&lt;li&gt;Gold itself used &lt;code&gt;||&lt;/code&gt; in &lt;strong&gt;1 of 500&lt;/strong&gt; questions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last line is the decision. When a construct shows up in 3% of your losses, none of your wins, and almost none of the gold answers, discouraging it is near-pure upside — there is essentially nothing on the other side of the trade to regress.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix is one sentence in the planner prompt
&lt;/h2&gt;

&lt;p&gt;No fine-tune, no reranker: one projection directive — &lt;em&gt;return each requested attribute as its own column unless the question explicitly asks for a single combined string&lt;/em&gt;. Before shipping it we de-concatenated the 7 flagged predictions by hand and re-executed them against the real SQLite databases to get a deterministic ceiling: &lt;strong&gt;+3 questions&lt;/strong&gt; flip wrong-to-right, zero regressions. The live re-measure confirmed the direction: run-wide &lt;code&gt;||&lt;/code&gt; concatenations dropped from 7 to 3, and 2 of the 3 ceiling questions flipped to matches.&lt;/p&gt;

&lt;p&gt;The general method matters more than this one directive: bucket your loss mass with a structural differ &lt;em&gt;first&lt;/em&gt;, compute the deterministic ceiling of a candidate fix &lt;em&gt;second&lt;/em&gt;, and only then edit the prompt. Prompt directives written from vibes overfit; directives written from a 7-losses/0-wins histogram are as close to a free lunch as engine work gets.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule
&lt;/h2&gt;

&lt;p&gt;The model's job is to match the shape of the answer, not to make it pretty. A helpful concatenation is still a wrong result set — and unless you bucket your losses, you'll never notice that some of your "wrong" answers were the model being more helpful than your gold.&lt;/p&gt;

&lt;p&gt;(This directive now ships in &lt;a href="https://nlqdb.com" rel="noopener noreferrer"&gt;nlqdb&lt;/a&gt;, the data layer you ask in English. The eval harness that found it — structural mismatch classifier, deterministic-ceiling re-scorer — runs against public BIRD/Spider plus our own ICP-shaped persona benchmark, so prompt changes land with a measured delta, not a hunch.)&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://nlqdb.com/blog/llm-concatenates-columns-text-to-sql" rel="noopener noreferrer"&gt;nlqdb.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>sql</category>
      <category>llm</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
