Find first unused number in MySQL table - mysql

I am looking for a solution to find an unused number in the table. The most of the solutions I came across so far is creating a temporary table with all the numbers and used left join to find the unused number. In my case, I have no opportunity to create a temporary table.
The number range with leading zeros: 0001-1999. These numbers are dialing pad numbers and it has to be 4 digits in length.
listings table:
+----+--------+------------+
| id | title | pad_number |
+----+--------+------------+
| 1 | Foo | 0001 |
| 2 | bar | 0005 |
| 3 | Baz | 1999 |
| 10 | FooBar | 0002 |
+----+--------+------------+
Expected result:
0003
Is there any way to retrieve the number?

I would do this as:
select lpad(min(pad_number) + 1, 4, '0')
from t
where not exists (select 1 from t t2 where t2.pad_number = t.pad_number + 1);

Use not exists.
select lpad(cast(cast(pad_number as unsigned)+1 as char(4)),4,'0')
from tbl t
where not exists (select 1 from tbl t1
where cast(t.pad_number as unsigned)=cast(t1.pad_number as unsigned)-1)
and cast(pad_number as unsigned) >=0 and cast(pad_number as unsigned) < 1999
order by 1
limit 1

Related

MySQL query to find the count of matches in a column for every value in a given parameter list

In MySQL 8.0.16, say I have a table like this:
CREATE TABLE example (letter VARCHAR(10));
INSERT INTO example VALUES ('v'),('w'),('x'),('y'),('z');
> SELECT * FROM example;
+--------+
| letter |
+--------+
| v |
| w |
| x |
| y |
| z |
+--------+
I am trying to create a query that represents the question:
"For each of the values in a given arbitrary list, tell me how many times those values occur in the example.letter column".
Within the given list, some subset of values may or may not occur in the example.letter column.
As an example, say we have the list ('a','b','x','y'). Then I'm looking for the output:
> /* some query */
+--------+----------+
| letter | count |
+--------+----------+
| a | 0 |
| b | 0 |
| x | 1 |
| y | 1 |
+--------+----------+
I can get the x,y part, but I can't seem to figure out how to get counts for items that don't exist!
SELECT letter, COUNT(*)
FROM example
WHERE letter in ('a','b','x','y')
GROUP BY letter;
+--------+----------+
| letter | COUNT(*) |
+--------+----------+
| x | 1 |
| y | 1 |
+--------+----------+
/* I want the 0-counts for a and b to be here as well! */
Maybe this is surprisingly simple and possibly a duplicate due to me not searching for the right terms! Apologies if so!
You want a left join:
SELECT v.letter, COUNT(e.letter)
FROM (SELECT 'a' as letter UNION ALL
SELECT 'b' UNION ALL
SELECT 'x' UNION ALL
SELECT 'y'
) v(letter) LEFT JOIN
example e
ON e.letter = v.letter
GROUP BY v.letter;

How does this matrix multiply work in SQL?

