MYSQL count distinct datas depends on if condition - mysql

I have really different problem about database query. There is a little bit different scenarios:
I have a table created with 3 columns. They have ID, ItemId, TypeId columns. I need a count query, it should count ItemId and TypeId together but except duplicate columns. For example;
Id ItemId TypeId
-- ------ ------
1 1 1 -> count +1
2 1 1 -> ignore
3 1 2 -> count -1
4 1 2 -> ignore
5 1 1 -> count +1
result count = 1
In the end, if distinct row repeated, count ignore that row. But TypeId data changed for one specific Item it should increase or decrease count. TypeId equals to 1 count +=1, equals to 2 count -=1.

In MySQL, you would seemingly use count(distinct):
select count(distinct itemId, typeId)
from t;
However, you really have a gaps-and-islands problem. You are looking at the ordering to see where things change.
If I trust that the id has no gaps, you can do:
select count(*)
from t left join
t tprev
on t.id = tprev.id + 1
where not ((t.itemId, t.typeid) <=> (tprev.itemId, t.prev.id))

Try the following query. This employs User-defined session variables. It will work in all the cases (including gaps in Id):
SELECT
SUM(dt.factor) AS total_count
FROM
( SELECT
#factor := IF(#item = ItemId AND
#type = TypeId,
0,
IF(TypeID = 2, -1, 1)
) AS factor,
#item := ItemId,
#type := TypeId
FROM your_table
CROSS JOIN (SELECT #item := 0,
#type := 0,
#factor := 0) AS user_init_vars
ORDER BY Id
) AS dt
DB Fiddle DEMO

Related

MySQL: how to select the Nth value of each group with GROUP BY

