SQL - Multiple grouping within report - mysql

I have a table structured similarly to this:
ID
Incident_Name
Category
Source
I need a report to show all incidents grouped by category but then the amount of incidents in that category that have a certain source value.
Category | Amount | Percentage of Total | Source_1 | Source_2 | Source 3
----------------------------------------------------------------------------
Category 1 | 5 | 25% | 1 | 3 | 2
Category 2 | 15 | 75% | 10 | 2 | 3
I'm using MySQL - how would I go about doing this.
Grouping and getting the amount/percentage is fine but not sure how I'd go about doing the rest.
SELECT Category, COUNT(*) AS Amount, (COUNT(*) / (SELECT COUNT(*) FROM MyTable)) * 100 AS 'Percentage of Total',
FROM MyTable
GROUP BY Category;
Any advice

I think this is what you are trying to do.
SELECT Category,
COUNT(*) AS Amount,
(COUNT(*) / (SELECT COUNT(*) FROM MyTable)) * 100 AS 'Percentage of Total',
SUM(source=someval1) as Source_1, --this may need a change
SUM(source=someval2) as Source_2, --this may need a change
SUM(source=someval3) as Source_3 --this may need a change
FROM MyTable
GROUP BY Category;

Related

Calculate unique items seen by users via sql

I need help to resolve the next case.
The data which users want to see is accessible by pagination requests and later these requests are stored in the database in the next form:
+----+---------+-------+--------+
| id | user id | first | amount |
+----+---------+-------+--------+
| 1 | 1 | 0 | 5 |
| 2 | 1 | 10 | 10 |
| 3 | 1 | 10 | 5 |
| 4 | 1 | 15 | 10 |
| 5 | 2 | 0 | 10 |
| 6 | 2 | 0 | 5 |
| 7 | 2 | 10 | 5 |
+----+---------+-------+--------+
The table is ordered by user id asc, first asc, amount desc.
The task is to write the SQL statement which calculate what total unique amount of data the user has seen.
For the first user total amount must be 20, since the request with id=1 returned first 5 items, with id=2 returned another 10 items. Request with id=3 returns data already 'seen' by request with id=2. Request with id=4 intersects with id=2, but still returns 5 'unseen' pieces of data.
For the second user total amount must be 15.
As a result of SQL statement, I should get the next output:
+---------+-------+
| user id | total |
+---------+-------+
| 1 | 20 |
+---------+-------+
| 2 | 15 |
+---------+-------+
I am using MySQL 5.7, so window functions are not available for me. I stuck with this task for a day already and still cannot get the desired output. If it is not possible with this setup, I will end up calculating the results in the application code. I would appreciate any suggestions or help with resolving this task, thank you!
This is a type of gaps and islands problem. In this case, use a cumulative max to determine if one request intersects with a previous request. If not, that is the beginning of an "island" of adjacent requests. A cumulative sum of the beginnings assigns an "island", then an aggregation counts each island.
So, the islands look like this:
select userid, min(first), max(first + amount) as last
from (select t.*,
sum(case when prev_last >= first then 0 else 1 end) over
(partition by userid order by first) as grp
from (select t.*,
max(first + amount) over (partition by userid order by first range between unbounded preceding and 1 preceding) as prev_last
from t
) t
) t
group by userid, grp;
You then want this summed by userid, so that is one more level of aggregation:
with islands as (
select userid, min(first) as first, max(first + amount) as last
from (select t.*,
sum(case when prev_last >= first then 0 else 1 end) over
(partition by userid order by first) as grp
from (select t.*,
max(first + amount) over (partition by userid order by first range between unbounded preceding and 1 preceding) as prev_last
from t
) t
) t
group by userid, grp
)
select userid, sum(last - first) as total
from islands
group by userid;
Here is a db<>fiddle.
This logic is similar to Gordon's, but runs on older releases of MySQL, too.
select userid
-- overall length minus gaps
,max(maxlast)-min(minfirst) + sum(gaplen) as total
from
(
select userid
,prevlast
,min(first) as minfirst -- first of group
,max(last) as maxlast -- last of group
-- if there was a gap, calculate length of gap
,min(case when prevlast < first then prevlast - first else 0 end) as gaplen
from
(
select t.*
,first + amount as last -- last value in range
,( -- maximum end of all previous rows
select max(first + amount)
from t as t2
where t2.userid = t.userid
and t2.first < t.first
) as prevlast
from t
) as dt
group by userid, prevlast
) as dt
group by userid
order by userid
See fiddle

Additional condition for equal values using MAX() function

I have a simple database for auctions. It includes a table that contains the bids.
+---------+---------+--------+------------+
| item_id | user_id | amount | time |
+---------+---------+--------+------------+
| 3 | 2 | 500 | 1540152972 |
| 3 | 4 | 500 | 1540151466 |
+---------+---------+--------+------------+
At the end of the auction I need to find which users won which items (highest amount). I've considered the following query for that
SELECT item_id, user_id, MAX(amount)
FROM auction_bids
GROUP BY item_id
Which appears to work fine, until multiple users have made a bid with the same amount.
In that case I need to retrieve the earliest one (i.e: the lowest time value).
How do I work this into my GROUP BY query?
Return a row if no other row with the same item_id has a higher price, or, if the prices are the same, the other row is later.
SELECT item_id, user_id, amount
FROM auction_bids a1
WHERE NOT EXISTS (select 1 from auction_bids a2
where a2.item_id = a1.item_id
and (a2.amount > a1.amount
or (a2.amount = a1.amount and a2.time < a1.time)))
No, this is filtering, not aggregation:
SELECT ab.*
FROM auction_bids ab
WHERE ab.amount = (SELECT MAX(ab2.amount)
FROM auction_bids ab2
WHERE ab2.item_id = ab.item_id
);

