top contibuting users for particular category - mysql

I would like to find top contributors of particular state:
The candidates below have gathered particular votes for that state.
Find Top candidates for that states.
create table uservotes(id int, name varchar(50), vote int,state int);
INSERT INTO uservotes VALUES
(1, 'A', 34,1),
(2, 'B', 80,1),
(3, 'bA', 30,1),
(4, 'C', 8,1),
(5, 'D', 4,1),
(6, 'E', 14,2),
(7, 'F', 304,2),
(8, 'AA', 42,3),
(9, 'Ab', 6,3),
(10, 'Aa', 10,3);
States
create table states(state_id int, name_state varchar(50));
INSERT INTO states VALUES
(1, 'CA'),
(2, 'AL'),
(3, 'AZ'),
I am looking for:
for
CAL
2
1
3
4
5
based on the ranks of contribution.
How do I get that.
I really appreciate any help.
Thanks in Advance.
Code tried :
select uv.*, (#rank := #rank + 1) as rank
from uservotes uv,states s cross join
(select #rank := 0) const on uv.statesid = s.state_id
where name_state = 'CAL'
order by vote desc;

This is easy. You can use join and a group_concat():
select name_state, substring_index(group_concat(id order by votes desc), ',', 5)
from uservotes uv join
states s
on uv.state = s.state
group by name_state;
group_concat() will put all the id's in order with the highest votes first. substring_index() will extract the first five.
EDIT:
To get the top ranked users in one row, just add a where name_state = 'CA' to the above query.
To get them in different rows:
select uv.*
from uservotes uv join
states s
on uv.state = s.state
where state = 'CA'
order by votes desc
limit 5;

Related

Find the longest chain of records not containing 1 record

I have a table that contains 4 columns - UserID, FromLocation, ToLocation, and Date. I need to pull the "length" of the longest chain (UserID going from FromLocation to ToLocation, as long as the chain does not contain "FAKE_LOCATION").
So, based on the following data set:
CREATE TABLE IF NOT EXISTS `tableA` (
`UserID` int(11) unsigned NOT NULL,
`FromLocation` varchar(20) NOT NULL,
`ToLocation` varchar(20) NOT NULL,
`Date` datetime NOT NULL
) DEFAULT CHARSET=utf8;
INSERT INTO `tableA` (`UserID`, `FromLocation`, `ToLocation`, `Date`) VALUES
(1, 'Loc 1', 'Loc 2', '2022-01-01'),
(1, 'Loc 2', 'Loc 3', '2022-01-02'),
(1, 'Loc 3', 'Loc 5', '2022-01-03'),
(1, 'Loc 5', 'Loc 18', '2022-01-04'),
(1, 'Loc 18', 'Loc 2', '2022-01-05'),
(1, 'Loc 2', 'Loc 4', '2022-01-06'),
(1, 'Loc 4', 'FAKE_LOCATION', '2022-01-07'),
(1, 'FAKE_LOCATION', 'Loc 7', '2022-01-08'),
(1, 'Loc 7', 'Loc 17', '2022-01-09'),
(2, 'Loc 3', 'Loc 4', '2022-01-05'),
(2, 'Loc 4', 'Loc 5', '2022-01-06'),
(2, 'Loc 5', 'FAKE_LOCATIOIN', '2022-01-07'),
(3, 'Loc 3', 'Loc 4', '2022-01-05'),
(3, 'Loc 4', 'FAKE_LOCATIOIN', '2022-01-07'),
(3, 'FAKE_LOCATIOIN', 'Loc 3', '2022-02-07'),
(3, 'Loc 3', 'Loc 5', '2022-02-08');
I'm trying to generate the following data set:
UserID
Longest Chain
1
7
2
3
3
2
For UserID 1, the longest chain is: Loc 1 -> Loc 2 -> Loc 3 -> Loc 5 -> Loc 18 -> Loc 2 -> Loc 4.
For User ID 2, the longest chain is: Loc 3 -> Loc 4 -> Loc 5
For User ID 3, the longest chain is: Loc 3 -> Loc 4. As well as, Loc 3 -> Loc 5
I've created an SQLFiddle for it. Any help shall be appreciated!
Based on the data you provided, I'm assuming that a record that follows another for an ID will always have the same from location as the prior records to location (sorting by UserID and date).
If that's the case, this should solve your problem -
#get max chain count by UserID
SELECT
UserID,
MAX(chain_count) AS LongestChain
FROM(
#count the number of records in each chain, add one for terminal ToLocation for chain
SELECT
UserID,
chained,
COUNT(chained) + 1 AS chain_count
FROM(
#start a new chain every time FAKE_LOCATION is present for a UserID
SELECT
UserID,
FromLocation,
ToLocation,
Date,
(#row_number4:=CASE
WHEN (ToLocation = 'FAKE_LOCATION' OR FromLocation = 'FAKE_LOCATION')
THEN #chain:= #chain + 1
ELSE #chain
END) AS chained
FROM tableA as a, (SELECT #chain:=0,#row_number4:=0) as chain
ORDER BY UserID, Date) chainvalues
WHERE ToLocation != 'FAKE_LOCATION' AND FromLocation != 'FAKE_LOCATION'
GROUP BY UserID, chained) chaincounts
GROUP BY UserID
ORDER BY UserID
;
It was a bit more challenging using MySQL 5.6 than it otherwise could have been since it doesn't allow for window functions.
Here is the fiddle with the query
Here is another example that addresses instances where movement is not necessarily restricted on the next record to the from location for the prior record:
#get max chain count by UserID
SELECT
UserID,
MAX(chain_count) AS LongestChain
FROM(
#count the number of records in each chain, add one for terminal ToLocation for chain
SELECT
UserID,
chained,
COUNT(chained) + 1 AS chain_count
FROM(
#start a new chain every time FAKE_LOCATION is present for a UserID, or b.UserID is null where not the first user record
SELECT
a.UserID,
a.FromLocation,
a.ToLocation,
a.Date,
b.UserID as bUserID,
(#row_number4:=CASE
WHEN (a.ToLocation = 'FAKE_LOCATION' OR a.FromLocation = 'FAKE_LOCATION' OR b.UserID IS NULL) AND userrow != 1
THEN #chain:= #chain + 1
ELSE #chain
END) AS chained,
userrow
FROM (
#get left side of table, userrow increments starting at one for each user instance, num shifts down one to join to b sides to location to ensure date order is followed
SELECT
(#row_number3:=CASE
WHEN #user = UserID
THEN #row_number3 + 1
ELSE 1
END) AS userrow,
#user:=UserID UserID,
FromLocation,
ToLocation,
Date,
(#row_number:=#row_number + 1) - 1 AS num
FROM tableA, (SELECT #row_number:=0) AS t, (SELECT #user:=0,#row_number3:=0) as z
ORDER BY UserID, Date) a
LEFT JOIN (
#right side, effectively shifts tableA up one
SELECT
UserID,
FromLocation,
ToLocation,
Date,
(#row_number2:=#row_number2 + 1) AS num
FROM tableA, (SELECT #row_number2:=0) AS t
ORDER BY UserID, Date) b
ON a.FromLocation = b.ToLocation and a.UserID = b.UserID and a.num = b.num, (SELECT #chain:=0,#row_number4:=0) as chain
ORDER BY a.num) chainvalues
WHERE ToLocation != 'FAKE_LOCATION' AND FromLocation != 'FAKE_LOCATION'
GROUP BY UserID, chained) final
GROUP BY UserID
ORDER BY UserID
;
Here is the fiddle for that example (I added in another record for testing purposes)

Efficient way of writing subquery join statement in mysql

I have a reviews table like the one below:
A user can up vote or down vote these reviews. For which, I am maintaining another table named review_counts. It looks like the one below:
Here, 1 means up vote and -1 is down vote.
Now, I am joining these two tables such that I will get reviews with total number of up vote counts and down vote counts all together. To achieve this, I have written the below query which is working fine.
SELECT * FROM `reviews` as x
LEFT JOIN
(SELECT count(votes) as vote_up, review_id FROM `review_counts` WHERE votes = 1) as y ON x.review_id = y.review_id
LEFT JOIN
(SELECT count(votes) as vote_down, review_id FROM `review_counts` WHERE votes = -1) as z ON x.review_id = z.review_id
For which, I get the result like this:
Now, the question is that I am using two JOINS on same table to get the vote up and vote down, Is there any other way through which I can achieve similar results using single join statement?
You could do this with a single LEFT JOIN and SUM(CASE WHEN...END):
CREATE TABLE reviews(
id INT,
review_id VARCHAR(10),
review VARCHAR(10)
)
CREATE TABLE review_counts(
id INT,
review_id VARCHAR(10),
votes INT
)
INSERT INTO reviews VALUES
(1, 'review1', 'Review 1'),
(2, 'review2', 'Review 2');
INSERT INTO review_counts VALUES
(1, 'Review1', 1),
(2, 'Review1', 1),
(3, 'Review1', 1),
(4, 'Review1', 1),
(5, 'Review1', 1),
(6, 'Review2', -1),
(7, 'Review2', -1),
(8, 'Review2', -1),
(9, 'Review2', -1),
(10, 'Review2', -1);
SELECT
r.*,
SUM(CASE WHEN c.votes = 1 THEN 1 ELSE 0 END) AS Vote_Up,
SUM(CASE WHEN c.votes = -1 THEN 1 ELSE 0 END) AS Vote_Down
FROM reviews r
LEFT JOIN review_counts c
ON c.review_id = r.review_id
GROUP BY r.id, r.review_id, r.review
DROP TABLE reviews
DROP TABLE review_counts
RESULT
id review_id review Vote_Up Vote_Down
----------- ---------- ---------- ----------- -----------
1 review1 Review 1 5 0
2 review2 Review 2 0 5

how does count work in sql query

I'm doing a exercise about SQL query and I came up with an error when it comes to SUM. I know what is does, but I somehow get an error...
select
client.LName, client.FName,
COUNT(b.Total)
from
ClientBank client
INNER JOIN
Bank b ON (client.ClientID = b.ClientID)
where
client.LName = 'name' AND client.FName = 'a';
But then I get this error...
Column 'ClientBank.LName' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
I have tried to do a Group BY, don't really know how to use it well.. Any help would be great!
You have not given the structure of the ClientBank and Bank tables. I am taking that you inserted records something like this:
INSERT INTO `ClientBank`
(`ClientID`, `FName`, `Lname`)
VALUES
(1, 'Abraham', 'Backman'),
(2, 'Carl', 'Dacosta'),
(3, 'Erwin', 'Fabio'),
(4, 'Gabriel', 'Haddon');
INSERT INTO `Bank`
(`DepositID`, `ClientID`, `Deposit`)
VALUES
(1, 1, 100),
(2, 2, 200),
(3, 3, 300),
(4, 4, 400),
(5, 2, 500),
(6, 3, 600);
You can get the total of deposit for each client with this:
SELECT
client.LName, client.FName, SUM(b.Deposit) as Total
FROM
ClientBank client
INNER JOIN
Bank b ON (client.ClientID = b.ClientID)
GROUP By
client.LName, client.FName;
If you want only for a particular client (filtering using their name than Client Id):
SELECT
client.LName, client.FName, SUM(b.Deposit) as Total
FROM
ClientBank client
INNER JOIN
Bank b ON (client.ClientID = b.ClientID)
WHERE
client.LName = 'Fabio' AND client.FName = 'Erwin'
GROUP By
client.LName, client.FName;

Get reply numbers in mysql

I am working on a product review page where it will display several submitted reviews as well as the number of comments to each of them.
I thought I could use
SELECT title AS review_title,COUNT(id_group) AS Approved_reply_number
WHERE approved <> '0'
GROUP BY id_group`
but read somewhere that it isn't possible to copy the id values into another row on the insert process. So if someone submits a review, the id_group field for the reviews has to be left empty.
Here is the table example:
CREATE TABLE product_review
(`ID` int, `title` varchar(21), `id_group` int,`approved` int)
;
INSERT INTO product_review
(`ID`, `title`, `id_group`,`approved`)
VALUES
(1, 'AAA', Null,1),
(2, 'BBB', 1,1),
(3, 'CCC', Null,1),
(4, 'DDD', 3,0),
(5, 'EEE', 1,1),
(6, 'FFF', Null,1),
(7, 'GGG', 6,1),
(8, 'HHH',1,1),
(9, 'III', 6,1)
;
Those that are Null in id_group are the submitted reviews. The rest are replies and they contain the id of their corresponding reviews. I was wondering how can I get an output like this:
review_title approved_reply_number
AAA 3
CCC 0
FFF 2
You can use a self join and count query with group by and also a where clause to filter out reviews only
select t.title review_title ,count(*) approved_reply_number
from product_review t
left join product_review t1 on(t.id = t1.id_group)
where t.id_group is null
group by t.id
Demo

MySQL JOIN returning unrelated rows when combined with LEFT JOIN, WHERE and OR

I have the following table structure. The idea is that users have permissions to a forum either by their class or specific user overrides. ('action' in both cases is an enum with values 'read' & 'write')
user (id, class)
forum (id, name)
forum_permissions (forum_id, class_id, action)
forum_user_permissions (forum_id, user_id, action)
With the following query, I'm getting extra results based on rows in forum_permissions that I don't expect. By this I mean that every row on forum_permissions with forum_id = 3 is returned even though the class_id does not match.
SELECT forum.id AS forum_id, forum.name
FROM forum
JOIN forum_permissions ON forum_permissions.forum_id = forum.id
LEFT JOIN forum_user_permissions ON (
forum_user_permissions.forum_id = forum.id AND forum_user_permissions.user_id = 3 )
WHERE (( forum_permissions.class_id = 1 AND forum_permissions.action = 'read' )
OR
( forum_user_permissions.action = 'read' ))
e.g. I get this:
FORUM_ID NAME
1 chat
2 support
3 secret
3 secret
3 secret
3 secret
but expected this:
FORUM_ID NAME
1 chat
2 support
3 secret
I have made an SQL Fiddle with the specific example including data http://sqlfiddle.com/#!2/75c3a/5/0
Your left join is adding those extra lines. mybe if you change the WHERE
WHERE forum_user_permissions.user_id is not null and (
(forum_permissions.class_id = 1 AND forum_permissions.action = 'read')
OR
(forum_user_permissions.action = 'read')
)
Or
SELECT
forum.id AS forum_id, forum.name
FROM
forum
JOIN
forum_permissions
ON
forum_permissions.forum_id = forum.id
LEFT JOIN
forum_user_permissions
ON (
forum_user_permissions.forum_id = forum.id
)
WHERE forum_user_permissions.user_id = 3 and (
(forum_permissions.class_id = 1 AND forum_permissions.action = 'read')
OR
(forum_user_permissions.action = 'read')
)
But it depends on the results your are trying to get
Okay, I solved it myself by using LEFT JOIN's for both permission tables, rather than put the logic in a WHERE clause. I'm not very sure if this is a better approach than my first attempt and will gladly upvote if someone can explain.
SELECT forum.id AS forum_id, forum.name
FROM forum
LEFT JOIN forum_permissions
ON ( forum_permissions.forum_id = forum.id
AND forum_permissions.class_id = 1
AND forum_permissions.action = 'read' )
LEFT JOIN forum_user_permissions
ON ( forum_user_permissions.forum_id = forum.id
AND forum_user_permissions.user_id = 3
AND forum_user_permissions.action = 'read' )
WHERE forum_permissions.forum_id IS NOT null OR forum_user_permissions.forum_id IS NOT null
The full dataset is included below as I guess the fiddle will expire at some point.
INSERT INTO user
(`id`, `class`)
VALUES
(1, 1), (2, 1), (3, 1), (4, 2);
INSERT INTO forum
(`id`, `name`)
VALUES
(1, 'chat'), (2, 'support'), (3, 'secret');
INSERT INTO forum_permissions
(`forum_id`, `class_id`, `action`)
VALUES
(1, 1, 'read'), (1, 1, 'write'),
(1, 2, 'read'), (1, 2, 'write'),
(2, 1, 'read'), (2, 1, 'write'),
(2, 2, 'read'), (2, 2, 'write'),
(3, 2, 'read'), (3, 2, 'write'),
(3, 3, 'read'), (3, 3, 'write');
INSERT INTO forum_user_permissions
(`forum_id`, `user_id`, `action`)
VALUES
(3, 3, 'read'), (3, 3, 'write');