r/SQL • • 2h ago

Discussion Language-lawyer: != vs <>

13 Upvotes

Almost every SQL DBMS I can think of accept != as the "not equal operator". However, I realized that W3Schools only shows <>.

I was under the impression that <> is "the good-ol ANSI standard one" and every implementation out there was allowing the use of !=. But somebody was correcting me and saying that it has been on-spec for a while. I don't own the ANSI (a bit pricey for a language-lawyer curiosity!). Was I wrong?

Edit: After seeing quite a concensus on these answers, I talked with John (the one correcting me before). We checked the 2023 standard and his confusion came from the use of != as an alternative not equals operator for SQL/JSON lexical elements. That means that inside SQL/JSON blocks you may use != but on regular SQL (e.g. in the plain WHERE) using != is not covered by the standard (even if it is widely accepted for modern engines). It was a "coffee discussion", not something relevant IRL, but we both wanted to know the truth behind it.


r/SQL • • 34m ago

Discussion A 7th way to catch transaction fraud: let the database score how unusual each row is (and the fraud it still misses)

• Upvotes

The "Six SQL patterns I use to catch transaction fraud" thread here was great, and every one of those patterns is a rule: you know what fraud looks like, you write the WHERE clause. Here's a 7th that doesn't need the pattern up front, and the case where it falls flat.

Toy dataset: 123 transactions, 40 accounts. Each account makes three ordinary payments ($20–$199, daytime, domestic). ACC-33 also makes payments of $2,840, $3,120 and $2,975 at 3am, cross-border. Hidden among the ordinary accounts is a 5-account ring (ACC-11 to ACC-15). Nothing in the table marks them.

Train an anomaly model, no labels

Isolation forest, no target column. It learns what normal rows look like and scores distance from that.

CREATE EXPERIMENT tx_exp AS
  SELECT amount, hour_of_day, cross_border FROM tx
WITH (task_type = 'anomaly_detection',
  algorithms = ['isolation_forest'],
  max_trials = 5, random_seed = 42);

DEPLOY MODEL tx_model FROM EXPERIMENT tx_exp;

Score every row in a SELECT

SELECT account_id, amount, hour_of_day, cross_border,
  ANOMALY_SCORE('tx_model', amount, hour_of_day, cross_border) AS score
FROM tx
ORDER BY score DESC
LIMIT 10;

ACC-33's payments come out on top at 0.8167, 0.8167 and 0.8064. Then a straight drop to 0.5267, with the next normal rows between 0.5100 and 0.5181. Higher means more unusual; it's a ranking, not a probability. It's just an expression, so it works in WHERE, GROUP BY, views.

Features must be passed in training order. Swap two and you get a feature mismatch error instead of quietly wrong scores.

Where it falls flat: the ring

SELECT account_id, amount, hour_of_day,
  ANOMALY_SCORE('tx_model', amount, hour_of_day, cross_border) AS score
FROM tx
WHERE account_id IN ('ACC-11','ACC-12','ACC-13','ACC-14','ACC-15')
ORDER BY score DESC;

15 rows, from 0.4453 to 0.5081. Same as honest customers. The model isn't wrong: each payment is ordinary. A ring isn't a property of any single row.

What gives it away is shared identifiers: ACC-11/12 share a phone, 12/13 a card, 13/14 a phone, 14/15 a card. ACC-13 has a confirmed chargeback. Walking shared devices and cards 2–4 hops from it finds the other four:

linked via
ACC-14 ACC-13 → DEV-778 → ACC-14
ACC-12 ACC-13 → CARD-4242 → ACC-12
ACC-15 ACC-13 → DEV-778 → ACC-14 → CARD-9001 → ACC-15
ACC-11 ACC-13 → CARD-4242 → ACC-12 → DEV-777 → ACC-11

In most databases that's a recursive CTE over account–device and account–card link tables. We used a graph query in the same engine:

MATCH p = (s:Account {id: 'ACC-13'})-[*2..4]-(x:Account)
WHERE x.id <> 'ACC-13'
RETURN x.id AS linked_account, [n IN nodes(p) | n.id] AS via
ORDER BY length(p);

An honest household (ACC-21/22 share a tablet) isn't returned. Sharing a device isn't fraud; being connected to a chargeback is.

