MySQL - Count by two columns in one query [duplicate] - mysql

This question already has answers here:
multiple query same table but in different columns mysql
(4 answers)
Closed 4 years ago.
Here is my table:
| ID | TYPE |
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 0 |
| 5 | 0 |
and now, I want to count how many records exists with TYPE=1 and how many records exist with TYPE = 0. Is it possible?
I know, I can do it in two queries:
SELECT COUNT(*) AS all_zero FROM `table WHERE type = 0;
SELECT COUNT(*) AS all_one FROM `table WHERE type = 1;
but I want do it in one query.
Is it possible?

Conditional aggregation will do it...
SELECT SUM(t.type=0) AS cnt_zero
, SUM(t.type=1) AS cnt_one
, SUM(t.type IN (0,1)) AS cnt_one_and_zero
, SUM(1) AS cnt_all
FROM mytable t
;
Note that the usage of type=0 and type=1 is MySQL-specific shorthand, which other database probably wouldn't accept.
If we ignore the handling of NULL values, this is basically equivalent to
SELECT SUM(IF( t.type=0 ,1,0)) AS cnt_zero
, SUM(IF( t.type=1 ,1,0)) AS cnt_one
, SUM(IF( t.type IN (0,1), 1,0)) AS cnt_one_and_zero
, SUM(1) AS cnt_all
FROM mytable t
;
A more ANSI standard compliant equivalent
SELECT SUM(CASE WHEN t.type=0 THEN 1 ELSE 0 END) AS cnt_zero
, SUM(CASE WHEN t.type=1 THEN 1 ELSE 0 END) AS cnt_one
, SUM(CASE WHEN t.type IN (0,1) THEN 1 ELSE 0 END) AS cnt_one_and_zero
, SUM(1) AS cnt_all
FROM mytable t
;
To get the same handling of NULL values and the same result as the first query in this answer, we could write it like this:
SELECT SUM(CASE WHEN t.type=0 THEN 1 WHEN t.type IS NULL THEN NULL ELSE 0 END) AS cnt_zero
, SUM(CASE WHEN t.type=1 THEN 1 WHEN t.type IS NULL THEN NULL ELSE 0 END) AS cnt_one
, SUM(CASE WHEN t.type IN (0,1) THEN 1 WHEN t.type IS NULL THEN NULL ELSE 0 END) AS cnt_one_and_zero
, SUM(1) AS cnt_all
FROM mytable t
;
In all of these, the theme remains the same: we're aggregating the results of a condition. Aka conditional aggregation.

GROUP BY does exactly this:
SELECT type, COUNT(*) AS count_of_type FROM my_table GROUP BY type
This would give you multiple rows, one per type:
+------+---------------+
| type | count_of_type |
+------+---------------+
| 0 | 2 |
| 1 | 3 |
+------+---------------+
If you have more types, but only want 0 and 1, you may add a HAVING clause:
SELECT type, COUNT(*) AS count_of_type FROM my_table GROUP BY type HAVING type IN (0,1)
If you prefer to get different counts in a single result-set-row, please see the answer by spencer7593.

Very simple:
SELECT
`type`,
count(*) as `typecount`
FROM table
GROUP BY `type`

Related

Sum hours value, count and display based on hours using SQL

I have 2 tables which are Teacher and Activities.
CREATE TABLE teacher (
TeacherId INT, BranchId VARCHAR(5));
INSERT INTO teacher VALUES
("1121","A"),
("1132","A"),
("1141","A"),
("2120","B"),
("2122","B");
CREATE TABLE activities (
ID INT, TeacherID INT, Hours INT);
INSERT INTO activities VALUES
(1,1121,2),
(2,1121,1),
(3,1132,1),
(4,1141,NULL),
(5,2120,NULL),
(6,2122,NULL);
NULL indicates no activities and will be convert to 0 on output table. I want to produce a query to count total of hours and count how many activities base on teacher hours such as the following table:
+-----------+------------+------------+
| Hours | A | B |
+-----------+------------+------------+
| 0 | 1 | 2 |
| 1 | 1 | 0 |
| 2 | 0 | 0 |
| 3 | 1 | 0 |
+-----------+------------+------------+
Edited: Sorry I don't know how to elaborate accurately, but here is the fiddle i received from other member https://www.db-fiddle.com/f/mmtuZquKyUqdhPvTFN9qaF/1
Edit: Last, modification need, to sum the hours and count the hours base on branch id and teacher id as the output.
Expected output here (red text): https://drive.google.com/file/d/1wyZ_aX5hz_7I1Ncf5sXLpstYk6FT8PMg/view?usp=sharing
We can handle this via the use of a calendar table of hours joined to an aggregation subquery:
SELECT
t1.Hours,
SUM(CASE WHEN t2.BranchId = 'A' THEN t2.cnt ELSE 0 END) AS A,
SUM(CASE WHEN t2.BranchId = 'B' THEN t2.cnt ELSE 0 END) AS B
FROM (SELECT 0 AS Hours UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3) t1
LEFT JOIN
(
SELECT t.BranchId, COALESCE(a.Hours, 0) AS Hours, COUNT(*) AS cnt
FROM Teacher t
LEFT JOIN Activities a ON a.TeacherId = t.TeacherId
GROUP BY t.BranchId, COALESCE(a.Hours, 0)
) t2
ON t1.Hours = t2.Hours
GROUP BY
t1.Hours
ORDER BY
t1.Hours
Demo
This is basically a JOIN and aggregation . . . but you need to start with all the hours you want:
SELECT h.Hours,
COALESCE(SUM(t.BranchId = 'A'), 0) AS A,
COALESCE(SUM(t.BranchId = 'B'), 0) AS B
FROM (SELECT 0 AS Hours UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3
) h LEFT JOIN
activities a
ON h.hours = COALESCE(a.hours, 0) LEFT JOIN
teacher t
ON t.TeacherId = a.TeacherId
GROUP BY h.Hours
ORDER BY h.Hours;
Here is a db<>fiddle.

