Converting CHAR Primary Key to INT in MySQL/MariaDB - mysql

I have a table that uses CHAR as the primary key for customers. I am attempting to load this table into a schema such that the primary key should be an INT.
DROP TABLE IF EXISTS `customers`;
CREATE TABLE `customers` (
`customer_id` char(5) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `customers` VALUES ('99944'),('99946'),('99976'),('A0014'),('A0049'),('A0124'),('C01AH'),('C01AQ'),('C01AW'),('C01AX'),('C01AY'),('C01AZ');
Fiddle
I have attempted variations on select cast(customer_id AS UNSIGNED) FROM customers; but only get back 0s for the non-int rows. How do I cast the non-int rows into a consistent INT result?
The ideal result would look like this:
For customer IDs that are solely integers, leave them alone.
For customer IDs that contain any letter, replace everything in the ID with a unique numerical identifier.
Expected result:
SELECT * FROM Customers;
`customer_id`
-------
99944
99946
99976
13871911
13871912
13871913
13872128
13872229
13872293
13872505
13872512
13872561

GMB did give me a other idea.
Using the HEX() and CONV(.., 16, 10) to convert from hexadecimals into decimales
Query
SELECT
customers.customer_id
, CASE
WHEN (customers.customer_id >> 0) > 0
THEN customers.customer_id >> 0
ELSE
CONV(HEX(customers.customer_id), 16, 10)
END
AS customer_id_int
FROM
customers;
Result
| customer_id | customer_id_int |
| ----------- | --------------- |
| 99944 | 99944 |
| 99946 | 99946 |
| 99976 | 99976 |
| A0014 | 279981338932 |
| A0049 | 279981339705 |
| A0124 | 279981404724 |
| C01AH | 288571343176 |
| C01AQ | 288571343185 |
| C01AW | 288571343191 |
| C01AX | 288571343192 |
| C01AY | 288571343193 |
| C01AZ | 288571343194 |
p.s
It might be generating a to large int you need to use a BIGINT datatype.
see demo
Updated
A other method to generate smaller int's (UNSIGNED INT) which uses a "SQL number generator", SUBSTRING(), ORD() and GROUP_CONCAT().
Query
SELECT
customers.customer_id
CASE
WHEN customers.customer_id >> 1 > 0
THEN customers.customer_id
ELSE
GROUP_CONCAT(
CASE
WHEN SUBSTRING(customers.customer_id, number_generator.number, 1) NOT BETWEEN 'A' AND 'Z'
THEN SUBSTRING(customers.customer_id, number_generator.number, 1) >> 1
ELSE ORD(SUBSTRING(customers.customer_id, number_generator.number, 1))
END
ORDER BY
number_generator.number ASC
SEPARATOR ''
)
END
) AS customer_id_int
FROM (
SELECT
record_1.number
FROM (
SELECT 1 AS number UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5
) AS record_1
) AS number_generator
CROSS JOIN
customers
GROUP BY
customers.customer_id
ORDER BY
customers.customer_id ASC
Result
| customer_id | customer_id_int |
| ----------- | --------------- |
| 99944 | 99944 |
| 99946 | 99946 |
| 99976 | 99976 |
| A0014 | 650002 |
| A0049 | 650024 |
| A0124 | 650012 |
| C01AH | 67006572 |
| C01AQ | 67006581 |
| C01AW | 67006587 |
| C01AX | 67006588 |
| C01AY | 67006589 |
| C01AZ | 67006590 |
see demo

With Maria DB >= 10.0.5, here is a solution to turn a string primary key to an integer primary key in a predictable manner :
SELECT
customer_id old_id,
CAST(
REGEXP_REPLACE(customer_id, '([^0-9])', ORD('$1'))
AS UNSIGNED
) new_id
FROM customers;
REGEXP_REPLACE() captures non-numeric characters (anywhere in the string) and ORD() turns each of them into its ordinal (numerical) represtation.
Demo on DB Fiddle :
old_id | new_id
:----- | -------:
99944 | 99944
9Z946 | 936946
A9CZ6 | 36936366
A0C14 | 3603614
0ABC0 | 3636360

Using MySQL 8.0 REGEXP_REPLACE:
select cast(REGEXP_REPLACE(customer_id,'[^0-9]','') AS UNSIGNED) FROM customers;
db<>fiddle demo

Related

