count groupings of multiple columns - mysql

I have a table of tickets to multiple dates of shows shows. basically, it looks like this...
+----+---------------+--------------+-----------+
| ID | ticket_holder | ticket_buyer | show_date |
+----+---------------+--------------+-----------+
ticket_holder and ticket_buyer are both user ids
If I wanted to count the total number of tickets that one ticket holder has, I could group by that holder and count the rows, but I want more stats than that.
I want to know a user's total bought tickets, how many they hold and how many shows they've bought tickets for.
+------+---------+--------+-------+
| USER | HOLDING | BOUGHT | DATES |
+------+---------+--------+-------+
| 1 | 12 | 24 | 7 |
+------+---------+--------+-------+
| 2 | 3 | 4 | 2 |
+------+---------+--------+-------+
| 3 | 1 | 2 | 1 |
+------+---------+--------+-------+
is it possible to put all this in a query, or do i need to do php stuff to make it happen?

I would do it in multiple queries. You can't group by either ticket_holder or ticket_buyer like you want, in a single query. If you try GROUP BY ticket_holder, ticket_buyer then it will group by both columns, which is not what you want.
SELECT ticket_holder, COUNT(*) AS tickets_held
FROM `a table of tickets` GROUP BY ticket_holder;
SELECT ticket_buyer, COUNT(*) as tickets_bought
FROM `a table of tickets` GROUP BY ticket_buyer;
SELECT ticket_buyer, COUNT(DISTINCT show_date) AS shows_bought
FROM `a table of tickets` GROUP BY ticket_buyer;
Not every task has to be accomplished in a single query! It's part of the design of SQL that it should be used by some application language, and you're expected to handle formatting and display in the application.

Related

How to select for percentage and count calculatio from different table

Please i need help to complete the code to do the following:
calculate the percentage of voters who have voted based on batches.
get the number of voters by batch.
my table :
voters table
+-------------+-------------+----------------+
| stud_id | name | batch |
+-------------+-------------+----------------+
| 1 | Peter | 2016 |
| 2 | John | 2017 |
| 3 | Wick | 2017 |
+-------------+-------------+----------------+
vote table
+-------------+----------------+
| vote_id | stud_id |
+-------------+----------------+
| 1 | 1 |
| 2 | 2 |
+-------------+----------------+
ive tried this query:
SELECT voters.batch,COUNT(*) AS voted_batch, 100.0 * COUNT(*) / (SELECT COUNT(*) FROM vote) AS percentage
FROM vote JOIN voters WHERE voters.stud_id=vote.stud_id
GROUP BY batch asc
the code can only display the percentage of voting (not from a batch) and can only do a total of voting and I am confused to show a total of voters
and my expected table selection is:
+-------------+----------------+----------------+----------------+
| batch |total_batch | voted_batch | percentage |
+-------------+----------------+----------------+----------------+
|2016 | 1 | 1 | 100 |
|2017 | 2 | 1 | 50 |
+-------------+----------------+----------------+----------------+
Much appreciate for your support, thank you very much.
To differentiate between the total number of students in a batch, and the number of students who voted in a given batch, you need to use different fields in your COUNT expressions:
SELECT
voters.batch,
COUNT(*) AS total_batch,
COUNT(vote.vote_id) AS voted_batch,
(COUNT(vote.vote_id) * 1.0) / (COUNT(*)) AS percentage
FROM voters
LEFT JOIN vote ON
vote.stud_id = voters.stud_id
GROUP BY voters.batch
Note that:
COUNT(*) counts all students, even those who haven't voted (since we're using a LEFT JOIN).
COUNT(vote.vote_id), i.e. counting by the primary key of the table on the right side of the join, only counts students who have voted.
We have to multiple one of the COUNTs by 1.0, to coerce the result to a decimal (otherwise, you run into integer division).
Finally, you might want to consider changing your table names to be consistent: One is named voters (plural), while the other is named vote (singular). To avoid starting a religious war, I won't tell you which one to go for.

SQL: many-to-many relationship and the 'ALL' clause