One review queue, both signals

SELECT s.account_id, ROUND(s.max_anomaly, 2) AS max_anomaly, r.linked_via
FROM (
  SELECT account_id, MAX(score) AS max_anomaly
  FROM (SELECT account_id,
    ANOMALY_SCORE('tx_model', amount, hour_of_day, cross_border) AS score
    FROM tx) t
  GROUP BY account_id
) s
LEFT JOIN ring_members r ON r.account_id = s.account_id
WHERE s.max_anomaly > 0.7 OR r.account_id IS NOT NULL
ORDER BY s.max_anomaly DESC;
account max_anomaly linked_via
ACC-33 0.82 (model)
ACC-15 0.51 DEV-778 > CARD-9001
ACC-12 0.51 CARD-4242
ACC-13 0.49 chargeback
ACC-14 0.48 DEV-778
ACC-11 0.48 CARD-4242 > DEV-777

Five of six would never cross 0.7. The model found one fraudster; the identifier walk found five.

Takeaways

  • Unsupervised scoring is a decent 7th pattern for the lone weird account you didn't write a rule for.
  • It's blind to coordinated fraud where every row looks normal.
  • Synthetic data built to show the gap. Real scores will be noisier; use them to rank for review, not as verdicts.

Anyone here run anomaly scoring in-database, or do you export to Python and join scores back?

Disclosure: I work on SynapCores, where ANOMALY_SCORE and the graph query come from. The rule-based patterns from the original thread still do most of the work.


r/SQL • • 2h ago

PostgreSQL A self-hosted wiki that charts live SQL from Postgres, MySQL and MariaDB

Thumbnail
gallery
5 Upvotes

Hi r/SQL, I'm David, one of the two developers of Oxynote. My friend Simon and I also maintain ttlcache, a Go caching library with about 1,300 GitHub stars, so this isn't our first open-source project.

I've spent 8 years writing software, 2 of them as an SRE and 3 as a team lead. Every team I've been on had the same documentation problem. The useful stuff lived in someone's head or a private Slack thread, and the wiki was full of stale docs nobody could find.

So we set out to solve this problem by building Oxynote, a self-hosted wiki for engineering teams. A chart on a page runs a SQL query against your database whenever someone opens the page, so the number and the notes explaining what it means sit together. Think "signups per day" or "failed payments by provider" on the same page as the explanation of how it's counted and what to do when it looks wrong.

It works with PostgreSQL, MySQL, and MariaDB. A wiki that queries production should never be able to write to it, so Oxynote accepts only read-only connections. A user with write access fails the connection test with "has write access", and Oxynote asks for a read-only one instead. Queries support Grafana-style macros like `$__timeFilter` and `$__timeGroup`, so one query follows whatever time range the reader picks. On pages you mark as reviewable, changes to a chart's query go through a draft and a diff that a teammate approves, like a pull request.

The name comes from oxygen + note: docs that breathe. Live data gives people a reason to open the wiki every day, and docs people open every day stay up to date.

It's Apache-2.0 and runs as a single Docker image: https://github.com/oxynote/oxynote

We started it about a year ago, and we made it public about 3 months ago. If you have any questions, feedback, or suggestions, feel free to reach out. I'm happy to answer any questions.


r/SQL • • 10h ago

Discussion Looking for feedback on my SQL client (MySQL, PostgreSQL, SQLite)

Post image
13 Upvotes

Hey all. I'm a solo dev from Korea, so yes, this is self-promo (I checked with the mods first).

I've been working on a desktop SQL client called DBerio. It does MySQL/MariaDB, Postgres and SQLite (plus Redis if that's your thing), on Mac and Windows. I mostly built it because I bounce between a Windows box and a Mac and wanted one tool that works the same on both. I write code all day too, so the UI is basically laid out the way I like to work.

What it does right now:

- the usual GUI client stuff: browse schemas and tables, run queries, edit cells and add/delete rows in the grid (when the result comes from one table with a PK)

- you can mark a connection as production. running UPDATE or DELETE always asks first, and on a production connection things like DB, user and privilege changes ask again. the really destructive ones there make you type the name

- it shows you the SQL before a structure change actually runs

