postgreSQL ranking query with the given user_id - php

I am trying to get rank of a user by their two dimension params: donation sum and total donor count.
My rank formula is: rank of [rank of donation_sum + rank of donor_count / 2]
Sample table:
donation_id | user_id | donor_id | donation_sum
-----------------------------------------------
1 | 1 | 1 | 10
2 | 1 | 2 | 5
3 | 2 | 3 | 10
4 | 3 | 1 | 50
...
As you see, some donors make donation to different users, so I used sum(donation_sum) and count(distinct(donation_id)) to get exact rankings
I am able to get list of ranking separately by donation sum and total donor count with 2 sql but my need is to get a user rank with that formula above by given user_id in postgreSQL v. 9.4
Do you have any solution for it? so I will use that sql query in a Yii2 PHP framework
Thanks
Edit:
We added donation_date to the tbl_donation and modified actual query as below:
is it true usage of where donation_date?
with list as (
select
s.runner_id, sum, count, rank_sum, rank_count,
(rank_sum+ rank_count)::float/ 2 as rank_avg,
row_number() over (order by rank_sum) as rank
from (
select *, rank() over (order by sum desc) rank_sum
from (
select runner_id, sum(donation_sum)
from tbl_donation
where donation_date >= '2015-01-01'
group by 1
) s
) s
join (
select *, rank() over (order by count desc) rank_count
from (
select runner_id, count(distinct(donator_id))
from tbl_donation
where donation_date >= '2015-01-01'
group by 1
) c
) c
using (runner_id)
)
select rank
from list
where runner_id = 251;

Make two rankings in separate subqueries:
select
s.user_id, sum, count, rank_sum, rank_count,
(rank_sum+ rank_count)::float/ 2 as rank_avg,
row_number() over (order by rank_sum) as rank
from (
select *, rank() over (order by sum desc) rank_sum
from (
select user_id, sum(donation_sum)
from donations
group by 1
) s
) s
join (
select *, rank() over (order by count desc) rank_count
from (
select user_id, count(distinct(donation_id))
from donations
group by 1
) c
) c
using (user_id);
user_id | sum | count | rank_sum | rank_count | rank_avg | rank
---------+-----+-------+----------+------------+----------+------
3 | 100 | 1 | 1 | 2 | 1.5 | 1
1 | 30 | 2 | 2 | 1 | 1.5 | 2
2 | 20 | 1 | 3 | 2 | 2.5 | 3
(3 rows)
If you want to select rank for a single user_id use with query, e.g.:
with list as (
-- place here the above query
)
select rank
from list
where user_id = 2;

Related

MySQL get first existing row with value OR get another ordered

I need to write mysql query for table like this:
id | product_id | version | status
1 | 1 | 1 | 0
2 | 1 | 2 | 1
3 | 1 | 3 | 0
4 | 2 | 9 | 0
5 | 2 | 10 | 0
I need to get rows (unique for product_id - one for each product_id) but:
-if there is row for product_id with status=1 - grab it
-it there is no row as described get row with higher value or version
So for described table result should be
id | product_id | version | status
2 | 1 | 2 | 1
5 | 2 | 10 | 0
My only idea is to get rows with status 1, and then make second query using WHERE product_id NOT IN and then order by version DESC and GROUP BY product_id
Can join back to the table in this case
SELECT p1.id, p1.product_id, p1.version, p1.status FROM products p1
LEFT OUTER JOIN (
SELECT MAX(version) AS version FROM products p2
) p2 ON p1.version = p2.version OR p1.status = 1
GROUP BY p1.product_id
SQL Fiddle
You can achieve it with a UNION
select myTable.id, product_id, version, status from myTable
where id in(select id from myTable where status > 0)
union
select myTable.id, product_id, version, status from myTable
join (select max(id) as id from myTable group by product_id)
as m on m.id = myTable.id
and product_id not in(select product_id from myTable where status > 0 )

Finding Consecutive Streak and Display Count

