MySQL: how to show rows where count is 0 using group by? - mysql

MySQL
I have a table, two columns column'N' and column'V', the value of 'V' is either 1 or 0 (may be other values, so don't use SUM()).
mysql> select * from counter;
+------+------+
| N | V |
+------+------+
| A | 0 |
......
| D | 1 |
+------+------+
I wish to count how many 0 and how many 1, as below:
mysql> select N, V, count(V) from counter group by N,V;
+------+------+----------+
| N | V | count(V) |
+------+------+----------+
| A | 0 | 2 |
| A | 1 | 7 |
| B | 0 | 7 |
| B | 1 | 2 |
| C | 0 | 3 |
| D | 1 | 3 |
+------+------+----------+
The problem is I want to show rows where the count(V) is 0. In this case, my expected result should look like as below:
+------+------+----------+
| N | V | count(V) |
+------+------+----------+
| A | 0 | 2 |
| A | 1 | 7 |
| B | 0 | 7 |
| B | 1 | 2 |
| C | 0 | 3 |
| C | 1 | 0 | **
| D | 0 | 0 | **
| D | 1 | 3 |
+------+------+----------+
How can I achieve this? And if the table is large, how to get the best performance?

Here's one option creating a cartesian product between N and V with a cross join. Then you can use an outer join to get all results:
select t.n, t.v, count(c.n)
from (select distinct t1.n, t2.v
from counter t1 cross join counter t2) t left join
counter c on t.n = c.n and t.v = c.v
group by t.n, t.v
SQL Fiddle Demo

Related

Count records in a MySQL table based for each record of another table

I have two tables in a MySQL database -
table_a:
+----+---------+-------------+-----------------
| id | section | sub_section | ...other_fields
+----+---------+-------------+-----------------
| 1 | A | X |
| 2 | A | Y |
| 3 | A | Z |
| 4 | B | P |
| 5 | B | Q |
| 6 | C | L |
| 7 | C | M |
| 8 | C | N |
| 9 | C | O |
+----+---------+-------------+-----------------
table_b:
+----+-------------+---------+-----------------
| id | sub_section | b_count | ...other_fields
+----+-------------+---------+-----------------
| 1 | X | 1 |
| 2 | Y | 1 |
| 3 | L | 0 |
| 4 | P | 1 |
| 5 | P | 1 |
| 6 | X | 0 |
| 7 | M | 1 |
| 8 | Y | 0 |
| 9 | Q | 1 |
+----+-------------+---------+-----------------
I want to find the count of sub_section in table_b and the sum of b_count from Table B for each distinct section in table_a -
Expected Result:
+---------+--------------------+--------------+
| section | COUNT(sub_section) | SUM(b_count) |
+---------+--------------------+--------------+
| A | 4 | 2 |
| B | 3 | 3 |
| C | 2 | 1 |
+---------+--------------------+--------------+
One way to do this would be to run Count(section) number of queries and then combine the results.
Something like:
SELECT 'A' AS section, COUNT(sub_section), SUM(b_count) FROM table_b WHERE sub_section IN (SELECT DISTINCT sub_section FROM table_a WHERE section='A')
UNION
SELECT 'B' AS section, COUNT(sub_section), SUM(b_count) FROM table_b WHERE sub_section IN (SELECT DISTINCT sub_section FROM table_a WHERE section='B')
UNION
SELECT 'C' AS section, COUNT(sub_section), SUM(b_count) FROM table_b WHERE sub_section IN (SELECT DISTINCT sub_section FROM table_a WHERE section='C');
Is there a better way to do this in a query?
The section list in table_a is dynamic and might change and I do not want to update my query each time the values change.
SELECT t1.section, COUNT(DISTINCT t1.sub_section), SUM(t2.b_count)
FROM table_a t1
LEFT JOIN table_b t2 USING (sub_section)
GROUP BY t1.section;
fiddle

How to determine what's changed between database records

Presume first, that the following table exists in a MySQL Database
|----|-----|-----|----|----|-----------|--------------|----|
| id | rid | ver | n1 | n2 | s1 | s2 | b1 |
|----|-----|-----|----|----|-----------|--------------|----|
| 1 | 1 | 1 | 0 | 1 | Hello | World | 0 |
| 2 | 1 | 2 | 1 | 1 | Hello | World | 0 |
| 3 | 1 | 3 | 0 | 0 | Goodbye | Cruel World | 0 |
| 4 | 2 | 1 | 0 | 0 | Hello | Doctor | 1 |
| 5 | 2 | 2 | 0 | 0 | Hello | Nurse | 1 |
| 6 | 3 | 1 | 0 | 0 | Dippity | Doo-Dah | 1 |
|----|-----|-----|----|----|-----------|--------------|----|
Question
How do I write a query to determine whether for any given rid, what changed between the most recent version and the version immediately preceding it (if any) such that it produces something like this:
|-----|-----------------|-----------------|-----------------|
| rid | numbers_changed | strings_changed | boolean_changed |
|-----|-----------------|-----------------|-----------------|
| 1 | TRUE | TRUE | FALSE |
| 2 | FALSE | TRUE | FALSE |
| 3 | n/a | n/a | n/a |
|-----|-----------------|-----------------|-----------------|
I think that I should be able to do this by doing a cross-join between the table and itself but I can't resolve how to perform this join to get the desired output.
I need to generate this "report" for a table with 10's of columns and 1-10 versions of 100's of records (resulting in 1000's of rows). Note the particular design of the database is not my own and altering the structure of the database (at this time) is not an acceptable approach.
The actual format of the output isn't important - and if it simplifies the query getting a "full breakdown" of what changed for each "change set" would also be acceptable, for example
|-----|-----|-----|----|----|----|----|----|
| rid | old | new | n1 | n2 | s1 | s2 | b1 |
|-----|-----|-----|----|----|----|----|----|
| 1 | 1 | 2 | Y | N | N | N | N |
| 1 | 2 | 3 | Y | Y | Y | Y | N |
| 2 | 4 | 5 | N | N | N | Y | N |
|-----|-----|-----|----|----|----|----|----|
Note that it is also ok, in this case to omit rid records which only have a single version, as for the purposes of this report I only care about records that have changed and getting a separate list of records that haven't changed is an easy query
You can join every row with the following one with
select *
from history h1
join history h2
on h2.rid = h1.rid
and h2.id = (
select min(h.id)
from history h
where h.rid = h1.rid
and h.id > h1.id
);
Then you just need to compare every column from the two rows like h1.n1 <> h2.n1 as n1.
The full query would be:
select h1.rid, h1.id as old, h2.id as new
, h1.n1 <> h2.n1 as n1
, h1.n2 <> h2.n2 as n2
, h1.s1 <> h2.s1 as s1
, h1.s2 <> h2.s2 as s2
, h1.b1 <> h2.b1 as b1
from history h1
join history h2
on h2.rid = h1.rid
and h2.id = (
select min(h.id)
from history h
where h.rid = h1.rid
and h.id > h1.id
);
Result:
| rid | old | new | n1 | n2 | s1 | s2 | b1 |
|-----|-----|-----|----|----|----|----|----|
| 1 | 1 | 2 | 1 | 0 | 0 | 0 | 0 |
| 1 | 2 | 3 | 1 | 1 | 1 | 1 | 0 |
| 2 | 4 | 5 | 0 | 0 | 0 | 1 | 0 |
Demo: http://sqlfiddle.com/#!9/2e5d12/5
If the columns can contain NULLs, You might need something like NOT h1.n1 <=> h2.n1 as n1. <=> is a NULL-save equality check.
If the version within a rid group is guaranteed to be consecutive, you can simplify the JOIN to
from history h1
join history h2
on h2.rid = h1.rid
and h2.ver = h1.ver + 1
Demo: http://sqlfiddle.com/#!9/2e5d12/7

How to fetch only continuous records from mysql?

I have a database table like below
___________
id | speed
-----------
1 | 3
2 | 2
3 | 0
4 | 0
5 | 0
6 | 2
7 | 0
8 | 0
9 | 2
10 | 0
Now I want to get the records where speed is 0 but only from 3 to 5 which are continuous and greater than any other continuous records. I don't want 7,8 records or the 10th record. How can I achieve this?
Probably the fastest method is to use MySQL session variables to increment the "group" each time the speed changes, as you scan through the rows.
select n.*, #groupid:=IF(#prev_speed=speed,#groupid,#groupid+1) as groupid, #prev_speed:=speed
from (select #groupid:=0, #prev_speed=-1) _init
cross join n
order by id;
+----+-------+---------+--------------------+
| id | speed | groupid | #prev_speed:=speed |
+----+-------+---------+--------------------+
| 1 | 3 | 1 | 3 |
| 2 | 2 | 2 | 2 |
| 3 | 0 | 3 | 0 |
| 4 | 0 | 3 | 0 |
| 5 | 0 | 3 | 0 |
| 6 | 2 | 4 | 2 |
| 7 | 0 | 5 | 0 |
| 8 | 0 | 5 | 0 |
| 9 | 2 | 6 | 2 |
| 10 | 0 | 7 | 0 |
+----+-------+---------+--------------------+
Then using the above query as a derived table, calculate the lowest and highest id per group, and the count of rows. Sort the groups by the count of rows.
select min(id) as minid, max(id) as maxid, count(*) as count
from (
select n.*, #groupid:=IF(#prev_speed=speed,#groupid,#groupid+1) as groupid, #prev_speed:=speed
from (select #groupid:=0, #prev_speed=-1) _init
cross join n
order by id
) as t1
group by t1.groupid
order by count desc;
+-------+-------+-------+
| minid | maxid | count |
+-------+-------+-------+
| 3 | 5 | 3 |
| 7 | 8 | 2 |
| 1 | 1 | 1 |
| 2 | 2 | 1 |
| 6 | 6 | 1 |
| 9 | 9 | 1 |
| 10 | 10 | 1 |
+-------+-------+-------+
Then using the first row from the above as another derived table, join to the original table for the rows in the range from the min to max id.
select n.*
from (
select min(id) as minid, max(id) as maxid, count(*) as count
from (
select n.*, #groupid:=IF(#prev_speed=speed,#groupid,#groupid+1) as groupid, #prev_speed:=speed
from (select #groupid:=0, #prev_speed=-1) _init
cross join n
order by id
) as t1
group by t1.groupid
order by count desc limit 1
) as t2
inner join n on n.id between t2.minid and t2.maxid
+----+-------+
| id | speed |
+----+-------+
| 3 | 0 |
| 4 | 0 |
| 5 | 0 |
+----+-------+

How to check if specific set of ID's exists?

I have a source table (piece of it):
+--------------------+
| E M P L O Y E E |
+--------------------+
| ID | EQUIPMENT |
+--------------------+
| 1 | tv,car,phone |
| 2 | car,phone |
| 3 | tv,phone |
+----+---------------+
After normalization process I ended with two new tables:
+----------------+
| DICT_EQUIPMENT |
+----------------+
| ID | EQUIPMENT |
+----------------+
| 1 | tv |
| 2 | car |
| 3 | phone |
+----+-----------+
+---------------------+
| SET_EQUIPMENT |
+----+--------+-------+
| ID | SET_ID | EQ_ID |
+----+--------+-------+
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 1 | 3 |
| 4 | 2 | 2 |
| 5 | 2 | 3 |
| 6 | 3 | 1 |
| 7 | 3 | 3 |
+----+--------+-------+
(the piece/part)
+-----------------+
| E M P L O Y E E |
+-----------------+
| ID | EQ_SET_ID |
+-----------------+
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
+----+------------+
And now when I want to find correct SET_ID I can write something like this:
SELECT SET_ID
FROM SET_EQUIPMENT S1,
SET_EQUIPMENT S2,
SET_EQUIPMENT S3
WHERE S1.SET_ID = S2.SET_ID
AND S2.SET_ID = S3.SET_ID
AND S1.EQ_ID = 1
AND S2.EQ_ID = 2
AND S3.EQ_ID = 3;
Maybe any ideas for optimize this query? how find the correct set?
First, you should use explicit join syntax for the method you are using:
SELECT S1.SET_ID
FROM SET_EQUIPMENT S1 JOIN
SET_EQUIPMENT S2
ON S1.SET_ID = S2.SET_ID JOIN
SET_EQUIPMENT S3
ON S2.SET_ID = S3.SET_ID
WHERE S1.EQ_ID = 1 AND
S2.EQ_ID = 2 AND
S3.EQ_ID = 3;
Commas in a from clause are quite outdated. (And, this fixes a syntax error in your query.)
An alternative method is to use group by with a having clause:
SELECT S.SET_ID
FROM SET_EQUIPMENT S
GROUP BY S.SET_ID
HAVING SUM(CASE WHEN S.EQ_ID = 1 THEN 1 ELSE 0 END) > 0 AND
SUM(CASE WHEN S.EQ_ID = 2 THEN 1 ELSE 0 END) > 0 AND
SUM(CASE WHEN S.EQ_ID = 3 THEN 1 ELSE 0 END) > 0;
Which method works better depends on a number of factors -- for instance, the database engine you are using, the size of the tables, the indexes on the tables. You have to test which method works better on your system.
You've normalised wrongly. Get rid of set_equipment
Change to have three tables: employee, equipment, employee_equipment.
If you're looking for the equipment for a given employee you want to use:
select id, equipment
from equipment eq
inner join employee_equipment ee on eq.id = ee.eq_id
inner join employee emp on emp.id = ee.emp_id
where emp.id = 2

select distinct pair values mysql

i have the following table and I'm hoping to extract all the unique pairs for ex:
+------+------+
| a | b |
+------+------+
| 1 | 2 |
| 1 | 3 |
| 1 | 6 |
| 1 | 9 |
| 2 | 1 |
| 2 | 3 |
| 3 | 1 |
| 3 | 2 |
| 4 | 1 |
| 6 | 5 |
+------+------+
the out put should be
+------+------+
| a | b |
+------+------+
| 1 | 2 |
| 1 | 3 |
| 1 | 6 |
| 1 | 9 |
| 2 | 3 |
| 6 | 5 |
+------+------+
Try this:
select least(a, b) as a, greatest(a, b) as b
from table t
group by least(a, b), greatest(a, b);
This could produce output that is not in the original table (such as 5/6 instead of 6/5). If you want to preserve the original order:
select distinct a, b
from table t
where a <= b or
not exists (select 1 from table t2 where t2.b = t.a and t2.a = t.b);
That is, select all pairs where a is less than or equal to b or a is greater than b and no row exists with the values in the other order.
SELECT a,b
FROM tbl //your tableName
GROUP BY a,b
HAVING COUNT(*) > 1
try this my friend
SELECT *
FROM NAMES2
GROUP BY Id,id2
HAVING COUNT(*) > 1;
How about this ?