Remove Duplicates From a MySQL Database Table

Remove duplicates from a MySQL database table

I discovered a bug in a web app that I built a few years ago. It was difficult to debug because it only happened intermittently. As any programmer knows, issues that can’t be reproduced consistently (and locally) present the most pain. Ultimately, it was causing database records to be created in double – but only when certain conditions evaluated true in the app state.

I’ll get into the code fix in another post. Here, I’ll show you how I cleaned up the database. This application has over ten-thousand data records in production. The first thing I did before messing around was to export a back-up of the prod DB. That was only me being extra careful –  I already have a nightly job that dumps the entire production database as a .sql file to an S3 bucket. Taking an export on the fly is easy through phpMyAdmin.

Export database from phpMyAdmin
Export database from phpMyAdmin

Step one is to identify duplicates and store them in a temporary table, using a GROUP BY clause. In MySQL (and most other SQL-based database systems),GROUP BY is used to group rows from a table based on one or more columns.

The duplicate rows that I am interested in have all identical values, except for their primary keys. I can group those rows (and put them into a new, temporary, table) by including all of the table columns names (except the primary key) in my SQL statement. You can list those names in phpMyAdmin with this command:

SHOW COLUMNS FROM `records`;

Show all columns

My tables have quite a few columns. Instead of copy/pasting each field name, I used SQL code to list them out together. This was possible by leveraging the INFORMATION_SCHEMA database, a special system database that provides metadata about the database server itself in MySQL.  I could retrieve the column names and then concatenate them into a single string using the GROUP_CONCAT function:

SELECT GROUP_CONCAT(column_name SEPARATOR ', ') AS column_list
FROM information_schema.columns
WHERE table_schema = 'bjjtracker'
  AND table_name = 'records';

The result displayed as abbreviated until I selected “Full texts” from the options menu (highlighted below)

mysql columns

I could now copy/paste that column_list into my SQL statement. Remove the primary key field (usually the first one), or else no duplicates will be found (unless your use-case involves records having repeated primary key values, which is a less likely scenario).

CREATE TABLE TempTable AS
SELECT userid, type, date, beltrank, medal, weight, notes, created_date, style -- List all columns except the primary key
FROM `records`
GROUP BY userid, type, date, beltrank, medal, weight, notes, created_date, style -- Group by all columns except the primary key
HAVING COUNT(*) > 1; -- Indicates their is more than one record with exactly matching values

Now we have a new table that contains records that are duplicative in our original table. Step 2 is to delete the duplicates from the original table.

DELETE FROM `records`
WHERE (userid, type, date, beltrank, medal, weight, notes, created_date, style) IN (
    SELECT userid, type, date, beltrank, medal, weight, notes, created_date, style
    FROM TempTable
);

Don’t forget to delete that temporary table before you leave:

DROP TEMPORARY TABLE IF EXISTS TempTable;

Dealing with NULL values

On the first table I used this on, everything worked as expected. On a subsequent run against another table, zero rows were deleted even though my temp table contained duplicate records. I deduced that it was because of NULL values causing the comparison to not work as expected. I figured that I had to handle NULL values explicitly using the IS NULL condition on each field.

DELETE FROM recordsdetails
WHERE 
    (userid IS NULL OR userid, 
     recordid IS NULL OR recordid, 
     detailtype IS NULL OR detailtype, 
     technique IS NULL OR technique, 
     reps IS NULL OR reps, 
     partnername IS NULL OR partnername, 
     partnerrank IS NULL OR partnerrank, 
     pointsscored IS NULL OR pointsscored, 
     pointsgiven IS NULL OR pointsgiven, 
     taps IS NULL OR taps, 
     tappedout IS NULL OR tappedout, 
     result IS NULL OR result, 
     finish IS NULL OR finish, 
     created_date IS NULL OR created_date) 
IN (
    SELECT userid, recordid, detailtype, technique, reps, partnername, partnerrank, pointsscored, pointsgiven, taps, tappedout, result, finish, created_date
    FROM TempTable
);

