Suppose there is a table with only two columns (an example is shown below). Every '1' entry should be followed (in the sorted order given below) by a '0'. However, as you can see, in the table, there are some 'orphans' where there are two consecutive '1's.
How can I create a query that returns all the rows, except for the first of any consecutive '1's? (This would reduce the example below from 16 rows to 14)
1 E
0 A
1 T
0 S
1 R
0 E
1 F
0 T
1 G
1 T
0 R
1 X
1 R
0 R
1 E
0 T
I'm going to try and clarify my problem, I think that above I simplified it too much. Imagine one table called logs, with four columns:
user (a string containing a username)
machine (a string uniquely identifying various PCs)
type (event's type: a 1 for login and a 0 for logout)
time (the time of the event being logged)
[The machine/time pair provides a unique key, as no machine can be logged in or out of twice at the same instant. Presumably an 'ID' column could be artificially created based on machine/time sort if needed.]
The idea is that every login event should be accompanied by a logout event. In an ideal word it would be fairly easy to match logins to logouts, and hence analyse the time spent logged in.
However, in the case of a power cut, the logout will not be recorded. Therefore (considering only one machine's data, sorted by time) if there are two login events in a row, we want to ignore the first login, because we don't have any reliable data from it. This is the problem I am trying to solve.
Provided, that
only 1's are dupes, never 0's
You want to get rid of all the first 1's if there are more.
Your text says "except for the first of any consecutive", but I think, this is what you want. Or there can only ever be 2, then it is the same.
SELECT x.*
FROM x
LEFT JOIN x y on y.id = (x.id + 1)
WHERE (x.nr = y.nr) IS NOT TRUE -- OR x.nr = 0
ORDER BY x.id
If you want to preserve double 0's, use the commented clause additionally, but probably not needed.
Edit after question edit:
You may want to add an auto-increment column to your data to make this simpler:
Generate (i.e. write) a row number index column in MySQL
Other RDBMS (PostgreSQL, Oracle, SQL Server, ..) have window functions like row_number() or lag() and lead() that make such an operation much easier.
Assuming you get an id (add column, set column id = record number in database) use:
select a.*
from the_table a
left join the_table b on b.id = a.id + 1
and b.col1 = 0
where a.col1 = 1
and b.id is null
Try:
select l.*
from logs l
where l.type = 0 or
not (select type
from (select * from logs order by `time` desc) n
where n.machine = l.machine and
n.user = l.user and
n.time > l.time)
group by () )
USING a CTE to separate the lag-logic from the selection criteria.
DROP TABLE tmp.bits;
CREATE TABLE tmp.bits
( id SERIAL NOT NULL
, bit INTEGER NOT NULL
, code CHAR(1)
);
INSERT INTO tmp.bits(bit, code) VALUES
(1, 'T' )
, (0, 'S' )
, (1, 'R' )
, (0, 'E' )
, (1, 'F' )
, (0, 'T' )
, (1, 'G' )
, (1, 'T' )
, (0, 'R' )
, (1, 'X' )
, (1, 'R' )
, (0, 'R' )
, (1, 'E' )
, (0, 'T' )
;
SET search_path='tmp';
SELECT * FROM bits;
-- EXPLAIN ANALYZE
WITH prevnext AS (
SELECT
bt.id AS thisid
, bt.bit AS thisbit
, bt.code AS thiscode
, bp.bit AS prevbit
, bp.code AS prevcode
FROM bits bt
LEFT JOIN bits bp ON (bt.id > bp.id)
AND NOT EXISTS ( SELECT * FROM bits nx
WHERE nx.id > bp.id
AND nx.id < bt.id
)
)
SELECT thisid, thisbit, thiscode
FROM prevnext
WHERE thisbit=0
OR prevbit IS NULL OR thisbit <> prevbit
;
EDIT:
for those poor soals that cannot use CTEs, it is easy to create a view instead:
CREATE VIEW prevnext AS (
SELECT
bt.id AS thisid
, bt.bit AS thisbit
,bt.code AS thiscode
, bp.bit AS prevbit
, bp.code AS prevcode
FROM bits bt
LEFT JOIN bits bp ON (bt.id > bp.id)
AND NOT EXISTS ( SELECT * FROM bits nx
WHERE nx.id > bp.id
AND nx.id < bt.id
)
)
;
SELECT thisid, thisbit, thiscode
FROM prevnext
WHERE thisbit=0
OR prevbit IS NULL OR thisbit <> prevbit
;
Related
I have a database with a table having content as below :
message_number message_type message_chat
0 IN Hi
1 OB Hello
2 IN Help
3 IN Want to find this thing
4 OB Sure
5 OB Please let me know
I have written 5 rows since i want to incorporate all possible cases that i want in my query in the example table that i showed.
Now in my query output, i want something like :
message_in message_out
Hi Hello
Help NULL
Want to find this string Sure
NULL Please let me know
So the cases that i want to consider are :
suppose if message_number=0 and message_number=1 both have message_type value as IN then put message_chat_in as message_chat(at message_number=0) and message_chat out as NULL and the iterate over message_number=1
if message_number =0 have message_type=IN and message_number =1 have message_type=OB, then show message_chat(at message_number=0) as message_chat_in and message_chat(at message_number=1) as message_out and dont iterate over message_number=1;
hope i have clarified the condition though i have included all three condition in the expected output.How should my sqlquery look like?
Edit : I am using mysql version 5.5.8
Try the following query
SELECT
q1.message_number in_num,
q1.message_chat in_chat,
q2.message_number out_num,
q2.message_chat out_chat
FROM
(
SELECT *,#i1:=IFNULL(#i1,0)+1 num
FROM Chat
ORDER BY message_number
) q1
LEFT JOIN
(
SELECT *,#i2:=IFNULL(#i2,0)+1 num
FROM Chat
ORDER BY message_number
) q2
ON q2.num=q1.num+1 AND q2.message_type<>q1.message_type
WHERE q1.message_type='IN'
UNION ALL
SELECT
q1.message_number in_num,
q1.message_chat in_chat,
q2.message_number out_num,
q2.message_chat out_chat
FROM
(
SELECT *,#i3:=IFNULL(#i3,0)+1 num
FROM Chat
ORDER BY message_number
) q1
RIGHT JOIN
(
SELECT *,#i4:=IFNULL(#i4,0)+1 num
FROM Chat
ORDER BY message_number
) q2
ON q2.num=q1.num+1 AND q2.message_type<>q1.message_type
WHERE q2.message_type='OB'
AND q1.message_type IS NULL
ORDER BY IFNULL(in_num,out_num)
SQL Fiddle - http://sqlfiddle.com/#!9/95a515/1
The second variant
SET #i1 = 0;
SET #i2 = 0;
SET #i3 = 0;
SET #i4 = 0;
-- the same query
SQL Fiddle - http://sqlfiddle.com/#!9/95a515/2
Or
SELECT 0,0,0,0 INTO #i1,#i2,#i3,#i4;
-- the same query
SQL Fiddle - http://sqlfiddle.com/#!9/95a515/5
why not using a analytic function here? I would do it with Lead() like this:
with inc as (
--Do the incorporation in this block. could be subquery too
--but its easier to read this way.
select
case when message_type = 'IN'
then message_chat
end as message_in
,case when LEAD(message_type) OVER (Order by message_number) = 'OB' --get the next message by number if it is type OB
then LEAD(message_chat) OVER (order by message_number)
end as message_out
from input
)
select *
from inc
where coalesce(message_in, message_out) is not null --filter out rows where with in & out is null
Ok, since there is no analytical functions in MySQL less than 8 the code may not be easy to follow:
with data_rn as
(
-- this isolate consecutive rows with the same message_type
select d1.*, count(d2.message_number) rn
from data d1
left join data d2 on d1.message_number > d2.message_number and d1.message_type != d2.message_type
group by d1.message_number
),
data_rn2 as
(
-- this marks the rows where new rows has to be added (i.e. when rn2 != 0)
select d1.*, count(d2.message_number) rn2
from data_rn d1
left join data_rn d2 on d1.rn = d2.rn and d1.message_type = d2.message_type and d1.message_number > d2.message_number
group by d1.message_number
),
data_added as
(
-- this add new rows
select message_number, message_type, message_chat
from data_rn2
union all
select message_number - 0.5, 'OB', NULL from data_rn2 where message_type = 'IN' and rn2 != 0
union all
select message_number - 0.5, 'IN', NULL from data_rn2 where message_type = 'OB' and rn2 != 0
order by message_number
), data_added_rn as
(
-- this compute new row numbering
select d1.*, ceil((count(d2.message_number)+1)/2) rn
from data_added d1
left join data_added d2 on d1.message_number > d2.message_number
group by d1.message_number
)
-- this will do the final formating
select max(case when message_type = 'IN' then message_chat end) message_in,
max(case when message_type = 'OB' then message_chat end) message_out
from data_added_rn
group by rn
demo
I have tried to comment each section appropriately.
I need to combine 2 tables that may or may not have the data in them, but than I need a full outer join where the last table (if has content where IsActive = 1) gets shown that data, instead of the combined first 2 tables.
Currently have this:
( SELECT qp.ItemName AS name
, qp.TimeAdded AS created
, '' AS effective
, qp.VendorName AS supplier
, qp.Source AS source
, qp.VendorType AS type
, qp.Price AS cost
, '' AS price
, '' AS markup
, '' AS customer
, '' AS customerListID
, qp.VendorListID AS vendorListID
, '' AS itemListID
FROM wp_quantum_purchases AS qp
WHERE qp.IsActive = 1 AND
NOT EXISTS ( SELECT 1
FROM wp_hunter_quote_parts AS hqp
WHERE qp.ItemName = hqp.ItemName AND
hqp.IsActive = 1 ))
UNION ALL
( SELECT qs.ItemName AS name
, qs.TimeAdded AS created
, qs.SalesDate AS effective
, '' AS supplier
, qs.Source AS source
, '' AS type
, '' AS cost
, qs.Price AS price
, '' AS markup
, qs.CustomerName AS customer
, qs.CustomerListID AS customerListID
, '' AS vendorListID
, '' AS itemListID
FROM wp_quantum_sales AS qs
WHERE qs.IsActive = 1 AND
NOT EXISTS ( SELECT 1
FROM wp_hunter_quote_parts AS hqp
WHERE qs.ItemName = hqp.ItemName AND
hqp.IsActive = 1 ))
UNION ALL
( SELECT hqp.ItemName AS name
, hq.Quote_Date AS created
, hqp.SalesDate AS effective
, hqp.VendorName AS supplier
, hqp.Source AS source
, hqp.VendorType AS type
, hqp.Cost AS cost
, hqp.Price AS price
, CAST(( ( ( CAST(hqp.Price AS DECIMAL(10, 2)) - CAST(hqp.Cost AS DECIMAL(10, 2)) ) / CAST(hqp.Cost AS DECIMAL(10, 2)) ) * 100 ) AS DECIMAL(10, 2)) AS markup
, IFNULL(hq.Customer_FullName, 'N/A') AS customer
, hq.Customer_ListID AS customerListID
, hqp.VendorListID AS vendorListID
, hqp.Item_ListID AS itemListID
FROM wp_hunter_quote_parts AS hqp
LEFT JOIN wp_hunter_quotes AS hq
ON ( hq.id = hqp.QuoteID )
WHERE hqp.IsActive = 1)
ORDER BY NAME ASC;
But this is duplicating the data in 1st and 2nd tables and shows the data twice. I need the data from 1st and 2nd tables to be combined as 1 (if exists), but to prioritize the last table (wp_hunter_quote_parts) in here as the content to show from, if IsActive = 1 exists in the last table (wp_hunter_quote_parts). However, if IsActive = 1 does not exist in wp_hunter_quote_parts for ItemName than I would like to combine both wp_quantum_purchases and wp_quantum_sales as if it were 1 row.
Can not do a LEFT JOIN since data could exist in wp_quantum_purchases, but not in wp_quantum_sales OR data could exist in wp_quantum_sales and not in wp_quantum_purchases, OR data could not exist in either of these, and only exist in wp_hunter_quote_parts as well as data might not even exist in wp_hunter_quote_parts.
So, basically, if ItemName exists in wp_quantum_purchases AND IsActive = 1 AND wp_hunter_quote_parts does not have ItemName in table, get purchase data from wp_quantum_purchases, else if ItemName exists in wp_hunter_quote_parts get data from hunter_quote_parts instead.
If ItemName exists in wp_quantum_sales AND IsActive = 1 AND wp_hunter_quote_parts does not have ItemName in table, get sales data from wp_quantum_sales, else if ItemName exists in wp_hunter_quote_parts get data from hunter_quote_parts instead.
How can I combine first and second table, than do an outer join on it with another table?
Another Attempt here:
(SELECT IFNULL(qp.ItemName, qs.ItemName) AS name, IFNULL(qp.TimeAdded, qs.TimeAdded) AS created, qs.SalesDate AS effective, qp.VendorName AS supplier, qp.Source AS source, qp.VendorType AS type, qp.Price AS cost, qs.Price AS price, CAST((((CAST(qs.Price AS DECIMAL(10,2)) - CAST(qp.Price AS DECIMAL(10,2))) / CAST(qp.Price AS DECIMAL(10,2))) * 100) AS DECIMAL(10,2)) AS markup, qs.CustomerName AS customer, qs.CustomerListID AS customerListID, qp.VendorListID AS vendorListID, '' AS itemListID
FROM wp_quantum_purchases AS qp, wp_quantum_sales AS qs
WHERE (qp.IsActive = 1 OR qs.IsActive = 1)
AND NOT EXISTS (
SELECT 1
FROM wp_hunter_quote_parts AS hqp
WHERE (qp.ItemName = hqp.ItemName || qs.ItemName = hqp.ItemName) AND hqp.IsActive = 1
)
)
UNION ALL
(SELECT hqp.ItemName AS name, hq.Quote_Date AS created, hqp.SalesDate AS effective, hqp.VendorName AS supplier, hqp.Source AS source, hqp.VendorType AS type, hqp.Cost AS cost, hqp.Price AS price, CAST((((CAST(hqp.Price AS DECIMAL(10,2)) - CAST(hqp.Cost AS DECIMAL(10,2))) / CAST(hqp.Cost AS DECIMAL(10,2))) * 100) AS DECIMAL(10,2)) AS markup, IFNULL(hq.Customer_FullName, 'N/A') AS customer, hq.Customer_ListID AS customerListID, hqp.VendorListID AS vendorListID, hqp.Item_ListID AS itemListID
FROM wp_hunter_quote_parts AS hqp
LEFT JOIN wp_hunter_quotes AS hq ON (hq.id = hqp.QuoteID)
WHERE (hqp.IsActive = 1))
ORDER BY name ASC
Figured this one would work, but seems that it just keeps going and going and going, and doesn't seem to ever finish the query. No errors that I can see, but doesn't finish ever... And these tables are very small, that is odd...
I may not be understanding your question fully, but you could create a view of the first two tables and then do an outer join with the third table.
This is obviously wrong, but what would be the correct way to average the SUM of 3 columns and exclude the 0's?
SELECT (
AVG(NULLIF(`dices`.`Die1`,0)) +
AVG(NULLIF(`dices`.`Die2`,0)) +
AVG(NULLIF(`dices`.`Die3`,0))
) /3 as avgAllDice
FROM (
SELECT `Die1`,`Die2`,`Die3` FROM `GameLog`
WHERE PlayerId = "12345"
) dices
Thanks.
If I was keeping the inline view query (it's not clear why it's needed). I'd probably do something like this:
SELECT AVG( NULLIF( CASE d.i
WHEN 1 THEN dices.`Die1`
WHEN 2 THEN dices.`Die2`
WHEN 3 THEN dices.`Die3`
END
,0)
) AS `avgAllDice`
FROM ( SELECT gl.`Die1`
, gl.`Die2`
, gl.`Die3`
FROM `GameLog` gl
WHERE gl.playerId = '12345'
) dices
CROSS
JOIN ( SELECT 1 AS i UNION ALL SELECT 2 UNION ALL SELECT 3 ) d
The trick is the cross join operation, giving me three rows for each row returned from dices, and an expression that picks out values of Die1, Die2 and Die3 on each of three rows, respectively.
To exclude values of 0, we replace 0 with with NULL (since AVG doesn't include NULL values.)
Now with all of the non-zero DieN values stacked into a single column, we can just use the AVG function.
Another way to do it would be to get the numerator and denominator for each of Die1, Die2, Die3.... and then total up the numerators, total up the denominators, and then divide the total numerator by the total denominator.
This will should give an equivalent result.
SELECT ( IFNULL(t.n_die1,0) + IFNULL(t.n_die2,0) + IFNULL(t.n_die3,0) )
/ ( t.d_die1 + t.d_die2 + t.d_die3 )
AS avgAllDice
FROM ( SELECT SUM( NULLIF(gl.die1,0)) AS n_die1
, COUNT(NULLIF(gl.die1,0)) AS d_die1
, SUM( NULLIF(gl.die2,0)) AS n_die2
, COUNT(NULLIF(gl.die2,0)) AS d_die2
, SUM( NULLIF(gl.die3,0)) AS n_die3
, COUNT(NULLIF(gl.die3,0)) AS d_die3
FROM `GameLog` gl
WHERE gl.playerid = '12345'
) t
(I didn't work out what gets returned in the edge and corner cases... no matching rows in GameLog, all values of Die1, Die2 and Die3 are zero, etc., for either query. The results might be slightly different, returning a zero instead of NULL, divide by zero edge case, etc.)
FOLLOWUP
I ran a quick test of both queries.
CREATE DATABASE d20170228 ;
USE d20170228 ;
CREATE TABLE GameLog
( playerid VARCHAR(5) DEFAULT '12345'
, die1 TINYINT
, die2 TINYINT
, die3 TINYINT
);
INSERT INTO GameLog (die1,die2,die3)
VALUES (3,0,0),(2,1,0),(4,3,3),(3,3,3),(0,0,0),(4,4,4),(5,4,0),(0,0,2)
;
SELECT (3+2+1+4+3+3+3+3+3+4+4+4+5+4+2)/15 AS manual_avg
manual_avg is coming out 3.2.
Both queries are also returning 3.2
If you want to eliminate zeroes and NULLs, you can simply SELECT from the filtered master set multiple times, doing a UNION ALL on the results, then averaging against that.
SELECT AVG(`allDice`.`DieResult`)
FROM (
SELECT `Die1` AS `DieResult` FROM `GameLog` WHERE COALESCE(`Die1`, 0) <> 0 AND PlayerId = '12345'
UNION ALL
SELECT `Die2` FROM `GameLog` WHERE COALESCE(`Die2`, 0) <> 0 AND PlayerId = '12345'
UNION ALL
SELECT `Die3` FROM `GameLog` WHERE COALESCE(`Die3`, 0) <> 0 AND PlayerId = '12345'
) AS `allDice`
There's no need to overthink this one, it's not too difficult a problem
I have two tables like this in mysql
a.cardnumber (unique)
a.position (numerical 3 digits or null)
a.serial
b.serial (unique)
b.lastused
I want to update any rows in "a" where position is above 600 AND "a.serial" is blank with any serial from "b.serial" where "b.lastused" is either null or more than 30 days ago. When the serial is copied into "a.serial" I want to update "b.lastused" with today's date so I know that the relevant "b.serial" has been used today.
There is no relation to the two tables apart from the serial and any serial from b can be used with any cardnumber in a.
I've tried this using my limited knowledge of mysql but I keep getting an error from my mysql desktop program to say I have an error in my query :(
Any help much appreciated!
I'm assuming here that you want to use a separate b.serial for each row to be updated in a. (This isn't specifically stated, but it seems to me to be most likely; please feel free to correct my assumption if it is wrong.)
I setup a small example. It wasn't clear what the datatypes for each of the columns, so I used INT where I wasn't sure. I used DATE datatype (rather than DATETIME) for lastused.
CREATE TABLE a (`cardnumber` VARCHAR(10) NOT NULL PRIMARY KEY, `position` INT, `serial` INT);
CREATE TABLE b (`serial` INT NOT NULL PRIMARY KEY, lastused DATE);
INSERT INTO a VALUES ('x0000',555,NULL),('x0001',700,123),('a1111',601,NULL),('a2222',602,NULL);
INSERT INTO b VALUES (100,'2012-07-15'),(101,NULL),(102,'2010-01-01'),(103,NULL),(104,NULL);
SELECT * FROM a;
SELECT * FROM b;
Based on the conditions you give, the rows with cardnumbers 'a1111' and 'a2222' should get updated, the other two rows should not (position <= 600, serial already assigned).
Before we run an UPDATE, we want to first run a SELECT that returns the rows to be updated, along with the values that will be assigned. Once we get that, we can convert that to a multi-table UPDATE statement.
SELECT a.cardnumber AS `a.cardnumber`
, a.position AS `a.position`
, a.serial AS `a.serial`
, b.serial AS `b.serial`
, b.lastused AS `b.lastused`
FROM (
SELECT #i := #i + 1 AS i
, aa.*
FROM a aa
JOIN (SELECT #i := 0) ii
WHERE aa.position > 600 /* assuming `position` is numeric datatype */
AND aa.serial IS NULL /* assuming 'blank' represented by NULL */
ORDER BY aa.cardnumber
) ia
JOIN (
SELECT #j := #j + 1 AS j
, bb.serial
, bb.lastused
FROM b bb
JOIN (SELECT #j := 0) jj
WHERE bb.lastused IS NULL
OR bb.lastused < DATE_ADD(NOW(),INTERVAL -30 DAY)
ORDER BY bb.serial
) jb
ON ia.i = jb.j
JOIN a ON a.cardnumber = ia.cardnumber
JOIN b ON b.serial = jb.serial
To convert that to an UPDATE, replace the SELECT ... FROM with UPDATE, and add a SET clause to assign new values to the tables.
UPDATE (
SELECT #i := #i + 1 AS i
, aa.*
FROM a aa
JOIN (SELECT #i := 0) ii
WHERE aa.position > 600
AND aa.serial IS NULL
ORDER BY aa.cardnumber
) ia
JOIN (
SELECT #j := #j + 1 AS j
, bb.serial
, bb.lastused
FROM b bb
JOIN (SELECT #j := 0) jj
WHERE bb.lastused IS NULL
OR bb.lastused < DATE_ADD(NOW(),INTERVAL -30 DAY)
ORDER BY bb.serial
) jb
ON ia.i = jb.j
JOIN a ON a.cardnumber = ia.cardnumber
JOIN b ON b.serial = jb.serial
SET a.serial = b.serial
, b.lastused = DATE(NOW())
-- 4 row(s) affected
You can run the queries for the inline views seperately (ia, jb) to verify that these are getting the rows you want to update.
The join from ia to a, and from jb to b, should be on the primary keys unique key.
The purpose of the ia and jb inline views is to get sequential numbers assigned to those rows so we can match them to each other.
The joins to a and b are to get back to the row in the original table, which is what we want to update.
(Obviously, some adjustments need to be made if serial is not an INT, or lastused is a DATETIME rather than a DATE.)
But this is an example of how I would go about doing the UPDATE you want to do (as best I understood it.)
NOTE: This approach works with MySQL versions that support subqueries. For MySQL 4.0, you would need to run this in steps, storing the results from the "ia" and "jb" inline views (subqueries) into actual tables. Then reference those tables in the query in place of the inline views. The ii and jj subqueries can be removed, and replaced with separate SELECT #i := 0, #j := 0 statement prior to the execution of the queries that reference these variables.
let me know if this works
Update table_a
set serial =
(
select b.serial from table_b b
where b.lastused = NULL
OR b.lastused < (current date - 30) limit 1
)
where cardnumber in
(
select a.cardnumber
from table_a a
where a.position > 600
and a.serial = NULL
)
update table_b b
set b.lastused = current date
where b.lastused = NULL
OR b.lastused < (current date - 30)
I have a table in a MySQL DB with an UNIQUE INT(10) column. The table is pretty populated and the row contains non-consecutive entries of Integer numbers in that column. I would like to do a query, which gets me the smallest number (or the n smallest numbers) that is not in any row.
Example: The table contains rows with values (1, 2, 3, 5, 7, 8, 10, 12, 15) for the column. The sql statement should return i.e. the five lowest non-contained values, which are 4, 6, 9, 11, 13 in this case.
Is this possible with MySQL?
You can use a "numbers" table (it's handy for various operations):
CREATE TABLE num
( i UNSIGNED INT NOT NULL
, PRIMARY KEY (i)
) ;
INSERT INTO num (i)
VALUES
(1), (2), ..., (1000000) ;
Then:
SELECT
num.i
FROM
num
LEFT JOIN
tableX AS t
ON num.i = t.columnX
WHERE
t.columnX IS NULL
ORDER BY
num.i
LIMIT 5
or:
SELECT
num.i
FROM
num
WHERE
NOT EXISTS
( SELECT *
FROM tableX AS t
WHERE num.i = t.columnX
)
ORDER BY
num.i
LIMIT 5
Another approach, without using an auxilary table, would be to use MySQL variables. You can test it in SQL-Fiddle, test-2. The output is not the same as the previous (just to show that it can be done):
SELECT start_id, end_id
FROM
( SELECT
IF( t.columnX <> #id, #id, NULL) AS start_id
, IF( t.columnX <> #id, t.columnX-1, NULL) AS end_id
, #rows := #rows + (t.columnX - #id) AS r
, #id := t.columnX + 1 AS running_id
FROM
tableX AS t
CROSS JOIN
( SELECT #rows := 0
, #id := 1
) AS dummy
WHERE
#rows < 5
ORDER BY
t.columnX
) AS tmp
WHERE
start_id IS NOT NULL
This will work, but I think it is pretty inefficient. You won't need an extra table though (a table that would be (2^31-1)*4/1024^3 = 8GB for all positive numbers in INT). Also I advise you look at why you need this, because it might not be neccesary.
Also it will return the start and end of a range, but not all numbers in that range. (e.g. if you have numbers 1 and 5 it will return {0,2,4,6})
SELECT (t.num-1) AS bound FROM t
WHERE t.num-1 NOT IN (SELECT t.num FROM t)
UNION
SELECT (t.num+1) AS bound FROM t
WHERE t.num+1 NOT IN (SELECT t.num FROM t)
As I said this will be pretty inefficient, JOINs might be faster but you would need benchmark it.
SELECT (t.num-1) AS bound FROM t
LEFT JOIN t AS u ON t.num-1 = u.num
WHERE u.num IS NULL
UNION
SELECT (t.num+1) AS bound FROM t
LEFT JOIN t AS u ON t.num+1 = u.num
WHERE u.num IS NULL