MySQL Multiple COUNT() with different conditions in single QUERY - mysql

I have a table with the following structure
id | season
1 | 1
2 | 1
2 | 2
3 | 2
4 | 2
5 | 1
I want the query to return the count of the the total IDs like:
total_season2 | new | existing
3 | 2 | 1
I can do it with multiple Queries or using php but I want to have the results in a single query

Since you don't need to select all the rows you can use an empty select with subqueries to get the data you want. There may be more efficient ways to getting the same data, but this will work:
SELECT
(SELECT COUNT(id) FROM seasons s WHERE s.season = 2) as total_season2,
(SELECT COUNT(id) FROM seasons s WHERE s.season = 2 AND (SELECT distinct COUNT(ss.id) FROM seasons ss WHERE ss.id = s.id) = 1) as new,
(SELECT COUNT(id) FROM seasons s WHERE s.season = 2 AND (SELECT distinct COUNT(ss.id) FROM seasons ss WHERE ss.id = s.id) > 1) as existing

Related

Can I count multiple columns with Group By?

I have a table with these columns:
s, s2, s3
1, 2, 3
4
1, 3
4, 2,
2, 1
3, 4
4
I want to know how many times the unique values in column s appears in the columns s, s2 and s3.
So far I have:
$query = "SELECT s, COUNT(*) as count FROM table GROUP BY s";
This will give me:
1 - count 2
2 - count 1
3 - count 1
4 - count 3
But I want to count the column s2 and s3 also so the outcome will be:
1 - count 3
2 - count 3
3 - count 3
4 - count 4
Any idea how I must edit the query so I can count the columns s, s2 and s3 group by the values of column s?
Kind regards,
Arie
You need a UNION ALL for all the columns and then count them:
select
t.s, count(*) counter
from (
select s from tablename union all
select s2 from tablename union all
select s3 from tablename
) t
where t.s is not null
group by t.s
See the demo.
Results:
| s | counter |
| --- | ------- |
| 1 | 3 |
| 2 | 3 |
| 3 | 3 |
| 4 | 4 |
If in the columns s2 and s3 there are values that do not exist in the column s and you want them excluded, then instead of:
where t.s is not null
use
where t.s in (select s from tablename)
#forpas answer is a good one. However, two things you should consider.
Due to the use of union the query will become slower as the data size increases.
If the input is as following:
s, s2, s3
1, 2, 3
4
1, 3
4, 2,
2, 1
3, 4
4 5
The result of the provided query will be:
| s | counter |
| --- | ------- |
| 1 | 3 |
| 2 | 3 |
| 3 | 3 |
| 4 | 4 |
| 5 | 1 |
whereas it should remain the same as 5 is not present into the s column.
In order to resolve both of the above issues, I propose the approach to use JOIN instead of UNION:
SELECT t3.s, IF(t3.s = t4.s3, cnt1 + 2, cnt1 + 1) as counter FROM
(SELECT *, count(*) AS cnt1 FROM
(SELECT s from table) AS t1
LEFT JOIN
(SELECT s2 FROM table) AS t2
ON t1.s = t2.s2 GROUP BY t1.s
) AS t3
LEFT JOIN
(SELECT s3 FROM table) AS t4
ON t3.s = t4.s3
ORDER BY t3.s
The query might look a bit lengthy and complicated but it is really simple when you look into the logic.
Step 1
What I have done here is to make a left join from s column to s2 and counted results for that so it will give you 1 lesser number than how many numbers are present in total as it will make relation left to right.
Step 2
Then I have made a left join from s to s3, and only increase the count of step 1 by 1 if the relation is found.
Step 3
Ultimately I have increased the count by 1 so that we can convert the number of relations to the number of the enities.
I hope it makes sense

Count multiple columns with Group By and Union All?

