How to show count of records - mysql

I have the following data
ReasonId Team Division Location
2 A L1
3 B D1 L2
2 A D2 L1
2 A D3 L3
I want to show the count grouped by the ReasonId for each team,division & location. There could be instances where division could be null.
I am trying something like this,
SELECT
COUNT(*) AS TotalRequests, Reason, team
FROM
reports
GROUP BY Reason , team
UNION SELECT
COUNT(*) AS TotalRequests, Reason, location
FROM
reports
GROUP BY Reason , location
UNION SELECT
COUNT(*) AS TotalRequests, Reason, division
FROM
reports
WHERE
ISNULL(division) = 0
GROUP BY Reason , division
;
The output I am getting for the above is,
TotalRequests Reason team
1 2
3 2 A
1 3 B
1 3 D1
1 2 D2
1 2 D3
2 2 L1
1 3 L2
1 2 L3
Is it possible to get an output that looks like this,
ReasonId Team TotalByTeam Location TotalByLocation Division TotalByDivision
2 A 3 L1 2 0
2 A 3 L3 1 D2 1
2 A 3 L3 1 D3 1
3 B 1 L2 1 D1 1
I am using mysql 8.0.17 Here's a sample schema and dbfiddle of same
CREATE TABLE `reports` (
`Reason` int(11) DEFAULT NULL,
`Team` text,
`Division` text,
`Location` text
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO reports (Reason,Team,Division,Location) values (2, 'A',null,'L1');
INSERT INTO reports (Reason,Team,Division,Location) values (3, 'A','D1','L2');
INSERT INTO reports (Reason,Team,Division,Location) values (2, 'A','D2','L1');
INSERT INTO reports (Reason,Team,Division,Location) values (2, 'A','D3','L3');

You should use analytic functions COUNT(...) OVER (...) for this. They are available in MySQL since version 8.0.
select
reasonid,
team,
count(team) over (partition by team) as total_by_team,
location,
count(location) over (partition by location) as total_by_location,
division,
count(division) over (partition by division) as total_by_division
from reports;
Demo: https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=79891554331e8222041ec34eea3fc4ee

Try this below script-
Demo Here
SELECT A.ReasonId,
A.Team,
(SELECT COUNT(*) FROM your_table B WHERE B.ReasonId = A.ReasonId AND B.Team = A.Team) TotalByTeam,
A.Division,
(SELECT COUNT(*) FROM your_table B WHERE B.ReasonId = A.ReasonId AND B.Division = A.Division) TotalByDivision,
A.Location,
(SELECT COUNT(*) FROM your_table B WHERE B.ReasonId = A.ReasonId AND B.Location = A.Location) TotalByLocation
FROM your_table A

Related

MySQL : extract rows having 2 different values in a column

I have a table with 3 columns looking like this :
id
key
status
1
1
a
2
2
a
3
3
a
4
1
b
5
2
b
6
4
b
I want to extract rows where a key have a "a" AND a "b" as status...
id
key
status
1
1
a
2
2
a
4
1
b
5
2
b
I KNOW that I need some GROUP BY and HAVING but I twisted my brain for few hours and I don't find any solution...
Thanx !
You can use having count
select `key`
from my_table
where status in ('a','b')
group by `key`
having count(distinct status) =2;
If you need the other values you can use inner join with subquery
select m.*
from my_table m
inner join (select `key`
from my_table
where status in ('a','b')
group by `key`
having count(distinct status) =2
) tbl on m.`key`=tbl.`key`;
https://dbfiddle.uk/yq5OiPtU

Getting difference of a column from same table

I have a table with data that is similar to this table below
id
session_id
user_id
action
log_time
1
1
3
join
1642645048
2
1
3
left
1642645048
3
1
3
join
1642645552
4
1
3
left
1642646072
5
1
3
join
1642646632
6
1
3
left
1642646736
7
1
5
join
1642647083
8
1
5
join
1642649879
9
1
5
left
1642649951
10
1
5
join
1642650112
11
1
5
join
1642650159
12
1
5
join
1642651005
log_time is saved as a unix time
Question: Is it possible to retrieve the total amount of time that a user was in a session?
So it would do something like total_participation = ("1st left" - "1st join") + ("2nd left" - "2nd join") + ("3rd left - "3rd join")
I've already got the difference between first join and last left time by doing the following query:
SELECT s1.session_id as 'Id',
u.name AS 'Participant',
IFNULL(TIME_FORMAT(SEC_TO_TIME(s2.time_log - s1.time_log), '%Hh %im %ss'), 0) AS 'TotalParticipation'
FROM tblSessionLog AS s1
LEFT JOIN tblSessionLog AS s2 ON (
s2.id = (
SELECT id
FROM tblSessionLog
WHERE action = 'left'
AND user_id = s1.user_id
AND id > s1.id
ORDER BY time_log DESC
LIMIT 1
)
)
LEFT JOIN tblUser AS u ON u.id = s1.user_id -- used only to get participant name
WHERE s1.action = 'join'
GROUP BY s1.session_id, s1.user_id
ORDER BY s1.session_id, s1.user_id;
But I can't seem to get how to remove the time in between the participant have left and join backed again. Or is that not possible on SQL and should be handled on backend code?
Not exactly a copy but more or less, the actual data is like this sample SQL Fiddle here: http://sqlfiddle.com/#!9/2d8f6c/1/0
Update:
#Akina's solution work well when the data is consistent which is very much appreciated but I found out it will not be suitable on my case as there are instances where it is possible to have more than one join action before having left action, as well as no left action after join action. I updated the example table above to further show the actual data. Updated the sample fiddle as well.
Any leads will be really appreciated. And apologies as for some reason I need to do this on MySQL without the help of backend code. Thanks!
Perhaps something like this:
WITH cte1 AS(
SELECT user_id, action, time_log,
ROW_NUMBER() OVER (PARTITION BY user_id, action ORDER BY time_log) AS rn
FROM tblSessionLog
WHERE action='join'),
cte2 AS(
SELECT user_id, action, time_log,
ROW_NUMBER() OVER (PARTITION BY user_id, action ORDER BY time_log) AS rn
FROM tblSessionLog
WHERE action='left')
SELECT *,
cte2.time_log-cte1.time_log
FROM cte1
LEFT JOIN cte2
ON cte1.user_id=cte2.user_id
AND cte1.rn=cte2.rn;
Which on your current data will return the following results:
user_id
action
time_log
rn
user_id
action
time_log
rn
cte2.time_log-cte1.time_log
3
join
1642645048
1
3
left
1642645048
1
0
3
join
1642645552
2
3
left
1642646072
2
520
3
join
1642646632
3
3
left
1642646736
3
104
5
join
1642647083
1
5
left
1642649951
1
2868
5
join
1642649879
2
NULL
NULL
NULL
NULL
NULL
5
join
1642650112
3
NULL
NULL
NULL
NULL
NULL
5
join
1642650159
4
NULL
NULL
NULL
NULL
NULL
5
join
1642651005
5
NULL
NULL
NULL
NULL
NULL
With two generated common table expressions (cte), each result from cte assigned with ROW_NUMBER() then LEFT JOIN them with matching user_id and the generated ROW_NUMBER(). As your sample data only have 1 left for user_id=5 then it only paired for the first found join for the same user_id and returned NULL for the rest. If a left action was added for user_id=5 afterwards, it will occupy as the left action for the join action that is NULL. For example, if we add:
INSERT INTO tblSessionLog (id, session_id, user_id, action, time_log)
VALUES (13, 1, 5, 'left', 1642652005);
INSERT INTO tblSessionLog (id, session_id, user_id, action, time_log)
VALUES (14, 1, 5, 'left', 1642652085);
then the results will be something like this:
user_id
action
time_log
rn
user_id
action
time_log
rn
cte2.time_log-cte1.time_log
3
join
1642645048
1
3
left
1642645048
1
0
3
join
1642645552
2
3
left
1642646072
2
520
3
join
1642646632
3
3
left
1642646736
3
104
5
join
1642647083
1
5
left
1642649951
1
2868
5
join
1642649879
2
5
left
1642652005
2
2126
5
join
1642650112
3
5
left
1642652085
3
1973
5
join
1642650159
4
NULL
NULL
NULL
NULL
NULL
5
join
1642651005
5
NULL
NULL
NULL
NULL
NULL
One thing to take note is that the ROW_NUMBER() I generated here is order by time_log and not by id (or which data was inserted first). If you wish to pair them by which data comes first, you can simply change the ORDER BY time_log to ORDER BY id on both of the ROW_NUMBER() assignment.
Demo fiddle

Select duplicates while concatenating every one except the first

I am trying to write a query that will select all of the numbers in my table, but those numbers with duplicates i want to append something on the end that shows it as a duplicate. However I am not sure how to do this.
Here is an example of the table
TableA
ID Number
1 1
2 2
3 2
4 3
5 4
SELECT statement output would be like this.
Number
1
2
2-dup
3
4
Any insight on this would be appreciated.
if you mysql version didn't support window function. you can try to write a subquery to make row_number then use CASE WHEN to judgement rn > 1 then mark dup.
create table T (ID int, Number int);
INSERT INTO T VALUES (1,1);
INSERT INTO T VALUES (2,2);
INSERT INTO T VALUES (3,2);
INSERT INTO T VALUES (4,3);
INSERT INTO T VALUES (5,4);
Query 1:
select t1.id,
(CASE WHEN rn > 1 then CONCAT(Number,'-dup') ELSE Number END) Number
from (
SELECT *,(SELECT COUNT(*)
FROM T tt
where tt.Number = t1.Number and tt.id <= t1.id
) rn
FROM T t1
)t1
Results:
| id | Number |
|----|--------|
| 1 | 1 |
| 2 | 2 |
| 3 | 2-dup |
| 4 | 3 |
| 5 | 4 |
If you can use window function you can use row_number with window function to make rownumber by Number.
select t1.id,
(CASE WHEN rn > 1 then CONCAT(Number,'-dup') ELSE Number END) Number
from (
SELECT *,row_number() over(partition by Number order by id) rn
FROM T t1
)t1
sqlfiddle
I made a list of all the IDs that weren't dups (left join select) and then compared them to the entire list(case when):
select
case when a.id <> b.min_id then cast(a.Number as varchar(6)) + '-dup' else cast(a.Number as varchar(6)) end as Number
from table_a
left join (select MIN(b.id) min_id, Number from table_a b group by b.number)b on b.number = a.number
I did this in MS SQL 2016, hope it works for you.
This creates the table used:
insert into table_a (ID, Number)
select 1,1
union all
select 2,2
union all
select 3,2
union all
select 4,3
union all
select 5,4

Finding unique users from linked values

I have values in my Table of this form.
id | val1 | val2
--------------------
1 | e1 | m1
2 | e1 | m2
3 | e2 | m2
4 | e3 | m1
5 | e4 | m3
6 | e5 | m3
7 | e5 | m4
8 | e4 | m5
From this, I have to recover unique users like this and give them a unique id to identify.
User1 -> (val1 : e1, e2, e3 | val2: m1, m2)
e1 <-> m1, e1 <-> m2, m1 <-> e3, e2 <-> m2 ( <-> means linked).
e1 is connected to m1.
e1 is connected to m2.
m2 is connected to e2.
So e1,m1 are connected to e2.
Similarly, we find e1, e2, e3, m1, m2 all are linked. We need to identify these chains.
User2 -> (val1 : e4, e5 | val2: m3, m4, m5)
I have written two queries based on grouping my val1 and then by val2 separately and joining them in code (Java).
I want this to do this directly in MySQL/BigQuery query itself as we are building some reports on this.
Is this possible in a single query? Please help.
Thank you.
Update :
Desired output -
[
{
id : user1,
val1 : [e1, e2, e3],
val2 : [m1, m2]
},
{
id : user2,
val1 : [e4, e5],
val2 : [m3, m4, m5]
}
]
or
id | val1 | val2 | UUID
------------------------
1 | e1 | m1 | u1
2 | e1 | m2 | u1
3 | e2 | m2 | u1
4 | e3 | m1 | u1
5 | e4 | m3 | u2
6 | e5 | m3 | u2
7 | e5 | m4 | u2
8 | e4 | m5 | u2
To make it simple, assuming values of val1 and val2 are nodes and are connected if present in the same row.
The rows of the table form graphs (user1, user2) and we need to identify these graphs.
Wanted to jump-in with option of solving your task with pure BigQuery (Standard SQL)
Pre-requisites / assumptions: source data is in sandbox.temp.id1_id2_pairs
You should replace this with your own or if you want to test with dummy data from your question - you can create this table as below (of course replace sandbox.temp with your own project.dataset)
Make sure you set respective destination table
Note: you can find all respective Queries (as text) at the bottom of this answer, but for now I am illustrating my answer with screenshots - so all is presented - query, result and used options
So, there will be three steps:
Step 1 - Initialization
Here, we just do initial grouping of id1 based on connections with id2:
As you can see here - we created list of all id1 values with respective connections based on simple one-level connection through id2
Output table is sandbox.temp.groups
Step 2 - Grouping Iterations
In each iteration we will enrich grouping based on already established groups.
Source of Query is output table of previous Step (sandbox.temp.groups) and Destination is the same table (sandbox.temp.groups) with Overwrite
We will continue iterations till when count of found groups will be the same as in previous iteration
Note: you can just have two BigQuery Web UI Tabs opened (as it is shown above) and without changing any code just run Grouping and then Check again and again till iteration converge
(for specific data that I used in pre-requisites section - I had three iterations - first iteration produced 5 users, second iteration produced 3 users and third iteration produced again 3 users - which indicated that we done with iterations.
Of course, in real life case - number of iterations could be more than just three - so we need some sort of automation (see respective section at the bottom of answer).
Step 3 – Final Grouping
When id1 grouping is completed - we can add final grouping for id2
Final result now is in sandbox.temp.users table
Used Queries (do not forget to set respective destination tables and overwrites when needed as per above described logic and screenshots):
Pre-requisites:
#standardSQL
SELECT 1 id, 'e1' id1, 'm1' id2 UNION ALL
SELECT 2, 'e1', 'm2' UNION ALL
SELECT 3, 'e2', 'm2' UNION ALL
SELECT 4, 'e3', 'm1' UNION ALL
SELECT 5, 'e4', 'm3' UNION ALL
SELECT 6, 'e5', 'm3' UNION ALL
SELECT 7, 'e5', 'm4' UNION ALL
SELECT 8, 'e4', 'm5' UNION ALL
SELECT 9, 'e6', 'm6' UNION ALL
SELECT 9, 'e7', 'm7' UNION ALL
SELECT 9, 'e2', 'm6' UNION ALL
SELECT 888, 'e4', 'm55'
Step 1
#standardSQL
WITH `yourTable` AS (select * from `sandbox.temp.id1_id2_pairs`
), x1 AS (SELECT id1, STRING_AGG(id2) id2s FROM `yourTable` GROUP BY id1
), x2 AS (SELECT id2, STRING_AGG(id1) id1s FROM `yourTable` GROUP BY id2
), x3 AS (
SELECT id, (SELECT STRING_AGG(i ORDER BY i) FROM (
SELECT DISTINCT i FROM UNNEST(SPLIT(id1s)) i)) grp
FROM (
SELECT x1.id1 id, STRING_AGG((id1s)) id1s FROM x1 CROSS JOIN x2
WHERE EXISTS (SELECT y FROM UNNEST(SPLIT(id1s)) y WHERE x1.id1 = y)
GROUP BY id1)
)
SELECT * FROM x3
Step 2 - Grouping
#standardSQL
WITH x3 AS (select * from `sandbox.temp.groups`)
SELECT id, (SELECT STRING_AGG(i ORDER BY i) FROM (
SELECT DISTINCT i FROM UNNEST(SPLIT(grp)) i)) grp
FROM (
SELECT a.id, STRING_AGG(b.grp) grp FROM x3 a CROSS JOIN x3 b
WHERE EXISTS (SELECT y FROM UNNEST(SPLIT(b.grp)) y WHERE a.id = y)
GROUP BY a.id )
Step 2 - Check
#standardSQL
SELECT COUNT(DISTINCT grp) users FROM `sandbox.temp.groups`
Step 3
#standardSQL
WITH `yourTable` AS (select * from `sandbox.temp.id1_id2_pairs`
), x1 AS (SELECT id1, STRING_AGG(id2) id2s FROM `yourTable` GROUP BY id1
), x3 as (select * from `sandbox.temp.groups`
), f AS (SELECT DISTINCT grp FROM x3 ORDER BY grp
)
SELECT ROW_NUMBER() OVER() id, grp id1,
(SELECT STRING_AGG(i ORDER BY i) FROM (SELECT DISTINCT i FROM UNNEST(SPLIT(id2)) i)) id2
FROM (
SELECT grp, STRING_AGG(id2s) id2 FROM f
CROSS JOIN x1 WHERE EXISTS (SELECT y FROM UNNEST(SPLIT(f.grp)) y WHERE id1 = y)
GROUP BY grp)
Automation:
Of course, above "process" can be executed manually in case if iterations converge fast - so you will end up with 10-20 runs. But in more real-life cases you can easily automate this with any client of your choice

I'm trying to only get one row from a LEFT JOIN

Temp ordering table
temp_id series_id news_ticket_breakdown_id quantity expiry_time
58db09bba25b4 2 2 2 1490750973
58db09bba25b4 2 4 1 1490750973
NEWS ITEMS TABLE
news_id series_id news_title
26 2 GENERIC TICKETING
27 2 GENERIC TICKETING
When I do a left join on series_id I get back 4 rows and I only want 2. I'm not sure how to limit the amount of joined rows to one row.
CURRENT QUERY
SELECT
*
FROM
ticket_ordering_temp
LEFT JOIN
news_items
on
( news_items.series_id = SELECT
DISTINCT(event_id)
FROM
news_items
where
series_id=ticket_ordering_temp.series_id
)
where
temp_id='58db09bba25b4''
DESIRED RESULT
I want just 1 row from table 2 and not 2 rows joined as stated above
temp_id series_id news_ticket_breakdown_id quantity expiry_time news_title
58db09bba25b4 2 2 2 1490750973 GENERIC TICKETING
58db09bba25b4 2 4 1 1490750973 GENERIC TICKETING
For your sample data and your desired result, you can try following sql to do:
SELECT
ticket_ordering_temp.*, t.news_title
FROM
ticket_ordering_temp
LEFT JOIN(
SELECT DISTINCT series_id, news_title
FROM news_items
) t ON ticket_ordering_temp.series_id = t.series_id
WHERE temp_id = '58db09bba25b4'
however, I think you should tell us the logic behind these sample data.
Demo in SqlFiddle
try this Querying with tempTable:
select distinct * from
(select * from
(select '58db09bba25b4' as temp_id, 2 as series_id,2 as news_ticket_breakdown_id,2 as quantity,1490750973 as expiry_time union all
select '58db09bba25b4' , 2 , 4 , 1, 1490750973) as tbl_order) as tbl_order
left join
(select series_id,news_title from
(select 26 as news_id, 2 as series_id, 'GENERIC TICKETING' as news_title union all
select 27 , 2 , 'GENERIC TICKETING') as tbl_items) tbl_items
on tbl_order.series_id = tbl_items.series_id where temp_id ='58db09bba25b4'
Querying With actual table:
select DISTINCT * from
(select temp_id,series_id,news_ticket_breakdown_id,quantity,expiry_time from tbl_order) as tb_order
left join
(select series_id,news_title from tbl_items) as tb_items
on tb_order.series_id = tb_items.series_id where temp_id ='58db09bba25b4'
result:
58db09bba25b4 2 2 2 1490750973 2 GENERIC TICKETING
58db09bba25b4 2 4 1 1490750973 2 GENERIC TICKETING
Just giving an another solution.