How to ignore next duplicated row? - mysql

I need your help!
I have a table:
CREATE TABLE `table` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`res` varchar(255) DEFAULT NULL,
`value` int(6) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;
-- Records of table
INSERT INTO `table` VALUES (1, 'gold', 44);
INSERT INTO `table` VALUES (2, 'gold', 44);
INSERT INTO `table` VALUES (3, 'gold', 45);
INSERT INTO `table` VALUES (4, 'gold', 46);
INSERT INTO `table` VALUES (5, 'gold', 44);
INSERT INTO `table` VALUES (6, 'gold', 44);
INSERT INTO `table` VALUES (7, 'gold', 44);
INSERT INTO `table` VALUES (8, 'gold', 47);
i need to make SELECT request which will ignored next or previous duplicated rows and i receive data like this:
- gold:44 (ignored 1 record)
- gold:45
- gold:46
- gold:44 (ignored 2 records)
- gold:47
there is no object which duplicated record will ignore (first,second,last).
(i tried to use group by value or distinct but this way removes other records with same value)

You can solve this with a gaps and islands solution.
- Normally that involves ROW_NUMBER() which is not present in MySQL
- The solution below mimics ROW_NUMBER() with variables and ORDER BY
Link to example : http://sqlfiddle.com/#!9/32e72/12
SELECT
MIN(id) AS id,
res,
value
FROM
(
SELECT
IF (#res = res AND #val = value, #row := #row + 1, #row := 1) AS val_ordinal,
id AS id,
res_ordinal AS res_ordinal,
#res := res AS res,
#val := value AS value
FROM
(
SELECT
IF (#res = res , #row := #row + 1, #row := 1) AS res_ordinal,
id AS id,
#res := res AS res,
#val := value AS value
FROM
`table`,
(
SELECT #row := 0, #res := '', #val := 0
)
AS initialiser
ORDER BY
res, id
)
AS sequenced_res_id,
(
SELECT #row := 0, #res := '', #val := 0
)
AS initialiser
ORDER BY
res, value, id
)
AS sequenced_res_val_id
GROUP BY
res,
value,
res_ordinal - val_ordinal
ORDER BY
MIN(id)
;
If I add res_ordinal, val_ordinal and res_ordinal - val_ordinal to your data, it can be seen that you can now differentiate between the two sets of 44
GROUP
INSERT INTO `table` VALUES ('1', 'gold', '44'); 1 - 1 = 0 (Gold, 44, 0)
INSERT INTO `table` VALUES ('2', 'gold', '44'); 2 - 2 = 0
INSERT INTO `table` VALUES ('3', 'gold', '45'); 3 - 1 = 2 (Gold, 45, 2)
INSERT INTO `table` VALUES ('4', 'gold', '46'); 4 - 1 = 3 (Gold, 46, 3)
INSERT INTO `table` VALUES ('5', 'gold', '44'); 5 - 3 = 2 (Gold, 44, 2)
INSERT INTO `table` VALUES ('6', 'gold', '44'); 6 - 4 = 2
INSERT INTO `table` VALUES ('7', 'gold', '44'); 7 - 5 = 2
INSERT INTO `table` VALUES ('8', 'gold', '47'); 8 - 1 = 7 (Gold, 47, 7)
NOTE: According to your data I could use id instead of making my own res_ordinal. doing it this way, however, copes with gaps in the id sequence and having multiple different resources. This means that in the following example the two golds are considered to be duplicates of each other...
1 Gold 44 1 - 1 = 0 (Gold, 44, 0)
2 Poop 45 1 - 1 = 0 (Poop, 45, 0)
3 Gold 44 2 - 2 = 0 (Gold, 44, 0) -- Duplicate
4 Gold 45 3 - 1 = 2 (Gold, 44, 2)

select t1.*
from `table` t1
where not exists ( select 1
from `table` t2
where t1.id = 1+t2.id
and t1.res = t2.res
and t1.value = t2.value
);
works fine

Use the DISTINCT clause to select unique rows like so:
SELECT DISTINCT res, value FROM table

Use Select DISTINCT res, value FROM table ... to avoid redundancy

Related

How to insert to a table if there exist a value in another table

i have tried this
INSERT into examqst set qno=1, qst="aa" ,qan1="a" , qan2="B", qan3="C", qan4="d", qant="A", qtype=0, examid=1
WHERE EXISTS (SELECT * FROM examname where examid=1 and s_id=10);
I want to insert to examqst only if there is a row examid in examname table and s_id in examname have a perticilar value
You could an INSERT INTO...SELECT:
INSERT INTO examqst (qno, qst, qan1, qan2, qan3, qan4, qant, qtype, examid)
SELECT 1, 'aa', 'a', 'B', 'C', 'd', 'A', 0, 1
WHERE EXISTS (SELECT 1 FROM examname WHERE examid = 1 AND s_id = 10);

How to find median given frequency of numbers?

The Numbers table keeps the value of number and its frequency.
+----------+-------------+
| Number | Frequency |
+----------+-------------|
| 0 | 7 |
| 1 | 1 |
| 2 | 3 |
| 3 | 1 |
+----------+-------------+
In this table, the numbers are 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 2, 3, so the median is (0 + 0) / 2 = 0. How to find median (output shown) given frequency of numbers?
+--------+
| median |
+--------|
| 0.0000 |
+--------+
I found the following solution here. However, I am unable to understand it. Can someone please explain the solution and/or post a different solution with explanation?
SELECT AVG(n.Number) AS median
FROM Numbers n LEFT JOIN
(
SELECT Number, #prev := #count AS prevNumber, (#count := #count + Frequency) AS countNumber
FROM Numbers,
(SELECT #count := 0, #prev := 0, #total := (SELECT SUM(Frequency) FROM Numbers)) temp ORDER BY Number
) n2
ON n.Number = n2.Number
WHERE
(prevNumber < floor((#total+1)/2) AND countNumber >= floor((#total+1)/2))
OR
(prevNumber < floor((#total+2)/2) AND countNumber >= floor((#total+2)/2))
Here's the SQL script for reproducibility:
CREATE TABLE `Numbers` (
`Number` INT NULL,
`Frequency` INT NULL);
INSERT INTO `Numbers` (`Number`, `Frequency`) VALUES ('0', '7');
INSERT INTO `Numbers` (`Number`, `Frequency`) VALUES ('1', '1');
INSERT INTO `Numbers` (`Number`, `Frequency`) VALUES ('2', '3');
INSERT INTO `Numbers` (`Number`, `Frequency`) VALUES ('3', '1');
Thanks!
You can use a cumulative sum and then take the midway point. I think the logic looks like this:
select avg(number)
from (select t.*, (#rf := #rf + frequency) as running_frequency
from (select t.* from t order by number) t cross join
(select #rf := 0) params
) t
where running_frequency - frequency >= ceil(#rf / 2) and
running_frequency <= ceil((#rf + 1) / 2);

Use MySQL variable to create a group by scoring

On MySQL 5.6 server I have this table:
CREATE TABLE `student` (
`course` INT(5) NULL DEFAULT NULL,
`course_desc` VARCHAR(50) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci',
`name` VARCHAR(50) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci',
`vote` INT(2) NULL DEFAULT NULL,
UNIQUE INDEX `course_name` (`course`, `name`)
)
COLLATE='utf8_unicode_ci'
ENGINE=InnoDB;
With data:
INSERT INTO `student` (`course`, `course_desc`, `name`, `vote`) VALUES (100, 'Math', 'Mario', 10);
INSERT INTO `student` (`course`, `course_desc`, `name`, `vote`) VALUES (100, 'Math', 'Giovanna', 8);
INSERT INTO `student` (`course`, `course_desc`, `name`, `vote`) VALUES (100, 'Math', 'Federico', 8);
INSERT INTO `student` (`course`, `course_desc`, `name`, `vote`) VALUES (100, 'Math', 'Arianna', 5);
INSERT INTO `student` (`course`, `course_desc`, `name`, `vote`) VALUES (200, 'History', 'Mario', 9);
INSERT INTO `student` (`course`, `course_desc`, `name`, `vote`) VALUES (200, 'History', 'Giovanna', 7);
INSERT INTO `student` (`course`, `course_desc`, `name`, `vote`) VALUES (200, 'History', 'Patrizio', 3);
INSERT INTO `student` (`course`, `course_desc`, `name`, `vote`) VALUES (200, 'History', 'Teresa', 10);
INSERT INTO `student` (`course`, `course_desc`, `name`, `vote`) VALUES (300, 'Literacy', 'Giovanna', 7);
INSERT INTO `student` (`course`, `course_desc`, `name`, `vote`) VALUES (300, 'Literacy', 'Federico', 6);
INSERT INTO `student` (`course`, `course_desc`, `name`, `vote`) VALUES (300, 'Literacy', 'Arianna', 10);
INSERT INTO `student` (`course`, `course_desc`, `name`, `vote`) VALUES (400, 'Science', 'Giovanni', 9);
INSERT INTO `student` (`course`, `course_desc`, `name`, `vote`) VALUES (400, 'Science', 'Giovanna', 7);
INSERT INTO `student` (`course`, `course_desc`, `name`, `vote`) VALUES (400, 'Science', 'Maria', 9);
INSERT INTO `student` (`course`, `course_desc`, `name`, `vote`) VALUES (400, 'Science', 'Teresa', 0);
INSERT INTO `student` (`course`, `course_desc`, `name`, `vote`) VALUES (400, 'Science', 'Carlo', 7);
INSERT INTO `student` (`course`, `course_desc`, `name`, `vote`) VALUES (400, 'Science', 'Federico', 6);
INSERT INTO `student` (`course`, `course_desc`, `name`, `vote`) VALUES (500, 'Philosophy', 'Maria', 10);
This query:
SELECT
(#id := #id + 1) AS "ID",
t1.`course` AS "COURSE",
t1.`course_desc` AS "COURSE_DESC",
t1.`name` AS "NAME",
t1.`vote` AS "VOTE",
CASE
WHEN #prev_course = t1.`course` THEN
(
CASE
WHEN #prev_vote = t1.`vote` THEN #rank_count
WHEN #prev_vote := t1.`vote` THEN #rank_count := #rank_count + 1
END
)
WHEN #prev_course := t1.`course` THEN
(
#rank_count := 1
)
END AS "RANK"
FROM
(SELECT #id := 0) AS t0,
`student` AS t1,
(SELECT #prev_course := NULL) AS t2,
(SELECT #prev_vote := NULL) AS t3,
(SELECT #rank_count := 0) AS t4
ORDER BY
t1.`course`,
t1.`vote` DESC;
produces a WRONG result:
------------------------------------------------------------------
ID COURSE COURSE_DESC NAME VOTE RANK
------------------------------------------------------------------
1 100 Math Mario 10 1
2 100 Math Giovanna 8 2
3 100 Math Federico 8 2
4 100 Math Arianna 5 3
5 200 History Teresa 10 1
6 200 History Mario 9 2
7 200 History Giovanna 7 3
8 200 History Patrizio 3 4
9 300 Literacy Arianna 10 1
10 300 Literacy Giovanna 7 2
11 300 Literacy Federico 6 3
12 400 Science Giovanni 9 1
13 400 Science Maria 9 2
14 400 Science Giovanna 7 3
15 400 Science Carlo 7 3
16 400 Science Federico 6 4
17 400 Science Teresa 0 NULL
18 500 Philosophy Maria 10 1
------------------------------------------------------------------
This other query:
SELECT
(#id := #id + 1) AS "ID",
t1.`course` AS "COURSE",
t1.`course_desc` AS "COURSE_DESC",
t1.`name` AS "NAME",
t1.`vote` AS "VOTE",
CASE
WHEN #prev_course = t1.`course_desc` THEN
(
CASE
WHEN #prev_vote = t1.`vote` THEN #rank_count
WHEN #prev_vote := t1.`vote` THEN #rank_count := #rank_count + 1
END
)
WHEN #prev_course := t1.`course_desc` THEN
(
#rank_count := 1
)
END AS "RANK"
FROM
(SELECT #id := 0) AS t0,
`student` AS t1,
(SELECT #prev_course := NULL) AS t2,
(SELECT #prev_vote := NULL) AS t3,
(SELECT #rank_count := 0) AS t4
ORDER BY
t1.`course`,
t1.`vote` DESC;
produces a VERY WRONG result
------------------------------------------------------------------
ID COURSE COURSE_DESC NAME VOTE RANK
------------------------------------------------------------------
1 100 Math Mario 10 NULL
2 100 Math Giovanna 8 1
3 100 Math Federico 8 1
4 100 Math Arianna 5 2
5 200 History Teresa 10 3
6 200 History Mario 9 4
7 200 History Giovanna 7 5
8 200 History Patrizio 3 6
9 300 Literacy Arianna 10 7
10 300 Literacy Giovanna 7 8
11 300 Literacy Federico 6 9
12 400 Science Giovanni 9 10
13 400 Science Maria 9 10
14 400 Science Giovanna 7 11
15 400 Science Carlo 7 11
16 400 Science Federico 6 12
17 400 Science Teresa 0 NULL
18 500 Philosophy Maria 10 13
------------------------------------------------------------------
The goal is ranking the table from top (1) to bottom (n) depending on vote value.
Same vote = same rank.
Grouping by course.
I need some help
Tks a lot
MR
Your question is a bit unclear on what you want. But your use of variables is wrong. You should not assign a variable in one expression and then refer to it in another. MySQL does not guarantee the order of evaluation of expressions in a select, so they might be evaluated in the wrong order.
I think you want something like this:
select s.*,
(#rn := if(#c = course_desc, #rn + 1,
if(#c := course_desc, 1, 1)
)
) as rank
from (select s.*
from student s
order by s.course_desc, s.vote desc
) s cross join
(select #c := '', #rn := 0) params;
If you want students with the same vote to have the same value:
select s.*,
(#rn := if(#cv = concat_ws(':', course_desc, vote), #rn,
if(#cv like concat(course_desc, ':%'),
if(#cv := concat_ws(':', course_desc, vote), #rn + 1, #rn + 1),
if(#cv := concat_ws(':', course_desc, vote), 1, 1)
)
)
) as rank
from (select s.*
from student s
order by s.course_desc, s.vote desc
) s cross join
(select #cv := '', #rn := 0) params
Here's another way using LEFT JOIN instead of variables:
SELECT s1.course, s1.course_desc, s1.name, s1.vote, COUNT(s2.name) + 1 AS rank
FROM student AS s1
LEFT JOIN student AS s2
ON s1.course = s2.course AND s1.name <> s2.name AND s1.vote < s2.vote
GROUP BY s1.course, s1.course_desc, s1.name, s1.vote
ORDER BY s1.course, rank
The query assumes each student is uniquely identified by his/her name. In a real-world scenario you would substitute the name with an id.
Demo here

Count consecutive rows with a particular status

I need to count whether there are three consecutive failed login attempts of the user in last one hour.
For example
id userid status logindate
1 1 0 2014-08-28 10:00:00
2 1 1 2014-08-28 10:10:35
3 1 0 2014-08-28 10:30:00
4 1 0 2014-08-28 10:40:00
In the above example, status 0 means failed attempt and 1 means successful attempt.
I need a query that will count three consecutive records of a user with status 0 occurred in last one hour.
I tried below query
SELECT COUNT( * ) AS total, Temp.status
FROM (
SELECT a.status, MAX( a.id ) AS idlimit
FROM loginAttempts a
GROUP BY a.status
ORDER BY MAX( a.id ) DESC
) AS Temp
JOIN loginAttempts t ON Temp.idlimit < t.id
HAVING total >1
Result:
total status
2 1
I don't know why it display status as 1. I also need to add a where condition on logindate and status field but don't know how would it work
For consecutive count you can use user defined variables to note the series values ,like in below query i have use #g and #r variable, in inner query i am storing the current status value that could be 1/0 and in case expression i am comparing the value stored in #g with the status column if they both are equal like #g is holding previous row value and previous row's status is equal to the current row's status then do not change the value stored in #r,if these values don't match like #g <> a.status then increment #r with 1, one thing to note i am using order by with id column and assuming it is set to auto_increment so for consecutive 1s #r value will be same like #r was 3 for first status 1 and the again status is 1 so #r will 3 until the status changes to 0 same for status 0 vice versa
SELECT t.userid,t.consecutive,t.status,COUNT(1) consecutive_count
FROM (
SELECT a.* ,
#r:= CASE WHEN #g = a.status THEN #r ELSE #r + 1 END consecutive,
#g:= a.status g
FROM attempts a
CROSS JOIN (SELECT #g:=2, #r:=0) t1
WHERE a.`logindate` BETWEEN '2014-08-28 10:00:00' AND '2014-08-28 11:00:00'
ORDER BY id
) t
GROUP BY t.userid,t.consecutive,t.status
HAVING consecutive_count >= 3 AND t.status = 0
Now in parent query i am grouping results by userid the resultant value of case expression i have name is it as consecutive and status to get the count for each user's consecutive status
One thing to note for above query that its necessary to provide the
hour range like i have used between without this it will be more
difficult to find exactly 3 consecutive statuses with in an hour
Sample data
INSERT INTO attempts
(`id`, `userid`, `status`, `logindate`)
VALUES
(1, 1, 0, '2014-08-28 10:00:00'),
(2, 1, 1, '2014-08-28 10:10:35'),
(3, 1, 0, '2014-08-28 10:30:00'),
(4, 1, 0, '2014-08-28 10:40:00'),
(5, 1, 0, '2014-08-28 10:50:00'),
(6, 2, 0, '2014-08-28 10:00:00'),
(7, 2, 0, '2014-08-28 10:10:35'),
(8, 2, 0, '2014-08-28 10:30:00'),
(9, 2, 1, '2014-08-28 10:40:00'),
(10, 2, 1, '2014-08-28 10:50:00')
;
As you can see from id 3 to 5 you can see consecutive 0s for userid 1 and similarly id 6 to 8 userid 2 has consecutive 0s and they are in an hour range using above query you can have results as below
userid consecutive status consecutive_count
------ ----------- ------ -------------------
1 2 0 3
2 2 0 3
Fiddle Demo
M Khalid Junaid's answer is great, but his Fiddle Demo didn't work for me when I clicked it.
Here is a Fiddle Demo which works as of this writing.
In case it doesn't work later, I used the following in the schema:
CREATE TABLE attempts
(`id` int, `userid` int, `status` int, `logindate` datetime);
INSERT INTO attempts
(`id`, `userid`, `status`, `logindate`)
VALUES
(1, 1, 0, '2014-08-28 10:00:00'),
(2, 1, 1, '2014-08-28 10:10:35'),
(3, 1, 0, '2014-08-28 10:30:00'),
(4, 1, 0, '2014-08-28 10:40:00'),
(5, 1, 0, '2014-08-28 10:50:00'),
(6, 2, 0, '2014-08-28 10:00:00'),
(7, 2, 0, '2014-08-28 10:10:35'),
(8, 2, 0, '2014-08-28 10:30:00'),
(9, 2, 1, '2014-08-28 10:40:00'),
(10, 2, 1, '2014-08-28 10:50:00')
;
And this as the query:
SELECT t.userid,t.consecutive,t.status,COUNT(1) consecutive_count
FROM (
SELECT a.* ,
#r:= CASE WHEN #g = a.status THEN #r ELSE #r + 1 END consecutive,
#g:= a.status g
FROM attempts a
CROSS JOIN (SELECT #g:=2, #r:=0) t1
WHERE a.`logindate` BETWEEN '2014-08-28 10:00:00' AND '2014-08-28 11:00:00'
ORDER BY id
) t
GROUP BY t.userid,t.consecutive,t.status
HAVING consecutive_count >= 3 AND t.status = 0;

Two Limit Clauses in one statement?

I am trying to build a product review page where users can comment any submitted reviews like Amazon. I want the page to display 3 user reviews as well as 2 responses to each of the reviews if they exist.
Here's the table
CREATE TABLE product_review
(`ID` int, `username` varchar(21), `review_title` varchar(30), `review_or_reply` int)
;
INSERT INTO product_review
(`ID`, `username`, `review_title`, `review_or_reply`)
VALUES
(1, 'Tom', 'Rip-off', 0),
(2, 'Peter', 'Rip-off', 1),
(3, 'May', 'Rip-off', 1),
(4, 'June', 'Rip-off', 1),
(5, 'Tommy', 'Worth the Price', 0),
(6, 'Sammy', 'Worth the Price', 1),
(7, 'Sam', 'Worth the Price',1),
(8, 'Bryan','Worth the Price',1),
(9, 'Sally', 'Average Product', 0)
;
The review_or_reply field is effectively a Yes or No field, where 0 means it's a review and 1 is the review's comments by other users.
Is there a single select statement that can limit 3 reviews and bring up two of their comments? For example:
Select `username`,`review_title`,`reply` from product_review where review_or_reply ='0' Limit 3
Select `username`,`review_title`,`reply` from product_review where review_or_reply = '1' and title = 'Rip-off' Limit 2
Select `username`,`review_title`,`reply` from product_review where review_or_reply = '1' and title = 'Worth the Price' Limit 2
Select `username`,`review_title`,`reply` from product_review where review_or_reply = '1' and title = 'Average Product' Limit 2
I want the output to be like this:
username review_title review_or_reply
Tom Rip-off 0
Peter Rip-off 1
May Rip-off 1
Tommy Worth the Price 0
Sammy Worth the Price 1
Sam Worth the Price 1
Sally Average Product 0
this will return 3 review_titles and then pull out two responses to that
SELECT
pr.*,
IF( #A = t.review_title,
IF(#B = 3, #B := 1, #B := #B +1)
, #B
) AS group_col,
#A := t.review_title
FROM (
SELECT
id,
username,
review_title
FROM product_review
WHERE reply ='0' LIMIT 3
) t
JOIN product_review pr ON pr.review_title=t.review_title
CROSS JOIN (SELECT #A := "", #B := 1) AS temp
GROUP BY group_col, review_title
ORDER BY id;
EDIT:
if there are more than one reply that is 0 in the database like so then this query will check for that. (since you did specify in the other queries that the reply had to be 1).
INSERT INTO product_review
(`ID`, `username`, `review_title`, `reply`)
VALUES
(1, 'Tom', 'Rip-off', 0),
(2, 'Peter', 'Rip-off', 1),
(3, 'May', 'Rip-off', 0),
(4, 'June', 'Rip-off', 1),
(5, 'Tommy', 'Worth the Price', 0),
(6, 'Sammy', 'Worth the Price', 1),
(7, 'Sam', 'Worth the Price',1),
(8, 'Bryan','Worth the Price',1),
(9, 'Sally', 'Average Product', 0),
(10, 'Timothy', 'Rip-off', 1)
notice that at id 3 there is a reply of 0 with id 10 a reply of 1. this query will correctly skip the reply = 0.
SELECT
pr.*,
IF( #A = t.review_title,
IF(pr.reply = 0, 1,
IF(#B = 3, #B := 1, #B := #B +1)
), #B
) AS group_col,
#A := t.review_title
FROM (
SELECT
DISTINCT
id,
username,
review_title
FROM product_review
WHERE reply ='0'
GROUP BY review_title
LIMIT 3
) t
JOIN product_review pr ON pr.review_title=t.review_title
CROSS JOIN (SELECT #A := "", #B := 1) AS temp
GROUP BY group_col, review_title
ORDER BY id;
DEMO
...or slower but simpler...
SELECT x.*
FROM product_review x
JOIN product_review y
ON y.review_title = x.review_title
AND y.id <= x.id
GROUP
BY x.id
HAVING COUNT(*) <= 3
ORDER
BY MIN(y.id)
LIMIT 3;