Hash the result from the database , Is it possible?

I don't know if the question wording is correct or not, but I want to do the following:
I have a table named sales It contain following columns:
------------------------------------------------
| PRODUCT_NAME | PRODUCT_QUANTITY | ExpierDate |
------------------------------------------------
if I SELECT * FROM sales then the result will be :
------------------------------------------------
| PRODUCT_NAME | PRODUCT_QUANTITY | ExpierDate |
------------------------------------------------
| TestName | 5 | 2021-6-12 |
| TestName | 2 | 2024-10-18 |
------------------------------------------------
What I need to do is to select query and get the result look like this :
------------------------------------------------
| PRODUCT_NAME | PRODUCT_QUANTITY | ExpierDate |
------------------------------------------------
| TestName | 1 | 2021-6-12 |
| TestName | 2 | 2021-6-12 |
| TestName | 3 | 2021-6-12 |
| TestName | 4 | 2021-6-12 |
| TestName | 5 | 2021-6-12 |
| TestName | 1 | 2024-10-18 |
| TestName | 2 | 2024-10-18 |
------------------------------------------------
Is this even possible ?
How can I do this..!?
I would recommend directly using a recursive CTE:
with recursive cte as (
select product_name, product_quantity, expire_date, 1 as n
from sales s
union all
select product_name, product_quantity, expire_date, n + 1
from cte
where n < product_quantity
)
select *
from cte
order by product_name, product_quantity, expire_date, n;
Here is a db<>fiddle.
As I mention in comment, with cte create pseudo data to fill up the row is really easy:
with RECURSIVE quan(quantity) AS (
SELECT 1
UNION ALL
SELECT quantity+1 FROM quan WHERE quantity < 10 --you may have to increase this
)
SELECT tb.PRODUCT_NAME ,quan.quantity as PRODUCT_QUANTITY,tb.ExpierDate
FROM [tb] -- your result table
JOIN quan on tb.PRODUCT_QUANTITY >= quan.quantity
ORDER BY tb.time,quan.quantity
here is db<>fiddle with pseudo data.
using int column as time column in pseudo data but I think query itself will still work.
In recursive cte you may have to increase the where part to create more pseudo quantity data depend on your max quantity.

Selecting the most popular destination of my database on mysql