I have a table products and a table locations which are linked together in a many-to-many relationship with a table products_locations. Now a client can select a set of products, and I want to run a query that selects only the locations, where ALL of the selected products are available.
This seemed pretty straight forward at first, but I see myself being quite baffled by how to achieve this. I initially thought I could get all the correct location-ids with something like
SELECT location_id
FROM products_locations
WHERE product_id = ALL [the user selected product ids]
But on second thought that does not appear to make sense either (the structure of products_locations is quite simply [product_id, location_id].
Any suggestion on how to structure such a query would be appreciated. I feel like I am overlooking something basic..
EDIT: I am using mysql syntax/dialect
Quick sample: Given the following tables
| products | | locations | | products_locations |
| id | name | | id | name | | product_id | location_id |
|------------| |-----------| |--------------------------|
| 1 | prod1 | | 1 | locA | | 1 | 2 |
| 2 | prod2 | | 2 | locB | | 2 | 1 |
| 3 | prod3 | |-----------| | 2 | 2 |
|------------| | 3 | 1 |
|--------------------------|
If a user selects products 1 and 2, the query should return only location 2. If the user selects products 2 and 3, the query should return location 1. For 1, 2, and 3, no location would be valid, and for product 2, both locations would be valid.
I figured out a query that achieves what I need. Though it is not as clean as I had hoped, it seems to be a robust approach to what I'm trying to query:
SELECT t.location_id
FROM (SELECT location_id, COUNT(*) as n_hits
FROM products_locations
WHERE product_id IN [the user selected products]
GROUP BY location_id) t
WHERE n_hits = [the number of user selected products];
Explanation:
I create a temporary table t which contains every location_id that has at least one matching product in the user's selection, together with the number of times that location matches a product in the user's selection. This is achieved by grouping the query by location_id.
I select the location_id(s) from that temporary table t, where the number of hits is equal to the number of products the user had selected. If that number is lower, I know that at least one product did not match that location.

MySQL SELECT value from a table column based on a different table and sum()

I've got 2 tables setup like so:
> events
-----------------------------------
id | event | eventHandle | points
-----------------------------------
1 | Event One | eventOne | 5
2 | Event Two | eventTwo | 10
> entries
-----------------------------------
id | user | eventHandle
-----------------------------------
1 | 1 | eventOne
2 | 1 | eventTwo
3 | 1 | eventTwo
5 | 5 | eventOne
And what I need to do is get the amount of 'points' each user has gained related to each event.
For example, user 1 has got 25 points and user 5 has 5 points.
What I can't figure out is how get the points, based one the eventHandle and sum them together.
I managed to select the different data from different tables, and do a basic sum with a different query, but not combined. Mind boggling.
Any help is mighty appreciated!
All you need to do is a simple inner join between the 2 tables on eventHandle fields and sum points by users:
select en.user, sum(ev.points)
from events ev
inner join entries en on ev.eventHandle=en.eventHandle
group by en.user

MySQL: Sum the total number of unique ids

In my database, I wish to output the total number of books with reviews after a certain date:
> SELECT book_id, AVG(score)
FROM review
WHERE review.date > "2012-07-11"
GROUP BY review.book_id ;
+---------+------------+
| book_id | AVG(score) |
+---------+------------+
| 345335 | 3.5 |
| 974147 | 3 |
| 723923 | 4 |
| 281192 | 3 |
| 384423 | 3.5 |
| 123122 | 3.5 |
| 112859 | 3 |
| 234892 | 5 |
+---------+------------+
Now, I would like to know the "total number" of books which meet this condition. That is, I need a total sum of the book_id.
However, I am not sure how to do this. How do you SELECT the SUM(book_id)?
First of all, I'm pretty sure you don't want the SUM because that would be 3,179,893. SUM means adding up all the values and totaling them.
Instead you probably want the COUNT of DISTINCT ids that match your criteria. COUNTing means "how many rows" or using your words the "total number" of entities. And DISTINCT is the keyword which only looks at unique values.
So in SQL, this would be:
select count(distinct book_id)
from review
where review.date > '2012-07-11'
Maybe using COUNT() is what you want:
https://dev.mysql.com/doc/refman/5.7/en/counting-rows.html

MySQL query to select rows from table 2 if *all* rows from table 1 are not present

I'm doing a kind of point-of-sale system whose MySQL database has (among other things) a table with items for sale, a table with sales, and a table with purchases (a purchase being my ad-hoc notation for any single item bought in a sale; if the same person buys three items at once, for example, that's one sale consisting of three purchases). All these tables have logical IDs, viz. item_id, sale_id, purchase_id, and are easily joined with simple pivotal tables.
I am now trying to add a discount feature; basically your garden-variety supermarket discount: buy these particular items and pay X instead of paying the full sum of the regular item prices. These 'package deals' have their own table and are linked to the items table with a simple pivotal table containing deal_id and item_id.
My problem is getting to the point of figuring out when this is to be applied. To give some example data:
items
+---------+--------+---------+
| item_id | title | price |
+---------+--------+---------+
| 12 | Shoe | 10 |
| 76 | Coat | 23 |
| 82 | Whip | 19 |
+---------+--------+---------+
sales
+---------+-----------+
| sale_id | timestamp |
+---------+-----------+
| 2973 | 144995839 |
| 3092 | 144996173 |
+---------+-----------+
purchases
+-------------+-------------+---------+----------+---------+
| purchase_id | no_of_items | item_id | at_price | sale_id |
+-------------+-------------+---------+----------+---------+
| 12993 | 1 | 12 | 10 | 2973 |
| 12994 | 1 | 76 | 23 | 2973 |
| 12996 | 1 | 82 | 19 | 2973 |
| 13053 | 1 | 12 | 10 | 3092 |
| 13054 | 1 | 82 | 19 | 3092 |
+-------------+-------------+---------+----------+---------+
package_deals
+---------+-------+
| deal_id | price |
+---------+-------+
| 1 | 40 |
+---------+-------+
deals_items
+---------+---------+
| deal_id | item_id |
+---------+---------+
| 1 | 12 |
| 1 | 76 |
| 1 | 82 |
+---------+---------+
As is hopefully obvious from that, we have a shoe that cost $10 (let's just assume we use dollars as our currency here, doesn't matter), a coat that costs $23, and a whip that costs $19. We also have a package deal that if you buy both a shoe, a coat, and a whip, you get the whole thing for $40 altogether.
Of the two sales given, one (2973) has purchased all three things and will get the discount, while the other (3092) has purchased only the shoe and the whip and won't get the discount.
In order to find out whether or not to apply the package-deal discount, I of course have to find out whether all the item_ids in a package deal are present in the purchases table for a given sale_id.
How do I do this?
I thought I should be able to do something like this:
SELECT deal_id, item_id, purchase_id
FROM package_deals
LEFT JOIN deals_items
USING (deal_id)
LEFT JOIN purchases
USING (item_id)
WHERE
sale_id = 2973
AND item_id IS NULL
GROUP BY deal_id
In my head, that retrieved all rows from the package_deal table where at least one of the item_ids associated with the package deal in question does not have a corresponding match in the purchases table for the sale_id given. This would then have told me which packages don't apply; i.e., it would return zero rows for purchase 2973 (since none of the items associated with package deal 1 are absent from the purchases table filtered on sale_id = 2973) and one row for 3092 (since one of the items associated with package deal one—namely the coat, item_id 76—is absent from the purchases table filtered on sale_id = 3092).
Obviously, it doesn't do what I naïvely thought it would—rather, it just always returns zero rows, no matter what.
It doesn't really matter much to me whether the resulting set gives me one row for each package deal that should apply, or one for each package deal that shouldn't apply—but how do I get it to show me either in a single query?
Is it even possible?
The problem with your query above is that sale_id is also NULL in the missing row that you're interested in, due to the LEFT JOIN.
This query will return the deal_id for any deals that DO NOT apply to a given order:
SELECT DISTINCT
pd.deal_id
FROM package_deals pd
JOIN deals_items di on pd.deal_id = di.deal_id
WHERE di.item_id NOT IN (SELECT item_id FROM purchases WHERE sale_id = 3092)
From that it's easy to work out the ones that do apply. Note that for a fully functioning system, you'd still need to take the purchase quantities into account - e.g. if the customer had bought 2 of two the items in the deal, but only 1 of the third... etc.
A SQL fiddle demonstrating the query is here: http://sqlfiddle.com/#!9/f2ae4/8
Note that I've made my joins using the ON syntax, as I'm simply more familiar than with USING. I expect that would work too if you prefer it.