SQL counting occurrences of conditional statements using values from 2 different roles

I have a table with fields including time (UTC) and accountID.
accountID | time | ...
1 |12:00 |....
1 |12:01 |...
1 |13:00 |...
2 |14:00 |...
I need to make an sql query to return the accountID with a new field counting 'category' where 'category' can be 'a' or 'b'. If there is a row entry from the same accountID that has a positive time difference of 1 minute or less, category 'a' needs to be incremented, otherwise 'b'. The results from the above table would be
accountID| cat a count| cat b count
1 | 1 | 2
2 | 0 | 1
What approaches can I take to compare values between different rows and output occurrences of comparison outcomes?
Thanks
To compute this categories you'll need to pre-compute the findings of close rows in a "table expression". For example:
select
accountid,
sum(case when cnt > 0 then 1 else 0 end) as cat_a_count,
sum(case when cnt = 0 then 1 else 0 end) as cat_b_count
from (
select
accountid, tim,
( select count(*)
from t b
where b.accountid = t.accountid
and b.tim <> t.tim
and b.tim between t.tim and addtime(t.tim, '00:01:00')
) as cnt
from t
) x
group by accountid
Result:
accountid cat_a_count cat_b_count
--------- ----------- -----------
1 1 2
2 0 1
For reference, the data script I used is:
create table t (
accountid int,
tim time
);
insert into t (accountid, tim) values
(1, '12:00'),
(1, '12:01'),
(1, '13:00'),
(2, '14:00');
Use lag() and conditional aggregation:
select accountid,
sum(prev_time >= time - interval 1 minute) as a_count,
sum(prev_time < time - interval 1 minute or prev_time is null) as b_count
from (select t.*,
lag(time) over (partition by accountid order by time) as prev_time
from t
) t
group by accountid;

How to select a column value depending if id is even or odd