Full disclosure, I'm a noob at SQL
Given two sparce matrices A and B, defined as:
A(row_number, column_number, value) and B(row_number, column_number, value)
I don't understand how this query represents the multiplication of the two matrices:
SELECT A.row_number, B.column_number, SUM(A.value * B.value)
FROM A, B
WHERE A.column_number = B.row_number
GROUP BY A.row_number, B.column_number
My confusion lies in the SUM syntax and the GROUP BY / SELECT syntax
So for my GROUP BY / SELECT confusion, I don't understand why the expressions
A.row_number and B.column_number are necessary after the SELECT statement
Why do we have to specify that when we're already using SELECT and WHERE ? To me that seems like we're saying we want to SELECT using those expressions (A.row_number and B.column_number) even though we're given back a table from WHERE already. Would it not make more sense to just say SELECT * ? I'm assuming that GROUP BY just requires you to type out the expressions it uses in the SELECT statement, but I don't know for sure.
For the SUM, I just want to clarify, the SUM is only using the A.value and the B.value from whatever is returned by the WHERE correct? Otherwise, you would be multiplying all A.value with all B.value.
Clarifying either of these would be immensely helpful. Thank you!
create table A
( column_number int,
row_number int,
value int
);
create table B
( column_number int,
row_number int,
value int
);
insert A (column_number,row_number,value) values (1,1,1),(1,2,2),(2,1,3),(2,2,4);
insert B (column_number,row_number,value) values (1,1,10),(1,2,20),(2,1,30),(2,2,40);
Data with your old style (non explicit) join without aggregage or group by:
SELECT A.row_number as Ar, B.column_number as Bc,
A.value as Av,B.value as Bv,A.value*B.value as product
FROM A, B
WHERE A.column_number = B.row_number
+------+------+------+------+---------+
| Ar | Bc | Av | Bv | product |
+------+------+------+------+---------+
| 1 | 1 | 1 | 10 | 10 |
| 2 | 1 | 2 | 10 | 20 |
| 1 | 1 | 3 | 20 | 60 |
| 2 | 1 | 4 | 20 | 80 |
| 1 | 2 | 1 | 30 | 30 |
| 2 | 2 | 2 | 30 | 60 |
| 1 | 2 | 3 | 40 | 120 |
| 2 | 2 | 4 | 40 | 160 |
+------+------+------+------+---------+
Seeing the above, the below gets a little more clarity:
SELECT A.row_number, B.column_number,sum(A.value * B.value) as theSum
FROM A, B
WHERE A.column_number = B.row_number
GROUP BY A.row_number, B.column_number
+------------+---------------+--------+
| row_number | column_number | theSum |
+------------+---------------+--------+
| 1 | 1 | 70 |
| 1 | 2 | 150 |
| 2 | 1 | 100 |
| 2 | 2 | 220 |
+------------+---------------+--------+
Giving table name after SELECT will identify which table to refer to. Mainly useful in the case where both tables have same column names.
GROUP BY will aggregate the data and display one record per grouped-by value. That is, in your case, you'll end up with only one record per row-column combination.
By definition multiplication of two matrices A(n,m) and B(m,p) produces a matrix C(n,p).
So the SQL for multiplication should return same data structure as was used for storage of A and B, which is three columns:
row_number
column_number
value
, with one value per (row, column) combination.
This is why you need first two in the group by clause.
WHERE clause is independent from SELECT. First is responsible for getting the right records, second for getting the right columns.

MySQL Combining Tables Specific Order