I have a query where I want to count the values of 3 columns s, s2 and s3 and use Group By the values of column s?
So far i have made this db-fiddle
This will give me the output
1 - count 3
2 - count 3
4 - count 4
This is almost correct but I also want to include the Where clause correct so the count will only be where season = '2018/2019' and round = 34.
The wanted output must be:
1 - count 3
2 - count 2
4 - count 2
Any idea how to edit this query so the Where clause will work in the counted values?
Kinds regards,
Arie
You have to expand the unioned queries to include season and round and add conditions in the where clause:
SELECT t.s, COUNT(*) as count FROM (
SELECT s, season, round FROM tablename UNION all
SELECT s2, season, round FROM tablename UNION all
SELECT s3, season, round FROM tablename
) as t
WHERE
t.s IN (
SELECT s FROM tablename
WHERE season = '2018/2019'
AND round = 34
AND s is not null
)
AND t.season = '2018/2019'
AND t.round = 34
AND t.s is not null
GROUP BY t.s
ORDER BY count DESC, t.s
See the demo.
Results:
| s | count |
| --- | ----- |
| 1 | 3 |
| 2 | 2 |
| 4 | 2 |
You should filter the rows for each table in union
SELECT t.s, COUNT(*) as count FROM (
SELECT s
FROM tablename
WHERE season = '2018/2019' and round = 34
UNION all
SELECT s2
FROM tablename
WHERE season = '2018/2019' and round = 34
UNION all
SELECT s3
FROM tablename
WHERE season = '2018/2019' and round = 34
) as t
GROUP BY t.s
ORDER BY count DESC

Join with Group By & Order by using Case in MySQL

Why this query wont work? Is it beacause combinaton of order by and group by?
One table is with adverts, other with subscriptions, third is with services, and fourth is many to many relation between services and locations (location is position where advert should be shown).
What i want is to order adverts stored in adverts table having location 2 first, then those who don't have location defined and then with location 1 (this order is generated programmicaly)
adverts table:
id, name, subscription_id
subscriptions table:
subscription_id, service_id, date, paid etc...
service_locations table:
service_id, location_id
as you can se there is fourth table in this case, but it is unimportant
The query:
select adverts.id, GROUP_CONCAT(service_locations.location_id) AS locations from adverts
left join subscriptions
on adverts.subscription_id = subscriptions.id
left join service_locations
on service_locations.service_id = subscriptions.service_id
group by adverts.id
order by case service_locations.location_id
when 2 then 1
when 1 then 3
else 2
end
Expected results:
+----+-----------+
| id | locations |
+----+-----------+
| 1 | 2 |
| 3 | 1,2 |
| 2 | null |
+----+-----------+
What i actually get (the third in row has location 2 but it is placed after null):
+----+-----------+
| id | locations |
+----+-----------+
| 1 | 2 |
| 2 | null |
| 3 | 1,2 |
+----+-----------+
When you use group by, all columns not in the group by should have aggregation functions. So, I think you intend something like this:
select a.id, GROUP_CONCAT(sl.location_id) AS locations
from adverts a left join
subscriptions s
on a.subscription_id = s.id left join
service_locations sl
on sl.service_id = s.service_id
group by a.id
order by max(case sl.location_id
when 2 then 1
when 1 then 3
else 2
end);
I'm not sure if max() is what you really need, but you do need an aggregation function. This specifically produces the output in the question:
order by (case min(sl.location_id)
when 2 then 1
when 1 then 2
else 3
end);
I have found a solution, order by must be executed before group by, which is not a default behaivor, more about that behaivour here: https://stackoverflow.com/a/14771322/4329156) (a subquery must be used)
So, query should look like
select *, GROUP_CONCAT(location_id) as locations from (
select adverts.id AS id, service_locations.location_id AS location_id from adverts
left join subscriptions
on adverts.subscription_id = subscriptions.id
left join service_locations
on service_locations.service_id = subscriptions.service_id
order by case service_locations.location_id
when 2 then 1
when 1 then 3
else 2
end
) as table
group by table.id
order by case table.location_id
when 2 then 1
when 1 then 3
else 2
end

SELECT value x WHERE x has same unknown value AND WHERE value y is in array