MySql value and sum(value) in same row without group

I want to get the current row quantity with COUNT(*) and the total row quantity over all rows as column in every row (needed for a report trying to avoid scripting it outside the sql).
I can't use SUM(qty) because i don't want to group my result by reasons and when i use a parameter with := i only get the total qty in the last row.
My current Query looks something like
SET #sumTotal:=0;
SELECT reason, qty, (#sumTotal := #sumTotal + qty) AS total_qty
FROM
(
SELECT reason, COUNT(*) AS qty
FROM someTable
--Imagine a huge amount of joins here
GROUP BY someTableId
)base
The table someTable looks like
----------------------
projectid | reason
----------------------
1 | reason11
1 | reason12
2 | reason21
2 | reason22
2 | reason23
3 | reason31
.
.
.
3 | reason35
----------------------
The result should look something like
----------------------------
reason | qty | totalqty
----------------------------
reason1 | 2 | 10
reason2 | 3 | 10
reason3 | 5 | 10
----------------------------
Am i maybe thinking in the wrong direction and there is a easy way to fix this?
Use auxiliary SELECT query to count the number of someTable rows.
SELECT reason, qty, total_qty
FROM
(
SELECT reason, COUNT(*) AS qty
FROM someTable
GROUP BY someTableId
),
(
SELECT count(*) AS total_qty
FROM someTable
)
This produces a Cartesian product between the derived tables of subqueries. Second subquery will consist of single row with total quantity. Thus, the total quantity will be added to the first subquery.

How to select records without duplicate on just one field and duplicate field total in SQL

I have a table with 3 columns like this:
+------------+---------------+-------+
| id | category | price |
+------------+---------------+-------+
1 | Home | 20
2 | Transport | 30
3 | General | 40
4 | General | 50
My desired result is something like this:
category price
+------------+---------------+-------+
Home 20
Transport 30
General 90
I need to have category names without any duplicate. Actually I need their id and category , What is the best SQL command to make this? I used DISTINCT in the form below but I could not achieve an appropriate result.
SELECT
MIN(id) AS id, `categories`, `expenses_price`
FROM
expensess
WHERE
SUM(expenses_price)
GROUP BY `categories`
but total not calculate
Try this to get the min id and sum of prices:
SELECT
min(id) as ID, category, SUM(price)
FROM
expenses
GROUP BY category
ORDER by ID
SQLFiddle Example
It's simple group by
SELECT
min(id) id, `categories`, SUM(price) `expenses_price`
FROM
expensess
GROUP BY `categories`

Select distinct values from query excluding sorting-purpose column

Description:
I want to select my site content's categories. Most of them will be created by users so I will ve to deal with problem of many categories in table. I want to respect some kind content's trends on my site. My solution is:
Select all categories from past 2 days and sort it by number of appearances (ascending),
Union query (distinct)
Select all categories from date < past 2 days and sort it like above.
Thanks to it I ve all most popular categories from small amount of time + most popular categories in global scope.
Query:
(SELECT category, COUNT(*) AS number FROM data WHERE date BETWEEN ADDDATE(NOW(), INTERVAL -2 DAY) AND NOW() GROUP BY category)
UNION
(SELECT category, COUNT(*) AS number FROM data WHERE date < ADDDATE(NOW(), INTERVAL -2 DAY) GROUP BY category)
ORDER BY number DESC LIMIT 50
Output:
+----------+--------+
| category | number |
+----------+--------+
| 2 | 3 |
| 4 | 3 |
| 6 | 3 |
| 5 | 2 |
| 1 | 2 |
| 2 | 1 |
+----------+--------+
6 rows in set (0.00 sec)
Note there is duplicated content in category (id 2), UNION DISTINCT (default) is not excluding this because it compares rows from both columns, so:
+----------+--------+
| category | number |
+----------+--------+
| 2 | 3 | //is not equal to
| 2 | 1 | //below values
+----------+--------+
//wont be excluded
Problem to slove:
I need to select distinct values from only category column.
(number is only for sorting purposes and used only in this query)
If I understand your question correctly, this should be the query that you need:
SELECT category
FROM (
SELECT category, COUNT(*) AS number
FROM data WHERE date BETWEEN ADDDATE(NOW(), INTERVAL -2 DAY) AND NOW()
GROUP BY category
UNION ALL
SELECT category, COUNT(*) AS number
FROM data WHERE date < ADDDATE(NOW(), INTERVAL -2 DAY)
GROUP BY category
ORDER BY number DESC
) s
GROUP BY category
ORDER BY MAX(number) DESC
LIMIT 50
I removed brackets () around your two queries that make your union query because the ORDER BY of your UNION query will be applied to both. I also used UNION ALL instead of UNION because categories are grouped again in the outer query, i would try both UNION/UNION ALL to see which one is faster.
Then I'm grouping again, by category, and ordering by the MAX(number) of your category, and keeping only the first 50 rows.