Optimally querying a database of ratings? - sql

I have a table of ratings that stores a user ID, object ID, and a score (+1 or -1). Now, when I want to display a list of objects with their total scores, the number of +1 votes, and the number of -1 votes.
How can I do this efficiently without having to do SELECT COUNT(*) FROM rating WHERE (score = +/-1 AND object_id = ..)? That's two queries per object displayed, which is unacceptable. Is the database design reasonable?

While it doesn't address your question of reasonable design, here's a query that gets you both counts at once:
select
sum(case when score = 1 then 1 else 0 end) 'positive'
, sum(case when score = -1 then 1 else 0 end) 'negative'
, objectId
from
ratings
where
objectId = #objectId ...
group by
objectId

This should do it:
SELECT
UserID, ObjectID,
SUM(CASE WHEN score=1 Then 1 Else 0 End) as UpVotes,
SUM(CASE WHEN score=-1 Then 1 Else 0 End) as DownVotes,
FROM YourTable
GROUP BY UserID, ObjectID

select object_id,
sum(case when score = 1 then 1 else 0) upvotes,
sum(case when score = -1 then -1 else 0) downvotes,
sum(score)
from ratings
group by object_id
Perhaps something like that.

Related

GROUP BY SUM CASE expression

I want to group by account number, but I am running into problems if I get multiple RATE_CD's for an account - I get a NONCOMPLIANT_CNT of 2, but I want it to be only 1 per account even if there is more than 1 RATE_CD.
Below is the SQL I'm playing around with, any ideas on how I can return the NONCOMPLIANT_CNT per account, and not roll up the count if there is more than 1 RATE_CD?
SELECT ID
,ACCOUNT_NBR SUM(CASE
WHEN GROUP_CD = 'RED'
AND TYPE_CD IN ('CHK')
THEN 1
ELSE 0
END) AS 'COMPLIANT_CNT'
,SUM(CASE
WHEN GROUP_CD = 'RED'
AND TYPE_CD IN (
'CN'
,'RN'
)
AND RATE_CD <> 'BLK'
THEN 1
ELSE 0
END) AS 'NONCOMPLIANT_CNT'
,SUM(CASE
WHEN GROUP_CD = 'RED'
AND TYPE_CD IN (
'CN'
,'RN'
,'CHK'
)
THEN 1
ELSE 0
END) AS 'TOTAL_CNT'
FROM DETAIL
LEFT OUTER JOIN RATE_LOOKUP ACCOUNT_NBR = ACCOUNT_NBR
GROUP BY ID
,ACCOUNT_NBR
,RATE_CD
If you only want 1 instead of how many actual, change your SUM() to MAX(). So if they have 5 entries, it would still show as at least 1, otherwise will be 0 for the given column aggregate.

Trying to get count of votes in SQL based on ID

Table structures:
Solution_Votes:
ID int
SolutionID string
Vote int
Solution:
ID int
Solution
VotesUp
VotesDown
Code:
SELECT
*,
(SELECT SUM(CASE WHEN voteUp = 1 THEN 1 ELSE 0 END)
FROM Solutions_Votes) AS VoteCountUp,
(SELECT SUM(CASE WHEN voteDown = 0 THEN 1 ELSE 0 END)
FROM Solutions_Votes) AS VoteCountDown
FROM
Solution
When I run this query it gives me the count on each row for voteUpCount and voteDownCount. I need the count to be based on the solution ID so that each solution has its count of up votes and down votes. If anybody can help it would be appreciated. Thanks in advance!
Just use conditional aggregation. In your case this is simple:
select sv.solutionid,
sum(case when sv.voteUp = 1 then 1 else 0 end) as VoteCountUp,
sum(case when sv.voteDown = 0 then 1 else 0 end) as VoteCountDown
from solutions_votes sv
group by sv.solutionid;
You only need the solutions table if some solutions have no votes and you want to include them.
EDIT:
You would include solutions in various way. Here is one:
select s.*, ss.VoteCountUp, ss.VoteCountDown
from solutions s left join
(select sv.solutionid,
sum(case when sv.voteUp = 1 then 1 else 0 end) as VoteCountUp,
sum(case when sv.voteDown = 0 then 1 else 0 end) as VoteCountDown
from solutions_votes sv
group by sv.solutionid
) ss
on s.solutionid = ss.solutionid;

What does a multiple count query in SQL return?