I want to select the 2nd response column value of each new_threads group, with a zero as the value if it is a group of 1 row.
new_treads|response
------------------
1 | 0
1 | 1
2 | 0
2 | 0
2 | 1
... | ...
9 | 0
9 | 1
9 | 0
10 | 0
The output being:
new_treads|response
------------------
1 | 1
2 | 0
... | ...
9 | 1
10 | 0
So far, I understand how to get the first with MIN, but I need the 2nd
SELECT
thread,
min(response)
FROM messages
GROUP BY thread;
I would like to use GROUP BY because I'm using GROUP BY for other SELECTs as well
Thanks!
Since the rows are not "numbered", you need to create a number for each group and then select it. I'd do that with user variables:
select thread, response
from (
select #n := (case
when m.thread = #prev_thread then #n
else 0
end) + 1 as n -- If the current thread is the same as the
-- previous row, then increase the counter,
-- else, reset it
, #prev_thread := m.thread as thread -- Update the value of
-- #prev_thread
, m.response
from
(select #n := 0, #prev_thread := 0) as init
-- The 'init' subquery initializes the
-- temp variables:
-- #n is a counter
-- #prev_thread is an identifier for the
-- previous thread id
, messages as m
order by m.thread -- You need to add a second column to order
-- each response (such as "response_id", or
-- something like that), otherwise the returned
-- row may be a random one
) as a
where n = 2; -- Select the rows from the subquery where the counter equals 2
The above works quite fine to find the 2nd row of each group, but only if there's one. So now: how to get a NULL value if there isn't a second row?
The easiest way to do this would be to use a left join:
select t.thread, b.response
from (select distinct thread from messages) as t
left join (
-- Put the above query here
) as b on t.thread = b.thread;
SELECT
thread,
min(response)
FROM messages
GROUP BY thread
HAVING response > min(response)
try this just want to know if it works
I would like to elaborate on the answer above. While it worked well for me, it took some time to piece together the context and generalize it so I could apply it to my code. I hope that this answer will better generalize what is laid out above...
SELECT *
FROM (SELECT distinct keyField --- keyField is the field the query is grouping by
FROM TABLE
-- Add keyField Constraint --
-- Add non-keyField Constraint --
INNER JOIN (SELECT *,
#n:=(CASE -- Iterating through...
WHEN keyField = #prev_keyField -- When keyField value == previous keyField value
THEN #n:=#n+1 -- Define n as the row in the group
ELSE 1 -- When keyField value != previous keyField value, then n is the 1st row in the group
END) as n,
#prev_keyField:= keyField -- Define previous keyField value for the next iteration
FROM (SELECT #n:=0,#prev_keyField:=0) r,TABLE as p
-- Add non-keyField Constraint--
ORDER BY keyField,sortField DESC -- Order by keyField and the field you are sorting by
-- ei. keyField could be `thread`,
-- and sort field could be `timestamp` if you are sorting by time
) s ON s.keyField = p.keyField
WHERE s.n = 2 -- Define which row in the group you want in the query

How to group rows in mysql to be as chunks with a specific size

How to group rows in mysql to be as chunks with a specific size ?
I have a table called users
Which has these fields id name
I want to make a query which group users into chucks with the size of 3 names per row.
Sample table:
1 name1
2 name2
3 name4
4 name5
5 name5
6 name6
7 name7
result should be
name1,name2,name3
name4,name5,name6
name7
Usign GROUP_CONCAT of course to do that
http://sqlfiddle.com/#!9/a6b42/5
SELECT GROUP_CONCAT(name),
(IF(#i = 3 OR #i IS NULL, #i:=1, #i:=#i+1 ))as idx,
(IF(#gr_idx IS NULL, #gr_idx:=1, IF(#i = 1, #gr_idx:=#gr_idx+1,#gr_idx) )) as gr_idx
FROM users
GROUP BY gr_idx
You can use modulo arithmetic and conditional aggregation to get three columns:
select max(case when mod(id, 3) = 1 then name end),
max(case when mod(id, 3) = 2 then name end),
max(case when mod(id, 3) = 0 then name end)
from users u
group by floor((id - 1) / 3);
Alternatively, if you only want one column, use group_concat():
select group_concat(name) as names
from users u
group by floor((id - 1) / 3);
Both these solutions assume that id increases by 1 with no gaps. If not, there are ways to assign a sequential number using variables.
SELECT
GROUP_CONCAT(name SEPARATOR ',')
FROM users
GROUP BY
floor((id - 1) / 3);
Here is an SQL fiddle demonstrating this: http://sqlfiddle.com/#!9/f3158/2/0
If the IDs are not ascending or not succeeding then this query may be it:
SELECT names from (
SELECT GROUP_CONCAT(name) as names,
#rownum := #rownum + 1 AS rank
FROM users u,
(SELECT #rownum := 0) r
GROUP BY floor(#rownum / 3)
) _users ;
Fiddle again: http://sqlfiddle.com/#!9/f3158/13/0

how to find the number of consecutive repeats

So I'm trying to write a mysql script to find the number of consecutive repeats in 'value' column of this table.
id value result
-- ----- ------
1 1 0
2 1 1
3 2 0
4 3 0
5 3 1
So in this case I want get the value 2
Get the next value using user variables,
GROUP so consecutive values more than 2 are not counted again,put all in a subquery,and use a simple CASE to increment the value you need in case value=next value.Add salt and pepper.
SELECT SUM(CASE WHEN y.value=y.next_value THEN #var+1 ELSE #var END) consecIds
FROM
(SELECT t.id, t.value, next_id, n.value next_value
FROM
(
SELECT t.id, t.value,
(
SELECT id
FROM table1
WHERE id > t.id
ORDER BY id
LIMIT 1
) next_id
FROM table1 t,(SELECT #var:=0)x
) t LEFT JOIN table1 n
ON t.next_id = n.id
GROUP BY t.value,n.value)y
FIDDLE
SELECT COUNT(DISTINCT column_name) FROM table_name;
DISTINCT will erase duplicated repetitions from specified column in result.
COUNT will count the rows in result.
The COUNT(DISTINCT column_name) function returns the number of distinct values of the specified column.

Cannot cumulatively sum `COUNT(*)`

The second section of this answer uses variables to create a cumulative sum of another column. I'm doing the same thing, except that I am using a GROUP BY statement, and summing COUNT(*) instead of a column. Here is my code to create a minimal table and insert values:
CREATE TABLE `test_group_cumulative` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`group_id` int(11) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `test_group_cumulative` (`id`, `group_id`)
VALUES
(1, 1),
(2, 2),
(3, 3);
And here is the code that is failing:
SELECT
`group_id`,
COUNT(*) AS `count`,
#count_cumulative := #count_cumulative + COUNT(*) AS `count_cumulative`
FROM `test_group_cumulative` AS `tgc`
JOIN (SELECT #count_cumulative := 0) AS `_count_cumulative`
GROUP BY `group_id`
ORDER BY `id`;
Here is the result:
group_id count count_cumulative
1 1 1
2 1 1
3 1 1
As you can see, count_cumulative is NOT summing correctly. However, here's the weird part. If I replace the COUNT(*) in count_cumulative with it's value, 1, the query works correctly.
#count_cumulative := #count_cumulative + 1 AS `count_cumulative`
Here is the correct result:
group_id count count_cumulative
1 1 1
2 1 2
3 1 3
Obviously, in my app, there will be more than one item in each group, so COUNT(*) won't always be 1. I know there are ways to do this with joins or subqueries, and I'll do that if I have to, but in my mind this SHOULD work. So why isn't COUNT(*) working inside of a cumulative sum?
I agree with #Ashalynd, the value of count(*) is not evaluated yet. Here is a little experiment I did :
1.
SELECT
GROUP_ID,
#COUNTER := #COUNTER + COUNT(*) GROUPCOUNT,
#COUNTER COUNTER
FROM
TEST_GROUP_CUMULATIVE,
(SELECT #COUNTER := 0) R
GROUP BY
GROUP_ID;
-- RESULT
============
GROUP_ID GROUPCOUNT COUNTER
------------------------------------
1 1 0
2 1 0
3 1 0
2.
SELECT #COUNTER;
-- RESULT
=============
#COUNTER
--------
1
For each group the variable is being initialized as 0. This means COUNT(*) has not been evaluated yet.
Also, when you do:
1.
SELECT
GROUP_ID,
#COUNTER := #COUNTER + 1 GROUPCOUNT,
#COUNTER COUNTER
FROM
TEST_GROUP_CUMULATIVE,
(SELECT #COUNTER := 0) R
GROUP BY
GROUP_ID;
-- RESULT
============
GROUP_ID GROUPCOUNT COUNTER
------------------------------------
1 1 1
2 1 2
3 1 3
2.
SELECT #COUNTER;
-- RESULT
=============
#COUNTER
--------
3
It does not have to evaluate 1. It directly sums it up and it gives you the cumulative sum.
This is a problem I often face when doing time series analysis. My preferred way to tackle this is to wrap it into a second select and introduce the counter in the last layer. And you can adapt this technique to more complicated data flows using temporary tables, if reqiured.
I did this small sqlfiddle using the schema you present: http://sqlfiddle.com/#!2/cc97e/21
And here is the query to get the cumulative count:
SELECT
tgc.group_id, #count_cumulative := #count_cumulative + cnt as cum_cnt
FROM (
SELECT
group_id, COUNT(*) AS cnt
FROM `test_group_cumulative`
group by group_id
order by id) AS `tgc`,
(SELECT #count_cumulative := 0) AS `temp_var`;
This is the result I get:
GROUP_ID CUM_CNT
1 1
2 2
3 3
The reason your attempt did not work:
When you do a group by with the temporary variable, mysql executes individual groups independently, and at the time each group is assigned the temporary variable current value, which in this case is 0.
If, you ran this query:
SELECT #count_cumulative;
immediately after
SELECT
`group_id`,
COUNT(*) AS `count`,
#count_cumulative := #count_cumulative + COUNT(*) AS `count_cumulative`
FROM `test_group_cumulative` AS `tgc`
JOIN (SELECT #count_cumulative := 0) AS `_count_cumulative`
GROUP BY `group_id`
ORDER BY `id`;
you would get the value 1. For each of your groups, the #count_cumulative is being reset to 0.
Hence, in my proposed solution, I circumvent this issue by generating the 'group-counts' first and then doing the accumulation.

Getting mysql query results based on a column value

I have a MySql table with two columns namely category and name. I have 4 unique values of category and there are thousands of records in this table. But all these records fall into either of the 4 categories present in the table.
Now, What I want is that as output, I should get 2 results of each category i.e. 2 results of first category, then 2 results of next category and so on.
Is it possible with a single query ?
set #num := 0, #cat := '';
select category,name
#num := if(#cat = category, #num + 1, 1) as row_number,
#cat := category as dummy
from MyTable
group by cateogry, name
having row_number <= 2;
What about this?
SELECT * FROM your_table WHERE category = 1 LIMIT 2
UNION
SELECT * FROM your_table WHERE category = 2 LIMIT 2
UNION
SELECT * FROM your_table WHERE category = 3 LIMIT 2
UNION
SELECT * FROM your_table WHERE category = 4 LIMIT 2