Another gap detection - mysql

Have looked through many examples, but i have not found a good answer for my specific situation.
Basically, I have table with an unique ID and a sequence for each ID.
I want to detect gaps in the sequence for each ID.
I managed to do it using a query, but the query takes the exact ID in the where clause, and given the number of rows in my table, this is not wanted.
CREATE TABLE `t` (
`target_id` varchar(100) NOT NULL,
`version` int(11) NOT NULL,
PRIMARY KEY (`target_id`,`version`)
);
insert into t values
('abc',1),
('abc',2),
('abc',3),
('abc',4),
('abc',5),
('abc',6),
('abc',7),
('abc',8),
('xyz',1),
('xyz',2),
('xyz',3),
('xyz',5),
('xyz',6);
http://sqlfiddle.com/#!9/8c280a/7
I tried with something like
select distinct target_id as target, gap_ends_at,gap_starts_at from
category_event e inner join
(
SELECT target_id as x, (t1.version + 1) as gap_starts_at,
(SELECT MIN(t3.version) -1 FROM category_event t3 WHERE t3.version >
t1.version and target_id=e.target) as gap_ends_at
FROM category_event t1
WHERE NOT EXISTS (SELECT t2.version FROM category_event t2 WHERE t2.version = t1.version + 1 and target_id=e.target) and target_id=e.target
HAVING gap_ends_at IS NOT NULL
) as x;
But that fails.
I expect a resultset like
id, gap_starts_at, gap_ends_at

