The other day, I had a problem with some data that I never dreamed I would ever see. In a case insensitive database, in a table’s column that was case insensitive, the customer was using the data as case sensitive. Firstly, let’s just go ahead and say it. “This was a sucky implementation.” But as is common, in my typical role as a data architect in the data warehousing team, I get to learn all sorts of interesting techniques for finding and dealing with “data” that has been used in “interesting” ways.
What is kind of interesting is actually figuring out what that duplicated data was. The case that I was dealing with wasn’t a kind of useful packed surrogate value, where you may use a base 62 number, with a-z, A-Z and 0-9 as characters. So 1, 2, … , 9, 0, a, b, c, … x, y, z, A, B.. etc. 1A1 is a different value in that sequence than 1a1, and is greater . Neat technique, and one that I have been threatening to develop using a SEQUENCE object, where you can pack in a lot of sequential data in a small number of bytes. No, this wasn’t a useful case such as this, in this case, one value was lower case, another had leading capitals. So perhaps “active customer” and “Active Customer”. Yeah, seriously, they meant different things.
Note: The query I will use will help to find the permutations of values that you have in your data (Like “United States”, ‘UNITED STATES” for example.) Hence this is not a completely esoteric exercise.
To find the data, I figured I would use a case sensitive collation, but it isn’t as straightforward as grouping on the data using a collation. For a sample set of data, I will use the following:
1 2 3 4 5 6 7 8 9 |
DROP TABLE IF EXISTS #Color --Note, because of the COLLATE argument, you must alias the column SELECT ColorID, ColorName COLLATE Latin1_General_CI_AS AS ColorName--Make sure column is case insensitive, no matter what your platform INTO #Color FROM WideWorldImporters.Warehouse.Colors INSERT INTO #Color(ColorID, ColorName) SELECT TOP 3 ColorID + 36, LOWER(ColorName) AS ColorName FROM #Color; |
Now, because it is case insensitive, to the normal user, you can’t really tell the difference, but in this contrived set, we will have > 1 row with the same color value (in my real example, there were thousands).
1 2 3 4 |
SELECT ColorName, COUNT(*) FROM #Color GROUP BY ColorName HAVING COUNT(*) > 1; |
This returns:
ColorName
-------------------- -----------
Azure 2
beige 2
Black 2
Now, whether or not “beige” and “Beige” are bizarrely enough different things, or just misformatted data, this is not what we want back. We want to treat them as different, so using a case sensitive collation (you need the COLLATE argument in the SELECT clause if you have it in the GROUP BY clause:
1 2 3 4 |
SELECT ColorName COLLATE Latin1_General_CS_AS, COUNT(*) FROM #Color GROUP BY ColorName COLLATE Latin1_General_CS_AS HAVING COUNT(*) > 1; |
This doesn’t return anything. Hmm. The trick is in the HAVING clause. Use COUNT DISTINCT on the column as case sensitive instead of the GROUP BY and we get the items we want, but still just one per:
1 2 3 4 |
SELECT ColorName, COUNT(*) FROM #Color GROUP BY ColorName HAVING COUNT(DISTINCT ColorName COLLATE Latin1_General_CS_AS) > 1; |
The output is the same as we had previously, with 2 rows per color (though this time they are all the lowercase items). However, now the ColorName column returned by the SELECT clause is case insensitive, so we can use it to get the case insensitive duplicates
1 2 3 4 5 6 7 |
SELECT * FROM #Color WHERE ColorName IN ( SELECT ColorName FROM #Color GROUP BY ColorName HAVING COUNT(DISTINCT ColorName COLLATE Latin1_General_CS_AS) > 1 ); |
Which returns the 6 rows with the duplicates we are hunting for:
ColorID ColorName
----------- --------------------
1 Azure
2 Beige
3 Black
37 azure
38 beige
39 black
With this set, you could then group on the values using the case sensitive collation to see how many duplicates you have, such as:
1 2 3 4 5 6 7 8 |
SELECT ColorName COLLATE Latin1_General_CS_AS AS ColorName, COUNT(*) AS NumberOfUses FROM #Color WHERE ColorName IN ( SELECT ColorName FROM #Color GROUP BY ColorName HAVING COUNT(DISTINCT ColorName COLLATE Latin1_General_CS_AS) > 1 ) GROUP BY ColorName COLLATE Latin1_General_CS_AS; |
Easy enough, but hopefully if you get stuck doing this some day, this will help you get going.
Load comments