having a table structure of id and a name:
create table Mytable (
id integer not null,
name varchar(30) not null,
unique(id)
);
insert into Mytable (id,name) values
(1 , 'one'),
(2 , 'two'),
(3 , 'three'),
(4 , 'four'),
(6 , 'six');
How may I get a mix of even and odd rows in a result table like:
even | odd
-----------
null one '0 is not in Mytable so it puts null value
two three
four null '5 and 6 are not in Mytable so it puts null value
six null
I was trying to first get the following as a template and use it later as
a dictionary:
SELECT MIN(id-1) as id,MAX(id-1) as col
FROM Mytable
GROUP BY FLOOR((id+1)/2);
I get:
id col
0 1
2 3
5 5
But I do not know how to continue
For MySQL Version <= 5.7, You can use the below query
Query 1:
SELECT
MAX(CASE WHEN m.id % 2 = 0 THEN name END) AS even,
MAX(CASE WHEN m.id % 2 = 1 THEN name END) AS odd
FROM
(
SELECT
(SELECT MAX(id) FROM mytable) AS maxid,
#rn := #rn + 1 AS rn,
(SELECT IF((#rn * 2) <= maxid, #rn, NULL)) AS rid
FROM
mytable
JOIN
(SELECT #rn := -1) AS var
) AS t
JOIN
mytable m ON FLOOR(m.id/2) = t.rid
GROUP BY rid;
Result 1:
even | odd
:--- | :----
null | one
two | three
four | null
six | null
Demo 1:
db fiddle
Query 2:
After confirmation based on #Madhur Bhaiya comment. If there is no row for id = 8 and 9 then it will show null, null.
SELECT
MAX(CASE WHEN m.id % 2 = 0 THEN name END) AS even,
MAX(CASE WHEN m.id % 2 = 1 THEN name END) AS odd
FROM
(
SELECT
(SELECT MAX(id) FROM mytable) AS maxid,
#rn := #rn + 1 AS rn,
(SELECT IF((#rn * 2) <= maxid, #rn, NULL)) AS rid
FROM
(SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) t
JOIN
(SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) t2
JOIN
(SELECT #rn := -1) var -- currently it will return 1..100, if needed more add joins based on your needs
) AS t
LEFT JOIN
mytable m ON FLOOR(m.id/2) = t.rid
GROUP BY rid HAVING rid IS NOT NULL;
Result 2:
even | odd
:------ | :-----
null | one
two | three
four | null
six | null
null | null
null | eleven
null | null
null | null
sixteen | null
Demo 2:
db fiddle
For MySQL Version > 8.0, You can use #Nick query but if you need null, null like Result 2 mentioned for <= v5.7 then add LEFT JOIN with ORDER BY clause.
Query:
with recursive maxid as (
select max(id) as id from Mytable)
, cte as (
select 0 as rid
union all
select rid + 1
from cte
cross join maxid
where (rid + 1) * 2 <= maxid.id)
select max(case when m.id % 2 = 0 then name end) as even,
max(case when m.id % 2 = 1 then name end) as odd
from cte
left join Mytable m on floor(m.id / 2) = cte.rid
group by rid order by rid;
Result:
even | odd
:------ | :-----
null | one
two | three
four | null
six | null
null | null
null | eleven
null | null
null | null
sixteen | null
Demo: db fiddle
Credits: Thanks to #Nick, #Madhur Bhaiya for the fiddle and the logic used to create this query.
Here's a CTE based query that will work in SQL Server and MySQL > v8.0 (with the addition of the keyword recursive before maxid). It generates a list of rows that encompasses the pairs of MyTable values (in the sample, this is 0,1,2,3) and then JOINs that to Mytable to extract the even/odd column values:
with maxid as (
select max(id) as id from Mytable)
, cte as (
select 0 as rid
union all
select rid + 1
from cte
cross join maxid
where (rid + 1) * 2 <= maxid.id)
select max(case when m.id % 2 = 0 then name end) as even,
max(case when m.id % 2 = 1 then name end) as odd
from cte
join Mytable m on m.id / 2 = cte.rid
group by rid
Output:
even odd
one
two three
four
six
Demo on dbfiddle

SQL Counting rows with columns that satisfy a condition

Suppose I have a table with columns ID and Content populated with data.
ID | Content
1 | a
1 | b
1 | c
2 | b
2 | a
3 | b
I want to find every ID that has at least one of each 'a', 'b', and 'c' so the returned table would be :
ID | Content
1 | a
1 | b
1 | c
Using conditional SUM validate if inside the group id exist 1 or more of each element.
Then select DISTINCT ID, Content to eliminate possible duplicates
SELECT DISTINCT ID, Content
From YourTable
WHERE ID IN (SELECT ID
FROM YourTable
GROUP BY ID
HAVING SUM(case when Content = 'a' then 1 else 0 end) >= 1
AND SUM(case when Content = 'b' then 1 else 0 end) >= 1
AND SUM(case when Content = 'c' then 1 else 0 end) >= 1
)
select * from (
where
Select ID ,SUM(case when Content = 'a' then 1 else 0 end) as sum_a ,UM(case when Content = 'b' then 1 else 0 end) as sum_b,
SUM(case when Content = 'c' then 1 else 0 end) as sum_c
FROM table
Group by ID)x
where x.sum_a >1 or sum_b>1 or sum_c>1
Here is another approach that checks for a certain number of distinct "content" items per ID. Here is a link to an SQL Fiddle for this solution.
select
id,
count(distinct(content))
from tableX
group by id
having count(distinct(content)) = 3;

Return Yes if only one record is found

I have a table that has this structure:
table
id | site_id
-------------------
240 | 1
240 | 2
240 | 3
320 | 1
320 | 2
421 | 1
520 | 2
-------------------
300k records
Now i am trying to write a query to return a yes or a no for each record (id).
For example if the records with id 240 only have a site_id 1 then return 'Yes', if it has 2, 3 and so on return 'No'
I am not sure how to approach it but here is a result sample:
result_table
.-----------------------.
| id | result |
|-----------------------|
| 240 | No | -- has a site_id 1, 2 and 3
| 320 | No | -- has a site_id 1 and 2
| 421 | Yes | -- has a site_id 1 only
| 520 | No | -- has a site_id 2
'-----------------------'
Here is the query i have so far, but it seems to be incorrect
SELECT CASE WHEN count(id) > 1 THEN 'N' ELSE 'Y' END as Result
FROM table sm
WHERE sm.site_id IN (1)
AND sm.site_id NOT IN (2,3,4,5,6,7,8,9)
AND id = 240
UPDATE
SO Here is my full query, i added the answer from #gordon
SELECT
m.merchant_name,
m.merchant_id,
ut.realname,
a.affiliate_name,
(select (case when count(site_id) = 1 then 'Yes' else 'No' end) as result
from site_merchant sm
WHERE sm.merchant_id = m.merchant_id
group by merchant_id) as isTjoos, -- y or no
(select (case when count(site_id) = 2 then 'Yes' else 'No' end) as result
from site_merchant sm
WHERE sm.merchant_id = m.merchant_id
group by merchant_id) as isUCOnly
-- isdlkonly -- y or no
FROM merchant m
LEFT JOIN merchant_editor_assignment mea ON mea.merchant_id = m.merchant_id
LEFT JOIN user_table ut ON ut.user_id = mea.user_id
LEFT JOIN affiliate a ON a.affiliate_id = m.affiliate_id_default
I interpreted this question as you want ids that have only one value for site_id. I took the example in the question to be an example, with site_id = 1. To do this:
You want to use count(distinct):
select id, (case when count(distinct site_id) = 1 then 'Yes' else 'No' end) as result
from site_merchant sm
group by id
A slightly more efficient version is to use min() and max(), assuming that the site_id is never NULL:
select id, (case when min(site_id) = max(site_id) then 'Yes' else 'No' end) as result
from site_merchant sm
group by id
This is because min and max generally require a bit less processing than count(distinct).
If you want to check that the site_id is "1" and never anything else, then add the condition and min(site_id) = 1 to the when clause.
If you want to check that the site_id is 1 and there is exactly one row, then you can do:
select id, (case when count(site_id) = 1 and min(site_id) = 1 then 'Yes' else 'No' end) as result
from site_merchant sm
group by id
And, if you want to check that there is exactly one row:
select id, (case when count(site_id) = 1 then 'Yes' else 'No' end) as result
from site_merchant sm
group by id
SELECT
it,
CASE WHEN COUNT(CASE WHEN site_id THEN 1 END)=1
AND COUNT(CASE WHEN site_id!=1 THEN 1 END)=0 THEN 'Yes'
ELSE 'No'
END
FROM sm
GROUP BY it
Please see fiddle here.
SELECT ID, CASE WHEN EXTRA > 1 THEN 'No' ELSE 'Yes' END AS Result
FROM
(SELECT ID, Sum(site_id) AS Extra
from myTable
GROUP BY ID
) AS Test
EDIT: I suppose this should work in MySQL. I haven't worked on it though.
The idea is to SUM up the site_id. For records with only site_id = 1, the sum will be 1.
Your query seems overcomplicated. Just to start, why the IN(1) and NOT IN(2,3...9)? And why limit to a single ID (AND id = 240) when your "result sample" clearly doesn't want that? It does not make any sense. How about this?
SELECT CASE WHEN count(merchant_id) > 1 THEN 'N' ELSE 'Y' END as isTjoos
FROM site_merchant
GROUP BY site_id;
I would use a Having Count Statement. Something like that:
SELECT site_id
FROM site_merchant
HAVING (count(merchant_id) > 1)
GROUP BY site_id;
Here is the solution i found. I used #Gordons query to get started and what was missing was the site_id, and the group by was not needed:
SELECT
m.merchant_name,
m.merchant_id,
ut.realname,
a.affiliate_name,
(select (case when count(*)>0 then 'No' else 'Yes' end) as result
from site_merchant sm
WHERE sm.merchant_id = m.merchant_id
AND site_id != 1) as isTjoos,
(select (case when count(*)> 0 then 'No' else 'Yes' end) as result
from site_merchant sm
WHERE sm.merchant_id = m.merchant_id
AND site_id != 2) as isUCOnly,
(select (case when count(*)> 0 then 'No' else 'Yes' end) as result
from site_merchant sm
WHERE sm.merchant_id = m.merchant_id
AND site_id != 3) as isDLKonly
FROM merchant m
LEFT JOIN merchant_editor_assignment mea ON mea.merchant_id = m.merchant_id
LEFT JOIN user_table ut ON ut.user_id = mea.user_id
LEFT JOIN affiliate a ON a.affiliate_id = m.affiliate_id_default
Thank you for the help.