Because this is MySQL, you can solve it just using vars:
MySQL 5.6 Schema Setup:
CREATE TABLE `t` (
`target_id` varchar(100) NOT NULL,
`version` int(11) NOT NULL,
PRIMARY KEY (`target_id`,`version`)
);
insert into t values
('abc',1),
('abc',2),
('abc',3),
('abc',4),
('abc',5),
('abc',6),
('abc',7),
('abc',8),
('xyz',1),
('xyz',2),
('xyz',3),
('xyz',5),
('xyz',6)
Query 1:
select
gap
from (
Select
target_id,
#v := case when #t <> target_id then 1 else #v+1 end,
#t := case when #t <> target_id then target_id else #t end,
case when #v <> version then CONCAT_WS(' ' ,
'gap for ',
target_id,
' start at',
CAST(#v-1 as CHAR(50)) ,
'ends at ',
CAST(version as CHAR(50))
)
else null end as gap,
#v := version
from
(select target_id, version
from t
order by target_id, version ) S,
( select #t:='', #v=0 ) I
) X
where gap > ''
Results:
| gap |
|-------------------------------------|
| gap for xyz start at 3 ends at 5 |

Related

MySQL - Return from function only if single row matches

I have a MySQL table of users as follows:
CREATE TABLE `users` (
`ID` INT NOT NULL, -- NOTE: in practice, I'm using BINARY(16) and
-- the UUID()-function to create user IDs.
-- 'INT' is only a simplification for this
-- stackoverflow question.
`FirstName` NVARCHAR(100) NOT NULL,
`LastName` NVARCHAR(100) NOT NULL,
-- ...
PRIMARY KEY (`ID`)
);
INSERT INTO `users` (`ID`, `FirstName`, `LastName`)
VALUES (0, 'Albus', 'Dumbledore'),
(1, 'Lord', 'Voldemort'),
(2, 'Harry', 'Potter'),
(3, 'Hermione', 'Granger');
I'd like to create a user-defined function which returns the ID of the row matching a FirstName and LastName combination if (and only if) the results are unique (i.e. only one row matches the query):
CREATE FUNCTION `FindUser`(`first_name` NVARCHAR(100), `last_name` NVARCHAR(100)
RETURNS INT
BEGIN
RETURN (SELECT `ID`
FROM `users`
WHERE ((first_name is NULL) OR (`FirstName` LIKE CONCAT('%', first_name, '%')))
AND ((last_name Is NULL) OR (`LastName` LIKE CONCAT('%', last_name, '%')))
LIMIT 1);
END
This works as expected on the following examples:
SELECT `FindUser`(NULL, 'potter');
-- | ID |
-- |----|
-- | 2 |
SELECT `FindUser`('obama', NULL);
-- | ID |
-- |----|
However, this does not work on SELECT FindUser(NULL, 'or');, as the token 'or' could match 0 | Albus | Dumbledore and 1 | Lord | Voldemort.
I tried the following:
SET #cnt = 0;
SET #id = NULL;
SELECT #id = u.id, #cnt = COUNT(id)
FROM users u
WHERE ...; -- same conditions as before
RETURN IF(#cnt = 1, #id, NULL);
However, that does not work, as #id and #cnt will always be overwritten by the last line.
The alternative would be to perform two queries, but that is inefficient.
How could I solve the problem most efficiently?
Providing you're using a MySql version that supports window functions a simple modification you can make is to conditionally count the number of rows:
RETURN (
SELECT CASE WHEN count(*) over() = 1 then ID ELSE null END
FROM users
WHERE (first_name is NULL OR FirstName LIKE CONCAT('%', first_name, '%'))
AND (last_name Is NULL OR LastName LIKE CONCAT('%', last_name, '%'))
LIMIT 1
);
Demo Fiddle
You could use aggregation and set the condition in the HAVING clause:
CREATE FUNCTION FindUser(first_name NVARCHAR(100), last_name NVARCHAR(100))
RETURNS INT
BEGIN
RETURN (
SELECT MAX(ID)
FROM users
WHERE (first_name IS NULL OR FirstName LIKE CONCAT('%', first_name, '%'))
AND (last_name IS NULL OR LastName LIKE CONCAT('%', last_name, '%'))
GROUP BY NULL -- you can omit this clause
HAVING COUNT(*) = 1
);
END;
See the demo.
I suspect that for the edge case where there is only 1 row in the table and the parameters that you pass for the function are both null you don't want the ID of that row returned.
For this case you should add one more condition in the WHERE clause to make sure that at least one of the parameters is not null:
WHERE (first_name IS NOT NULL OR last_name IS NOT NULL)
AND (first_name IS NULL OR FirstName LIKE CONCAT('%', first_name, '%'))
AND (last_name IS NULL OR LastName LIKE CONCAT('%', last_name, '%'))

Operand Should Contain 1 Column(s), Trying to generate volunteer data

I can't seem to troubleshoot my problem.
My stored procedure:
CREATE DEFINER=`myschoolusername`#`%` PROCEDURE `generate_volunteers`(in nfolks int)
BEGIN
set #i=0;
while #i < nfolks do
insert into Volunteer(firstname, lastname, dateofbirth)
values (((floor(1+(rand()*(4-1))), "Fred", "Wang", "Fatimah", "Marcella")),
((floor(1+(rand()*(3-1))), "Kaser", "Fang", "Kumar")),
DATE_ADD('1965-01-01', INTERVAL rand()*200000 DAY));
set #i = #i+1;
end while;
END
Additionally, here is my volunteer table in my MYSQL script:
drop table if exists Volunteer;
create Table Volunteer(
member_num int not null auto_increment primary key,
firstname varchar(20) not null,
lastname varchar(20) not null,
dateofbirth date not null
);
I am trying to insert 500 lines into this table, however error 1305 is coming up.
Any help is heavily appreciated, I am quite unsure of where to go from this point.
This logic doesn't do anything:
(floor(1+(rand()*(4-1))), "Fred", "Wang", "Fatimah", "Marcella"))
Although not the most efficient, this should be fine for 500 rows:
insert into Volunteer(firstname, lastname, dateofbirth)
select f.firstname, l.lastname,
DATE_ADD('1965-01-01', INTERVAL rand()*200000 DAY)
from (select 'Fred' as firstname union all
select 'Wang' union all
select 'Fatimah' union all
select 'Marcella'
) f cross join
(select 'Kaser' as lastname union all
select 'Fang' union all
select 'Kumar'
) l
order by rand()
limit 1;
I think you are actually trying to write:
insert into Volunteer(firstname, lastname, dateofbirth)
select elt(floor(rand() * 4) + 1,
'Fred', 'Wang', 'Fatimah', 'Marcella'
) as firstname,
elt(floor(rand() * 3) + 1,
'Kaser', 'Fang', 'Kumar'
) as lastname,
DATE_ADD('1965-01-01', INTERVAL rand()*200000 DAY);

How to get the row before and after a specific value in MySQL

Lets suppose MySQL table is structured as follows:
(Timestamp | Value)
(4h00 | 3) ;
(5h00 | 5) ;
(6h00 | 0) ;
(7h00 | 0) ;
(8h00 | 2) ;
(9h00 |10) ;
(10h00 | 0) ;
(11h00 | 3) ;
I want to get the line where (Value != 0) before any appearance of (Value = 0) AND the first line where (Value != 0) after the appearance of (Value = 0).
Means in this case, the query should return
(5h00 | 5) ;
(8h00 | 2) ;
(9h00 | 10) ;
(11h00 | 3) ;
this is the structure of my table :
CREATE TABLE IF NOT EXISTS `latency` (
`key` int(100) NOT NULL AUTO_INCREMENT,
`idReport` varchar(45) NOT NULL,
`timestamp` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`value` float NOT NULL,
`loss` float NOT NULL,
PRIMARY KEY (`idReport`,`timestamp`),
UNIQUE KEY `key` (`key`)
)
SELECT * FROM `table` AS tcurrent
INNER JOIN `table` AS tnext
ON tnext.timestamp = tcurrent.timestamp+60*60
WHERE tnext.value = 0
AND tcurrent.value != 0
You may adapt the +60*60: a DATE_ADD for 1 HOUR is easier to read. Also, do not forget to index columns properly (a UNIQUE INDEX (timestamp, value) sounds good).
You could use row numbering logic , something like this
SELECT T.*
FROM
(
SELECT DISTINCT RN FROM
(
SELECT L.*,
IF (L.VALUE <> #P , #RN:=#RN+1,#RN) RN ,
#P:=L.VALUE P
FROM (SELECT #RN:=0, #P:=0 ) RN,`LATENCY` L
#ORDER BY
) S
WHERE S.VALUE = 0
) S1
JOIN
(SELECT L.*,
IF (L.VALUE <> #P1 , #RN1:=#RN1+1,#RN1) RN ,
#P1:=L.VALUE P
FROM (SELECT #RN1:=0, #P1:=0 ) RN,`LATENCY` L
#ORDER BY
) T
ON S1.RN = T.RN-1 OR S1.RN = T.RN+1
The sub queries s and s1 identify blocks with value = 0 and the join simply picks adjacent row numbers.

MySQL - Select multiple values across multiple columns where all matches have a result

I am trying to construct a query that will allow me to "filter" on pairs of columns for particular criteria. I need to be able to construct multiple filters for the same given pair. The end result should only return instances that have data for the case where all filters are applied.
I constructed a trivial example demonstrating what I would like to be able to do.
Using the follow table definition:
DROP TABLE IF EXISTS foo;
CREATE TEMPORARY TABLE `foo` (
`ID` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`Index` INT UNSIGNED NOT NULL,
`Header` VARCHAR(50) NOT NULL,
`Value` VARCHAR(255) NOT NULL,
PRIMARY KEY (`ID`),
UNIQUE INDEX `ID_UNIQUE` (`ID` ASC));
INSERT INTO `foo` (`Index`, `Header`, `Value`)
VALUES
(0, 'Header_1', 'a'),
(0, 'Header_2', 'b'),
(1, 'Header_1', 'a'),
(1, 'Header_2', 'c');
I would like a query that would return the following, given that you are looking for the case where 'Header_1' == 'a' and 'header_2' == 'b':
Index | Header | Value
------------------------
0 | Header_1 | a
0 | Header_2 | b
My current attempt is as follows:
SELECT `Index`, `Header`, `Value` FROM `foo`
WHERE (
(`Header` = 'Header_1') AND (`Value` = 'a')
OR (
(`Header` = 'Header_2') AND (`Value` = 'b')
)
)
GROUP BY `Header`, `Value`
HAVING COUNT(DISTINCT `Index`) = 2
ORDER BY `Index`, `Header`;
That code returns the following:
Index | Header | Value
------------------------
0 | Header_1 | a
I am missing one of my return rows. How can I restructure the query to return all of the matching rows?
Note that I declared the table as a temporary table. This is important, as I am working with temporary tables, and they have special restrictions to keep in mind (namely not being able to open it more than once in the same statement).
Your query returns only header_1 because the clause:
HAVING COUNT(DISTINCT `Index`) = 2
is only correct for Header_1.
Header_2 has count=1, therefore removed from the end result.
To get a clearer picture of what i say use:
SELECT `Index`, `Header`, `Value`, COUNT(DISTINCT `Index`) FROM `foo`
WHERE (
(`Header` = 'Header_1') AND (`Value` = 'a')
OR (
(`Header` = 'Header_2') AND (`Value` = 'b')
)
)
GROUP BY `Header`, `Value`
ORDER BY `Index`, `Header`;
and take a look at the last column.
I couldn't figure out how to do this with only the one temporary table. I'm not happy with this result, but at least it works.
DROP TABLE IF EXISTS `foo2`;
CREATE TEMPORARY TABLE `foo2` (
SELECT `Index` FROM `foo`
WHERE (
(`Header` = 'Header_1') AND (`Value` = 'a')
OR (
(`Header` = 'Header_2') AND (`Value` = 'b')
)
)
GROUP BY `Index`
HAVING COUNT(DISTINCT `Header`) = 2
);
SELECT DISTINCT t1.`Index`, t1.`Header`, t1.`Value` FROM `foo` t1
INNER JOIN `foo2` t2 ON t2.`Index` = t1.`Index`
ORDER BY t1.`Index`, t1.`Header`;
How about...
SELECT `index`
FROM foo
WHERE (header,value) IN (('header_1','a'))
OR (header,value) IN (('header_2','b'))
GROUP
BY `index`
HAVING COUNT(*) = 2;

Order by multiple conditions

im very noob and this became ungoogleable (is that a word?)
the rank is by time but..
time done with ( A=0 ) AND ( B=0 ) beat everyone
time done with ( A=0 ) AND ( B=1 ) beat everyone with ( A=1 )
time done with ( A=1 ) AND ( B=0 ) beat everyone with ( A=1 + B=1 )
rank example (track=desert)
pos--car------time---A----B
1.---yellow----90----No---No
2.---red-------95----No---No
3.---grey-----78-----No---Yes
4.---orange--253---No---Yes
5.---black----86----Yes---No
6.---white----149---Yes---No
7.---pink-----59----Yes---Yes
8.---blue-----61----Yes---Yes
to make it even worst, the table accept multiple records for the same car
here is the entries
create table `rank`
(
`id` int not null auto_increment,
`track` varchar(25) not null,
`car` varchar(32) not null,
`time` int not null,
`a` boolean not null,
`b` boolean not null,
primary key (`id`)
);
insert into rank (track,car,time,a,b) values
('desert','red','95','0','0'),
('desert','yellow','89','0','1'),
('desert','yellow','108','0','0'),
('desert','red','57','1','1'),
('desert','orange','120','1','0'),
('desert','grey','85','0','1'),
('desert','grey','64','1','0'),
('desert','yellow','90','0','0'),
('desert','white','92','1','1'),
('desert','orange','253','0','1'),
('desert','black','86','1','0'),
('desert','yellow','94','0','1'),
('desert','white','149','1','0'),
('desert','pink','59','1','1'),
('desert','grey','78','0','1'),
('desert','blue','61','1','1'),
('desert','pink','73','1','1');
please, help? :p
ps: sorry about the example table
To prioritize a, then b, then time, use order by b, a, time.
You can use a not exists subquery to select only the best row per car.
Finally, you can add a Pos column using MySQL's variables, like #rn := #rn + 1.
Example query:
select #rn := #rn + 1 as pos
, r.*
from rank r
join (select #rn := 0) init
where not exists
(
select *
from rank r2
where r.car = r2.car
and (
r2.a < r.a
or (r2.a = r.a and r2.b < r.b)
or (r2.a = r.a and r2.b = r.b and r2.time < r.time)
)
)
order by
b
, a
, time
See it working at SQL Fiddle.