This seems to be a convoluted problem, but I'll try my best to articulate the idea and illustrate a scenario. Essentially I have two tables that need to be combined and returned as the result set for a single query. One table needs to be merged into the other in a specific order.
Say table one is called Articles and table two is called Features. Both tables have an ID field with unique numbers. Articles has a date field which will be used to initially sort its records in descending order. The Features table has a Delta field which be used initially to sort its records. Some of the records in the Features table are placeholders and are not meant to be included in the final set. Their only purpose is to affect the sort order. Each record has a unique value in the Delta field, from 1 - X which will be used to sort these records. Another field called Skip has a value of 1 if it should be eliminated when merging the two tables together. Again, the only purpose to the skipped records is to take up space during the initial sort on the Features table. Even though they are unnecessary, they exist and can't be deleted.
The tricky part is that when the results from both tables are merged, any non-skipped records from the Features table need to be inserted into the results from the Articles table in the exact order they appears in the Features table.
So lets say I have 6 records in the Features table, A - F and the order field ranges from 1 - 6. Records A,B,D,E all have a value of 1 in the Skip field. That means I'm only interested in records C and F both of which need to be inserted into the final record set in positions 3 and 6 respectively.
The records may look something like this for the Articles table:
+----+------------+
| id | date |
+----+------------+
| 1 | 9999999999 |
+----+------------+
| 2 | 9999999998 |
+----+------------+
| 3 | 9999999997 |
+----+------------+
| 4 | 9999999996 |
+----+------------+
| 5 | 9999999995 |
+----+------------+
| 6 | 9999999994 |
+----+------------+
| 7 | 9999999993 |
+----+------------+
| 8 | 9999999992 |
+----+------------+
| 9 | 9999999991 |
+----+------------+
| 10 | 9999999990 |
+----+------------+
The Features table may look something like this:
+----+------+-------+------+
| id | name | delta | skip |
+----+------+-------+------+
| 11 | A | 1 | 1 |
+----+------+-------+------+
| 12 | B | 2 | 1 |
+----+------+-------+------+
| 13 | C | 3 | 0 |
+----+------+-------+------+
| 14 | D | 4 | 1 |
+----+------+-------+------+
| 15 | E | 5 | 1 |
+----+------+-------+------+
| 16 | F | 6 | 0 |
+----+------+-------+------+
The results would look something like this (not including any additional fields that might be needed to achieve my goal):
+----+
| id |
+----+
| 1 |
+----+
| 2 |
+----+
| 13 | (record from the Features table in the third position)
+----+
| 3 |
+----+
| 4 |
+----+
| 16 | (record from the Features table in the sixth position)
+----+
| 5 |
+----+
| 6 |
+----+
| 7 |
+----+
| 8 |
+----+
| 9 |
+----+
| 10 |
+----+
Hope my explanation makes sense. Any ideas?
Thanks,
Howie
I assume that there is a mistake in your example - record id=16 is sixth row in Features table, so should be after id=5 in results, not before.
Try the blelow query. Here is SQLFiddle.
select id from (
select `date`, null delta, id
from Articles
union all
select a.`date`, f.delta, f.id
from (
select (#x:=#x+1) rn, a.*
from Articles a, (select #x:=0) z
order by a.`date` desc
) a
join (
select (#y:=#y+1) rn, f.id, f.delta, f.skip
from Features f, (select #y:=0) z
order by f.delta
) f
on a.rn = f.rn
where f.skip <> 1
order by `date` desc, isnull( delta ), delta
) merge
Looks like this example in SQL Fiddle did it for me.
SELECT id, sort_order FROM (
SELECT `date`, NULL delta, id, (#a_count:=#a_count+1) sort_order
FROM Articles a_main, (SELECT #a_count:=-1) z
UNION ALL
SELECT a.`date`, f.delta, f.id, f.weighted_rn
FROM (
SELECT (#x:=#x+1) rn, a.*
FROM Articles a, (SELECT #x:=-1) z
ORDER BY a.`date` DESC
) a
JOIN (
SELECT (#y:=#y+1) rn, TRUNCATE((f.delta - #y - (1/#y)),2) AS weighted_rn, f.id, f.delta, f.skip
FROM Features f, (SELECT #y:=-1) z
WHERE f.skip <> 1
ORDER BY f.delta
) f
ON a.rn = f.rn
ORDER BY sort_order
) merge
Thanks to Kordirko for the framework.

MySql sort ascendingly conditionally

Trying to sort rows from lowest to highest continually, or rather repeatedly using MySql. For example: if a column has the following values: 1,3,2,4,2,1,4,3,5, then it should end up like this 1,2,3,4,5,1,2,3,4. So it goes from lowest to highest, but tries to sort again from lowest to highest multiple times.
For large sets, the semi-JOIN operation (the approach in the answer from Strawberry) may create an unwieldy resultset. (Then again, MySQL may have some optimizations in there.)
Another alternative available in MySQL is to use "user variables", like this:
SELECT r.mycol
FROM ( SELECT IF(q.mycol=#prev,#seq := #seq + 1,#seq := 1) AS seq
, #prev := q.mycol AS mycol
FROM mytable q
JOIN (SELECT #prev := NULL, #seq := NULL) p
ORDER BY q.mycol
) r
ORDER BY r.seq, r.mycol
Let me unpack that a bit, and explain what it's doing, starting with the inner query (inline view aliased as r.) We're telling MySQL to get the column (mycol) containing the values you want to sort, e.g. 1,3,2,4,2,1,4,3,5 and we're telling MySQL to order these in ascending sequence: 1,1,2,2,3,3,4,4,5.
The "trick" now is to use a MySQL user variable, so that we can compare the mycol value from the current row to the mycol value from the previous row, and we use that to assign an ascending sequence value, from 1..n on each distinct value.
With that resultset, we can tell MySQL to order by that assigned sequence value first, and then by the value from mycol.
If there is a unique id on each row, then a correlated subquery can be used to get an equivalent result (although this approach is very unlikely to perform as well on large sets)
SELECT r.mycol
FROM mytable r
ORDER
BY ( SELECT COUNT(1)
FROM mytable q
WHERE q.mycol = r.mycol
AND q.id <= r.id
)
, r.mycol
Here's the setup for the test case:
CREATE TABLE mytable (id INT, mycol INT);
INSERT INTO mytable (id, mycol) VALUES
(1,1),(2,3),(3,2),(4,4),(5,2),(6,1),(7,4),(8,3),(9,5);
there is no order two time just this
ORDER BY column ASC
Let's pretend that the PK is a unique integer. Consider the following...
CREATE TABLE seq(id INT NOT NULL PRIMARY KEY,val INT);
INSERT INTO seq VALUES (8,1),(4,2),(1,3),(2,4),(7,0),(6,1),(3,2),(5,5);
SELECT * FROM seq ORDER BY val;
+----+------+
| id | val |
+----+------+
| 7 | 0 |
| 6 | 1 |
| 8 | 1 |
| 3 | 2 |
| 4 | 2 |
| 1 | 3 |
| 2 | 4 |
| 5 | 5 |
+----+------+
SELECT x.*
, COUNT(*) rank
FROM seq x
JOIN seq y
ON y.val = x.val
AND y.id <= x.id
GROUP
BY id
ORDER
BY rank
, val;
+----+------+------+
| id | val | rank |
+----+------+------+
| 7 | 0 | 1 |
| 6 | 1 | 1 |
| 3 | 2 | 1 |
| 1 | 3 | 1 |
| 2 | 4 | 1 |
| 5 | 5 | 1 |
| 8 | 1 | 2 |
| 4 | 2 | 2 |
+----+------+------+

Expanding a mySQL table using only mySQL

Let's say I have a mySQL table whose structure is like this:
mysql> select * from things_with_stuff;
+----+---------+----------+
| id | counter | quantity |
+----+---------+----------+
| 1 | 101 | 1 |
| 2 | 102 | 2 |
| 3 | 103 | 3 |
+----+---------+----------+
My goal is to "expand" the table so I end up with something like:
mysql> select * from stuff;
+----+---------+
| id | counter |
+----+---------+
| 1 | 101 |
| 2 | 102 |
| 3 | 102 |
| 4 | 103 |
| 5 | 103 |
| 6 | 103 |
+----+---------+
And I want to do this "expansion" using only mysql. Note that I end up with a row per quantity and per counter. Any suggestions? A stored procedure is not an option here (I know they offer while loops).
Thanks!
The following will do the trick as long as some_large_table has a length greater than or equal to the largest quantity in things_with_stuff. For example, let some_large_table be a big fact table in a data warehouse.
SELECT #kn:=#kn+1 AS id, counter
FROM (SELECT #kn:=0) k, things_with_stuff ts
INNER JOIN (
SELECT #rn:=#rn+1 AS num
FROM (SELECT #rn:=0) t, some_large_table
) nums ON num <= ts.quantity;
Assuming there is a maximum value for quantity, you could do:
INSERT INTO things SELECT counter FROM things_with_stuff WHERE quantity > 0;
INSERT INTO things SELECT counter FROM things_with_stuff WHERE quantity > 1;
INSERT INTO things SELECT counter FROM things_with_stuff WHERE quantity > 2;
--... and so on until the max.
It's a bit of a hack but it should do the job.
If the ordering is important you could do a clean up afterwards.
I have sometimes in databases a table named num (number) with a single column i, filled with all integers from 1 to 1000000. It's not hard to make such a table and populate it.
Then you could use this if stuff.id is auto incremented:
INSERT INTO stuff
( counter )
SELECT ts.counter
FROM things_with_stuff AS ts
JOIN num
ON num.i <= ts.quantity