mysql query sorting and ordering - mysql

Say i have a table called "users" and it has 40 rows of records, each row has fields:
id, firstname, group_id, login_count, stay_on_page_count
groups has
administrator (1), manager (2), employee (3)
is it possible to create a query that will sort and order the rows this way
group _id stay_on_page_count login_count
========= ================== ===========
1 100mins 100
1 90mins 90
2 100mins 100
3 100mins 100
1 80mins 80
1 70mins 70
2 90mins 90
3 90mins 90
1 60mins 60
1 50mins 50
2 80mins 80
3 80mins 80
1 40mins 40
1 30mins 30
2 70mins 70
3 70mins 70
Basically I would like to create a 4x4 grid view using the query result. the pseudo code is probably
SELECT all FROM user table and to group the result in to cluster of 4,
while each 4 should have ORDER BY group_id ASC as first priority (1,1,2,3)
AND stay_on_page_count ORDER BY DESC as second priority,
AND login_count ORDER BY DESC as last or third priority
i don't know if the pseudo code explains enough, but that's the only thing i can came up with :)
And if its possible, then will it sacrifice performance?
Is there any better approach to accomplish this?
I am using Mysql and PHP (CakePHP 2.x)
Thanks

One approach (using summarised as a table holding the summarised values listed in the question):
select * from
(select s1.*,
#rank1:=#rank1+1 as rankcalc,
floor(#rank1/2) rankgroup,
#rank1%2 rankingroup
from (select #rank1:=1) r1
cross join
(select * from summarised where group_id=1 order by stay_on_page_count desc) s1
union all
select s2.*,
#rank2:=#rank2+1 as rankcalc,
#rank2 rankgroup,
2 rankingroup
from (select #rank2:=0) r2
cross join
(select * from summarised where group_id=2 order by stay_on_page_count desc) s2
union all
select s3.*,
#rank3:=#rank3+1 as rankcalc,
#rank3 rankgroup,
3 rankingroup
from (select #rank3:=0) r3
cross join
(select * from summarised where group_id=3 order by stay_on_page_count desc) s3
) sq order by rankgroup, rankingroup
SQLFiddle here.
Note that this solution is dependent on the ordering being evaluated in the sequence specified in the sub-queries, and not overridden by the optimizer - this should work in current versions of MySQL, but may not work in MariaDB (the open source fork of MySQL) or future versions of MySQL.

While this will generate the order you want it's very much a forced and hard coded effort. It makes several assumptions like login_count is always in the ranges you listed.
SELECT group_ID, Stay_on_Page_count, Login_Count, myset
FROM (
SELECT group_ID, Stay_on_Page_Count, Login_Count, 1 as mySet
FROM USERS
WHERE (GROUP_ID=1 and Login_count >= 90) OR (group_ID in (2,3) and Login_Count=100)
UNION
SELECT group_ID, Stay_on_Page_Count, Login_Count, 2 as mySet
FROM USERS
WHERE (GROUP_ID=1 and Login_count Between 70 and 80)
OR (group_ID in (2,3) and Login_Count=90)
UNION
SELECT group_ID, Stay_on_Page_Count, Login_Count, 3 as mySet
FROM USERS
WHERE (GROUP_ID=1 and Login_count Between 50 and 60)
OR (group_ID in (2,3) and Login_Count=80)
UNION
SELECT group_ID, Stay_on_Page_Count, Login_Count, 4 as mySet
FROM USERS
WHERE (GROUP_ID=1 and Login_count Between 30 and 40)
OR (group_ID in (2,3) and Login_Count=70))
ORDER BY myset, group_ID, Login_count Desc

Related

trying to select few rows in sql

i have a table
Id
Month
Salary
1
1
20
2
1
20
1
2
30
2
2
30
3
2
40
1
3
40
3
3
60
1
4
60
3
4
70
I was trying to remove some max month in each id . I was trying the following query
select * from Employee
where id , month not in ( select distinct id, max(Month) over(partition by id ) from Employee)
I cant understand what wrong with this query why cant i do this way. Is there any alternative way for this
Your method should work. The syntax is:
select *
from Employee
where (id, month) not in (select distinct id, max(Month) over(partition by id )
from Employee
)
I wouldn't recommend this approach. The window function is superfluous, when you just want aggregation:
select *
from Employee
where (id, month) not in (select id, max(Month)
from Employee
group by id
);
Or a correlated subquery seems more natural to me:
select e.*
from Employee e
where month < (select max(e2.Month)
from Employee e2
where e2.id = e.id
);
This has the advantage that it can use an index on Employee(id, month) and is probably the best performing way to write the query.

Selecting the latest row for each customer that matches these params

I have an SQL table that stores reports. Each row has a customer_id and a building_id and when I have the customer_id, I need to select the latest row (most recent create_date) for each building with that customer_id.
report_id customer_id building_id create_date
1 1 4 1553561789
2 2 5 1553561958
3 1 4 1553561999
4 2 5 1553562108
5 3 7 1553562755
6 3 8 1553570000
I would expect to get report id's 3, 4, 5 and 6 back.
How do I query this? I have tried a few sub-selects and group by and not gotten it to work.
If you are using MySQL 8+, then ROW_NUMBER is a good approach here:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY customer_id, building_id
ORDER BY create_date DESC) rn
FROM yourTable
)
SELECT
report_id,
customer_id,
building_id,
create_date
FROM cte
WHERE rn = 1;
If there could be more than one customer/building pair tied for the latest creation date, and you want to capture all ties, then replace ROW_NUMBER with RANK, and use the same query.
Another variation:
SELECT a.*
FROM myTable a
WHERE a.create_date = (SELECT MAX(create_date)
FROM myTable b
WHERE b.customer_id = a.customer_id
AND b.building_id = a.building_id)
Can try doing a search for "effective dated records" to see various approaches.

MySQL grouping with detail

I have a table that looks like this...
user_id, match_id, points_won
1 14 10
1 8 12
1 12 80
2 8 10
3 14 20
3 2 25
I want to write a MYSQL script that pulls back the most points a user has won in a single match and includes the match_id in the results - in other words...
user_id, match_id, max_points_won
1 12 80
2 8 10
3 2 25
Of course if I didn't need the match_id I could just do...
select user_id, max(points_won)
from table
group by user_id
But as soon as I add match_id to the "select" and "group by" I have a row for every match, and if I only add the match_id to the "select" (and not the "group by") then it won't correctly relate to the points_won.
Ideally I don't want to do the following either because it doesn't feel particularly safe (e.g. if the user has won the same amount of points on multiple matches)...
SELECT t.user_id, max(t.points_won) max_points_won
, (select t2.match_id
from table t2
where t2.user_id = t.user_id
and t2.points_won = max_points_won) as 'match_of_points_maximum'
FROM table t
GROUP BY t.user_id
Are there any more elegant options for this problem?
This is harder than it needs to be in MySQL. One method is a bit of a hack but it works in most circumstances. That is the group_concat()/substring_index() trick:
select user_id, max(points_won),
substring_index(group_concat(match_id order by points_won desc), ',', 1)
from table
group by user_id;
The group_concat() concatenates together all the match_ids, ordered by the points descending. The substring_index() then takes the first one.
Two important caveats:
The resulting expression has a type of string, regardless of the internal type.
The group_concat() uses an internal buffer, whose length -- by default -- is 1,024 characters. This default length can be changed.
You can use the query:
select user_id, max(points_won)
from table
group by user_id
as a derived table. Joining this to the original table gets you what you want:
select t1.user_id, t1.match_id, t2.max_points_won
from table as t1
join (
select user_id, max(points_won) as max_points_won
from table
group by user_id
) as t2 on t1.user_id = t2.user_id and t1.points_won = t2.max_points_won
I think you can optimize your query by add limit 1 in the inner query.
SELECT t.user_id, max(t.points_won) max_points_won
, (select t2.match_id
from table t2
where t2.user_id = t.user_id
and t2.points_won = max_points_won limit 1) as 'match_of_points_maximum'
FROM table t
GROUP BY t.user_id
EDIT : only for postgresql, sql-server, oracle
You could use row_number :
SELECT USER_ID, MATCH_ID, POINTS_WON
FROM
(
SELECT user_id, match_id, points_won, row_number() over (partition by user_id order by points_won desc) rn
from table
) q
where q.rn = 1
For a similar function, have a look at Gordon Linoff's answer or at this article.
In your example, you partition your set of result per user then you order by points_won desc to obtain highest winning point first.

SELECT rows with minimum count(*)

Let's say i have a simple table voting with columns
id(primaryKey),token(int),candidate(int),rank(int).
I want to extract all rows having specific rank,grouped by candidate and most importantly only with minimum count(*).
So far i have reached
SELECT candidate, count( * ) AS count
FROM voting
WHERE rank =1
AND candidate <200
GROUP BY candidate
HAVING count = min( count )
But,it is returning empty set.If i replace min(count) with actual minimum value it works properly.
I have also tried
SELECT candidate,min(count)
FROM (SELECT candidate,count(*) AS count
FROM voting
where rank = 1
AND candidate < 200
group by candidate
order by count(*)
) AS temp
But this resulted in only 1 row,I have 3 rows with same min count but with different candidates.I want all these 3 rows.
Can anyone help me.The no.of rows with same minimum count(*) value will also help.
Sample is quite a big,so i am showing some dummy values
1 $sampleToken1 101 1
2 $sampleToken2 102 1
3 $sampleToken3 103 1
4 $sampleToken4 102 1
Here ,when grouped according to candidate there are 3 rows combining with count( * ) results
candidate count( * )
101 1
103 1
102 2
I want the top 2 rows to be showed i.e with count(*) = 1 or whatever is the minimum
Try to use this script as pattern -
-- find minimum count
SELECT MIN(cnt) INTO #min FROM (SELECT COUNT(*) cnt FROM voting GROUP BY candidate) t;
-- show records with minimum count
SELECT * FROM voting t1
JOIN (SELECT id FROM voting GROUP BY candidate HAVING COUNT(*) = #min) t2
ON t1.candidate = t2.candidate;
Remove your HAVING keyword completely, it is not correctly written.
and add SUB SELECT into the where clause to fit that criteria.
(ie. select cand, count(*) as count from voting where rank = 1 and count = (select ..... )
The HAVING keyword can not use the MIN function in the way you are trying. Replace the MIN function with an absolute value such as HAVING count > 10

Selecting sum of single column depending on two columns on same table

I have a table very similar to the one below. p1 and p2 on the table refer to id of player on an another table.
id score p1 p2 date
-- ----- -- -- ----
1 12 1 2 2011.10.21
2 23 3 4 2011.10.22
3 21 1 3 2011.10.23
4 35 5 1 2011.10.24
5 11 2 3 2011.10.25
What I want to do is the get the player id (p1 or p2) with highest score. My solution is something like select sum(score) but I can't form a query because a player may appear in both p1 or p2 columns.
Also a bigger problem is when I want to sort scores from highest to lowest. I dont know what to do. How can I sum and sort a score if I need to group to separate columns? The result I want is similar to this output:
pID score times_played
--- ----- ------------
1 68 3
3 55 3
5 35 1
2 23 2
4 23 1
Is my database design flawed? If there is a more intelligent way I'd like to know. Should I need seperate single queries so I can merge them on PHP or something?
Any help would be appreciated.
Cheers.
PS: I couldnt think a nice subject. Feel free to edit.
You can put the players in one column as so:
select id, score, p1 as player, date from yourtable
union all
select id, score, p2 as player, date from yourtable
You now have players in one column. You can do this to get the score sum for all players
select sum(score), player from (
select id, score, p1 as player, date from yourtable
union all
select id, score, p2 as player, date from yourtable
) group by player
Now, you say that you also want to know how many times the player played and sort them in descending order:
select sum(score), player, count(*) as timesPlayed from (
select id, score, p1 as player, date from yourtable
union all
select id, score, p2 as player, date from yourtable
) group by player order by sum(score) desc
Try this to get players with highest score (disregarding ties)
select id,p1,p2
from table t1
join (select max(score) as MaxS) xx on xx.MaxS = t1.Score
limit 1
To get player total score, try this
select Player as pID,Sum(tot) as Score, count(*) as TimesPlayed
from
(
select p1 as Player,sum(score) as Tot
from table
group by p1
union all
select p2,sum(score)
from table
group by p2
) xx
Group by xx.Player
order by Score desc
Alternatively to using UNION (ALL) on the table, you could try something like this:
SELECT
CASE p.PlayerNumber WHEN 1 THEN t.p1 ELSE t.p2 END AS pID,
SUM(t.score) AS score,
COUNT(*) AS times_played
FROM atable t
CROSS JOIN (SELECT 1 AS PlayerNumber UNION ALL SELECT 2) p
GROUP BY
pID /* this is probably MySQL-specific; most, if not all, other major
database systems would require repeating the entire pID expression here, i.e.
GROUP BY
CASE p.PlayerNumber WHEN 1 THEN t.p1 ELSE t.p2 END
*/
ORDER BY
score DESC,
times_played DESC /* this is based on your result set;
you might want to omit it or change it to ASC */
UPDATE, in an answer to a question in the comments: joining the result set to the user table:
SELECT
`user`.*, /* you should probably specify
the necessary columns explicitly */
totals.score,
totals.times_played
FROM `user` u
INNER JOIN (
SELECT
CASE p.PlayerNumber WHEN 1 THEN t.p1 ELSE t.p2 END AS pID,
SUM(t.score) AS score,
COUNT(*) AS times_played
FROM atable t
CROSS JOIN (SELECT 1 AS PlayerNumber UNION ALL SELECT 2) p
GROUP BY
pID
) totals ON user.id = totals.pID
ORDER BY
totals.score DESC,
totals.times_played DESC