I have a product table and every product might be delivered, idle, shipping, preparing.
I want to show a list with the counts of products for each state, and I can see how to query for that here:
How to get multiple counts with one SQL query?
However, what does this query return, and how do I assign the return value to lets say, 4 integers, called deliveredCount, idleCount, shippingCount, preparingCount?
PS: For the record, I am using SQLite with OrmLite in Android with JAVA
EDIT: In this SO question people explain what Query to do when you want to get multiple counts, but they don't tell us what does that query return and in what format. For example:
SELECT a.distributor_id,
(SELECT COUNT(*) FROM myTable WHERE level='personal' and distributor_id = a.distributor_id) as PersonalCount,
(SELECT COUNT(*) FROM myTable WHERE level='exec' and distributor_id = a.distributor_id) as ExecCount,
(SELECT COUNT(*) FROM myTable WHERE distributor_id = a.distributor_id) as TotalCount
FROM myTable a ;
What is the return type of this and what is the format?
PS2: Someone was really quick to downvote my question because it lacked sufficient information. Then I edited it, but the downvote still remains :(
Hard to say for sure but sounds like you need to use a version of the top answer in the link you have provided.
Something like;
SELECT ProductID,
COUNT(*) AS Total,
SUM(CASE WHEN pStatus = 'delivered' THEN 1 ELSE 0 END) DeliveredCount,
SUM(CASE WHEN pStatus = 'idle' THEN 1 ELSE 0 END) IdleCount,
SUM(CASE WHEN pStatus = 'shipping' THEN 1 ELSE 0 END) ShippingCount,
SUM(CASE WHEN pStatus = 'preparing' THEN 1 ELSE 0 END) PreparingCount
FROM ProductTable
GROUP BY ProductID
This will return something like;
ProductID | DeliveredCount | IdleCount | ...
1 | 250 | 3250 | ...
You might want to try this.
SELECT
SUM(CASE WHEN Prod = 'delivered' THEN 1 ELSE 0 END) as deliveredCount,
SUM(CASE WHEN Prod = 'idle' THEN 1 ELSE 0 END) as idleCount,
SUM(CASE WHEN Prod = 'shipping' THEN 1 ELSE 0 END) as shippingCount,
SUM(CASE WHEN Prod = 'preparing' THEN 1 ELSE 0 END) as preparingCount
FROM Product
select
concat(state, "Count"),
count(*)
from product
group by state
Which would return 4 rows (assuming four unique values of state):
fooCount | 15
etc

Query Votes table top items based on a percentage

I have a table of Votes that looks like:
id, type, scope, region, UserId, ItemId, updatedAt, createdAt
I am trying to total the top items by percentage based on up votes / total votes.
Last piece is I want a WHERE clause for the votes for createdAt between two timestamps.
I feel like this is a very normal thing to query for getting statistics. But I am not sure how to go about this.
The closest I have gotten is:
SELECT "Votes"."ItemId", count("Votes"."ItemId") tots, count("Votes"."type" = 'up') AS yes, count("Votes"."type" = 'down') AS NO
FROM "Votes"
WHERE "Votes"."ItemId" IN (
SELECT "Votes"."ItemId" FROM "Votes"
)
GROUP BY "Votes"."ItemId"
Which is a long ways from what is need. Hence why I would love some help here. Been having a really hard time finding good sql resources on this kind of stuff.
You can use a CASE statement to use 1 for each up vote and -1 for down vote and get sum of them
SELECT ItemId, SUM(CASE [type] WHEN 'up' THEN 1 WHEN 'down' THEN -1 END)
FROM Votes
WHERE createdAt >= 'startTime'
AND createdAt <= 'endTime'
GROUP BY ItemId
You can do this with conditional aggregation:
select itemid,
sum(case when type = 'up' then 1 else 0 end) up,
sum(case when type = 'down' then 1 else 0 end) down,
sum(1) total,
100.0*sum(case when type = 'up' then 1 else 0 end)/sum(1) upperc,
100.0*sum(case when type = 'down' then 1 else 0 end)/sum(1) downperc
from votes
group by itemid
Fiddle http://sqlfiddle.com/#!15/1337d/6

efficiently compute percentages in hive or sql

SELECT
(CASE WHEN tag=FRAUD THEN 0
ELSE 1 END) fraud_tag,
COUNT(DISTINCT account_id) AS distinct_account_count
FROM fraud_tags a
GROUP BY
(CASE WHEN c.name='riskclass_NotFraud' THEN 0
ELSE 1 END)
RESULT
fraud_tag distinct_account_count
0 100
1 500
Now I want to compute fraud_percentages, number of distinct accounts with fraud_tag=0 over total number of accounts. I have to do it two steps. Any suggestions to make it more efficient?
The easiest way is to do this with the values in one row:
SELECT COUNT(DISTINCT case when tag = FRAUD then account_id end) as distinct_fraud,
COUNT(DISTINCT case when tag = FRAUD then NULL else account_id end) as distinct_notfraud,
(COUNT(DISTINCT case when tag = FRAUD then account_id end)*1.0/count(distinct account_id)
) as fraud_rate
FROM fraud_tags ft;