How to generate a dynamic sequence table in MySQL? - mysql

I'm trying to generate a sequence table in MySQL, so that I can get unique ids from last_insert_id.
The problem is that I need multiple sequences dynamically.
At the first, I created a table:
CREATE TABLE `sequence` (
`label` char(30) CHARACTER SET latin1 NOT NULL,
`id` mediumint(9) NOT NULL DEFAULT '0',
PRIMARY KEY (`label`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8
And then tried to get the number, using example from http://dev.mysql.com/doc/refman/5.0/en/information-functions.html#function_last-insert-id
UPDATE sequence SET id = LAST_INSERT_ID(id + 1) WHERE label = 'test';
SELECT LAST_INSERT_ID();
After a while I realized that I also need to generate rows for new labels safely.
So I changed this schema into:
CREATE TABLE `sequence` (
`label` char(30) CHARACTER SET latin1 NOT NULL,
`id` mediumint(9) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`label`,`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8
And I simply gave up using WHERE clause to update its id.
INSERT INTO sequence (label) values ( ? )
SELECT LAST_INSERT_ID()
Is this a proper way? I want to know if there is a better solution.

The MyISAM engine will do it for you -
Table definition:
CREATE TABLE `sequence` (
`label` char(30) CHARACTER SET latin1 NOT NULL,
`id` mediumint(9) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`label`,`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
Populate table:
INSERT INTO sequence VALUES ('a', NULL); -- add some 'a' labels
INSERT INTO sequence VALUES ('a', NULL);
INSERT INTO sequence VALUES ('a', NULL);
INSERT INTO sequence VALUES ('b', NULL); -- add another labels 'b'
INSERT INTO sequence VALUES ('b', NULL);
INSERT INTO sequence VALUES ('a', NULL); -- add some 'a' labels
INSERT INTO sequence VALUES ('a', NULL);
Show result:
SELECT * FROM sequence;
+-------+----+
| label | id |
+-------+----+
| a | 1 |
| a | 2 |
| a | 3 |
| a | 4 |
| a | 5 |
| a | 6 |
| b | 1 |
| b | 2 |
+-------+----+

Related

how to increment column value by 1 and reset every 9 rows

I'm trying to increment column value by 1 but row number 10 reset and start with 1 like that
i have like more than 600k row
I tried call all the 600k row by php and update them but i stopped and give me some error
table sample:
CREATE TABLE `s1_table_play` (
`id` int(11) NOT NULL,
`player_id` int(11) DEFAULT 1,
`play_points` varchar(20) DEFAULT '0',
`original_points` varchar(20) DEFAULT '0',
`play_type` varchar(20) DEFAULT '0',
`created_at` timestamp NULL DEFAULT current_timestamp(),
`table_id` bigint(20) DEFAULT 0,
`play_number` int(11) DEFAULT 1
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `s1_table_play` (`id`, `player_id`, `play_points`, `original_points`, `play_type`, `created_at`, `table_id`, `play_number`) VALUES
(1, 1, '200', '0', '0', '2021-07-01 10:58:00', 67667, 1);
this the field "play_number" need to updated and reset every 10 rows
thanks
I am not really clear what your data looks like or when you want to reset but a trigger might be appropriate.
DROP TABLE IF EXISTS T;
create table t
(id int, playno int);
drop trigger if exists t;
delimiter $$
create trigger t before insert on t
for each row
begin
declare n int;
set n = (select playno from t where id < new.id order by id desc limit 1);
set n = coalesce(n+ 1,1);
if n >2 then set n = 1; end if;
set new.playno = n;
end $$
delimiter ;
insert into t(id) values (1),(2),(3),(4),(5);
select * from t;
+------+--------+
| id | playno |
+------+--------+
| 1 | 1 |
| 2 | 2 |
| 3 | 1 |
| 4 | 2 |
| 5 | 1 |
+------+--------+
5 rows in set (0.001 sec)

Null or empty instead of 0

I created a new table with:
CREATE TABLE `test`.`tab1` ( `id` INT NOT NULL AUTO_INCREMENT , `name` VARCHAR(50) NULL , `age` INT NULL , PRIMARY KEY (`id`));
When I insert a new row, but want to keep age empty, I do:
INSERT INTO `tab1` (`id`, `Name`, `Age`) VALUES (NULL, 'Peter', '');
I get this entry:
| id | name | age |
|----|-------|-----|
| 1 | Peter | 0 |
Why the age column just doesn't remain empty or NULL?
How can I set a default value, so that it just remains empty, when no value is specified while inserting?
Since the column is of type int the DB tries to convert the string '' to a number. That would be 0.
Just use null instead of '' if you don't have a value. That is what null is for.

MySQL JOIN Three Tables Using Row Values of A Table

This got complicated really quickly and I'm beginning to question the database design.
The basic concept of the application is:
User accounts
Features
Access levels
So, users have different access levels for each of the features. Fairly basic and common application I would think.
Schema:
CREATE TABLE `user_accounts` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_login` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`user_password` varchar(60) COLLATE utf8_unicode_ci NOT NULL,
`user_fname` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`user_lname` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`user_group` varchar(32) COLLATE utf8_unicode_ci NOT NULL DEFAULT 'Default',
PRIMARY KEY (`id`),
UNIQUE KEY `user_login` (`user_login`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ;
INSERT INTO `user_accounts` VALUES(1, 'email#example.com', 'secret', 'Example', 'Name', 'Admin');
INSERT INTO `user_accounts` VALUES(2, 'john#example.com', 'secret', 'John', 'Doe', 'Trainer');
INSERT INTO `user_accounts` VALUES(3, 'jane#example.com', 'secret', 'Jane', 'Doe', 'Default');
CREATE TABLE `user_access_meta` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`type` varchar(50) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `type` (`type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO `user_access_meta` VALUES(1, 'type_1');
INSERT INTO `user_access_meta` VALUES(2, 'type_2');
INSERT INTO `user_access_meta` VALUES(3, 'type_3');
INSERT INTO `user_access_meta` VALUES(4, 'type_4');
INSERT INTO `user_access_meta` VALUES(5, 'type_5');
INSERT INTO `user_access_meta` VALUES(6, 'type_6');
CREATE TABLE `user_access_levels` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_login` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`type` varchar(50) COLLATE utf8_unicode_ci NOT NULL,
`level` int(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `user_login_2` (`user_login`,`type`),
KEY `user_login` (`user_login`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ;
INSERT INTO `user_access_levels` VALUES(1, 'email#example.com', 'type_1', 1);
INSERT INTO `user_access_levels` VALUES(2, 'email#example.com', 'type_2', 1);
INSERT INTO `user_access_levels` VALUES(3, 'email#example.com', 'type_3', 0);
INSERT INTO `user_access_levels` VALUES(4, 'email#example.com', 'type_5', 2);
INSERT INTO `user_access_levels` VALUES(5, 'john#example.com', 'type_2', 1);
INSERT INTO `user_access_levels` VALUES(6, 'john#example.com', 'type_3', 1);
INSERT INTO `user_access_levels` VALUES(7, 'john#example.com', 'type_5', 3);
INSERT INTO `user_access_levels` VALUES(8, 'jane#example.com', 'type_4', 1);
These tables actually have a lot more fields and have foreign key constraints between them, but I've striped them down for this example. They are also used individually for other purposes.
I've successfully been able to join all three tables together for a single user with this:
SELECT
ua.`user_fname`,
uam.`type`,
ual.`level`
FROM `user_access_meta` uam
LEFT JOIN `user_access_levels` ual
ON ual.`user_login` = 'email#example.com'
AND uam.`type` = ual.`type`
JOIN `user_accounts` ua
ON ua.`user_login` = 'email#example.com';
Output:
| USER_FNAME | TYPE | LEVEL |
--------------------------------
| Example | type_1 | 1 |
| Example | type_2 | 1 |
| Example | type_3 | 0 |
| Example | type_4 | (null) |
| Example | type_5 | 2 |
| Example | type_6 | (null) |
Even this isn't ideal, but It's all I could come up with and it serves it's purpose.
Now, what I need to do is select all users including their access levels. It would look something like this:
| USER_FNAME | type_1 | type_2 | type_3 | type_4 | type_5 | type_6 |
--------------------------------------------------------------------------
| Example | 1 | 1 | 0 | (null) | 2 | (null) |
| John | (null) | 1 | 1 | (null) | 3 | (null) |
| Jane | (null) | (null) | (null) | 1 | (null) | (null) |
I feel this may not have been the best design, but the reason I went with this design is so that I can easily add and remove features or even temporarily disable them individually.
Should the design be rethought? Is it even possible to get the results I'm looking for with this design?
I've put this up on SQL Fiddle. http://sqlfiddle.com/#!2/bb313/2/0
I have a few suggestions on both your table design and then how to get the data in the format that you want.
First on the database design, the change I would advise is in the table user_access_levels. Alter you table to the following:
CREATE TABLE `user_access_levels` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`type_id` int(11) NOT NULL,
`level` int(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `user_id_2` (`user_id`,`type_id`),
KEY `user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ;
There is no need to store the user_login and type in this table when you can just store the user_id and the type_id. Use both of these as foreign keys to their respective tables.
Then to get the data in format that you want. MySQL does not have a PIVOT function so you will want to use a CASE statement with an aggregate function.
select ua.user_fname,
MIN(CASE WHEN uam.type = 'type_1' THEN ual.level END) type_1,
MIN(CASE WHEN uam.type = 'type_2' THEN ual.level END) type_2,
MIN(CASE WHEN uam.type = 'type_3' THEN ual.level END) type_3,
MIN(CASE WHEN uam.type = 'type_4' THEN ual.level END) type_4,
MIN(CASE WHEN uam.type = 'type_5' THEN ual.level END) type_5,
MIN(CASE WHEN uam.type = 'type_6' THEN ual.level END) type_6
FROM user_accounts ua
LEFT JOIN user_access_levels ual
ON ua.id = ual.user_id
LEFT JOIN user_access_meta uam
ON ual.type_id = uam.id
group by ua.user_fname
See a SQL Fiddle with a Demo
This version will work if you know ahead of time the type columns that you want to get the values for. But if it is unknown, then you can use prepared statements to generate this dynamically.
Here is a version of the query using prepared statements:
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'MIN(case when type = ''',
type,
''' then level end) AS ',
replace(type, ' ', '')
)
) INTO #sql
FROM user_access_meta;
SET #sql = CONCAT('SELECT ua.user_fname, ', #sql, ' FROM user_accounts ua
LEFT JOIN user_access_levels ual
ON ua.id = ual.user_id
LEFT JOIN user_access_meta uam
ON ual.type_id = uam.id
group by ua.user_fname');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
See a SQL Fiddle with Demo
While I'm not familiar with the specifics of MySQL, it seems to me you are describing a pretty fundamental example of a pivot table query. What you're looking for seems reasonable to me, so I don't think based on what you've shown here I'd get too concerned about revisiting the data model. You may find putting the "level" back with the "type" table, based on the ol' saw "Normalize til hit hurts, denormalize til it works :)"
Just my $0.02. Good luck.
I typically use a BIGINT column and use bit masking to set the values.
For example level1 = 2, level2=4, level3=8, level4=16, etc..
Give someone level1 and level2 access:
update user set access_level = 2 & 4
does someone have level2 access?
select 1 from user where access_level | 2 AND user_id = ?

MySQL - autoincrement to guid

I have a table with an auto-increment ID field as shown below.
+------------+-------------------------------------+
| company_id | name |
+------------+-------------------------------------+
| 1 | International Client |
| 2 | Oracle |
| 3 | test |
| 4 | testabc |
| 5 | testdef |
| 6 | abcd |
+------------+-------------------------------------+
I want to update the ID column to be a GUID using the uuid() function.
Additionally, how do I update the foreign key references to the correct GUID?
Use triggers.
CREATE TABLE `tbl_test` (
`GUID` char(40) NOT NULL,
`Name` varchar(50) NOT NULL,
PRIMARY KEY (`GUID`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
table and pk, now trigger..
DELIMITER //
CREATE TRIGGER `t_GUID` BEFORE INSERT ON `tbl_test`
FOR EACH ROW begin
SET new.GUID = uuid();
end//
DELIMITER ;
Now try,
insert into tbl_test(Name) value('trigger happy...');
regards,
/t
you can't use it with autoincrement
guid is char not intger
you need to insert it your self
also you will need to change the id to char(40)
insert into table_name (id,name) values (uuid(),'jon');
In mysql 8.0 you can use something like below:
For UUID without dashes:
create table temp (`company_id` VARCHAR(64) NOT NULL DEFAULT (REPLACE(UUID(),'-','')), 'name' varchar(100) NOT NULL);
For UUID with dashes:
create table temp (`company_id` VARCHAR(64) NOT NULL DEFAULT (UUID()), 'name' varchar(100) NOT NULL);
I hope this answers your question, Let me know if it is not or you need more.

MySQL filter query with relation

I'm having the following problem with 2 MySQL tables that have a relation:
I can easily query table 1 (address) when I want a full list or filter the result by name or email or such. But now I need to query table 1 and filter it based on the relational content of table 2 (interests). So, I need to find a row (usually many rows) in table 1 only if a (or more) conditions are met in table 2.
Here are the tables:
CREATE TABLE IF NOT EXISTS `address` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`email` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`countryCode` char(2) COLLATE utf8_unicode_ci DEFAULT NULL,
`languageCode` char(2) COLLATE utf8_unicode_ci DEFAULT NULL,
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `emailUnique` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
INSERT INTO `address` (`id`, `name`, `email`, `countryCode`, `languageCode`, `timestamp`) VALUES
(1, '', 'dummy#test.com', 'BE', 'nl', '2010-07-16 14:07:00'),
(2, '', 'test#somewhere.com', 'BE', 'fr', '2010-07-16 14:10:25');
CREATE TABLE IF NOT EXISTS `interests` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`address_id` int(11) unsigned NOT NULL,
`cat` char(2) COLLATE utf8_unicode_ci NOT NULL,
`subcat` char(2) COLLATE utf8_unicode_ci NOT NULL,
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `address_id` (`address_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
INSERT INTO `interests` (`id`, `address_id`, `cat`, `subcat`, `timestamp`) VALUES
(1, 1, 'aa', 'xx', '2010-07-16 14:07:00'),
(2, 1, 'aa', 'yy', '2010-07-16 14:07:00'),
(3, 2, 'aa', 'xx', '2010-07-16 14:07:00'),
(4, 2, 'bb', 'zz', '2010-07-16 14:07:00')
(5, 2, 'aa', 'yy', '2010-07-16 14:07:00');
ALTER TABLE `interests`
ADD CONSTRAINT `interests_ibfk_1` FOREIGN KEY (`address_id`) REFERENCES `address` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION;
For example, I need to find the address(es) that has (have) as interest cat=aa and subcat=xx. Or, another example, I need the address(es) with as interest both cat=aa and subcat=xx AND cat=aa and subcat=yy. Specially the latter is important and one has to keep in mind that both the address and the interest tables will be long lists and that the amount of cat/subcat combinations will vary. I'm working with reference queries through Zend_Db_Table (findDependentRowset) at the moment but that solution is way to slow for address lists numbering 100s and even 1000s of hits.
Thank you for your help.
SELECT a.name FROM address a
INNER JOIN interests i ON (a.id = i.address_id)
WHERE i.cat = "aa" AND i.subcat IN ('xx', 'yy')
I added another row in your interests table, to demonstrate a different result set between the two examples:
INSERT INTO interests VALUES (6, 2, 'aa', 'vv', '2010-07-16 14:07:00');
Then you may want to try using correlated subqueries as follows:
SELECT *
FROM address a
WHERE EXISTS (SELECT id
FROM interests
WHERE address_id = a.id AND
(cat = 'aa' and subcat = 'xx'));
Result:
+----+------+--------------------+-------------+--------------+---------------------+
| id | name | email | countryCode | languageCode | timestamp |
+----+------+--------------------+-------------+--------------+---------------------+
| 1 | | dummy#test.com | BE | nl | 2010-07-16 14:07:00 |
| 2 | | test#somewhere.com | BE | fr | 2010-07-16 14:10:25 |
+----+------+--------------------+-------------+--------------+---------------------+
2 rows in set (0.00 sec)
For the second example, we're testing for the new row added previously in order not to have the same result as above:
SELECT *
FROM address a
WHERE EXISTS (SELECT id
FROM interests
WHERE address_id = a.id AND
(cat = 'aa' and subcat = 'xx')) AND
EXISTS (SELECT id
FROM interests
WHERE address_id = a.id AND
(cat = 'aa' and subcat = 'vv'));
Result:
+----+------+--------------------+-------------+--------------+---------------------+
| id | name | email | countryCode | languageCode | timestamp |
+----+------+--------------------+-------------+--------------+---------------------+
| 2 | | test#somewhere.com | BE | fr | 2010-07-16 14:10:25 |
+----+------+--------------------+-------------+--------------+---------------------+
1 row in set (0.00 sec)
Using correlated subqueries is easy and straightforward. However keep in mind that it might not be the best in terms of performance, because the correlated subqueries will be executed once for each address in the outer query.