I have same problem like in this question Count rows until value in column changes mysql
But although the issue was resolved I not understand this query. Because I have low reputation points I can't leave comment there and must open new question.
My example:
mysql> select * from example;
+----+-------+------+
| id | name | succ |
+----+-------+------+
| 1 | peter | 1 |
| 2 | bob | 1 |
| 3 | peter | 0 |
| 4 | peter | 0 |
| 5 | nick | 1 |
| 6 | bob | 0 |
| 7 | peter | 1 |
| 8 | bob | 0 |
| 9 | peter | 1 |
| 10 | peter | 1 |
+----+-------+------+
10 rows in set (0.00 sec)
I want to count successive true values for peter (descending id, and results must be 3), I know how to set query like this :
mysql> select count(succ)
from example
where id > (select max(id) from example where succ = 0);
+-------------+
| count(succ) |
+-------------+
| 2 |
+-------------+
1 row in set (0.00 sec)
But how to get results just for peter, and if it is possible to get results grouped by name, like this:
+--------+------+
|name | succ |
+--------+------+
| peter | 3 |
| bob | 0 |
| nick | 1 |
+--------+------+
Use variables to count consecutive successes (re-starting when seeing a failure), and join with a query which selects highest id per name (somewhat similar to McNets's answer)
SELECT a.name, a.count FROM (
SELECT e1.id, e1.name, e1.succ,
#count_success := IF (#prev_name = e1.name AND e1.succ = 1, #count_success + 1, e1.succ) AS `count`,
#prev_name := e1.name AS `prev_name`
FROM `example` e1, (SELECT #count_success :=0, #prev_name := NULL) init
ORDER BY e1.name, e1.id ) `a`
JOIN (SELECT MAX(id) AS `max` FROM `example` GROUP BY `name`) `b` ON a.id = b.max
One way to solve this is with a self join. Using an outer join you exclude the rows that have a matching row with a higher id value and succ = 0, and then count the rows with succ = 1 using SUM() and CASE.
Here's the query for your example:
select e1.name,
sum(case when e1.succ = 1 then 1 else 0 end) as succ
from example e1
left outer join example e2 on e2.id > e1.id
and e2.name = e1.name
and e2.succ = 0
where e2.id is null
group by e1.name
if you need the count of records
select name, count(succ) from example group by name
or if you need the sum of the succ of every person you can use
select name, sum(succ) from example group by name
Related
I have an abstract problem which can be simplified as the following problem: Assume that we have two tables persons and names that look as follows:
SELECT * FROM persons;
+----+-------+--------+
| id | name | fan_of |
+----+-------+--------+
| 1 | alice | 2 |
| 2 | bob | 4 |
| 3 | carol | 1 |
| 4 | dave | 3 |
| 5 | bob | 2 |
+----+-------+--------+
and
SELECT * FROM names;
+----+-------+--------+
| id | name | active |
+----+-------+--------+
| 1 | alice | 1 |
| 2 | bob | 1 |
| 3 | carol | 0 |
| 4 | dave | 1 |
+----+-------+--------+
Every person (a row in the persons) table is a fan of itself or another person (represented by that other persons id in the fan_of column). The names table contains names that can be active or inactive.
For a given offset k, I want to SELECT the persons (rows of persons) that have the k+1-th active name as their name or that have one of these people as their fans. For example, if the offset is 1, the second active name is bob and hence I want to select all people with the name bob plus the people that have one of these bobs as their fans, which is in this example the row of persons with id=4. This means that I want to have the result:
+----+------+--------+
| id | name | fan_of |
+----+------+--------+
| 2 | bob | 4 |
| 4 | dave | 3 |
| 5 | bob | 2 |
+----+------+--------+
What I have so far is the following query:
1 SELECT * FROM persons WHERE
2 EXISTS (
3 SELECT * FROM (
4 SELECT * FROM names WHERE active=true LIMIT 1 OFFSET 1
5 ) AS selectedname WHERE (selectedname.name=persons.name)
6 )
7 OR
8 EXISTS (
9 SELECT * FROM(
10 SELECT * FROM persons WHERE EXISTS (
11 SELECT * FROM (
12 SELECT * FROM names WHERE active=true LIMIT 1 OFFSET 1
13 ) AS selectedname WHERE (selectedname.name=persons.name)
14 )
15 ) AS personswiththatname WHERE persons.id=personswiththatname.fan_of
16 );
It gives me the desired result from above but please note that it is inefficient because the lines 3-5 and 11-13 are the same.
I have the following two questions:
What can be done to avoid this inefficiency?
I actually need to distinguish between those rows that came from the
name condition (here the rows with name=bob) and those that came
from the fan_of condition (here the row with name=dave). This
could be done in the application code but then I would need another
database query before to find out the k+1-th active name and this might
be slow (please correct me if this is the better solution). I would
rather prefer an additional column z that helps me to distinguish
like
+----+------+--------+---+
| id | name | fan_of | z |
+----+------+--------+---+
| 2 | bob | 4 | 1 |
| 4 | dave | 3 | 0 |
| 5 | bob | 2 | 1 |
+----+------+--------+---+
How can such an output be achieved?
It looks like I can get the minimum you want to achieve using parameters (should this be an option).
It's not pretty, but I can't see a simple way of achieving what you're asking for, so this is what I have so far....(set #offset to suit 'k')
SET #offset = 1;
SET #name = (SELECT name FROM (select name, #rank := #rank +1 as Rank from names n, (SELECT #rank := 0) r where active !=0) as activeRanked where activeRanked.rank = (1 + #offset));
select
a.*
From persons a
where (a.name = #name) OR (a.id IN (SELECT fan_of from persons where name = #name));
If you still don't have an answer by the time I've had food, I'll look at part 2.
(hopefully I've read your brief correctly)
P.S. I've kept the #name SQL in a single line as it seems to read better in this context.
Edit: Here's a pretty messy but functional indicator of source, using your example. Z = 1 is where the row is from the name, '0' is from fan_of
SET #offset = 1;
SET #name = (SELECT name FROM (select name, #rank := #rank +1 as Rank from names n, (SELECT #rank := 0) r where active !=0) as activeRanked where activeRanked.rank = (1 + #offset));
select
a.*,'1' as z
From persons a
where (a.name = #name)
union
select
a.*,'0' as z
From persons a
where (a.id IN (SELECT fan_of from persons where name = #name));
Distinct ID Query:
SET #offset = 1;
SET #name = (SELECT name FROM (select name, #rank := #rank +1 as Rank from names n, (SELECT #rank := 0) r where active !=0) as activeRanked where activeRanked.rank = (1 + #offset));
SELECT id, name, fan_of, z FROM
(select
distinct a.id,
a.name,
a.fan_of,
1 as z
From persons a
where (a.name = #name)
union
select
distinct a.id,
a.name,
a.fan_of,
0 as z
From persons a
where (a.id IN (SELECT fan_of from persons where name = #name))
ORDER BY z desc) qry
GROUP BY id;
This produces:
+----+------+--------+---+
| id | name | fan_of | z |
+----+------+--------+---+
| 2 | bob | 4 | 1 |
| 5 | bob | 2 | 1 |
| 4 | dave | 3 | 0 |
+----+------+--------+---+
Please how can i check if a particular name is greater than a value in MySQL database. I want to do something like.
SELECT id FROM table WHERE COUNT(name = 'john') > 2
i know this does not work, but i really need to do something like this.
An Example: A student studying CS, From New York, and grad point is 4.5 wants to check into a hostel.
I have a hostel tables with fields course, state, and Grade_Point. i want to select the hostel_id where no same user with the same course > 2, state > 2 and grade_point > 2 are in the same room.
You can use the HAVING clause :
SELECT t.id FROM YourTable t
GROUP BY id
HAVING SUM(t.name = 'john') > 2
MySQL takes boolean expression as 0,1 , so SUM(t.name = 'john') will sum the number of occurences john appears for each ID , and will bring back those that appear more then twice.
Assuming that id is not unique and you want ids where 'john' appears 3 or more times:
select id
from t
where name = 'john'
group by id
having count(*) > 2;
This should be more efficient than any version that uses conditional aggregation because it reduces the size of the data before doing the aggregation.
Try this;)
SELECT id FROM table HAVING COUNT(IF(name = 'john', 1, null)) > 2
SELECT name, count(*)
FROM YourTable
where name = 'john'
GROUP BY name
HAVING count(*) > 2
Consiedr the following...
SELECT * FROM my_table ORDER BY id;
+----+------+
| Id | Name |
+----+------+
| 1 | Q |
| 2 | W |
| 3 | E |
| 4 | R |
| 5 | T |
| 6 | Y |
+----+------+
INSERT INTO my_table (name) SELECT 'Q' FROM (SELECT 1) x LEFT JOIN my_table y ON y.name = 'Q' WHERE y.id IS NULL;
Query OK, 0 rows affected (0.00 sec)
Records: 0 Duplicates: 0 Warnings: 0
SELECT * FROM my_table ORDER BY id;
+----+------+
| Id | Name |
+----+------+
| 1 | Q |
| 2 | W |
| 3 | E |
| 4 | R |
| 5 | T |
| 6 | Y |
+----+------+
INSERT INTO my_table (name) SELECT 'Z' FROM (SELECT 1) x LEFT JOIN my_table y ON y.name = 'Z' WHERE y.id IS NULL;
Query OK, 1 row affected (0.00 sec)
Records: 1 Duplicates: 0 Warnings: 0
SELECT * FROM my_table ORDER BY id;
+----+------+
| Id | Name |
+----+------+
| 1 | Q |
| 2 | W |
| 3 | E |
| 4 | R |
| 5 | T |
| 6 | Y |
| 7 | Z |
+----+------+
7 rows in set (0.00 sec)
Alternatively, you can just issue a simple INSERT on a UNIQUE column. And use the 'rows affected' as evidence of whether the name already exists.
I have a basic table:
+-----+--------+------+------+
| id, | name, | cat, | time |
+-----+--------+------+------+
| 1 | jamie | 1 | 100 |
| 2 | jamie | 2 | 100 |
| 3 | jamie | 1 | 50 |
| 4 | jamie | 2 | 150 |
| 5 | bob | 1 | 100 |
| 6 | tim | 1 | 300 |
| 7 | alice | 4 | 100 |
+-----+--------+------+------+
I tried using the "Left Joining with self, tweaking join conditions and filters" part of this answer: SQL Select only rows with Max Value on a Column but some reason when there are records with a value of 0 it breaks, and it also doesn't return every unique answer for some reason.
When doing the query on this table I'd like to receive the following values:
+-----+--------+------+------+
| id, | name, | cat, | time |
+-----+--------+------+------+
| 1 | jamie | 1 | 100 |
| 4 | jamie | 2 | 150 |
| 5 | bob | 1 | 100 |
| 6 | tim | 1 | 300 |
| 7 | alice | 4 | 100 |
+-----+--------+------+------+
Because they are unique on name and cat and have the highest time value.
The query I adapted from the answer above is:
SELECT a.name, a.cat, a.id, a.time
FROM data A
INNER JOIN (
SELECT name, cat, id, MAX(time) as time
FROM data
WHERE extra_column = 1
GROUP BY name, cat
) b ON a.id = b.id AND a.time = b.time
The issue here is that ID is unique per row you can't get the unique value when getting the max; you have to join on the grouped values instead.
SELECT a.name, a.cat, a.id, a.time
FROM data A
INNER JOIN (
SELECT name, cat, MAX(time) as time
FROM data
WHERE extra_column = 1
GROUP BY name, cat
) b ON A.Cat = B.cat and A.Name = B.Name AND a.time = b.time
Think about it... So what ID is mySQL returning form the Inline view? It could be 1 or 3 and 2 or 4 for jamie. Hows does the engine know to pick the one with the max ID? it is "free to choose any value from each group, so unless they are the same, the values chosen are indeterminate. " it could pick the wrong one resulting in incorrect results. So you can't use it to join on.
https://dev.mysql.com/doc/refman/5.0/en/group-by-handling.html
If you want to use a self join, you could use this query:
SELECT
d1.*
FROM
date d1 LEFT JOIN date d2
ON d1.name=d2.name
AND d1.cat=d2.cat
AND d1.time<d2.time
WHERE
d2.time IS NULL
It is very simple
SELECT MAX(TIME),name,cat FROM table name group by cat
Table Mytable1
Id | Actual
1 ! 10020
2 | 12203
3 | 12312
4 | 12453
5 | 13211
6 | 12838
7 | 10l29
Using the following syntax:
SELECT AVG(Actual), CEIL((#rank:=#rank+1)/3) AS rank FROM mytable1 Group BY rank;
Produces the following type of result:
| AVG(Actual) | rank |
+-------------+------+
| 12835.5455 | 1 |
| 12523.1818 | 2 |
| 12343.3636 | 3 |
I would like to take AVG(Actual) column and UPDATE a second existing table Mytable2
Id | Predict |
1 | 11133
2 | 12312
3 | 13221
I would like to get the following where the Actual value matches the ID as RANK
Id | Predict | Actual
1 | 11133 | 12835.5455
2 | 12312 | 12523.1818
3 | 13221 | 12343.3636
IMPORTANT REQUIREMENT
I need to set an offset much like the following syntax:
SELECT #rank := #rank + 1 AS Id , Mytable2.Actual FROM Mytable LIMIT 3 OFFSET 4);
PLEASE NOTE THE AVERAGE NUMBER ARE MADE UP IN EXAMPLES
you can join your existing query in the UPDATE statement
UPDATE Table2 T2
JOIN (
SELECT AVG(Actual) as AverageValue,
CEIL((#rank:=#rank+1)/3) AS rank
FROM Table1, (select #rank:=0) t
Group BY rank )T1
on T2.id = T1.rank
SET Actual = T1.AverageValue
I have data which is formed like this:
+----------+-------+-------+
| DAY | VALUE | Name |
+----------+-------+-------+
| 01/01/14 | 1030 | BOB
| 01/02/14 | 1020 | BOB
| 01/03/14 | 1080 | BOB
| 01/04/14 | 1090 | BOB
| 01/05/14 | 1040 | BOB
| 01/08/14 | 1030 | BOB
| 01/11/14 | 4030 | BOB
| 01/12/14 | 5000 | BOB
| 01/13/14 | 6000 | BOB
| 01/14/14 | 1096 | BOB
| 01/14/14 | 1200 | MIKE
| 01/15/14 | 1040 | MIKE
| 01/16/14 | 1600 | MIKE
| 01/17/14 | 1070 | MIKE
| 01/18/14 | 1340 | MIKE
| 01/19/14 | 1060 | MIKE
| 01/01/14 | 6000 | JANE
| 01/02/14 | 1700 | JANE
| 01/03/14 | 1070 | JANE
| 01/04/14 | 8000 | JANE
+----------+-------+------+
For each name there needs to be a row for the dates between 01/01/14 to 02/01/14 (1 month). As you can see Bob, Mike, and Jane (although in my real database there are thousands of names) are all missing dates between this time period. I would like to somehow insert the missing rows by interpolation of some sort. For example Bob is missing 01/06/14 and 01/07/14. I would like it to interpolate by adding these two dates and then the values to be the average of the two field between so these two missing fields would both have the value ((1040+1030)/2) = 1035. If there is no data before like for MIKE (starts at 01/14/14) I would like all the new rows to have 01/14/14 value now. I have tried various different techniques such as using coalesce command, cursors, but can't get it to work. Also I am not set on having these EXACT values, if there is some sort of math library which can interpolate I would be open to this as well. Thanks.
You have two problems, generating the rows and interpolating the values. You can generate the rows with this SQL:
select d.day, n.name, t.value
from (select distinct name from table t) n cross join
(select distinct day from table t) d left outer join
table t
on t.name = n.name and t.day = d.day;
Doing the interpolation is troublesome. You can do this using variables and multiple sorting. Here is logic:
select day, name, value, prev_value,
#value as next_value,
#value := if(#name = name and value is not null, value, #value),
#name := name
from (select d.day, n.name, t.value,
#value as prev_value,
#value := if(#name = name and value is not null, value, #value),
#name := name
from (select distinct name from table t) n cross join
(select distinct day from table t) d left outer join
table t
on t.name = n.name and t.day = d.day cross join
(select #name := '', #value := NULL) vars
order by n.name, d.day
) t cross join
(select #name := '', #value := NULL) vars
order by n.name, d.day desc;
This will probably work for you, but it is depending on MySQL evaluating the expressions in order in each select (for the assignment of variables). You can make the syntax more complicated to fix this, but that would hide the logic. You can now implement the logic that you want:
select day, name,
(case when value is not null then value
when prev_value is not null and next_value is not null
then (prev_value + next_value) / 2
when prev_value is null then next_value
else prev_value
end) as value
from (<previous query here>) t;