I have a table that stores product groups.
table "products_groups"
id | id_group | id_product
============================
1 | 1 | 1
2 | 1 | 2
3 | 1 | 3
4 | 2 | 1
5 | 2 | 2
6 | 3 | 2
I need a SQL query that will find the id_group that contains all id_product's that are in the given array.
product_ids = array(1); // should return no results
product_ids = array(1,2); // should only return id_group 2
product_ids = array(1,2,3); // should only return id_group 1
product_ids = array(1,2,3,4); // should return no results
I played/searched around, ended up stuck at
SELECT p1.id_group
FROM products_groups p1, products_groups p2
WHERE p1.id <> p2.id
AND p1.id_group = p2.id_group
AND (
p1.id_product = 1
OR p1.id_product = 2
OR p1.id_product = 3
)
But it obviously is not giving me the result I am looking for. I don't know if I am thinking too simple or too complex.
Notes: Of course the id_product values will be dynamically generated in the SQL. It will eventually be used with PHP/Codeigniter
Background info: Each product has a price, but products can be in a product group which has a package price. That is why I need to know for each order if the products are in a group.
This problem is called Relational Division
SELECT id_group
FROM products_groups
WHERE id_product IN (1,2)
GROUP BY id_group
HAVING COUNT(*) = 2
SQL of Relational Division
or something like this,
SELECT id_group
FROM products_groups
GROUP BY id_group
HAVING SUM(id_product IN (1,2)) = COUNT(*) AND
COUNT(*) = 2
SQLFiddle Demo

MySQL JOIN multiple results

I am trying to select all of the roles a specific user has access to within a specific server. This is for a system that allows a user to manage one or more services. The amount of access a user has is assigned by whoever the service belongs to. Roles are grouped and that group is then what gets assigned to a user. A user may have more than one group.
This is the query that I made and expected to work, but it doesn't. I am guessing it doesn't work because the serverPermissions table can return more than 1 groupId based on what a user is assigned.
SELECT serverGroupRoles.roleId FROM `serverGroupRoles`, `serverPermissions`, `servers`
WHERE servers.identifier='someUniqueString' AND
serverPermissions.serverId=servers.id AND
serverPermissions.userId=1 AND
serverGroupRoles.groupId=serverPermissions.groupId
Here's a visual look of the tables, 'servers' table has other data, but it's unrelated.
servers table, identifier is a unique key:
id | identifier | ...
--------------------------
1 | someString | ...
2 | someString02 | ...
serverPermissions table:
serverId | groupId | userId
--------------------------------
1 | 1 | 1
1 | 2 | 1
1 | 2 | 2
2 | 3 | 1
3 | 4 | 1
serverGroupRoles table:
groupId | roleId
------------------
1 | 1
1 | 2
1 | 3
2 | 1
2 | 3
3 | 4
4 | 2
The roleId's are mapped in the application to a certain action.
This is what I am trying to accomplish, but with 1 query:
If you did something like,
SELECT id FROM `servers` WHERE identifier = 'someString'
Returns
id
--
1
Then took the id that was returned from that,
SELECT groupId FROM `serverPermissions` WHERE serverId = 1 AND userId = 1
Then it would return
groupId
-------
1
2
Then with each groupId,
SELECT roleId FROM `serverGroupRoles` WHERE groupId = #
And the end result,
roleId
------
1
2
3
Is there a good way to do this with 1 query?
Edit, query that accomplishes the task:
SELECT DISTINCT sgr.roleID
FROM serverPermissions sp
INNER JOIN servers s ON s.id = sp.serverID
INNER JOIN serverGroupRoles sgr ON sgr.groupID = sp.groupID
WHERE sp.userID = 1
AND s.identifier = 'someString'
It's still a bit early here, but would this do what you want:
SELECT DISTINCT sgr.roleID
FROM serverPermissions sp
INNER JOIN serverGroupRoles sgr ON sgr.groupID = sp.groupID
WHERE sp.serverID = 1
AND sp.userID = 1
I could be off the mark here as I'm not sure where the servers table comes into this. If you're looking for data from that table you can join it in too:
SELECT DISTINCT sgr.roleID, s.fieldName
FROM serverPermissions sp
INNER JOIN servers s ON s.id = sp.serverID
INNER JOIN serverGroupRoles sgr ON sgr.groupID = sp.groupID
WHERE sp.serverID = 1
AND sp.userID = 1
Is this what you want?
SELECT roleId
FROM `serverGroupRoles`
WHERE groupId in (SELECT groupId
FROM `serverPermissions`
WHERE serverId = 1 AND userId = 1
)
Perhaps you actually want "SELECT distinct roleID" to eliminate duplicates.
You can extend this for servers, but I would do it as a set of joins:
SELECT distinct roleId
FROM `serverGroupRoles` sgr join
`serverPermissions` sp
on sgr.groupId = sp.groupId join
`server` s
on sp.serverid = s.id
WHERE s.identifier = 'someString' AND sgr.userId = 1