I am trying to create php script that generates 11,000,000 million unique ids in sequential order. However, I am trying to do it very quickly within 20 min it should generate these 11 million unique ids. Also, once it reaches 12,000,000 it should wrap around and start back at zero.
Here is what I have so far. This script would would only return one id at a time. I just added a loop to see how long it would take to generate the ids.
while(true){
try {
$this->getAdapter()->query('INSERT INTO generate_ids (assigned_id) SELECT (MAX(assigned_id)+1) FROM generate_ids');
$id = $this->getAdapter()->lastInsertId();
$sql = 'SELECT assigned_id FROM generate_ids WHERE id = $id';
$query = $this->getAdapter()->query($sql);
$result = $query->fetchAll();
//Live system would return id here
$assigned_id = $result[0]['assigned_id'];
} catch (Exception $e) {
//do nothing
}
if($count == 11000000){
die();
}
$count++;
}
}
If you create the following table:
CREATE TABLE sequence (
sequence_id BIGINT NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`sequence_id`)
)
Then issue these three queries one after the other:
INSERT INTO sequence () VALUES ();
DELETE FROM sequence WHERE sequence_id < LAST_INSERT_ID();
SELECT LAST_INSERT_ID() AS sequence;
The third query is guaranteed to return a unique sequence number. This guarantee holds even if you have dozens of different client programs connected to your database. That's the beauty of AUTO_INCREMENT.
Instead of just generating eleven million of these sequence numbers up front, you can use these SQL queries to get a unique sequence number whenever you need it.
If you must wrap around at sequence number 12 million you can use these queries instead.
INSERT INTO sequence () VALUES ();
DELETE FROM sequence WHERE sequence_id < LAST_INSERT_ID();
SELECT LAST_INSERT_ID() MOD 12000000 AS sequence;
The trick here is to use an auto-increment sequence number for uniqueness, but to also delete the rows in the table so it doesn't gobble up lots of space.
Note that you can also use the sequence number of LAST_INSERT_ID() for other purposes, like so for example.
INSERT INTO sequence () VALUES ();
DELETE FROM sequence WHERE sequence_id < LAST_INSERT_ID();
INSERT INTO user (userid, username, phone)
VALUES (LAST_INSERT_ID() MOD 12000000, 'Joe', '800-555-1212');
SELECT LAST_INSERT_ID() MOD 12000000 AS sequence;
If you need to insert all your ids at once (for some reason) the fastest approach would be to do it in pure SQL
insert into generate_ids (assigned_id)
select N
from
(
select a.N + b.N * 10 + c.N * 100 + d.N * 1000 + e.N * 10000 + f.N * 100000 + g.N * 1000000 + h.N * 10000000 + 1 as N
from (select 0 as N union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) a
,(select 0 as N union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) b
,(select 0 as N union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) c
,(select 0 as N union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) d
,(select 0 as N union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) e
,(select 0 as N union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) f
,(select 0 as N union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) g
,(select 0 as N union all select 1) h
order by N
) q
where N <= 11000000
It takes less than a minute to complete on my laptop.
Related
Hello i have mysql table which named numbers like this;
ID
Number
1
3002
2
3004
2000
7545
When i need to insert a new data products table i have to find first number which is not on this table between(3000 to 35000). I mean i need to find first number between this numbers not on numbers table. How can i find it?
$statement = $con->prepare("SELECT * FROM numbers");
$statement->execute();
$result = $statement->fetchAll(PDO::FETCH_ASSOC);
foreach($result as $row)
{
$arr1[] = $row['numbers'];
}
$arr2 = range(3000,35000);
$missing_numbers = array_diff($arr2,$arr1);
print_r($missing_numbers);
i tried array_diff but i gives results with different keys. When i write $missing_numbers[0] i want to see first missing number.
If you just want the first missing number, it may be easier to read the rows in number order and check for the first one which isn't in the sequence you are after. So a query (with the start and end points as parameters) order by the number and a counter which is what it's expecting to get on each row...
$startPoint = 3000;
$endPoint = 35000;
$statement = $db->prepare("SELECT number
FROM numbers
WHERE number >= :start
and number <= :end
order by number");
$statement->execute([
'start' => $startPoint,
'end' => $endPoint
]);
$expected = $startPoint;
while ($row = $statement->fetch()) {
if ( $row['number'] != $expected ) {
echo "Missing=" . $expected;
break;
}
$expected++;
}
I think your solution will work only thing missing is reordering of the indexes which can be done by using the array_values() function. Update your last lines of code as -
$missing_numbers = array_values(array_diff($arr2,$arr1));
print_r($missing_numbers);
so on printing $missing_numbers[0] you should get the first missing number.
Easy, just need reset keys after array_diff:
...
$missing_numbers = array_values(missing_numbers);
print_r($missing_numbers);
IF you want do it only by using MySQL query and later get the result in PHP you need to do something like this :
Create temporray table with your example :
create table test123(id integer, Number varchar(100))
#insert into test123 (ID, Number) values (1, '3002');
#insert into test123 (ID, Number) values (2, '3004');
#insert into test123 (ID, Number) values (2000, '7545');
CREATE TEMPORARY TABLE WITH RANGE 3000 - 35000
CREATE TEMPORARY TABLE IF NOT EXISTS tableTest AS (
SELECT #row := #row + 1 AS row FROM
(select 0 union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) t,
(select 0 union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) t2,
(select 0 union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) t3,
(select 0 union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) t4,
(select 0 union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) t5,
(SELECT #row:=3000) numbers WHERE #row < 35000
)
AND FINALLY you can just select numbers that's not inside your table with data
select * from tableTest t where t.row not in ( select Number from test123)
The output will be select all numbers as you wanted.
You must fill out the form table 1 to 1,000,000:
Table view
CREATE TABLE `test` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT , `value` INT UNSIGNED NOT NULL , PRIMARY KEY (`id`)) ENGINE = InnoDB;"
I wrote a function to do this, but adding data to the table is too slow, how can I improve the performance of insert data?
function InsertData(){
global $MySQL;
for($i = 1; $i != 1000000; $i++){
$MySQL->query("INSERT INTO `name` (`id`, `value`) VALUES ($i, $i);");
}
$MySQL->close();
}
You could use Transactions in order to only commit once every thousands inserts (or, if you are brave, after the million of queries). Here is the (brave) example:
function InsertData(){
global $MySQL;
// Start transactions
$MySQL->query('SET autocommit=0;');
$MySQL->query('START TRANSACTION;');
for($i = 1; $i != 1000000; $i++){
$MySQL->query("INSERT INTO `name` (`id`, `value`) VALUES ($i, $i);");
}
// So far, nothing as actually been saved to database
// Commit all inserts.
$MySQL->query('COMMIT;');
$MySQL->query('SET autocommit=1;');
$MySQL->close();
}
If this is too much for one single transactions due to some MySQL limit, you could perform the Commit every 10.000 inserts or so:
function InsertData(){
global $MySQL;
// Start transactions
$MySQL->query('SET autocommit=0;');
$MySQL->query('START TRANSACTION;');
for($i = 1; $i != 1000000; $i++){
$MySQL->query("INSERT INTO `name` (`id`, `value`) VALUES ($i, $i);");
if($i % 10000 == 0) {
$MySQL->query('COMMIT;');
$MySQL->query('START TRANSACTION;');
}
}
// So far, nothing as actually been saved to database
// Commit all inserts.
$MySQL->query('COMMIT;');
$MySQL->query('SET autocommit=1;');
$MySQL->close();
}
Pay attention to the eventual limit -> https://stackoverflow.com/a/2298325/2814721
And, of course, this is intended to be an experiment or one-shot script. Not advised to do in a production database.
How about moving the logic to the database, using a recursive CTE?
insert into name (id, value)
with recursive cte as (
select 1 id
union all select id + 1 from cte where i < 1000000
)
select id, id from cte
That may be to many rows to generate at once with recursion. An alternative is to generate just 10 rows, then multiply the rows:
insert into name (id, value)
with recursive cte as (
select 0 id
union all select id + 1 from cte where i < 9
)
select id, id
from (
select 1 + c0.id + c1.id * 10 + c2.id * 100 + c3.id * 1000 + c4.id * 10000 + c5.id * 100000 id
cte c0
cross join cte c1
cross join cte c2
cross join cte c3
cross join cte c4
cross join cte c5
) t
Try this query:
INSERT INTO `test` (`id`, `value`)
SELECT #row := #row + 1 AS row, #row
FROM (SELECT 0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) AS t1,
(SELECT 0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) AS t2,
(SELECT 0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) AS t3,
(SELECT 0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) AS t4,
(SELECT 0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) AS t5,
(SELECT 0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) AS t6,
(SELECT #row:=0) AS nums;
It's a "INSERT INTO... SELECT..." type of statement in which the SELECT statement itself is generating a million rows filled with pairs (1,1), (2,2), etc. Here is how it works:
The tables t1, t2, t3, t4, t5, t6 are 10 rows each. Cross joining them generates 10^6 = 1000000 combinations, so the resulting table will be with million rows;
For each of those rows, we SELECT the #row variable twice. And not only that but we also increment it with 1;
The nums table is used only to initialize the variable to 0 at the very beginning;
The resulting table is passed to the INSERT statement and the data is stored in the table.
A cleaner looking solution is to use recursive CTE with newer MySQL/MariaDB. It's the one that was submitted by user GMB:
INSERT INTO test (id, value)
WITH RECURSIVE temp AS (
SELECT 1 AS row
UNION SELECT row + 1
FROM temp
WHERE row < 1000000
)
SELECT row, row
FROM temp;
Based on my tests, it was a bit slower. I didn't monitor memory usage.
Mysql Table:
In My facility table is this
facility_name mbid date
yoga,aerobics,table tennis,tai chi, OM1111 2016-06-12
aerobics,tai chi, OM1111 2016-06-12
How to split row value one by one with mbid in mysql:
Facility_name mbid Number of count
yoga OM1111 1
aerobics OM1111 2
table tennis OM1111 1
tai chi OM1111 2
CREATE TABLE facility
(facility_name varchar(35), mbid varchar(6), date varchar(10))
;
INSERT INTO facility
(facility_name, mbid, date)
VALUES
('yoga,aerobics,table tennis,tai chi,', 'OM1111', '2016-06-12'),
('aerobics,tai chi,', 'OM1111', '2016-06-12')
;
Script :
Select T.VALUE,T.mbid,COUNT(T.VALUE)Cnt FROM (
SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(t.facility_name, ',', n.n), ',', -1) value,mbid
FROM facility t CROSS JOIN
(
SELECT a.N + b.N * 10 + 1 n
FROM
(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) a
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) b
ORDER BY n
) n
WHERE n.n <= 1 + (LENGTH(t.facility_name) - LENGTH(REPLACE(t.facility_name, ',', ''))))T
WHERE T.VALUE <> ''
GROUP BY T.VALUE,T.mbid
ORDER BY T.value
How to pass the date function in where condition to get count of activity :
Select facility.mbid,membership.name,membership.organization,
membership.designation,membership.division, facility.VALUE `Facility Name`,
COUNT(facility.VALUE)`Number of Activite` FROM ( SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(facility.facility_name, ',', n.n), ',', -1) value,mbid FROM facility CROSS JOIN
(
SELECT a.N + b.N * 10 + 1 n
FROM
(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) a
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) b
ORDER BY n
) n
WHERE n.n <= 1 + (LENGTH(facility.facility_name) -LENGTH(REPLACE(facility.facility_name, ',', ''))))T
facility Inner Join membership ON facility.mbid=membership.mbid
where facility.date Between '2016-06-04' and '2016-06-07' &&
facility.VALUE <> ''
GROUP BY facility.VALUE,facility.mbid ORDER BY facility.value
You have a very poor database structure. You should have one row per mbid and facility_name. In fact, I have no idea why mbid and date are the same in the two rows.
If I assume that you have facility names in another table, then you can use:
select fn.facility_name, f.mbid, count(*)
from facility f join
facility_names fn
on find_in_set(fn.facility_name, f.facility) > 0
group by fn.facility_name, f.mbid;
I should emphasize, though, that although you can do a query like this, you really need to fix your data structure. Storing lists of values in a string is the wrong way to store data in a SQL database.
I want to insert 0 to 10000000 (10 million) of primary keys into my table.
My table is as follow:
p_key--> (INT[10]) AUTO_INCREMENT PRIMARY KEY UNIQUE
hash_value--> (SMALLINT)
req_count--> (SMALLINT)
only adding 100 records is taking 11 seconds
for 500 records it is giving 30 second timeout has been crossed.(wamp server localhost php mysql)
for ($i=0;$i<100;$i++){
$query='INSERT INTO hash_table (hash_value, req_count) VALUES (0,0)';
$result=$con->query($query);
if(!$result){
echo ("Error message:".$con->error." \n");
exit();
}
}
Am I doing something terribly wrong.
You should insert more then one records at one SQL query.
For example:
$values='';
for ($i=0;$i<100;$i++){
$values.='(0,0),';
}
$values=substr($values,0,-1);
$query='INSERT INTO hash_table (hash_value, req_count) VALUES '.$values;
$result=$con->query($query);
if(!$result){
echo ("Error message:".$con->error." \n");
exit();
}
Another option, doing it in a single SQL statement:-
INSERT INTO hash_table (hash_value, req_count)
SELECT 0, 0
FROM
(SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9)units,
(SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9)tens,
(SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9)hundreds,
(SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9)thousands,
(SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9)tensthousands,
(SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9)hundredthousands,
(SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9)millions,
(SELECT 0 i UNION SELECT 1)tensmillions
WHERE (units.i +
tens.i * 10 +
hundreds.i * 100 +
thousands.i * 1000 +
tensthousands.i * 10000 +
hundredthousands.i * 100000 +
millions.i * 1000000 +
tensmillions.i * 10000000) BETWEEN 0 AND 10000000
I have the following details in MySQL as
user_id follow_user_id
1 2,3,3,3,3
5 1,2,3,3,3,3
6 1,2,3,3,3,3
i write the following code to get the unique code as follow:
SELECT LENGTH( follow_user_id ) - LENGTH( REPLACE( follow_user_id, ',', '' ) ) +1 AS no_of_follow FROM follow WHERE user_id =1;
But it provide the result:6
I need exactly unique rows: i.e:4
Apart from DB design questions you could use in PHP after fetching the row to $result:
count(array_unique(explode(",",$result["follow_user_id")));
$query="SELECT follow_user_id FROM follow WHERE user_id ='".$_POST['user_id']."' "; $query_run=mysql_query($query); $row= mysql_fetch_assoc($query_run);
$count= count(array_unique(explode(",",$row['follow_user_id'])));
$count;
This is better and faster ;)
count(array_flip(explode(",", $result["follow_user_id")));
Or doing it in SQL:-
SELECT COUNT(DISTINCT SUBSTRING_INDEX(SUBSTRING_INDEX(follow_user_id, ',', units.i + tens.i * 10), ',', -1) AS col1)
FROM sometable
CROSS JOIN (SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9)units
CROSS JOIN (SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9)tens
WHERE user_id = 1
(copes with up to 100 comma separated values).
But this would be so much easier with a properly normalised database design