But yet, I still got zero rows being deleted. This time though, I was seeing a warning. It complained: “Warning: #1292 Truncated incorrect DOUBLE value”

Zero rows found

This suggests that there is a data type mismatch or issue in the comparison involving numeric and string values. My guess is that the IS NULL handling was causing type conversion issues. To remedy this, I wrote a more explicit query by combining the AND and OR conditions.

DELETE FROM recordsdetails
WHERE 
    (userid IS NULL OR userid IN (SELECT userid FROM TempTable)) AND
    (recordid IS NULL OR recordid IN (SELECT recordid FROM TempTable)) AND
    (detailtype IS NULL OR detailtype IN (SELECT detailtype FROM TempTable)) AND
    (technique IS NULL OR technique IN (SELECT technique FROM TempTable)) AND
    (reps IS NULL OR reps IN (SELECT reps FROM TempTable)) AND
    (partnername IS NULL OR partnername IN (SELECT partnername FROM TempTable)) AND
    (partnerrank IS NULL OR partnerrank IN (SELECT partnerrank FROM TempTable)) AND
    (pointsscored IS NULL OR pointsscored IN (SELECT pointsscored FROM TempTable)) AND
    (pointsgiven IS NULL OR pointsgiven IN (SELECT pointsgiven FROM TempTable)) AND
    (taps IS NULL OR taps IN (SELECT taps FROM TempTable)) AND
    (tappedout IS NULL OR tappedout IN (SELECT tappedout FROM TempTable)) AND
    (result IS NULL OR result IN (SELECT result FROM TempTable)) AND
    (finish IS NULL OR finish IN (SELECT finish FROM TempTable)) AND
    (created_date IS NULL OR created_date IN (SELECT created_date FROM TempTable));

That worked! With a cleaned database, it was time to figure out what was causing the bug in the first place, and to fix the problem.


 

 

Clones in quiet dance; Copies of our code converge; Echoes of our souls;

Online Ordering for a Restaurant Website

online ordering system

A Digital Transformation Case Study: Boosting Restaurant Sales with Custom Web Development and Online Ordering Integration

Client background & challenge

When I was younger I worked as a pizza delivery driver. Years later, the pizzeria where I once worked commissioned me to build their website. They were busier than ever thanks to online ordering (GrubHub, Seamless, UberEats), but were getting hit with high service fees.

They wanted their own website to be able to take orders for food online and send a notification to their iPad. That way they could avoid using apps like GrubHub that charged additional fees.

Project overview & execution

I used a service called GloriaFood that provides ready-made website templates, a secure payment process, and a messaging system. It integrates with Stripe for processing payments. There is an iPad app that receives push notifications when new orders are placed. The website builder required no code, and had a ton of options. I was able to register the pizzeria’s domain name directly though the admin portal, and generate a sales optimized website with hosting all setup.  It was “seamless” – pun intended!

GloriaFood admin panel

There are also options for integrating their ordering UI with an existing website, a Facebook page, or a dine-in QR code. The an option to publish a custom app required an additional cost per month.

pizza website

I even traveled to this restaurant’s physical location, selected and purchased a tablet computer for them, installed the GloriaFood app to receive orders, and connected it to their mobile printer.

gloriafood order received

It’s amazing how much I was able to accomplish without writing a single line of code. The most technical part of this project was setting up a Stripe account and putting the API keys into the GloriaFood admin panel. GloriaFood is a product by Oracle, a company that specializes in providing a wide range of software and hardware products and services.

Print Design

As an extra part of this project, I designed a business card with a QR code linking to the new website. The business owner planned to give this to customers who ordered through other food ordering apps such as GrubHub, Seamless, UberEats, and Slice.

Business card for pizza business website

Results

Sales Increase

Since the launch of the new website, the restaurant has witnessed a notable surge in online orders, marking a 25% increase. This substantial rise not only signifies a successful digital transformation but also illustrates the growing customer preference for a seamless, direct ordering experience. The intuitive interface and easy navigation on the restaurant’s website have played a strong role in attracting and retaining customers, driving a higher volume of online orders and significantly contributing to the restaurant’s revenue growth.

