Large and Small SQL AI
Large and Small Local AI are Outstanding at SQL
This article will examine a brand-new open weight solution frontier model and a tiny 8B (6 GB) model. The tiny local model is better at SQL than any person I have ever known. The new frontier model is simply scary.
The Test
The test involves a very common real-world problem which is called the “Group-wise Maximum” problem or the “greatest-n-per-group” problem.
Then here is the question: Please write a SQL query to select the latest book which was added for each user. This problem was given to approximately 200 senior developers over 20 years of interviewing senior software developers. Around 10% of the interviewees correctly answered that question. At the end of those 20 years, I had been given 6 distinct, correct answers. Two years ago, I gave that problem to ChatGPT and it gave me 6 correct answers, but one of them was novel. Now I have 34 distinct solutions.
Minimax M3
Minimax M3 was just released, and it will be an open weight solution — something you can run on your desktop. Minimax M3 gave 25 correct solutions but 9 novel solutions. Reviewing all the solutions would be tedious, but here are the 9 new solutions to this problem. More than one use SQL Server features I have never used.
Novel Solutions
Get John Baima’s stories in your inbox
Join Medium for free to get updates from this writer.
None of the solutions below are the recommended solutions from Minimax. They simply demonstrate potential solutions. All of the AIs recommend the same “best” solution to this problem.
DECLARE @ReadingList TABLE
( UserId INT NOT NULL ,
BookId INT NOT NULL ,
DateAdded DATETIME NOT NULL );
INSERT INTO @ReadingList (UserId,BookId,DateAdded) VALUES (1,1,'2015-1-1');
INSERT INTO @ReadingList (UserId,BookId,DateAdded) VALUES (1,2,'2015-10-1');
INSERT INTO @ReadingList (UserId,BookId,DateAdded) VALUES (1,3,'2015-6-1');
INSERT INTO @ReadingList (UserId,BookId,DateAdded) VALUES (2,4,'2015-1-1');
-- `EXISTS` with Correlated Subquery
SELECT rl.UserId, rl.BookId, rl.DateAdded
FROM @ReadingList rl
WHERE EXISTS ( SELECT 1 FROM @ReadingList rl2 WHERE rl2.UserId = rl.UserId )
AND rl.DateAdded = (SELECT MAX(rl2.DateAdded) FROM @ReadingList rl2 WHERE rl2.UserId = rl.UserId);
-- CTE + `ROW_NUMBER` + `MAX(CASE…)` (Aggregation trick)
SELECT UserId,
MAX(CASE WHEN rn = 1 THEN BookId END) AS BookId,
MAX(CASE WHEN rn = 1 THEN DateAdded END) AS DateAdded
FROM (
SELECT UserId, BookId, DateAdded,
ROW_NUMBER() OVER (PARTITION BY UserId ORDER BY DateAdded DESC) AS rn
FROM @ReadingList
) t
GROUP BY UserId;
-- Scalar Subquery with `ORDER BY` (Top‑1 Subquery)
SELECT
UserId,
(SELECT TOP 1 BookId
FROM @ReadingList rl2
WHERE rl2.UserId = rl.UserId
ORDER BY DateAdded DESC) AS BookId,
MAX(DateAdded) AS DateAdded
FROM @ReadingList rl
GROUP BY UserId;
-- `APPLY` with a `VALUES` Constructor double cross apply
SELECT u.UserId, x.BookId, x.DateAdded
FROM (SELECT DISTINCT UserId FROM @ReadingList) u
CROSS APPLY (VALUES (NULL)) dummy(placeholder)
CROSS APPLY (
SELECT TOP 1 BookId, DateAdded
FROM @ReadingList
WHERE UserId = u.UserId
ORDER BY DateAdded DESC
) x;
-- Recursive CTE
WITH RecursiveCTE AS (
-- Anchor: each user's max date
SELECT UserId, MAX(DateAdded) AS DateAdded
FROM @ReadingList
GROUP BY UserId
),
Resolved AS (
SELECT r.UserId, r.DateAdded, rl.BookId
FROM RecursiveCTE r
CROSS APPLY (SELECT TOP 1 BookId FROM @ReadingList
WHERE UserId = r.UserId AND DateAdded = r.DateAdded) rl
)
SELECT UserId, BookId, DateAdded FROM Resolved;
--Delete‑and‑Keep Loop
WHILE 1 = 1
BEGIN
DELETE rl
FROM @ReadingList rl
INNER JOIN (
SELECT UserId, MAX(DateAdded) AS MaxDate
FROM @ReadingList
GROUP BY UserId
) m ON rl.UserId = m.UserId AND rl.DateAdded < m.MaxDate;
IF @@ROWCOUNT = 0 BREAK;
END
SELECT * FROM @ReadingList;
-- `MERGE` Statement
DECLARE @Latest TABLE(UserId INT PRIMARY KEY, BookId INT, DateAdded DATETIME);
MERGE INTO @Latest AS tgt
USING (
SELECT UserId, BookId, DateAdded,
ROW_NUMBER() OVER (PARTITION BY UserId ORDER BY DateAdded DESC) AS rn
FROM @ReadingList
) AS src
ON tgt.UserId = src.UserId
WHEN NOT MATCHED AND src.rn = 1 THEN
INSERT (UserId, BookId, DateAdded) VALUES (src.UserId, src.BookId, src.DateAdded);
SELECT * FROM @Latest;
-- `GROUPING SETS` (Rollup variant)
WITH Latest AS (
SELECT UserId, BookId, DateAdded,
ROW_NUMBER() OVER (PARTITION BY UserId ORDER BY DateAdded DESC) AS rn
FROM @ReadingList
)
SELECT UserId, BookId, DateAdded
FROM Latest
GROUP BY GROUPING SETS ((UserId, BookId, DateAdded, rn), ())
HAVING rn = 1;
-- `HASHBYTES`‑Based Hashing (for pre-grouping)
SELECT UserId, BookId, DateAdded
FROM @ReadingList rl
WHERE DateAdded = (
SELECT MAX(DateAdded)
FROM @ReadingList rl2
WHERE rl2.UserId = rl.UserId
AND HASHBYTES('MD5', CAST(rl2.UserId AS VARCHAR) + CAST(rl2.DateAdded AS VARCHAR))
= HASHBYTES('MD5', CAST(rl.UserId AS VARCHAR) + CAST(rl.DateAdded AS VARCHAR))
);Qwen3.5–9b (4bit quant)
The Qwen models are, in general, the best small open weight models. The flagship model is now Qwen 3.7, but for the smallest models, 9b parameters, the latest version is 3.5. Qwen 3.6 gave12 correct answers including 1 novel answer.
Qwen3.5–9b (4bit quant) which is about 6GB in size, gave 6 correct answers and did not give any incorrect answers. While that is not impressive compared to Minimax 3.0, a year ago it would have put it into the top tier of frontier models. Less than 2 years ago, other engineers at my company regularly and productively used ChatGPT which did no better.
The problem with the Qwen models is that they think a lot, much more than any other competitive model which makes them slower, and they require more KV Cache memory. This model, at times, can go off and think for minutes and then crash.
It can squeeze into 8GB of VRAM, but is much better with 12GB.
It is possible to turn thinking off for this model and it will run much faster and take less memory, but the possibility of it making a mistake is much higher. This model with thinking turned off will still find the best solution.
Granite-4.1–8b (4bit quant)
I’ve tested a large number of local models, and one of the great surprises is the IBM Granite-4.1–8b Q4_K_M. In the general programming benchmarks like SWE-Bench-Pro-Hard-AA Benchmark Score, it does not do well at all. However, for SQL it does brilliantly.
The Granite model had 5 correct answers, but it produced them much faster than the Qwen model. Both are dense models of about the same size, so the nominal tokens per second will be about the same for any given hardware assuming that the model and KV cache all fit into memory. However, the Granite model is not a thinking model, and the time to first token is very fast. If you want raw speed, this will be a speed demon.
john.baima@quantforge.software
quantforge.software maker of SQLBuilderIQ
Other articles in this series:
Small Local AI Retakes the SQL Test
Small Local AI Takes the SQL Test
SQL Fun and Games Redux
Cloud AI Takes the SQL Test
Big Local AI Takes the SQL Test