I have a MySQL table that shows the following:
ID DATE FREQUENCY
-- ---------- ---------
1 2017-08-01 1
2 2017-08-02 1
3 2017-08-03 0
4 2017-08-04 1
5 2017-08-05 1
6 2017-08-06 1
I am trying to get the easiest way to group every time there are consecutive 1's on the frequency column. Then I would like to display them.
Example
2 (There are 2 consecutive 1's)
3 (There are also 3 consecutive 1's)
Thank you
This is a typical gaps-and-island problem.
You can solve it by comparing the overal rank of records to their relative ranks in groups of records having the same frequency. The difference between the ranks gives you the group each record belongs to.
The rest is just filtering and aggregating groups that have a frequency of 1.
Query:
select
min(id) min_id,
max(id) max_id,
min(date) min_date,
max(date) max_date,
count(*) streak_length
from (
select
t.*,
row_number() over(order by date) rn1,
row_number() over(partition by frequency order by date) rn2
from mytable t
) t
where frequency = 1
group by rn1 - rn2
order by min_date
Demo on DB Fiddle with your sample data:
min_id | max_id | min_date | max_date | streak_length
-----: | -----: | :--------- | :--------- | ------------:
1 | 2 | 2017-08-01 | 2017-08-02 | 2
4 | 6 | 2017-08-04 | 2017-08-06 | 3
Note: window function row_number() is available starting MySQL 8.0.

MYSQL MULTIPLE COUNT AND SUM

I hope this is possible in MYSQL, I am scripting with PHP.
I am trying to create multiple column on SUM of values and COUNT on table1 based on each month based with individual conditions and groupings. The tables are already joined through the accountid.
I have two tables monthlyreport(table1) & planters(table2).
Desired Results is in table 1
MONTHLY REPORT (Table 1)
REPORTID|ACCOUNTID|COMPMONTH|SUMtoDATE|COUNTtoDATE|SUMcompDATE|COUNTcompDATE|
1 | 190 | JAN | 150 | 2 | 150 | 2 |
2 | 190 | FEB | 0 | 0 | 100 | 1 |
Planters (Table 2)
PlanterID | ACCOUNTID |PLANTER | SALARY | compDATE | toDATE |
1 | 190 | aaa | 100 | Jan-1-2013 | Jan-05-2013 |
2 | 190 | bbb | 50 | Jan-9-2013 | Jan-12-2013 |
3 | 190 | aaa | 100 | Feb-1-2013 | Mar-12-2013 |
4 | 190 | bbb | 0 | Mar-5-2013 | Mar-12-2013 |
A single query with inner join already works but if I run both I get nothing because I can't seem to get the logic if it is possible.
This is what I have so far from stackoverflow but getting error.
Wish someone can refactor it or make it work.
SELECT *,
(
SELECT COUNT(planters.todate), SUM(planters.todate)
FROM monthlyreport
INNER JOIN planters ON monthlyreport.accountid = planters.accountid
WHERE monthlyreport.accountid = 190 AND MONTH(monthlyreport.compmonth) = MONTH(planters.todate)
GROUP BY monthlyreport.mthreportid, month(planters.todate)
) AS count_1,
(
SELECT COUNT(planters.compdate), SUM(planters.compdate)
FROM monthlyreport
INNER JOIN planters ON monthlyreport.accountid = planters.accountid
WHERE monthlyreport.accountid = 190 AND MONTH(monthlyreport.compmonth) = MONTH(planters.compdate)
GROUP BY monthlyreport.mthreportid, month(planters.compdate)
) AS count_2
Its not very clear, but as far as I can think, what you want is to get the two results in a single query result. Try joining them on the basis of accountID from both the tables.AS:
SELECT *
from
(select accountID,COUNT(planters.todate) as count2date, SUM(planters.todate) as sum2date
-----
-----) count_1
inner join
(SELECT accountID,COUNT(planters.compdate) as countcomp, SUM(planters.compdate) as sumcomp
-----
-----) count_2
using(accountID);
Do not use "AS" before count_1 or count_2. It is better to replace * in the outer select query with more specific attributes, like count_1.count2date or like.
Hope this helps ! If anything else is what you are looking for, do let me know.
-----UPDATE-----
After looking at your file you uploaded, I came up with the following query:
SELECT count1.compmonth, IFNULL( todatecount, 0 ) , IFNULL( todatesum, 0 ) , IFNULL( compdatecount, 0 ) , IFNULL( compdatesum, 0 )
FROM count_1
LEFT JOIN count_2 ON count_1.compmonth = count_2.compmonth
UNION
SELECT count2.compmonth, IFNULL( todatecount, 0 ) , IFNULL( todatesum, 0 ) , IFNULL( compdatecount, 0 ) , IFNULL( compdatesum, 0 )
FROM count_1
RIGHT JOIN count_2 ON count_1.compmonth = count_2.compmonth
You can format the 0's as per your wish. Also, if your database platform supports the "FULL OUTER JOIN", you can use that instead of making a union of left and right joins.
You will have to replace "FROM count_1" with:
FROM (select accountID,COUNT(planters.todate) as count2date, SUM(planters.todate) as sum2date
-----
-----) count_1
Similarly for FROM count_2. I know this looks like a huge query, but all this does is joins the 2 tables on common dates, and all the other fields that don't match are specified NULL.

Count occurrences of distinct values in 2 fields

I am trying to find a MySQL query that will find distinct values in a particular field, count the number of occurrences of that value in 2 fields (1_user, 2_user) and then order the results by the count.
example db
+------+-----------+-----------+
| id | 1_user | 2_user |
+------+-----------+-----------+
| 1 | 2 | 1 |
| 2 | 3 | 2 |
| 3 | 8 | 7 |
| 4 | 1 | 8 |
| 5 | 2 | 8 |
| 6 | 3 | 8 |
+------+-----------+-----------+
expected result
user count
----- -----
8 4
2 3
3 2
1 2
The Query
SELECT user, count(*) AS count
FROM
(
SELECT 1_user AS USER FROM test
UNION ALL
SELECT 2_user FROM test
) AS all_users
GROUP BY user
ORDER BY count DESC
Explanation
List all the users in the first column.
SELECT 1_user AS USER FROM test
Combine them with the users from the second column.
UNION ALL
SELECT 2_user FROM test
The trick here is the UNION ALL which preserves duplicate values.
The rest is easy -- select the results you want from the subquery:
SELECT user, count(*) AS count
aggregate by user:
GROUP BY user
and prescribe the order:
ORDER BY count DESC
SELECT u, count(u) AS cnt
FROM (
SELECT 1_user AS u FROM table
UNION ALL
SELECT 2_user AS u FROM table
) subquery
GROUP BY u
ORDER by cnt DESC
Take the 2 queries:
SELECT COUNT(*) FROM table GROUP BY 1_user
SELECT COUNT(*) FROM table GROUP BY 2_user
Now combine them:
SELECT user, SUM(count) FROM
((SELECT 1_user as user FROM table)
UNION ALL
(SELECT 2_user as user FROM table))
GROUP BY user, ORDER BY count DESC;
I think this what you are looking for since your expected result did not include 7
select usr, count(usr) cnt from
(
select user_1 usr from users
union all
select user_2 usr from users
) u
where u.usr in (select user_1 from users)
group by usr
order by count(u.usr) desc

MySQL+PHP: optimise ranking query and count subquery

This is raw data, and want to rank them according to score (count(tbl_1.id)).
[tbl_1]
===========
id | name
===========
1 | peter
2 | jane
1 | peter
2 | jane
3 | harry
3 | harry
3 | harry
3 | harry
4 | ron
So make temporary table (tbl_2) to count score for each id.
SELECT id, name, COUNT( id ) AS score
FROM tbl_1
GROUP BY id
ORDER BY score DESC;
LIMIT 0, 30;
Then result is;
[tbl_2]
===================
id | name | score
===================
3 | harry | 4
1 | peter | 2
2 | jane | 2
4 | ron | 1
Then query this;
SELECT v1.id, v1.name, v1.score, COUNT( v2.score ) AS rank
FROM votes v1
JOIN votes v2 ON v1.score < v2.score
OR (
v1.score = v2.score
AND v1.id = v2.id
)
GROUP BY v1.id, v1.score
ORDER BY v1.rank ASC, v1.id ASC
LIMIT 0, 30;
Then result is;
==========================
id | name | score | rank
==========================
3 | harry | 4 | 1
1 | peter | 2 | 2
2 | jane | 2 | 2
4 | ron | 1 | 4
Is it possible to do this in one transaction (query) nicely?
Yes, it's possible to do this in a single query. But it's a total hairball in MySQL, because MySQL doesn't have a simple ROWNUM operation, and you need one for the rank computation.
Here's your vote query with the rank shown. The #ranka variable is used to number the rows.
SELECT #ranka:=#ranka+1 AS rank, id, name, score
FROM
(
SELECT id,
name,
COUNT( id ) AS score
FROM tbl_1
GROUP BY id
ORDER BY score DESC, id
) votes,
(SELECT #ranka:=0) r
As you have discovered already, you need to self-join this thing to get a proper ranking (which handles ties correctly). So, if you take your query and replace the two references to your votes table each with their own version of this subquery, you get what you need.
SELECT v1.id,
v1.name,
v1.score,
COUNT( v2.score ) AS rank
FROM (
SELECT #ranka:=#ranka+1 AS rank,
id,
name,
score
FROM
(
SELECT id,
name,
COUNT( id ) AS score
FROM tbl_1
GROUP BY id
ORDER BY score DESC, name
) votes,
(SELECT #ranka:=0) r) v1
JOIN (
SELECT #rankb:=#rankb+1 AS rank,
id,
name,
score
FROM
(
SELECT id,
name,
COUNT( id ) AS score
FROM tbl_1
GROUP BY id
ORDER BY score DESC, name
) votes,
(SELECT #rankb:=0) r) v2
ON (v1.score < v2.score) OR
(v1.score = v2.score AND v1.id = v2.id)
GROUP BY v1.id, v1.score
ORDER BY v1.rank ASC, v1.id ASC
LIMIT 0, 30;
Told you it's a hairball. Notice that you need different #ranka and #rankb variables in the two versions of the subquery that you're self-joining, to make the row numbering work correctly: these variables have connection scope, not subquery scope, in MySQL.
http://sqlfiddle.com/#!2/c5350/1/0 shows this working.
Edit: It's far easier to do this using PostgreSQL's RANK() function.
SELECT name, votes, rank() over (ORDER BY votes)
FROM (
SELECT name, count(id) votes
FROM tab
GROUP BY name
)x
http://sqlfiddle.com/#!1/94cca/18/0

Categories