I've created 3 tables:
CREATE TABLE participe
(
IDadherent INTEGER,
IDsortie INTEGER,
CONSTRAINT pk2 PRIMARY KEY (IDadherent, IDsortie)
);
CREATE TABLE sortie
(
IDsortie INTEGER PRIMARY KEY,
jour DATE,
Latitude_sortie FLOAT,
Longitude_sortie FLOAT
);
CREATE TABLE adherent
(
IDadherent INTEGER PRIMARY KEY,
nom VARCHAR(30),
prenom VARCHAR(30)
);
TL;DR:
Table 1) Adherent (this is the people potentially going to a set of destinations with adherentID as a primary key)
Table 2) Sortie (this is the table with the potential destinations with destinationID as a primary key)
Table 3) Participe (this table links both primary keys: AdherentID and destinationID
If I select the content of the table "participe" I get something like this:
+----------+------------+
| IDsortie | IDadherent |
+----------+------------+
| 5 | 1 |
| 5 | 3 |
| 5 | 5 |
| 4 | 2 |
| 3 | 1 |
| 3 | 4 |
| 3 | 5 |
| 2 | 3 |
| 2 | 5 |
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 0 | 6 |
+----------+------------+
I've attempted to order the above table and get a new table with the most popular destinations (IDsortie), expecting to get something like this:
+----------+----------------+
| IDsortie | Numeroadherent |
+----------+----------------+
| 5 | 3 |
| 4 | 1 |
| 3 | 3 |
| 2 | 2 |
| 1 | 3 |
| 0 | 1 |
+----------+----------------+
In order to achieve that glorious table I've used these queries (and failed miserably):
SELECT IDsortie, IDadherent
FROM participe
ORDER BY IDsortie DESC;
SELECT COUNT(IDsortie)
FROM participe
GROUP BY IDadherent
ORDER BY COUNT(IDadherent) ASC;
SELECT COUNT(IDsortie)
FROM
(SELECT COUNT(*) AS IDsortie
FROM participe
GROUP BY IDadherent) AS Results
One of the mysql mods has very kindly redirected me to other similar questions but I don't understand their answers, could someone please walk e through this (sorry for the inconvenience).
It looks like you want a single query:
SELECT IDsortie, COUNT(*) as Numeroadherent
FROM participe
GROUP BY IDsortie DESC
ORDER BY IDsortie DESC;
I'm not quite sure what your queries have to do with answering the question. Hence, it is unclear what your confusion is. The answer to your question is a simple GROUP BY query.
Try using the below query,
SELECT IDSortie
, COUNT(IDSortie) [Numeroadherent]
FROM Participe
GROUP
BY IDSortie
ORDER
BY IDSortie;

Mysql - Check if VARCHAR column has a missing value on its incrementation

I'm trying to find out if my values inserted are auto-incrementing correctly or if for any reason one has failed to be inserted, deleted or gone "missing". I've tried several answers from Stackoverflow but they were mainly pointing out autoincrementable int values so they did not help since mine is a VARCHAR value that follows the following sequence:
AA000001
AA000002
...
AA000100
...
AA213978
and so on...
Thanks for your time.
You can declare SQL Vars in Query and calculate the difference in each iteration, as shown in the example below:
Schema
create table MyTable
( ai int auto_increment primary key,
id varchar(100) not null
);
insert MyTable (id) values
('AA000001'),
('AA000002'),
('AA000005'),
('AA000008'),
('AA000009'),
('AA000010');
Query
select id
FROM
(
select
t.id,
SUBSTRING(t.id,3) as s,
CAST(SUBSTRING(t.id,3) AS UNSIGNED) - #lastId as diff,
if( #lastId = 0, 0, CAST(SUBSTRING(t.id,3) AS UNSIGNED) - #lastId) as Difference,
#lastId := CAST(SUBSTRING(t.id,3) AS UNSIGNED) as dummy
from
`MyTable` t,
( select #lastId := 0) SQLVars
order by
t.id
) d
WHERE diff>1;
This is the inside query (not the final result set of the above)
+----------+--------+------+------------+-------+
| id | s | diff | Difference | dummy |
+----------+--------+------+------------+-------+
| AA000001 | 000001 | 1 | 0 | 1 |
| AA000002 | 000002 | 1 | 1 | 2 |
| AA000005 | 000005 | 3 | 3 | 5 |
| AA000008 | 000008 | 3 | 3 | 8 |
| AA000009 | 000009 | 1 | 1 | 9 |
| AA000010 | 000010 | 1 | 1 | 10 |
+----------+--------+------+------------+-------+
Actual Results of Above Query:
+----------+
| id |
+----------+
| AA000005 |
| AA000008 |
+----------+
Here's the SQL Fiddle.
To simply test if there are missing values,
select count(*) <> max(right(col, 6))-min(right(col, 6))+1 || count(*) <> count(distinct col)

sum count as a column in mysql

I have a table:
id | type | subtype
how shall I create a query to output as following
type1 | subtype1 | count-subtype1 | count-type1
type1 | subtype2 | count-subtype2 | count-type1
type2 | subtype3 | count-subtype3 | count-type2
type2 | subtype4 | count-subtype4 | count-type2
Namely subtotal as a column in output.
With no "WITH ROLLUP"
To awnser this query sucessfully (and this is where some awsers fails) is that you need to know what a roollup does. If you don't want to perform a "manual" sql rollup, there is another answer around which solves your query.
what do you need is two queries, one to count the subtypes within the types and another to count the types.
first count the subtypes (and lets call this query s).
select count(*) count_subtype, type, subtype from Foo group by type, subtype;
and another query to count the types (and lets call this query t).
select count(*) count_type, type from Foo froup by type
and now you need to merge the two queries:
select t.type, s.subtype, s.count_subtype, t.conttype from
(select count(*) count_subtype, type, subtype from Foo group by type, subtype) as s
join
(select count(*) count_type, type from Foo froup by type) as t
on (t.type=s.type);
Assuming that I have this structure of table:
CREATE TABLE `test` (
`id` int(11) NOT NULL auto_increment,
`type` varchar(128) default NULL,
`subtype` varchar(128) default NULL,
KEY `id` (`id`));
And this data:
INSERT INTO `test` VALUES (1,'a','1'),(2,'a','2'),(3,'a','3'),(4,'a','4'),(5,'b','4'),
(6,'c','4'),(7,'c','1'),(8,'c','2'),(9,'c','2');
I can do this:
SELECT test.type, test.subtype, count(test.subtype) as countsubtype, testbytype.counttype
FROM (test)
LEFT JOIN (SELECT type, count(type) AS counttype FROM test group by type) AS testbytype ON test.type = testbytype.type
GROUP by type, subtype;
+------+---------+--------------+-----------+
| type | subtype | countsubtype | counttype |
+------+---------+--------------+-----------+
| a | 1 | 1 | 4 |
| a | 2 | 1 | 4 |
| a | 3 | 1 | 4 |
| a | 4 | 1 | 4 |
| b | 4 | 1 | 1 |
| c | 1 | 1 | 4 |
| c | 2 | 2 | 4 |
| c | 4 | 1 | 4 |
+------+---------+--------------+-----------+
Query:
SELECT type, subtype, sum(type), sum(subtype) from table_name GROUP BY id

query to fetch records and their rank in the DB

I have a table that holds usernames and results.
When a user insert his results to the DB, I want to execute a query that will return
the top X results ( with their rank in the db) and will also get that user result
and his rank in the DB.
the result should be like this:
1 playername 4500
2 otherplayer 4100
3 anotherone 3900
...
134 current player 140
I have tried a query with union, but then I didnt get the current player rank.
ideas anyone?
The DB is MYSQL.
10x alot and have agreat weekend :)
EDIT
This is what I have tried:
(select substr(first_name,1,10) as first_name, result
FROM top_scores ts
WHERE result_date >= NOW() - INTERVAL 1 DAY
LIMIT 10)
union
(select substr(first_name,1,10) as first_name, result
FROM top_scores ts
where first_name='XXX' and result=3030);
SET X = 0;
SELECT #X:=#X+1 AS rank, username, result
FROM myTable
ORDER BY result DESC
LIMIT 10;
Re your comment:
How about this:
SET X = 0;
SELECT ranked.*
FROM (
SELECT #X:=#X+1 AS rank, username, result
FROM myTable
ORDER BY result DESC
) AS ranked
WHERE ranked.rank <= 10 OR username = 'current';
Based on what I am reading here:
Your table structure is:
+--------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| name | varchar(50) | YES | | NULL | |
| result | int(11) | YES | | NULL | |
+--------+-------------+------+-----+---------+-------+
Table Data looks like:
+---------+--------+
| name | result |
+---------+--------+
| Player1 | 4500 |
| Player2 | 4100 |
| Player3 | 3900 |
| Player4 | 3800 |
| Player5 | 3700 |
| Player6 | 3600 |
| Player7 | 3500 |
| Player8 | 3400 |
+---------+--------+
You want a result set to look like this:
+------+---------+--------+
| rank | name | result |
+------+---------+--------+
| 1 | Player1 | 4500 |
| 2 | Player2 | 4100 |
| 3 | Player3 | 3900 |
| 4 | Player4 | 3800 |
| 5 | Player5 | 3700 |
| 6 | Player6 | 3600 |
| 7 | Player7 | 3500 |
| 8 | Player8 | 3400 |
+------+---------+--------+
SQL:
set #rank = 0;
select
top_scores.*
from
(select ranks.* from (select #rank:=#rank+1 AS rank, name, result from ranks) ranks) top_scores
where
top_scores.rank <= 5
or (top_scores.result = 3400 and name ='Player8');
That will do what you want it to do
assuming your table has the following columns:
playername
score
calculated_rank
your query should look something like:
select calculated_rank,playername, score
from tablename
order by calculated_rank limit 5
I assume you have PRIMARY KEY on this table. If you don't, just create one. My table structure (because you didn't supply your own) is like this:
id INTEGER
result INTEGER
first_name VARCHAR
SQL query should be like that:
SELECT #i := #i+1 AS position, first_name, result FROM top_scores, (SELECT #i := 0) t ORDER BY result DESC LIMIT 10 UNION
SELECT (SELECT COUNT(id) FROM top_scores t2 WHERE t2.result > t1.result AND t2.id > t1.id) AS position, first_name, result FROM top_scores t1 WHERE id = LAST_INSERT_ID();
I added additional condition into subquery ("AND t2.id > t1.id") to prevent multiple people with same result having same position.
EDIT: If you have some login system, it would be better to save userid with result and get current user result using it.