Cost Savings

Transitioning from third-party ordering platforms like GrubHub, Seamless, and UberEats to a self-hosted online ordering system through has led to big cost savings. Third-party platforms usually charge hefty commissions, which eat into the restaurant’s profits and inflate prices for customers. With the new website, the restaurant has eliminated these intermediary costs, ensuring better profitability while also offering customers more competitive pricing.

Customer Feedback

The feedback received from both the restaurant management and its customers has been overwhelmingly positive.  The restaurant staff has praised the streamlined process, which has simplified order management and allowed for a smoother operation during busy hours.

How I Helped a Business Optimize Their Google Ads Campaign

Google Ads being set up

After a client approached me about improving their Google Ads campaign, I could tell they were doing a lot of things right—but they weren’t getting the results they wanted. Their ads were active, their click-through rate was decent, and they were spending about $650 per month. On the surface, everything seemed fine.

But as I dug deeper, I saw an opportunity to transform their campaign from “good enough” into something that could truly drive new clients to their business.

The Problem

The first thing I noticed was that their ads were missing some key pieces. For starters, there was no way to measure what happened after someone clicked on an ad. Conversion tracking hadn’t been set up, so there was no way to tell if those clicks led to sign-ups, contact form submissions, or any other meaningful action. It’s like running a sales campaign and never finding out how many people bought something.

Another issue was the structure of the ads themselves. Everything was running through a single ad and ad group, all of which pointed to their homepage. While this kind of setup can work as a starting point, it’s not ideal for targeting specific audiences. Imagine searching for “self-defense classes” and landing on a generic homepage with no mention of self-defense—it’s a missed opportunity to connect with a potential client.

Finally, their campaign wasn’t linked to Google Analytics, which meant there was no insight into what visitors did once they arrived on the site. Did they spend time exploring? Did they leave immediately? Did they look at multiple pages? These questions were left unanswered.

The Plan

After reviewing their campaign, I proposed a complete overhaul. First, I explained the importance of defining what success looks like—what actions do we want people to take when they click on an ad? For this client, it made sense to track things like contact form submissions and clicks to their scheduling platform. By setting up conversion tracking, we could finally measure the real impact of their ad spend.

Next, I suggested restructuring their campaign into three distinct ad groups, each with its own audience and purpose. The first ad group would target people new to their services, highlighting beginner-friendly options. The second would focus on fitness enthusiasts looking for a fun and effective workout. The third would spotlight programs for women, emphasizing empowerment and inclusion.

Each ad group would feature two different ads to test different messages and see what resonated most with each audience. And instead of pointing all traffic to the homepage, I recommended creating dedicated landing pages tailored to the ad group. If someone clicked on an ad for fitness classes, they’d land on a page showcasing the physical and mental benefits of their programs. If they clicked on an ad for women’s classes, they’d be greeted with content specifically designed for that audience.

Finally, I emphasized the need to connect Google Analytics to their campaign. This would give us a clearer picture of how people interacted with the site after clicking on an ad, providing valuable insights for ongoing optimization.

The Work Ahead

The client loved the plan, and here’s what’s next. I’ll spend a couple of hours implementing these changes, setting up the ad groups, writing the ads, building the landing pages, and integrating analytics and conversion tracking. Once that’s done, we’ll be ready to relaunch the campaign with a much stronger foundation.

Of course, ads don’t run themselves. The client will need to decide when they want to turn the campaign back on and how much they’re comfortable spending each month. But with these optimizations in place, they’ll be able to make data-driven decisions and see exactly how their investment is paying off.

Why This Matters

Every business wants to get the most out of their marketing budget. For this client, that means attracting new customers who are truly interested in their services. By taking the time to set up conversion tracking, target specific audiences, and create tailored landing pages, we’re turning their Google Ads campaign into a powerful tool for growth.

If you’re running Google Ads and feel like you’re not getting the results you should, you’re not alone. With the right strategy and a little bit of work, you can transform your campaign into something that delivers real, measurable value.

Want to learn more about how I can help your business grow? Let’s talk.