- queries you run all the time can be saved as a view or kept as favorite queries per database

- CSV/TSV export escapes values Excel would treat as formulas

- SSH tunneling for DBs that are locked down from outside access (password, key or encrypted key)

- the UI comes in English, Korean and Japanese

Without paying you get 3 saved connections and 2 open at once, with every feature.

What I'd actually like to hear about:

  1. Does it cover your day-to-day work? If you'd have to keep another client open for something, what is it?

  2. Bugs. Anything that crashes, shows the wrong thing, or just feels off.

  3. Features you'd want added. What's the one thing that would make you actually switch?

Links if you want to poke at it (a test DB is totally fine):

Mac App Store: https://apps.apple.com/app/id6819625601

Microsoft Store: https://apps.microsoft.com/detail/9N53NQGJ3TNG

Thanks, I'll be around in the comments.


r/SQL • • 19h ago

Discussion The NOT IN trap that returns zero rows (and why NOT EXISTS doesn't have it)

41 Upvotes

I see this one trip people up all the time, including in interviews, so here's a quick write-up.

Say you want customers who have never referred anyone:

SELECT customer_id
FROM customers
WHERE customer_id NOT IN (SELECT referred_by FROM customers);

Looks right. Returns **zero rows**.

**Why:** most customers weren't referred, so `referred_by` is NULL for them. `x NOT IN (1, 2, NULL)` expands to `x <> 1 AND x <> 2 AND x <> NULL`. Comparing anything with NULL gives "unknown", not true, so the whole condition is never true and every row is filtered out.

**Fix 1: NOT EXISTS** (handles NULLs safely):

SELECT c.customer_id
FROM customers c
WHERE NOT EXISTS (
SELECT 1 FROM customers r
WHERE r.referred_by = c.customer_id
);

**Fix 2:** keep NOT IN but filter the NULLs out: `... WHERE referred_by IS NOT NULL`.

Plain IN doesn't have this problem, because `x = 1 OR x = 2 OR x = NULL` can still be true.

In an interview, mentioning the NULL issue before you write the query is an easy way to show you've worked with real data. Happy to answer questions!


r/SQL • • 5h ago

PostgreSQL PostgreSQL recipe: renaming a column without downtime (expand and contract)

1 Upvotes

A plain ALTER TABLE ... RENAME COLUMN is instant, but it breaks any running old version of the app during a rolling deploy. The usual workaround is to never have a moment where only one name exists. Here’s the sequence, with Postgres commands. Test it on a scratch database first.

1. Expand: add the new column

ALTER TABLE orders ADD COLUMN customer_ref text;

A nullable column with no default is a metadata-only change, so it’s fast.

2. Deploy the app writing both columns, still reading the old one.
The app sets customer_id_old and customer_ref on every insert and update. (A trigger can do this instead, but then the logic lives in the database. Decide which you prefer.)

3. Backfill in batches

UPDATE orders
SET customer_ref = customer_id_old
WHERE id BETWEEN 1 AND 10000
  AND customer_ref IS DISTINCT FROM customer_id_old;

Repeat for the next id range. Small batches avoid long locks and big bursts of WAL and replication lag. Run VACUUM as needed afterwards, since each update leaves dead rows.

4. Verify

SELECT count(*)
FROM orders
WHERE customer_ref IS DISTINCT FROM customer_id_old;

IS DISTINCT FROM treats NULLs properly, so you expect 0.

5. Deploy the app reading the new column (still writing both).

6. Deploy the app that no longer writes the old column.

7. Contract: drop the old column in a separate, later migration

ALTER TABLE orders DROP COLUMN customer_id_old;

This is also quick, but it’s irreversible without a backup, so check first that nothing reads it: views, functions, reports, other services, replicas used for analytics.

If the new column needs a constraint or index:

  • Use CREATE INDEX CONCURRENTLY so you don’t block writes.
  • For NOT NULL, adding a CHECK (customer_ref IS NOT NULL) NOT VALID and then VALIDATE CONSTRAINT avoids a long lock. Check the docs for your version, since behavior has changed across releases.

When I’d skip all this: a table with no other readers, where a short maintenance window is acceptable. Then a plain rename is simpler.

What do you do differently, for example triggers versus dual writes in the app?


r/SQL • • 10h ago

MySQL Project Idea

2 Upvotes

Guys can you suggest me a repo in GitHub or any idea about sql projects that will add weight in my resume?? I need your help. I'm a final year BTech student looking forward to building a career in data analytics.


r/SQL • • 1d ago

Discussion A year ago I started planning a gamified SQL platform. 5 months after launch, 2,500+ people have used it to learn

52 Upvotes

About a year ago I started planning QueryCase, where you learn SQL by working detective cases instead of grinding tutorials & videos. It took a while to get off the ground, and longer than I'd like to admit to actually start building the idea, but it went live in June. A lot of the early users came from this sub, and a lot of the feedback in the comments shaped what it became. So first, thanks everyone!

5 months in:

  • 2,500+ people have signed up, across 117 countries
  • About 70,000 queries written and graded
  • Over 17,500 cases, drills and exams solved
  • 500+ people have passed the free Rookie exam and earned the certificate.
  • Top detective is on 19,247 XP

Since then its grown past just SQL: an Excel course. It was never part of the initial plan. People kept asking for the same detective approach for spreadsheets, so I built out a browser-based Excel engine.

It has 57 cases on a spreadsheet that runs in your browser and checks your formulas, from IF and XLOOKUP to SUMIFS and pivot tables, and people have solved over 2,500 Excel cases so far. It turns out a lot of people learning SQL are doing it alongside Excel, not instead of it.

Something the data showed me that might help anyone learning right now: GROUP BY is one of the first walls. On the hardest GROUP BY step, 84% of runs came back wrong, and most of those never ran at all. The clauses were in the wrong order, or a column in SELECT wasn't in the GROUP BY. If that's you right now:

  1. SQL order is always SELECT, FROM, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT
  2. Every column in SELECT is either inside an aggregate (COUNT, SUM, AVG...) or listed in GROUP BY

I've been working a lot over past couple months to improve the site as well:

  • The Excel course above, with its own ranks, exams and certificate
  • Upload your own CSV into the sandbox and query your own data (and visualise in charts). It runs in your browser, so the file never leaves your machine and never touches QueryCase servers
  • A free first rank on every track, ending in an exam and a certificate
  • SQL interview questions you can read without an account
  • Much better experience on a phone
  • A LOT of bug fixes and improvements!!

What's next: a Python track, launching next month. Same idea, real Python running in your browser, starting with the basics and moving into data analysis with pandas.

The longer term goal is to have a platform user's can have fun learning all aspects of data. I'm merging PR's every day to make this possible so would love feedback in this thread if you've tried it and have any thoughts or suggestions!

It's free to start and you don't need an account to solve the first case, so you can poke at it before deciding whether it's for you or not 😄 Cheers!

querycase.com


r/SQL • • 1d ago

Discussion Is SUM(DISTINCT) ever an OK fix for a join fan-out?

18 Upvotes

Reviewing a query from a junior on my team. They joined orders to line items, the join fanned out and revenue came out about 3x too high.

Their fix was SUM(DISTINCT order_amount). On our dev data it returns the right total, so they think it's fine and kind of clean.

My gut says it's a time bomb: the day two different orders have the same amount, one of them silently drops out of the total. But "don't do that" isn't a great explanation, and they want a real reason.

How do you explain this to juniors? Or do you actually allow it in some cases?


r/SQL • • 20h ago

MySQL Title: Day 49-50 — Combined update: finished a basic Excel course after SQL, and 50 days in

Thumbnail
1 Upvotes

r/SQL • • 1d ago

MySQL What is the differtence between Window Function s and group by??

6 Upvotes

If we group by and use conditions and use aggregate fucntions we are soing the sam ein window fucntions tto, then what is the difference and do they have diffeent implementation?


r/SQL • • 1d ago

PostgreSQL Looking for PostgreSQL documentation?

3 Upvotes

I built a PostgreSQL Library where you can browse official manuals like real books, search, highlight, and bookmark pages.

Give it a try: https://haiderz07.github.io/postgresql-library/

I’d really value your feedback.


r/SQL • • 2d ago

SQL Server DBeaver Community vs Beekeeper Studio: which do you actually use day to day?

16 Upvotes

Genuinely want to hear from people who’ve used both DBeaver and Beekeeper Studio in real work, not just tried them once.

DBeaver Community is free and supports almost every database under the sun, but some people find it clunky. Beekeeper Studio has a free open-source edition plus paid tiers, and it gets mentioned a lot for being cleaner and faster.

Which one do you reach for daily, and why?

If you’ve paid for a database GUI (either one, or something else), was it worth it over the free version? What tipped it for you?

For teams: does anyone share connections or saved queries across the team, and how do you handle that? Do you have a team plan in place with either provider?

Any dealbreakers on either side, like performance, database support, stability, or UI?

Not looking for a best tool ever answer, just real experiences and feedback. Would especially love to hear from people working at larger orgs or with stricter security requirements.


r/SQL • • 2d ago

Discussion Career advice

32 Upvotes

I'm 27M. Hey everyone, my life has been a little different from that of most people, and I don't have a strong educational background.

Right now, I'm working at a grocery store, but I want to improve my life and learn some stable, valuable skills that can help me earn a reliable income.

I've come across these four skills:

Python

SQL

Excel/Spreadsheets

Power BI/Tableau

I don't know much about these skills or their demand, competition, value, and future prospects.

Technology is constantly evolving, trends are changing, and AI is replacing some jobs. The value of different skills also changes over time.

Before I start learning, I want to understand whether these are the right skills to invest my time in for a stable career and income in the long run.

I'd appreciate your advice. Should I go for these skills, or would you recommend something else?


r/SQL • • 2d ago

Discussion What’s next for DORA?

Thumbnail
leaddev.com
2 Upvotes

What does the legacy of DORA say about the modern tech industry?


r/SQL • • 2d ago

SQL Server If you evaluated Azure SQL Database Hyperscale for a migration, what made it win or lose decision?

Thumbnail
1 Upvotes

r/SQL • • 2d ago

MariaDB MariaDB Foundation has been pushing hard toward transparent, reproducible performance change detection.

Thumbnail
0 Upvotes

r/SQL • • 3d ago

MariaDB How do you handle many-to-many database migrations without breaking existing API consumers?

Thumbnail
9 Upvotes

r/SQL • • 4d ago

Discussion Two T-SQL strategies for controlling partition refresh on append-heavy fact tables

4 Upvotes

Hi everyone, my name is Salvatore and I work as a Data Analyst / Data Engineer.

Recently I ran into a problem related to Power BI incremental refresh on large, mostly append-heavy fact tables.

In the specific case, the issue was eventually solved by revisiting the data model and redesigning the star schema. However, working on it made me think more deeply about some of the problems that can arise when large amounts of data are managed through partitions.

In particular:

  • even a relatively small number of new records can end up involving multiple partitions that need to be processed;
  • some partitions can grow much larger than others over time and therefore become more expensive to refresh;
  • very wide fact tables, especially those containing many high-cardinality columns, can increase the processing and memory cost of the model;
  • on very large models, the combination of these factors can lead to very long refreshes and, in the worst cases, timeouts.

In Power BI, for example, a standard refresh on Premium capacity has a five-hour limit, so on very large datasets the way records are distributed across partitions can become relevant as well.

This led me to wonder whether it makes sense to separate two problems:

  1. identifying which records have changed;
  2. controlling which partitions, and how many of them, need to be rebuilt.

From there I started experimenting with an approach based on stable technical classes.

The basic idea is fairly simple.

During the initial load, each record is assigned to a class_id between 1 and N using a modulo-based distribution. The mapping between the stable record key (source_key) and its class_id is then persisted, so that once a key has been classified, it continues to belong to the same class in subsequent runs.

The class_id therefore becomes the technical key I use to control downstream partitions.

For new INSERTs, instead of distributing them across all classes every time, I use a round-robin assignment only within a subset of classes.

From this idea I ended up with two variants.

1. Rotating Window

It selects a window of K consecutive classes and distributes new INSERTs only within that window. In subsequent runs, the window rotates through the class space.

2. Least-Loaded

Instead, it selects the K classes that currently contain the fewest records and distributes the new INSERTs only among those classes.

In both approaches, K represents the maximum number of classes that an INSERT batch can automatically involve. An adaptive policy is therefore used to allow smaller batches to use a smaller sub-window — or, more generally, a smaller subset of classes — than this maximum.

For example, with K max = 10:

INSERT <=    500 -> K = 1
INSERT <=  5,000 -> K = 4
INSERT <= 20,000 -> K = 7
otherwise        -> K = 10

So a very small batch may involve only one partition, while larger batches can progressively use more classes up to the configured maximum.

I implemented both ideas in T-SQL for SQL Server, including persistent state, a PREPARE/FINALIZE lifecycle, automated tests, and some exploratory benchmarks.

Here are the complete reference implementations with README, code, tests, and benchmarks:

Rotating Window Partition Refresh
https://github.com/Salvatore97710/rotating-window-partition-refresh

Least-Loaded Class Partition Refresh
https://github.com/Salvatore97710/least-loaded-class-partition-refresh

These are reference implementations / prototypes, not solutions I would consider production-ready.

What I’m mainly looking for is technical feedback on the idea itself.

Do you see any conceptual problems I may be missing?

And more importantly: if you were facing the same problem — a large append-heavy fact table and the need to control both the number and the size of the partitions being refreshed — how would you approach it?


r/SQL • • 3d ago

Discussion A query language where stages read in execution order — what would you critique?

2 Upvotes

I've been building a query language where a program is a pipeline of stages in execution order: from → filter → group by + aggregate → having → sort → take, compiled to DuckDB SQL. The bet is that keyword order (SELECT before FROM, HAVING after GROUP BY) is the wrong mental model.

Two design choices I'd like critiqued:

  1. Splitting group by and aggregate into separate stages — clearer for beginners, or just more lines?
  2. Rejecting chained comparisons and bare select before from with stage-located errors — helpful guardrails or unnecessary restrictions?

The grammar is frozen at v1.5 (spec in the repo); I'm more interested in what you'd change than in stars: https://github.com/absterjr/UNSeQueL

Disclosure: Idea was mine, initial setup was mine, but it started getting more and more complex while using a local LLM and now I'm stuck.


r/SQL • • 5d ago

MySQL updated runtime.school after the feedback here

7 Upvotes

spent some time cleaning up runtime.school this week.

the lessons and practice are less busy now, the editor feels better, and i went through the course so questions only use sql that has actually been taught by that point.

i also kept the beginner lessons and practice on the same dataset so it’s easier to focus on the sql before switching contexts.

if anything feels confusing, off, or harder than it should be, let me know.

https://www.runtime.school/


r/SQL • • 4d ago

MySQL Cómo puedo realizar una base de datos referente a un punto de venta o control de inventario sobre un depósito o tienda de abarrotes en MySQL Workbench 8.0

Thumbnail
1 Upvotes

r/SQL • • 5d ago

SQL Server daffydb TDS listener and t-sql translator to duckdb, new features

3 Upvotes

​

I've been working on this one for a while, some new features, made a demo tape below

I added a pure TDS based custom assembly to allow SQL Server support (Yes I know duckdb has a community Ms SQL extension , but I made one that works better with my daffy product, my internal component is daffysql ) and a new bulk parquet export automation feature. currently mounted to 500 tables from a combo of SQL server tables, postgres tables, Excel sheets, & CSVs? one proc call writes 500 new parquet files.

connect to it from any SQL Server client, Ado, power bi, ssms, etc. i put together this demo of the new features, let me know what you think. very similar to how Babel fish for postgres works but this one is duckdb + more.

it's on nuget and code signed by azure

https://youtu.be/jTzbJsPn8jE?is=jMZIKm5TooYoyf8v

Scott


r/SQL • • 4d ago

Discussion ROUND(SUM(x), 2) gave two different cents for the same rows. how do you compare query results in a grader?

Post image
0 Upvotes

two correct queries over the same 1,390 rows: one rounds to 33223.80, the other to 33223.81. the exact decimal sum is 33223.8050, exactly half a cent, and in DOUBLE the order the rows get added decides which side it lands on. a window over monthly subtotals adds them in one order, a correlated subquery over the raw rows adds them in another.

i hit this because the checker in a SQL game i make (DuckDB in the browser) failed the second query on the visible sample and passed it on both hidden sets. here's how that checker decides a query is right, the other places it was wrong, and the part i still don't know how to handle.

how it checks: every problem has a reference query. when you submit, yours and mine both run on the sample data you can see, then on two datasets you can't, generated from other seeds. boss problems get four, labelled ties and duplicates, sparse groups and boundary dates. the sample always runs so you can see a diff, then it stops at the first hidden set that fails and says which one. a hidden set where my query returns zero rows gets skipped, since an empty result would let an empty answer pass.

then the two result sets get compared. column names are matched as a set, case-insensitive, so column order doesn't matter as long as the names are unique. row order only matters if the prompt asks for a sort. integers have to match exactly. fractional values get an absolute tolerance the problem sets, usually 0.0001, so on a money column it's tighter than a cent. NULL matches NULL. a DATE matches the same day at exactly midnight as a TIMESTAMP, because generate_series and date_trunc('day', ...) hand you timestamps and a correct "one row per day" answer was failing.

where it was wrong:

the tolerance was relative at first, tol * max(|a|, |b|). so on a big dollar total a "2 cents" check was actually a percent check, and a total that was 1.9% off passed on every seed. now it's absolute plus a tiny term for float noise.

Infinity passed against any finite number, because abs(inf - x) <= tol + inf is true. a later audit caught it.

for unordered results i used to sort both sides and zip them. that breaks once tolerance is involved: tiny values written in scientific notation sort differently as text, so a correct answer could get paired with the wrong row and fail. now it builds a which-row-can-match-which graph and does a bipartite matching (building the graph is quadratic, which is part of why results are capped at 5,000 rows).

then in october i ran 141 alternate answers, other ways to write each reference query that i expected to pass, through the same normalizer and comparator. 108 passed. a lot of the failures were my prompts, where a sentence had two readings and the visible sample couldn't tell them apart. one asks for deciles of users by lifetime notional, and if you include users with no trades at 0 you pass the sample and fail both hidden sets, because the sample has no non-traders and the hidden sets, built at a quarter of the size, have 74 and 91. a few more were regex checks i use to require a certain style of query, refusing equivalent ones. and one was the engine. on the duckdb-wasm build i'd shipped (DuckDB 1.5.1), this

SELECT user_id, asset_id
FROM wallet_balances_daily
WHERE user_id BETWEEN 1 AND 3
QUALIFY ROW_NUMBER() OVER (PARTITION BY asset_id, user_id ORDER BY balance_date DESC) = 1
ORDER BY user_id, asset_id
LIMIT 3

came back with user_id and asset_id swapped (the table has user_id first). native 1.5.3 got it right and so does wasm 1.5.4, which the demo build runs now, though i never tracked down the upstream issue.

this week's audit found two more. those boss labels promised more than the data did: the sets were mostly re-rolls of the same generator, so a >= 1800 written for "strictly more than 30 minutes" passed all three session problems. the next build plants real boundary rows and gap days in one hidden set. and the half cent above. the fix is one rounding unit of tolerance on the 10 columns where a half cent can happen, not a looser check everywhere.

the one i haven't solved is ties. a top-N pick (LIMIT n, rn <= n, QUALIFY) can fail a correct query if two rows tie at the cut and the prompt's sort key doesn't break the tie, and an ordered result has the same problem with ties anywhere in it. a september audit on native DuckDB checked all 48 top-N picks on every graded seed: no tie at a cut, 13 that a data change could expose. fee_usd has 600 tie groups across the graded seeds, the nearest about 1,600 rows from its cut. for now a script checks that on demand and the prompts name a tiebreaker where they can.

i don't log query text from players, only counters, so i can't tell you what people actually typed. the counters are small: over oct 1-14 the demo showed its "too few rows" nudge 12 times and "too many rows" 5 times.

for people who've built graders for interview tools or internal tests: do you compare aggregates as DECIMAL, give each column its own tolerance, or make the prompt require a specific ROUND? and for ties, is there a way that doesn't make the prompt name the tiebreaker? that works, but it hands over part of the answer.


r/SQL • • 5d ago

SQL Server How to Integrate two apps exclusively through the Database

Thumbnail
5 Upvotes