MySQL Group By Counting for Two fields - mysql

mysql> SELECT date_format(FROM_UNIXTIME(timestamp), '%M') AS month,
YEAR(FROM_UNIXTIME(timestamp)) as year, left(content_value, 12) AS status,
count(*) AS count FROM gg_groom_content
WHERE content_value LIKE '%created ofi%' OR
content_value LIKE '%ofi rejected%'
GROUP BY MONTH(from_unixtime(timestamp)), YEAR(from_unixtime(timestamp));
Result:
+-----------+------+--------------+-------+
| month | year | status | count |
+-----------+------+--------------+-------+
| January | 2014 | OFI Rejected | 861 |
| February | 2014 | Created OFI: | 777 |
| March | 2014 | Created OFI: | 537 |
| April | 2014 | OFI Rejected | 285 |
| May | 2014 | OFI Rejected | 198 |
| September | 2011 | (06:32:40 PM | 1 |
| November | 2013 | Created OFI: | 86 |
| December | 2013 | Created OFI: | 561 |
+-----------+------+--------------+-------+
8 rows in set (0.91 sec)
However I am trying to have each Status for each month:
For example:
May should have a total count of OFI Rejected and a total count of Created OFI. How can I accomplish this?

There may be a better solution but you can try making 2 separate queries using UNION ALL like below
SELECT date_format(FROM_UNIXTIME(timestamp), '%M') AS month,
YEAR(FROM_UNIXTIME(timestamp)) as year, left(content_value, 12) AS status,
count(*) AS count FROM gg_groom_content
WHERE content_value LIKE '%created ofi%'
GROUP BY MONTH(from_unixtime(timestamp)), YEAR(from_unixtime(timestamp));
UNION ALL
SELECT date_format(FROM_UNIXTIME(timestamp), '%M') AS month,
YEAR(FROM_UNIXTIME(timestamp)) as year, left(content_value, 12) AS status,
count(*) AS count FROM gg_groom_content
WHERE content_value LIKE '%ofi rejected%'
GROUP BY MONTH(from_unixtime(timestamp)), YEAR(from_unixtime(timestamp));

I think what you are looking for is conditional aggregation:
SELECT date_format(FROM_UNIXTIME(timestamp), '%M') AS month,
YEAR(FROM_UNIXTIME(timestamp)) as year, left(content_value, 12) AS status,
sum(content_value LIKE '%created ofi%') as CreatedOFI,
sum(content_value LIKE '%ofi rejected%') as RejectedOFI
FROM gg_groom_content
WHERE content_value LIKE '%created ofi%' OR
content_value LIKE '%ofi rejected%'
GROUP BY MONTH(from_unixtime(timestamp)), YEAR(from_unixtime(timestamp))
ORDER BY MIN(timestamp);

Related

MySQL : calculate total and percentage compared to previous year

I have this table structure
+------------+--------+
| date | amount |
+------------+--------+
| 2020-05-01 | 10 |
| 2020-10-01 | 15 |
| 2021-03-01 | 9 |
| 2021-09-01 | 10 |
| 2021-12-01 | 15 |
| 2022-06-01 | 19 |
| 2022-08-01 | 25 |
| 2022-09-01 | 13 |
+---------------------+
I would like to calculate the total for each year, and also the percentage from the previous year
SELECT YEAR(p.date) AS year, SUM(p.amount) AS year_total, SUM(p1.amount) AS prev_year, ROUND(SUM(p1.amount)/SUM(p.amount)*100) AS percentage
FROM payments p
LEFT JOIN payments p1
ON YEAR(p1.date) = YEAR(p.date)-1
GROUP BY year
ORDER BY year DESC;
But with this query the result goes crazy... The totals are not correct except for the first year.
+------+------------+-----------+------------+
| year | year_total | prev_year | percentage |
+------+------------+-----------+------------+
| 2022 | 171 | 102 | 60 |
| 2021 | 68 | 75 | 110 |
| 2020 | 25 | NULL | NULL |
+------+------------+-----------+------------+
I guess I have a group by issue but I can't find a solution.
Edit: forgot to mention I am using MariaDB 10
Your query joins each record of one year with each record of the previous year. Afterwards the sum grouped by year is taken. The simplest solution is to use a window function like LAG as Tim Biegeleisen did. If yoh have to use an older version of MySQL, you'll have to calculate the sums before joining.
Sketch:
SELECT c.year, c.total, p.total
FROM (SELECT year(date) AS year, sum(amount) AS total FROM payments GROUP BY year(date)) c
LEFT JOIN (SELECT year(date) AS year, sum(amount) AS total FROM payments GROUP BY year(date)) p
ON p.year = c.year - 1
On MySQL 8+, we can make use of LAG() here:
SELECT YEAR(date) AS year, SUM(amount) AS year_total,
LAG(SUM(amount)) OVER (ORDER BY YEAR(date)) AS prev_year,
100.0 * LAG(SUM(amount)) OVER (ORDER BY YEAR(date)) / SUM(amount) AS percentage
FROM payments
GROUP BY YEAR(date)
ORDER BY YEAR(date) DESC;

Group Rows in group by though it contains NULL value in mysql / postgres

I have a table from where I am getting month names and some quantity measures.
Table Name = Month_Name
SELECT month_name,q1,q2 FROM month_name;
mysql> SELECT * FROM MONTH;
+------------+------+------+
| month_name | q1 | q2 |
+------------+------+------+
| January | 10 | 20 |
| March | 30 | 40 |
| March | 10 | 5 |
+------------+------+------+
Expected Output:
mysql> SELECT month_name ,SUM(q1),SUM(q2) FROM MONTH GROUP BY month_name;
+------------+---------+---------+
| month_name | sum(q1) | sum(q2) |
+------------+---------+---------+
| January | 10 | 20 |
| Febuary | 0 | 0 |
| March | 40 | 45 |
| April | 0 | 0 |
+------------+---------+---------+
Group by month will not print February and April since these 2 months are not present in base table. I do not want to use Union All since there will be performance issues with union All, Is there any other optimised approach to this.
You can use a calendar table which keeps track of all the month names which you want to appear in your report.
SELECT
m1.month_name,
SUM(q1) AS q1_sum,
SUM(q2) AS q2_sum
FROM
(
SELECT 'January' AS month_name UNION ALL
SELECT 'February' UNION ALL
SELECT 'March' UNION ALL
...
SELECT 'December'
) m1
LEFT JOIN month m2
ON m1.month_name = m2.month_name
GROUP BY
m1.month_name;
Note that while this solve your immediate problem, it is still not ideal, because we don't have any easy way to sort the months. A much better table design would be to maintain a date column. The month name is easily derived from the date.

Is there a way to count occurrence of values of multiple column in SQL?

Let say I have a table storing survey result, and the syntax looks something like this:
id | q1 | ..... | q30 | created_at
created_at is a timestamp column and all others are integers fields.
Now I want to have a result of the survey according to month. To do that for one question, I have:
SELECT YEAR(created_at) as year, MONTH(created_at) as month, q1, count(*) as occurrence
FROM survey_table
GROUP BY YEAR(created_at), MONTH(created_at), q1
The return will be something like:
year | month| q1 | occurence
2016 | 11 | 1 | 10
2016 | 11 | 2 | 15
2016 | 11 | 3 | 2
2016 | 10 | 1 | 12
2016 | 10 | 2 | 2
2016 | 10 | 3 | 50
The data will be passed to my PHP script for further calculation and finally some data-display.
To do calculation on 30 columns, one way is to perform this query 30 times for different question. I am wondering if there is a way to do that in single query so that the output will be something like this:
year | month| q1_1 | q1_2 | q1_3 | q2_1 | q2_2 | q2_3 | ... | q30_1 | q30_2 | q30_3
2016 | 11 | 10 | 15 | 2 | 2 | 20 | 5 | ... | 5 | 15 | 7
2016 | 10 | 12 | 2 | 50 | 25 | 27 | 12 | ... | 20 | 24 | 20
Is there a way to do this in one query? If yes, is this performance better?
This is how your query would look:
select
year(created_at) as year,
month(created_at) as month,
count(q1 = 1) as q1_1,
count(q1 = 1) as q1_2,
count(q1 = 1) as q1_3,
count(q1 = 2) as q2_1,
...
count(q30 = 3) as q30_3
from survey_table
group by year(created_at), month(created_at);
It seems, however, it would be much better to change your table design:
q_type | q_value |created_at
-------+---------+----------
1 | 1 | 2016-10-05
2 | 3 | 2016-10-05
3 | 1 | 2016-10-05
4 | 2 | 2016-10-05
...
30 | 1 | 2016-10-05
...
29 | 1 | 2016-10-08
30 | 2 | 2016-10-08
And your query would simply be:
select
year(created_at) as year,
month(created_at) as month,
q_type,
q_value,
count(*)
from survey_table
group by year(created_at), month(created_at), q_type, q_value;
You'd do the formatting, i.e. putting the data in a grid, in PHP. This is more flexible, as your query doesn't have to know any longer how many q types and how many q values exist.
Here is the UNION ALL query I mentioned in a comment to my other answer. You don't have to know what q values exist when writing the query, and due to UNION ALL it's just one query that gets executed (so as to avoid unnecessary round trips).
It is still not a speeding fast query due to the current table design.
select year(created_at) as year, month(created_at) as month, 'Q1', q1, count(*) as cnt
from survey_table
group by year(created_at), month(created_at), q1
UNION ALL
select year(created_at) as year, month(created_at) as month, 'Q2', q2, count(*) as cnt
from survey_table
group by year(created_at), month(created_at), q2
UNION ALL
...
UNION ALL
select year(created_at) as year, month(created_at) as month, 'Q30', q30, count(*) as cnt
from survey_table
group by year(created_at), month(created_at), q30;

sql query to count twitter comments by month in 2016

I want to list the number of Tweets made according to month in 2016. I am new to SQL but have tried different ways to do this. Below is my latest attempt. I keep getting a message that I am not using datelogged properly. Lastly, I am not sure of how to format Total and Tweet_Cnt.
The format of the date in Twitter is as follows: MESSAGE_POSTED_TIME: 2015-08-06 21:48:34. FYI---- Column Name=MESSAGE_POSTED_TIME; Table Name=DTrumpCampaign_Tweets
Select
Year(DATELOGGED),
Sum(Case When Month(DATELOGGED) = 1 Then 1 Else 0 End) Jan,
Sum(Case When Month(DATELOGGED) = 2 Then 1 Else 0 End) Feb,
Sum(Case When Month(DATELOGGED) = 3 Then 1 Else 0 End) Mar
From
DTrumpCampaign_Tweets
Group By
Year(DATELOGGED);
I would like for my table's format to look like this
Month(2016) Tweet_Cnt
Jan 25
Feb 100
Mar 200
total 325
I greatly appreciate your help.
Thanks.
Assuming that you only want data of year 2016 only.
SELECT
(CASE WHEN t.`month` IS NULL THEN 'total'ELSE t.monthName END) AS 'Month(2016)',
t.Tweet_Cnt
FROM
(
SELECT
MONTHNAME(DATELOGGED) AS monthName,
YEAR (DATELOGGED) `year`,
MONTH (DATELOGGED) `month`,
COUNT(*) Tweet_Cnt
FROM DTrumpCampaign_Tweets
WHERE YEAR (DATELOGGED) = '2016'
GROUP BY `year`,`month` WITH ROLLUP
LIMIT 13
) t;
Demo with some sample data
You will get an output structure like below:
| Month(2016) | Tweet_Cnt |
|-------------|-----------|
| January | 1 |
| February | 2 |
| March | 1 |
| April | 1 |
| May | 1 |
| June | 1 |
| July | 1 |
| August | 1 |
| September | 1 |
| October | 1 |
| November | 1 |
| December | 1 |
| total | 13 |
More:
If you want the month names having only first three letters only then just change the corresponding line in the above query as below:
Change this line : SELECT MONTHNAME(DATELOGGED) AS monthName,
To this: SELECT DATE_FORMAT(DATELOGGED,"%b") AS monthName,
Demo of this modified query
Here is a solution, with a sqlfiddle demo:
http://sqlfiddle.com/#!9/786acb/5
SELECT
COALESCE(`Month(2016)`, 'Total') AS `Month(2016)`, Tweet_cnt
FROM
(
Select
DATE_FORMAT(datelogged, '%b') AS `Month(2016)`,
COUNT(*) AS Tweet_cnt
From dtrumpcampaign_tweets
WHERE YEAR(datelogged) = '2016'
Group BY `Month(2016)` WITH ROLLUP
) t;
The output is like:
+-------------+-----------+
| Month(2016) | Tweet_cnt |
+-------------+-----------+
| Feb | 1 |
| Jan | 2 |
| Mar | 3 |
| Total | 6 |
+-------------+-----------+
4 rows in set (0.00 sec)

simple unpivot on 2 rows table

I have table that contains data of query, sample data looks like this:
| YEAR | JAN | FEB | ... | DEC |
|------|-----|-----|-----|-----|
| 2013 | 90 | 40 | ... | 50 |
| 2014 | 30 | 20 | ... | 40 |
I'm trying to unpivot this table to have data like this:
| MONTH | 2013 | 2014 |
|-------|------|------|
| JAN | 90 | 30 |
| FEB | 40 | 20 |
| ... | ... | ... |
| DEC | 50 | 40 |
I've tried this:
select Month, 2013, 2014
from Data
unpivot
(
marks
for Month in (Jan, Feb, Mar, Apr)
) u
but all I get are months and years. Here is my sqlfiddle
I will always have 12 months, but I can have multiple data rows.
Can this be done without dynamic sql?
You're not going to get what you want with a single unpivot statement. Start with this unpivot:
with cte(Year, Month, Orders)
as
(
select Year, Month, Orders
from Data d
unpivot
(
orders
for Month in (Jan, Feb, Mar, Apr)
) u
)
I'm going to use those results in the next part, so I store it as a CTE. This query gives you results like this:
| YEAR | MONTH | ORDERS |
|------|-------|--------|
| 2013 | JAN | 90 |
| 2013 | FEB | 40 |
| 2013 | MAR | 30 |
etc...
I don't know what the numbers in your table represent, but I just called them orders. You can rename that column to whatever is appropriate. The next step is to pivot those results so that we can get the year displayed as columns:
select Month, [2013], [2014]
from cte
pivot
(
sum(orders)
for year in ([2013], [2014])
) p
order by datepart(mm, Month+'1900')
If you need to add more years, it should be obvious where to do that. Note the clever order by that sorts the months chronologically instead of alphabetically.
Here's a SQL Fiddle.