Every Monday a retail team pulls two files. The shop system exports units sold. The warehouse system exports units that left the shelf. Both files are "the week's products." The totals disagree. Someone opens a spreadsheet and lines the rows up with a lookup on the product name.
Most rows match. The ones that matter do not. "Mint tea 20 bags" in the shop file and "Mint Tea 20 Bags " in the warehouse file are different keys. A trailing space and a capital letter drop the row. The person doing the match fixes a handful by hand, eyeballs the rest, and sends a number on Wednesday afternoon. Next Monday the files arrive again, the formulas are half broken, and the match starts over.
The argument, until the unmatched rows are listed, is about which file is right. Often neither total is wrong. They are totals of two different sets, joined on a key that is not a key.
The key is the whole problem
Two systems can export the same fact and still not share an identifier. The shop thinks in the name printed on the shelf. The warehouse thinks in the name on the purchase order, typed by a different person, in a different case, with a different habit about spaces. A human can see that those are one product. A lookup cannot, because a lookup compares characters.
Once the key fails, every downstream symptom appears. The totals diverge. A product looks like it sold and was never stocked, or was stocked and never sold. Someone adds a manual override column that works for this week and is wrong next week, when the same product is spelled a third way. The match finishes late because the failures show up one row at a time, and next week remembers the override only if it was left in the sheet. The two systems never agreed on an id. The weekly process papers over that with a display name.
Why each usual tool gets you as far as one file
Excel, and the lookup functions in it, do exactly what they claim. They match equal keys. They do not know that a trailing space is insignificant, unless you add a column that trims and case-folds, and then you have to remember to match on that column rather than on the original. Teams skip the extra column because the raw name "usually" matches. The usual rows are not the ones that change the total.
A BI tool can model both sources, clean the key once, and refresh, after someone has loaded both feeds. The weekly pain exists because the files arrive and the person under time pressure opens them. The model, if it was started, is the project that did not get finished. The sheet cannot be postponed, so the sheet wins every Monday.
Two SQL clients are two grids. You can export each and you are back in the sheet. You cannot join them in either client, because each client is attached to one engine. The copy drops the types, the original spacing, and any id you forgot to select.
A text-to-SQL chat on one database can tidy one of the exports. It cannot see the other file. Ask it why the totals disagree and it will explain the table it can see. The other total is not in the prompt unless you pasted it, and a pasted total without the rows is not something you can match.
The tools are each competent at one side of the join. The pain is the join.
Make the key boring, and keep the rows that fail
Match on a stable id if one exists in both systems. A SKU, a barcode, a product id the shop already stores and the warehouse already receives. If the id is in the database and missing from the export, add it to the export. Matching on names because the export was convenient is how the space wins.
If no shared id exists, make one. A single column you maintain, product by product, is a better key than the display name. It is a small table. It is allowed to grow slowly. Next week's match uses it. The display name stays on the row so a person can check, and it stops being the key.
Normalize before you match, and keep the original beside the normalized form. Trim, collapse repeated spaces, and pick one case. Do it in the sheet or in the query, in a column with a dull name (name_key). Match on name_key. When a row still fails, you can see the raw strings next to each other and decide whether they are the same product or a real difference. Do not overwrite the raw name. You will need it when the normalization was wrong.
Separate the unmatched rows from the grand totals. The totals are the symptom. The unmatched list is the work. Save the steps so next Monday is a rerun: a query, a notebook, or a sheet that only needs the two new files. If the formulas live in someone's head, the match does not survive a holiday.
Write down which system is authoritative for which fact, in a sentence. The shop count is what was sold. The warehouse count is what left the shelf. They are allowed to differ by theft, by a late scan, by an order that was sold and not yet picked. A difference is then a question with a place to look. It is no longer an emergency about which file "the business" believes.
Fuzzy matching on names, the kind that decides "Mint tea" and "Mint Tea" are the same without an explicit rule, is a product feature you should not pretend you have. A trim and a case-fold are explicit. They are enough for the space and the capital letter. Anything fuzzier belongs in the alias table, where a person can see the pair.
Where DataTug fits
DataTug helps when both sources are ones it can open, by putting them in one project and letting you join them in a query or in AI chat. A shop database in PostgreSQL and a warehouse in MySQL is the shape. A SQLite extract and a REST API is another. The sources marked as working today are SQLite, PostgreSQL, MySQL, Oracle, SQL Server, BigQuery, inGitDB, and REST APIs. You run that from the web app, the CLI and terminal UI, or AI chat.
The join still needs a key that matches. The homepage does not show fuzzy name matching, and this article does not claim it. Trim and case-fold in the query, or join on the id you added to both exports. DataTug removes the copy between two clients that cannot see each other. It does not decide that two pretty names are one product.
Open both sources in one project on datatug.app when they are on that list. Personal and public projects are free. Keep the alias table even after the join is easy. The space in the name will be back next Monday if the key is still the label on the shelf.
