Can I count multiple columns with Group By? - mysql

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

Related

MySQL - select distinct value from two column

I have a table with the following structure:
IdM|IdS
-------
1 | 2
1 | 3
1 | 4
2 | 1
2 | 3
2 | 4
3 | 1
3 | 2
3 | 3
3 | 4
How could I make a select statement on this table, which will return some rows of this table, where in each row, a specific id appears only one, indifferent on which column it is specified?
For the above result set, I would like a query that would return:
-------
1 | 2
3 | 4
-------
To give another example, if you would omit the first row in the original dataset:
IdM|IdS
-------
1 | 3
1 | 4
2 | 1
2 | 3
2 | 4
3 | 1
3 | 2
3 | 3
3 | 4
the result set should be:
-------
1 | 3
2 | 4
-------
That's actually an interesting problem. If I follow you correctly, you want to iterate through the dataset and only retain rows where both values were never seen before. You could use a recursive query:
with recursive
data as (
select idm, ids, row_number() over(order by idm, ids) rn
from mytable
where idm <> ids
),
cte as (
select idm, ids, rn, 1 as to_keep , concat(idm, ',', ids) visited from data where rn = 1
union all
select d.idm, d.ids, d.rn,
(not find_in_set(d.idm, c.visited) and not find_in_set(d.ids, c.visited)),
case when (not find_in_set(d.idm, c.visited) and not find_in_set(d.ids, c.visited))
then concat_ws(',', c.visited, d.idm, d.ids)
else c.visited
end
from cte c
inner join data d on d.rn = c.rn + 1
)
select idm, ids from cte where to_keep
The first CTE enumerates the rows ordered by both columns. Then the recursive query walks the resultset, checks if both values are new, and sets a flag accordingly of the columns. Flagged numbers are retained to be used for filtering in the following iteration.
Demo on DB Fiddle
Note that, given your requirement, not all values may appear in the resultset. Consider the following dataset:
idm ids
+-----+---
1 2
1 3
1 4
Your logic will only return the first row.

returning rows satisfying multiple conditions for a column from a mysql table

I have mysql table with hospitals and treatments(associated with sub treatments) that they provide. I need to make mysql query on the table which returns hospitals providing all treatment/sub_treatment given in a list. For example:
From table below I need hospitals providing all treatments in list: (tretament_id, sub_treatment_id) = (1-1, 1-2). So result must be hospitals with id 1 and 8.
hospital_id | treatment_id | sub_treatment_id
-------------------------------------------------
1 | 1 | 1
1 | 1 | 2
1 | 1 | 3
_________________________________________________
4 | 1 | 1
4 | 2 | 1
_________________________________________________
8 | 1 | 1
8 | 1 | 2
_________________________________________________
7 | 2 | 1
I tried WHERE IN but it works like OR so returns hospital 4 which satisfies only (1,1). How can I write an sql query like WHERE IN but which works like AND?
Try this:
SELECT hospital_id
FROM mytable
WHERE (treatment_id, sub_treatment_id) IN ((1, 1), (1, 2))
GROUP BY hospital_id
HAVING COUNT(CASE
WHEN (treatment_id, sub_treatment_id) IN ((1, 1), (1, 2))
THEN 1
END) = 2
Demo here
You can do this using group by and having:
select hospital_id
from t
where treatment_id = 1 and sub_treatment_id in (1, 2)
group by hospital_id
having count(*) = 2;
Note: This assumes that there are no duplicates in the table. That is easy enough to fix using count(distinct), but probably not necessary.
Here is a solution using GROUP_CONCAT and JOIN:
select distinct t.hospital_id
from hospitals h and treatments t ON h.id = t.hospital_id
having GROUP_CONCAT(CONCAT(t.treatment_id, '-', t.sub_treatment_id)
ORDER BY t.treatment_id, t.sub_treatment_id)
= '1-1,1-2';

Mysql - Group by multiple table and columns

I have the two following tables:
content:
========
cid | iid | qty
---------------
1 | 7 | 42
2 | 7 | 1
3 | 8 | 21
ret:
====
rid | cid | qty
--------------
1 | 1 | 2
2 | 1 | 10
3 | 2 | 1
I would like to retrieve, for each iid, the sum of content.qty and ret.qty
For exemple, for given tables, the result would be:
iid=7, SUM(content.qty) = 43, SUM(ret.qty)=13
iid=8, SUM(content.qty) = 21, SUM(ret.qty)=0
Is there any way to do it in one query?
In advance, thank you!
This is a bit complicated, because you don't want duplicates in your sums. To fix that problem, do the aggregations separately as subqueries. The first is directly on content the second joins back to content from ret to get the iid column.
The following query follows this approach, and assumes that cid is a unique key on content:
select c.iid, c.qty + coalesce(r.qty, 0)
from (select c.iid, SUM(qty) as cqty
from content c
group by c.iid
) c left outer join
(select c.iid, SUM(r.qty) as rqty
from ret r join
content c
on r.cid = c.cid
group by c.iid
) r
on c.iid = r.iid;

