What I have
So I'm running this statement:
SELECT
i.id,
i.item_id,
v.item_to_map_id,
i.date,
COALESCE( SUM(CAST(CAST(v.score AS char) AS SIGNED)), 0 ) AS score
FROM item_to_map i
LEFT JOIN
vote_item v
ON i.id = v.item_to_map_id
GROUP BY
i.id, i.item_id, i.date, v.item_to_map_id
ORDER BY
item_id asc, score desc;
And I'm getting the following table:
+----+---------+----------------+---------------------+-------+
| id | item_id | item_to_map_id | date | score |
+----+---------+----------------+---------------------+-------+
| 1 | 1 | 1 | 2017-07-05 09:38:23 | 3 |
| 3 | 1 | 3 | 2017-07-05 09:38:23 | 0 |
| 2 | 1 | 2 | 2017-07-05 09:38:23 | -1 |
| 4 | 2 | NULL | 2017-07-05 09:38:23 | 0 |
| 5 | 2 | NULL | 2017-07-05 09:38:23 | 0 |
| 6 | 2 | NULL | 2017-07-05 09:38:24 | 0 |
+----+---------+----------------+---------------------+-------+
What I'm trying to do is select the first X of the repeated item_ids based on some ordering, for example, score or date.
What I've tried
I looked at this answer https://stackoverflow.com/a/1902167/6554121 and tried a modified version:
SELECT
i.id,
i.item_id,
v.item_to_map_id,
i.date,
COALESCE( SUM(CAST(CAST(v.score AS char) AS SIGNED)), 0 ) AS score
FROM item_to_map i
LEFT JOIN
vote_item v
ON i.id = v.item_to_map_id
WHERE
(
SELECT
COUNT(*)
FROM
item_to_map i2
WHERE
i2.item_id = i.item_id
) < 3
GROUP BY
i.id, i.item_id, i.date, v.item_to_map_id
ORDER BY item_id asc, score desc;
However this returns me no results
What I expected
If ordered by score:
+----+---------+----------------+---------------------+-------+
| id | item_id | item_to_map_id | date | score |
+----+---------+----------------+---------------------+-------+
| 1 | 1 | 1 | 2017-07-05 09:38:23 | 3 |
| 3 | 1 | 3 | 2017-07-05 09:38:23 | 0 |
| 4 | 2 | NULL | 2017-07-05 09:38:23 | 0 |
| 5 | 2 | NULL | 2017-07-05 09:38:23 | 0 |
+----+---------+----------------+---------------------+-------+
You can achieve this using session variables which simulate row number functionality:
SET #row_number = 0;
SET #item_id = 1;
SELECT t.id, t.item_id, t.item_to_map_id, t.date, t.score
FROM
(
SELECT
#row_number:=CASE WHEN #item_id = t.item_id
THEN #row_number + 1 ELSE 1 END AS rn,
#item_id:=t.item_id AS item_id,
t.id, t.item_to_map_id, t.date, t.score
FROM
(
SELECT
i.id,
i.item_id,
v.item_to_map_id,
i.date,
COALESCE( SUM(CAST(CAST(v.score AS char) AS SIGNED)), 0 ) AS score
FROM item_to_map i
LEFT JOIN vote_item v
ON i.id = v.item_to_map_id
GROUP BY
i.id, i.item_id, i.date, v.item_to_map_id
) t
ORDER BY
t.item_id, t.score DESC
) t
WHERE t.rn <= 2 -- this restricts to the first two rows per item_id group
-- as ordered by the logic in your ORDER BY clause
As far as I know, there is no nice way to get the first X records of a group in MySQL, unless your schema coincidentally happens to have row numbers already for each group. Using session variables as above is one way to handle this, and the performance might even be good as well.
Demo here:
Rextester
Related
I want to calculate count of order status changes within different states.
My Orderstatus table:
| id |ordr_id| status |
|----|-------|------------|
| 1 | 1 | pending |
| 2 | 1 | processing |
| 3 | 1 | complete |
| 4 | 2 | pending |
| 5 | 2 | cancelled |
| 6 | 3 | processing |
| 7 | 3 | complete |
| 8 | 4 | pending |
| 9 | 4 | processing |
Output I want:
| state | count |
|----------------------|-------|
| pending->processing | 2 |
| processing->complete | 2 |
| pending->cancelled | 1 |
Currently I'm fetching the results by SELECT order_id,GROUP_CONCAT(status) as track FROM table group by order_id and then process the data in php to get the output. But is that possible in query itself ?
Use lag():
select prev_status, status, count(*)
from (select t.*,
lag(status) over (partition by order_id order by status) as prev_status
from t
) t
group by prev_status, status;
LAG() is available in MySQL starting with version 8.
Note that you can filter out the first status for each order by putting where prev_status is not null in the outer query.
Your version is not quite correct, because it does not enforce the ordering. It should be:
SELECT order_id,
GROUP_CONCAT(status ORDER BY id) as track
EDIT:
In earlier versions of MySQL, you can use a correlated subquery:
select prev_status, status, count(*)
from (select t.*,
(select t2.status
from t t2
where t2.order_id = t.order_id and t2.id < t.id
order by t2.id desc
limit 1
) as prev_status
from t
) t
group by prev_status, status;
If id column ensure the sequence of records, you can use self join to achieve your requirement as below-
SELECT A.Status +'>'+ B.Status, COUNT(*)
FROM OrderStatus A
INNER JOIN OrderStatus B
ON A.id = B.id -1
WHERE B.Status IS NOT NULL
GROUP BY A.Status +'>'+ B.Status
With a join of the 3 status change types to the grouping of the table that you already did:
select c.changetype, count(*) counter
from (
select 'pending->processing' changetype union all
select 'processing->complete' union all
select 'pending->cancelled'
) c inner join (
select
group_concat(status order by id separator '->') changestatus
from tablename
group by ordr_id
) t on concat('->', t.changestatus, '->') like concat('%->', changetype, '->%')
group by c.changetype
See the demo.
Results:
> changetype | counter
> :------------------- | ------:
> pending->cancelled | 1
> pending->processing | 2
> processing->complete | 2
...or just a simple join...
SELECT CONCAT(a.status,'->',b.status) action
, COUNT(*) total
FROM my_table a
JOIN my_table b
ON b.ordr_id = a.ordr_id
AND b.id = a.id + 1
GROUP
BY action;
+----------------------+-------+
| action | total |
+----------------------+-------+
| pending->cancelled | 1 |
| pending->processing | 2 |
| processing->complete | 2 |
+----------------------+-------+
Note that this relies on the fact that ids are contiguous.
I am trying to create a query that allows me to select the data like shown below
id Counter_Type Champion_Name Counter_Lane
---|------------|------------|--------------
1 | 1 | Ahri | 1
2 | 1 | Ahri | 2
5 | 1 | Ahri | 2
3 | 1 | Ahri | 3
4 | 1 | Ahri | 2
6 | 1 | Teemo | 1
7 | 1 | Warwick | 4
8 | 1 | Warwick | 4
It should count the Counter_Lane and then the Counter_Type with the most should be shown for that name as shown below how it should show the data:
id Counter_Type Champion_Name Counter_Lane
---|------------|------------|--------------
1 | 1 | Ahri | 2
2 | 1 | Teemo | 1
3 | 1 | Warwick | 4
I have tried the following code and its the closest I have came for over 3 hours now, so could someone help please.
SELECT
a.Counter_Type, Champion_For, a.Counter_Lane, a.Champion_Name, COUNT(*) as Amount, sum(vote_type = 'up') as Upvotes, sum(vote_type = 'down') as Downvotes, sum(vote_type = 'up')-sum(vote_type = 'down') as Totalvotes
FROM Champion_Counters_Data a
JOIN ( SELECT c.Counter_Lane, c.Champion_Name, COUNT(*) magnitude
FROM Champion_Counters_Data c
WHERE
Champion_For = "Aatrox" AND Counter_Type = 1 GROUP BY Champion_Name, Counter_Lane ORDER BY magnitude) b ON a.Champion_Name = b.Champion_Name AND b.Counter_Lane = a.Counter_Lane
GROUP BY Champion_Name
SELECT * FROM your_table GROUP BY MAX(Counter_Lane)
This query is based on the output you want from the table you have given.
You will need many subqueries as MySQL doesn't allow windows function or CTE.
Rextester Demo
SELECT #rn := #rn + 1 as id,
t3.*
from
(select t1.*
FROM
(SELECT counter_type,
champion_name,
counter_lane,
count(*) AS cnt
FROM table53
GROUP BY champion_name,
counter_lane
) t1
INNER JOIN
(SELECT counter_type,
champion_name,
max(cnt) AS mcnt
FROM
(SELECT counter_type,
champion_name,
counter_lane,
count(*) AS cnt
FROM table53
GROUP BY champion_name,
counter_lane
) t
GROUP BY counter_type,
champion_name
) t2
ON t1.counter_type=t2.counter_type
AND t1.champion_name=t2.champion_name
AND t1.cnt=t2.mcnt
) t3
,(SELECT #rn := 0) t
;
Output
+-----+--------------+---------------+--------------+-----+
| id | counter_type | champion_name | counter_lane | cnt |
+-----+--------------+---------------+--------------+-----+
| 1 | 1 | Ahri | 2 | 3 |
| 2 | 1 | Teemo | 1 | 1 |
| 3 | 1 | Warwick | 4 | 2 |
+-----+--------------+---------------+--------------+-----+
Idea is to first group by champion_name and counter_lane and get the count . So for Ahri you will get cnt as 3. Now use another subquery to get corresponding counter_lane, which will be 2 for Ahri. At last, use a sequence number to generate id as 1,2,3 etc.
Try following SQL statement:
SELECT Counter_Type , Champion_Name, MAX(Counter_Lane) 'Counter_Lane'
FROM(
SELECT Counter_Type , Champion_Name , Counter_Lane
FROM Champion_Counters_Data
GROUP BY Counter_Type , Champion_Name
HAVING COUNT(Counter_Lane) >= 2
) tb
GROUP BY Counter_Type , Champion_Name
I am using MySQL..
I have a simple sales table as follow:
o----o----------o-----------o
| id | store_id | logDate |
o----o----------o-----------o
| 1 | 1 | 2015-1-13 |
| 2 | 1 | 2015-1-14 |
| 3 | 2 | 2015-1-11 |
| 4 | 2 | 2015-1-18 |
o----o----------o-----------o
And sale product table
o----o----------o---------o------------o
| id | sale_id | qty | price |
o----o----------o---------o------------o
| 1 | 1 | 1 | 10 |
| 2 | 2 | 1 | 10 |
| 3 | 2 | 1 | 10 |
| 4 | 3 | 1 | 10 |
| 5 | 3 | 1 | 10 |
| 6 | 3 | 1 | 10 |
| 7 | 4 | 1 | 10 |
| 8 | 4 | 1 | 10 |
o----o----------o---------o------------o
Expected Result
o-- --------o----------------o---------------------o
| store_id | SUM(price*qty) | Highest Date On |
o-----------o----------------o---------------------o
| 1 | 20 | 2015-1-14 |
| 2 | 30 | 2015-1-11 |
O-----------o----------------o---------------------o
How to achieve my expected result?
I have tried as follow but it didn't work as expected:
SELECT store_id, MAX(total), highestSingleDateOn
FROM (
SELECT SUM(price * qty) AS total,
DATE(s.logDate) AS highestSingleDateOn, s.store_id AS store_id
FROM sale_product sp JOIN sales s ON s.id = sp.sales_id
GROUP BY DATE(s.logDate), s.store_id
ORDER BY DATE(s.logDate) ASC
) AS result_for_highest_single_day
GROUP BY highestSingleDateOn, store_id
SELECT store_id, MAX(total), highestSingleDateOn
FROM (
SELECT SUM(price * qty) AS total,
DATE(s.logDate) AS highestSingleDateOn, s.store_id AS store_id
FROM sale_product sp JOIN sales s ON s.id = sp.sales_id
GROUP BY DATE(s.logDate), s.store_id
ORDER BY total DESC
) AS result_for_highest_single_day
GROUP BY store_id
I just have modified the script ORDER BY DATE(s.logDate) ASC >> ORDER BY total DESC
and GROUP BY highestSingleDateOn, store_id >> GROUP BY store_id.
*Above sql script,it uses the unstable features about group by of MYSQL.
*Then according to Mysql standard,I write a other version sql script.
select table1.*
from
( SELECT SUM(price * qty) AS total,
DATE(s.logDate) AS highestSingleDateOn, s.store_id AS store_id
FROM sale_product sp JOIN sales s ON s.id = sp.sale_id
GROUP BY DATE(s.logDate), s.store_id) as table1
,
(select tmp.store_id,MAX(tmp.total) as max_total from
(SELECT SUM(price * qty) AS total,
DATE(s.logDate) AS highestSingleDateOn, s.store_id AS store_id
FROM sale_product sp JOIN sales s ON s.id = sp.sale_id
GROUP BY DATE(s.logDate), s.store_id ) as tmp group by tmp.store_id) as table2
where table1.store_id = table2.store_id and table1.total=table2.max_total
One way to do this in MySQL is with multiple an aggregations and then a join. Perhaps an easier way is to use variables:
SELECT sd.*
FROM (SELECT sd.*,
(#rn := if(#s = store_id, #rn + 1,
if(#s := store_id, 1, 1)
)
) as rn
FROM (SELECT DATE(s.logDate) AS date, s.store_id, SUM(price * qty) AS total
FROM sale_product sp JOIN sales s ON s.id = sp.sales_id
GROUP BY DATE(s.logDate), s.store_id
ORDER BY s.store_id, total desc
) sd cross join
(SELECT #rn := 0, #s := -1) params
) sd
WHERE rn = 1;
I am working on ice hockey software: trying to find out who in your team has collected the most points with a specific player (in this example user_id = 1).
Data structure:
goal_user_id | assist_user_id | second_assist_user_id
-----------------------------------
1 | 13856 | null
1 | 15157 | null
1 | 15157 | null
1 | 15157 | 18733
345 | 1 | 28703
18733 | 1 | null
36014 | 34867 | 1
Desired output:
user_id | partner_id | total_points
-----------------------------------
1 | 15157 | 3
1 | 18733 | 2
1 | 13856 | 1
1 | 345 | 1
1 | 28703 | 1
1 | 34867 | 1
1 | 36014 | 1
SQL Fiddle:
http://sqlfiddle.com/#!9/b1587/4/0 (Note: SQLFiddle has been acting weird today).
I managed to get it done on the across two columns but couldn't figure out for three:
SELECT
COUNT(goal_user_id) as total_points,
CASE WHEN goal_user_id <> 1
THEN goal_user_id
ELSE assist_one_user_id
END as partner_id,
CASE WHEN goal_user_id < assist_one_user_id
THEN CONCAT(goal_user_id,'-',assist_one_user_id)
ELSE CONCAT(assist_one_user_id,'-',goal_user_id)
END as player_pair
FROM goals
WHERE
assist_one_user_id IS NOT NULL AND (goals.goal_user_id = 1 OR goals.assist_one_user_id = 1)
GROUP BY player_pair
ORDER BY total_points DESC
My inclination is to break all the rows out into pairs of users. Then do the aggregation based on that:
select u2, count(*) as total_points
from ((select goal_user_id as u1, assist_one_user_id as u2 from goals) union all
(select assist_one_user_id, goal_user_id from goals) union all
(select goal_user_id, assist_two_user_id from goals) union all
(select assist_two_user_id, goal_user_id from goals) union all
(select assist_one_user_id, assist_two_user_id from goals) union all
(select assist_two_user_id, assist_one_user_id from goals)
) uu
where uu.u1 = 1 and uu.u2 is not null
group by u2
order by total_points desc;
Here is a SQL Fiddle.
I have the following table:
+----+-----------+-----------+
| id | teacherId | studentId |
+----+-----------+-----------+
| 1 | 1 | 4 |
| 2 | 1 | 2 |
| 3 | 1 | 1 |
| 4 | 1 | 3 |
| 5 | 2 | 2 |
| 6 | 2 | 1 |
| 7 | 2 | 3 |
| 8 | 3 | 9 |
| 9 | 3 | 6 |
| 10 | 1 | 6 |
+----+-----------+-----------+
I need a query to find two teacherId's with maximum number of common studentId's.
In this case teachers with teacherIds 1,2 have common students with studentIds 2, 1, 3, which is greater than 1,3 having common students 6.
Thanks in Advance!
[Edit]: After several hours I've had the following solution:
SELECT * FROM (
SELECT r1tid, r2tid, COUNT(r2tid) AS cnt
FROM (
SELECT r1.teacherId AS r1tid, r2.teacherId AS r2tid
FROM table r1
INNER JOIN table r2 ON r1.studentId=r2.studentId AND r1.teacherId!=r2.teacherId
ORDER BY r1tid
) t
GROUP BY r1tid, r2tid
ORDER BY cnt DESC
) t GROUP BY cnt ORDER BY cnt DESC LIMIT 1;
I was sure that there must exist more short and elegant solution, but I could not find it.
You would do this with a self-join. Assuming no duplicates in the table:
select t.teacherid, t2.teacherid, count(*) as NumStudentsInCommon
from table t join
table t2
on t.studentid = t2.studentid and
t.teacherid < t2.teacherid
group by t.teacherid, t2.teacherid
order by NumStudentsInCommon desc
limit 1;
If you had duplicates, you would just replace count(*) with count(distinct studentid), but count(distinct) requires a bit more work.
select t.teacherId, t2.teacherId, sum(t.studentId) as NumStudentsInCommon
from table1 t join
table1 t2
on t.studentId = t2.studentId and
t.teacherId < t2.teacherId
group by t.teacherId, t2.teacherId
order by NumStudentsInCommon desc