Mysql join with counting results in another table

I have two tables, one with ranges of numbers, second with numbers. I need to select all ranges, which have at least one number with status in (2,0). I have tried number of different joins, some of them took forever to execute, one which I ended with is fast, but it select really small number of ranges.
SELECT SQL_CALC_FOUND_ROWS md_number_ranges.*
FROM md_number_list
JOIN md_number_ranges
ON md_number_list.range_id = md_number_ranges.id
WHERE md_number_list.phone_num_status NOT IN (2, 0)
AND md_number_ranges.reseller_id=1
GROUP BY range_id
LIMIT 10
OFFSET 0
What i need is something like "select all ranges, join numbers where number.range_id = range.id and where there is at least one number with phone_number_status not in (2, 0).
Any help would be really appreciated.
Example data structure:
md_number_ranges:
id | range_start | range_end | reseller_id
1 | 000001 | 000999 | 1
2 | 100001 | 100999 | 2
md_number_list:
id | range_id | number | phone_num_status
1 | 1 | 0000001 | 1
2 | 1 | 0000002 | 2
3 | 2 | 1000012 | 0
4 | 2 | 1000015 | 2
I want to be able select range 1, because it has one number with status 1, but not range 2, because it has two numbers, but with status which i do not want to select.
It's a bit hard to tell what you want, but perhaps this will do:
SELECT *
from md_number_ranges m
join (
SELECT md_number_ranges.id
, count(*) as FOUND_ROWS
FROM md_number_list
JOIN md_number_ranges
ON md_number_list.range_id = md_number_ranges.id
WHERE md_number_list.phone_num_status NOT IN (2, 0)
AND md_number_ranges.reseller_id=1
GROUP BY range_id
) x
on x.id=m.id
LIMIT 10
OFFSET 0
Is this what you're looking for?
SELECT DISTINCT r.*
FROM md_number_ranges r
JOIN md_number_list l ON r.id = l.range_id
WHERE l.phone_num_status NOT IN (0,2)
SQL Fiddle Demo

mysql select table where number of rows depends on dynamic values in two columns

I have a MySQL table:
id | style | minsize_id | maxsize_id
-------------------------------------
1 | Jacket | 1 | 3
2 | Pant | 2 | 4
3 | Hoody | 0 | 3
I would like to show an extra row for each size id between and including the minsize_id and maxsize_id (sizes can be any integer between 0 and 9), and a new column 'size_id' as follows:
id | style | size_id
----------------------
1 | Jacket | 1
1 | Jacket | 2
1 | Jacket | 3
2 | Pant | 2
2 | Pant | 3
2 | Pant | 4
3 | Hoody | 0
3 | Hoody | 1
3 | Hoody | 2
3 | Hoody | 3
How do I display the table in this way using only MySQL? Thanks for any help.
One way this could be achieved is to join this table onto a derived table which contains 10 rows, one for each size. For example
SELECT
yt.id,
yt.style,
sizes.size_id
FROM yourTable AS yt
INNER JOIN (
SELECT 0 AS size_id
UNION ALL
SELECT 1 AS size_id
UNION ALL
SELECT 2 AS size_id
UNION ALL
SELECT 3 AS size_id
UNION ALL
SELECT 4 AS size_id
UNION ALL
SELECT 5 AS size_id
UNION ALL
SELECT 6 AS size_id
UNION ALL
SELECT 7 AS size_id
UNION ALL
SELECT 8 AS size_id
UNION ALL
SELECT 9 AS size_id
) AS sizes
ON sizes.size_id BETWEEN yt.minsize_id AND yt.maxsize_id
If however you have a table "sizes" with primary key "size_id" then you can just INNER JOIN that instead of this derived table. Obviously replace yourTable and alias yt with whatever table name / alias you desire.
Here's one way:
SELECT id, style, size_id
FROM origTable ot
JOIN
(SELECT #rownum := #rownum + 1 AS size_id
FROM origTable ot1
CROSS JOIN origTable ot2,
(SELECT #rownum := -1) r) s
ON ot.minsize_id <= s.size_id AND ot.maxsize_id >= s.size_id
ORDER BY id, style, size_id
It may look a little messy at first glance but I'll try and explain:
MySQL doesn't have a row_number function like SQL Server does so I'm using a technique described here to artificially create this in the subquery (s). To create a lot of row numbers there needs to be a lot of rows in this so you could either choose a different table that already has a lot of rows or as I've done just do a CROSS JOIN of your original table - CROSS JOINs generally ends up with a lot of rows. Bit of a hack I know and may have degrading performance if origTable becomes large!
We need the row numbers to start at zero and what is selected by the subquery is pre-incremented by 1, hence the initialisation of #rownum to -1.
See SQL Fiddle Demo.