I have a database that looks like this SQL Fiddle: http://sqlfiddle.com/#!9/aa02e/1
CREATE TABLE Table1
(`Store` varchar(1), `Date` date, `Product` varchar(2), `Weekday` int, `Month` int, `Revenue` float)
;
INSERT INTO Table1
(`Store`, `Date`, `Product`, `Weekday`, `Month`, `Revenue`)
VALUES
('a', '20160101', 'aa', 5, 1, 1.5),
('a', '20160101', 'bb', 5, 1, 4),
('a', '20160101', 'cc', 5, 1, 3.5),
('a', '20160108', 'dd', 5, 1, 2.5),
('a', '20160108', 'ee', 5, 1, 5),
('b', '20160204', 'aa', 4, 2, 9.5),
('b', '20160204', 'bb', 4, 2, 4),
('b', '20160204', 'cc', 4, 2, 3),
('b', '20160211', 'dd', 4, 2, 1.5),
('b', '20160211', 'ee', 4, 2, 2.5)
;
SELECT * FROM table1;
+-------+------------+---------+---------+-------+---------+
| Store | Date | Product | Weekday | Month | Revenue |
+-------+------------+---------+---------+-------+---------+
| a | 2016-01-01 | aa | 5 | 1 | 1.5 |
| a | 2016-01-01 | bb | 5 | 1 | 4 |
| a | 2016-01-01 | cc | 5 | 1 | 3.5 |
| a | 2016-01-08 | dd | 5 | 1 | 2.5 |
| a | 2016-01-08 | ee | 5 | 1 | 5 |
| b | 2016-02-04 | aa | 4 | 2 | 9.5 |
| b | 2016-02-04 | bb | 4 | 2 | 4 |
| b | 2016-02-04 | cc | 4 | 2 | 3 |
| b | 2016-02-11 | dd | 4 | 2 | 1.5 |
| b | 2016-02-11 | ee | 4 | 2 | 2.5 |
+-------+------------+---------+---------+-------+---------+
It shows revenue data for stores incl. products, date and the respective day/month.
I want to select the following:
Store
Monthly revenue totals (i.e. what is the total revenue for store a in Jan?)
Weekday revenue averages (i.e. what is the avg revenue for store a on Thu?)
The first and second bullet are straightforward, but I'm having problems with the last one.
Currently, it takes the average over all products and all dates (assuming the weekday matches). What I need are the following steps:
Sum up all revenues for a store and a particular date (e.g. for store b: 9.5+4+3=16.5 for Feb 4th, and 1.5+2.5=4 for Feb 11th) if that date has the same weekday (here Thursday)
Take the average of the two values (e.g. avg(16.5,4)=10.25)
How can I accomplish that?
Thank you
Here is the query:
SELECT
Store,
SUM(CASE WHEN Month = 1 THEN Revenue ELSE NULL END) AS REVENUE_JAN,
SUM(CASE WHEN Month = 2 THEN Revenue ELSE NULL END) AS REVENUE_FEB,
AVG(CASE WHEN Weekday = 4 THEN Revenue ELSE NULL END) AS REVENUE_THU,
AVG(CASE WHEN Weekday = 5 THEN Revenue ELSE NULL END) AS REVENUE_FRI
FROM Table1
GROUP BY
Store
;
The weekday average is tricky. Your query is getting the average "order size" per weekday. But you want the total revenue.
One method is to first aggregate by weekday, but that is a bit of a mess. Instead, you can use this trick of calculating the average by dividing the total revenue by the number of days:
SELECT Store,
SUM(CASE WHEN Month = 1 THEN Revenue ELSE NULL END) AS REVENUE_JAN,
SUM(CASE WHEN Month = 2 THEN Revenue ELSE NULL END) AS REVENUE_FEB,
(SUM(CASE WHEN Weekday = 4 THEN Revenue END) /
COUNT(DISTINCT CASE WHEN Weekday = 4 THEN Date END)
) AS REVENUE_THU,
(SUM(CASE WHEN Weekday = 5 THEN Revenue END) /
COUNT(DISTINCT CASE WHEN Weekday = 5 THEN Date END)
) AS REVENUE_FRI
FROM Table1
GROUP BY Store;
SELECT
t1.store,
SUM(CASE WHEN Month = 1 THEN Revenue ELSE NULL END) AS REVENUE_JAN,
SUM(CASE WHEN Month = 2 THEN Revenue ELSE NULL END) AS REVENUE_FEB,
daily.REVENUE_THU,
daily.REVENUE_FRI
FROM Table1 t1
JOIN (
SELECT
Store,
weekday,
avg(CASE WHEN weekday = 4 THEN sum_rev END) as REVENUE_THU,
avg(CASE WHEN weekday = 5 THEN sum_rev END) as REVENUE_FRI
FROM (
SELECT
Store, date, weekday,
SUM(revenue) AS sum_rev
FROM Table1
GROUP BY
Store, date, weekday
) AS foo
GROUP BY Store, weekday
) AS daily ON daily.store = t1.store
GROUP BY
t1.store
How about this solution it return average for chosen day of chosen store
CREATE PROCEDURE sumForDayStore(IN vday INTEGER, IN vStore VARCHAR(50))
BEGIN
DECLARE totalDays INTEGER;
DECLARE totalRevenu INTEGER;
SET totalDays = (SELECT count(*) FROM Table1 WHERE WeekDay = vDay AND store = vStore);
SET totalRevenu = (SELECT sum(Revenue) FROM Table1 WHERE WeekDay = vDay AND store = vStore);
SELECT totalRevenu/totalDays;
END;
CALL sumForDayStore(5,'a');
How about this one:
SELECT mnth.Store, REVENUE_JAN, REVENUE_FEB, avg(rthu) REVENUE_THU, avg(rfri) REVENUE_FRI
FROM
(Select Store, sum(case when Month = 1 then Revenue else NULL END) REVENUE_JAN,
sum(case when Month = 2 then Revenue else NULL END) REVENUE_FEB
From Table1 group by Store) as mnth
join
(Select Store, sum(case when Weekday = 4 then Revenue end) rThu,
sum(case when Weekday = 5 then Revenue end) rFri from Table1 group by Store, Date) as dys
on mnth.Store = dys.Store
group by mnth.Store, REVENUE_JAN, REVENUE_FEB
I compared the performance of this with the query in the first answer and it shows better performance according to SQL server execution plan (1.6 times faster). Maybe this would be helpful on a larger data set.
Related
I have two tables
Account table
id | account_no
-----------------------
1 | 111
2 | 222
Account details
id | act_id (fk) | amount | created_dt_ | created_by
------------------------------------------------
1 | 1 | 10 | 2022-10-30 | SYSTEM
2 | 1 | 100 | 2022-11-05 | user1
3 | 1 | 144 | 2022-11-10 | user2
4 | 1 | 156 | 2022-11-16 | user3
5 | 2 | 50 | 2022-11-05 | SYSTEM
6 | 2 | 51 | 2022-11-10 | user2
7 | 3 | 156 | 2022-11-16 | SYSTEM
I need a query to fetch only rows from account details which has at least 2 records for an account id, and merge those rows to a single row showcasing the initial amount and user who created it and the last amount and who created it, something like this
act_id | ini_amt | ini_dt | ini_usr | fnl_amt | fnl_dt | fnl_usr
-------------------------------------------------------------------------------------
1 | 10 | 2022-10-30 | SYSTEM | 156 | 2022-11-16 | user3
2 | 50 | 2022-11-05 | SYSTEM | 51 | 2022-11-10 | user2
we need only the rows with more than one records. How do i fetch that?
In MySQL 8 you could do it like this.
If you need also information fom account, you simle can join it
CREATE TABLE Account
(`id` int, `account_no` int)
;
INSERT INTO Account
(`id`, `account_no`)
VALUES
(1, 111),
(2, 222)
;
Records: 2 Duplicates: 0 Warnings: 0
CREATE TABLE Account_details
(`id` int, `act_id` int, `amount` int, `created_dt_` varchar(10), `created_by` varchar(6))
;
INSERT INTO Account_details
(`id`, `act_id`, `amount`, `created_dt_`, `created_by`)
VALUES
(1, 1, 10, '2022-10-30', 'SYSTEM'),
(2, 1, 100, '2022-11-05', 'user1'),
(3, 1, 144, '2022-11-10', 'user2'),
(4, 1, 156, '2022-11-16', 'user3'),
(5, 2, 50, '2022-11-05', 'SYSTEM'),
(6, 2, 51, '2022-11-10', 'user2'),
(7, 3, 156, '2022-11-16', 'SYSTEM')
;
Records: 7 Duplicates: 0 Warnings: 0
WITH CTE_MIN as(
SELECT
`act_id`, `amount`, `created_dt_`, `created_by`,
ROW_NUMBER() OVER(PARTITION BY `act_id` ORDER BY `created_dt_` ASC,`id` ASC) rn
FROM Account_details),
CTE_MAX as(
SELECT
`act_id`, `amount`, `created_dt_`, `created_by`,
ROW_NUMBER() OVER(PARTITION BY `act_id` ORDER BY `created_dt_` DESC,`id` DESC) rn
FROM Account_details)
SELECT
mi.`act_id`, mi.`amount`, mi.`created_dt_`, mi.`created_by`, ma.`amount`, ma.`created_dt_`, ma.`created_by`
FROM
CTE_MIN mi JOIN CTE_MAX ma
ON mi.`act_id` = ma.`act_id`
AND mi.rn = ma.rn
AND mi.created_dt_!=ma.created_dt_
AND ma.rn = 1 ANd mi.rn = 1
act_id
amount
created_dt_
created_by
amount
created_dt_
created_by
1
10
2022-10-30
SYSTEM
156
2022-11-16
user3
2
50
2022-11-05
SYSTEM
51
2022-11-10
user2
fiddle
We can do this without CTEs, using window functions and conditional aggregation:
select act_id,
max(case when rn_asc = 1 then amount end) ini_amount,
max(case when rn_asc = 1 then created_dt end) ini_created_dt,
max(case when rn_asc = 1 then created_by end) ini_created_by,
max(case when rn_desc = 1 then amount end) fnl_amount,
max(case when rn_desc = 1 then created_dt end) fnl_created_dt,
max(case when rn_desc = 1 then created_by end) fnl_created_by
from(
select ad.*,
row_number() over(partition by act_id order by created_dt ) rn_asc,
row_number() over(partition by act_id order by created_dt desc) rn_desc,
count(*) over(partition by act_id) cnt
from account_details ad
) ad
where 1 in (rn_asc, rn_desc) and cnt > 1
group by act_id
In the subquery, row_number ranks records of the same account by ascending and descending date, while count checks how many records the account has.
Then, the outer query filters on accounts that have more than one record, and on the top/bottom record. We can then pivot the dataset with group by and conditional expressions to produce the expected result.
On older MySQL version which doesn't support windows functions:
select act_id,
max(case when new_col='min_value' then amount end) as ini_amt,
max(case when new_col='min_value' then created_dt end) as ini_dt,
max(case when new_col='min_value' then created_by end) as ini_usr,
max(case when new_col='max_value' then amount end) as fnl_amt,
max(case when new_col='max_value' then created_dt end) as fnl_dt,
max(case when new_col='max_value' then created_by end) as fnl_usr
from (
select ad.id,ad.act_id,ad.amount,ad.created_dt,ad.created_by,'max_value' as new_col
from AccountDetails ad
inner join (select act_id,max(created_dt) as max_created_dt
from AccountDetails
group by act_id
having count(*) >=2
) as max_val on max_val.act_id =ad.act_id and max_val.max_created_dt=ad.created_dt
union
select ad1.id,ad1.act_id,ad1.amount,ad1.created_dt,ad1.created_by,'min_value'
from AccountDetails ad1
inner join (select act_id,min(created_dt) as min_created_dt
from AccountDetails
group by act_id
having count(*) >=2
) as min_val on min_val.act_id =ad1.act_id and min_val.min_created_dt=ad1.created_dt
) as tbl
group by act_id;
https://dbfiddle.uk/q2Oxq0Ay
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 2 years ago.
Improve this question
This is a LeetCode question for interviews.
What would be the most efficient way (time/space complexity) to write this MySQL query? Does the coding style follow the MySQL variable naming conventions?
Problem
Table: Department
+---------------+---------+
| Column Name | Type |
+---------------+---------+
| id | int |
| revenue | int |
| month | varchar |
+---------------+---------+
(id, month) is the primary key of this table.
The table has information about the revenue of each department per month.
The month has values in ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"].
Write an SQL query to reformat the table such that there is a department id column and a revenue column for each month.
The query result format is in the following example:
Department table:
+------+---------+-------+
| id | revenue | month |
+------+---------+-------+
| 1 | 8000 | Jan |
| 2 | 9000 | Jan |
| 3 | 10000 | Feb |
| 1 | 7000 | Feb |
| 1 | 6000 | Mar |
+------+---------+-------+
Result table:
+------+-------------+-------------+-------------+-----+-------------+
| id | Jan_Revenue | Feb_Revenue | Mar_Revenue | ... | Dec_Revenue |
+------+-------------+-------------+-------------+-----+-------------+
| 1 | 8000 | 7000 | 6000 | ... | null |
| 2 | 9000 | null | null | ... | null |
| 3 | null | 10000 | null | ... | null |
+------+-------------+-------------+-------------+-----+-------------+
Note that the result table has 13 columns (1 for the department id + 12 for the months).
Create table If Not Exists Department (id int, revenue int, month varchar(5))
Truncate table Department
insert into Department (id, revenue, month) values ('1', '8000', 'Jan')
insert into Department (id, revenue, month) values ('2', '9000', 'Jan')
insert into Department (id, revenue, month) values ('3', '10000', 'Feb')
insert into Department (id, revenue, month) values ('1', '7000', 'Feb')
insert into Department (id, revenue, month) values ('1', '6000', 'Mar')
Attempt
SELECT id,
SUM(CASE WHEN month = 'jan' THEN revenue END) AS Jan_Revenue,
SUM(CASE WHEN month = 'feb' THEN revenue END) AS Feb_Revenue,
SUM(CASE WHEN month = 'mar' THEN revenue END) AS Mar_Revenue,
SUM(CASE WHEN month = 'apr' THEN revenue END) AS Apr_Revenue,
SUM(CASE WHEN month = 'may' THEN revenue END) AS May_Revenue,
SUM(CASE WHEN month = 'jun' THEN revenue END) AS Jun_Revenue,
SUM(CASE WHEN month = 'jul' THEN revenue END) AS Jul_Revenue,
SUM(CASE WHEN month = 'aug' THEN revenue END) AS Aug_Revenue,
SUM(CASE WHEN month = 'sep' THEN revenue END) AS Sep_Revenue,
SUM(CASE WHEN month = 'oct' THEN revenue END) AS Oct_Revenue,
SUM(CASE WHEN month = 'nov' THEN revenue END) AS Nov_Revenue,
SUM(CASE WHEN month = 'dec' THEN revenue END) AS Dec_Revenue
FROM department
GROUP BY id
ORDER BY id;
Reference
1179. Reformat Department Table
I would probably use MAX as the aggregate function, not SUM, assuming a given id and month would only have one record for revenue. That being said, if you want to use SUM, then your CASE expressions should have an else condition with zero revenue:
SELECT
id,
SUM(CASE WHEN month = 'Jan' THEN revenue ELSE 0 END) AS Jan_Revenue,
SUM(CASE WHEN month = 'Feb' THEN revenue ELSE 0 END) AS Feb_Revenue,
SUM(CASE WHEN month = 'Mar' THEN revenue ELSE 0 END) AS Mar_Revenue,
SUM(CASE WHEN month = 'Apr' THEN revenue ELSE 0 END) AS Apr_Revenue,
SUM(CASE WHEN month = 'May' THEN revenue ELSE 0 END) AS May_Revenue,
SUM(CASE WHEN month = 'Jun' THEN revenue ELSE 0 END) AS Jun_Revenue,
SUM(CASE WHEN month = 'Jul' THEN revenue ELSE 0 END) AS Jul_Revenue,
SUM(CASE WHEN month = 'Aug' THEN revenue ELSE 0 END) AS Aug_Revenue,
SUM(CASE WHEN month = 'Sep' THEN revenue ELSE 0 END) AS Sep_Revenue,
SUM(CASE WHEN month = 'Oct' THEN revenue ELSE 0 END) AS Oct_Revenue,
SUM(CASE WHEN month = 'Nov' THEN revenue ELSE 0 END) AS Nov_Revenue,
SUM(CASE WHEN month = 'Dec' THEN revenue ELSE 0 END) AS Dec_Revenue
FROM department
GROUP BY
id
ORDER BY
id;
I want to get the total maximum number of column CODE which the maximum is defined by the last five digits from mybarcode column.
mybarcode | code | judge | create_date |
-------------+------+--------+-------------+
M71X400001 | 7 | pass |
M71X400002 | 7 | pass |
M71X400005 | 7 | pass |
M71X400010 | 7 | pass |
M81X400001 | 8 | pass |
M81X400002 | 8 | pass |
M81X400007 | 8 | pass |
M91X400001 | 9 | pass |
M91X400003 | 9 | pass |
```
Example:
>The maximum value of 7 from CODE column is 10 ( from M71X4'00010')
>The maximum value of 8 from CODE column is 7 ( from M81X4'00007')
>The maximum value of 9 from CODE column is 3 ( from M91X4'00003')
The result should be 10+7+3=20.
And want display in the result table below.
```
SELECT DAY,
SUM(CASE WHEN judge = 'pass' then 1 else 0 end) pass,
SUM(CASE WHEN judge = 'fail' then 1 else 0 end) fail
**??? as number**
from MYTABLE
where MONTH(create_date) = '04' and YEAR(create_date) = '2019'
GROUP BY DAY
Result Table
day | pass | fail | number |
--------+------+--------+----------+
1 | 9 | 0 | 20 |
2 | 9 | 0 | ?? |
3 | 9 | 0 | ?? |
I think you need to do group by two times. Please try below code -
For MySQL -
SELECT
DAY,
SUM(pass),
SUM(fail),
SUM(max_barcode)
FROM (
SELECT
DAY,
SUM(CASE WHEN judge = 'pass' then 1 else 0 end) pass,
SUM(CASE WHEN judge = 'fail' then 1 else 0 end) fail,
Code,
CAST(MAX(SUBSTRING(mybarcode, 5)) AS SIGNED) AS max_barcode
FROM MYTABLE
WHERE MONTH(create_date) = '%s' and YEAR(create_date) = '%s'
GROUP BY DAY, Code
) AS CTE
GROUP BY DAY;
FOR MS SQL Server -
;WITH CTE AS (
SELECT
DAY,
SUM(CASE WHEN judge = 'pass' then 1 else 0 end) pass,
SUM(CASE WHEN judge = 'fail' then 1 else 0 end) fail,
Code,
max_barcode = cast(max(right(mybarcode, 5)) as int)
FROM MYTABLE
WHERE MONTH(create_date) = '%s' and YEAR(create_date) = '%s'
GROUP BY DAY, Code
)
SELECT
DAY,
SUM(pass),
SUM(fail),
SUM(max_barcode)
FROM CTE
GROUP BY DAY;
I am using this query for weekly reporting but can not found a way like this
week_number | week_startdate | organization_1 | organization_2
---------------------------------------------------------------
1 | 2013-01--05 |count(date) like 4,24,etc_ | count(date) like 4,24,etc_
SQL:
SELECT WEEK(signed_date) AS week_name, signed_date AS Week_Starting,
YEAR(signed_date), WEEK(signed_date), COUNT(*)
FROM business
WHERE YEAR(signed_date) = YEAR(CURDATE())
GROUP BY CONCAT(YEAR(signed_date), '/', WEEK(signed_date))
ORDER BY YEAR(signed_date), WEEK(signed_date
SAMPLE DATA:
signed_date | organization_id
01-01-2013 | 1
02-01-2013 | 1
03-01-2013 | 2
In 1 week organization_1 have 2 signed & organization_2 has 1 signed.
You should use case within count or sum:
SELECT WEEK(signed_date) AS week_name, signed_date AS Week_Starting,
YEAR(signed_date), WEEK(signed_date),
SUM(CASE WHEN organization_id=1 THEN 1 ELSE 0 END) as organization_1,
SUM(CASE WHEN organization_id=2 THEN 1 ELSE 0 END) as organization_2
FROM business
WHERE YEAR(signed_date) = YEAR(CURDATE())
GROUP BY CONCAT(YEAR(signed_date), '/', WEEK(signed_date))
ORDER BY YEAR(signed_date), WEEK(signed_date);
http://sqlfiddle.com/#!2/587ad/3
I'm doing an inner join where i select between a date range (say, BETWEEN '2011-01-01' AND '2011-02-01'), and grouping by an enumerated value. is there a way to do this for each month as a column for a range of months? I'm currently doing this by hand for each month.
Example:
vehicle_type | January | February | March
----------------------------------------------
sedan | 12 | 10 | 4
coupe | 5 | 7 | 23
truck | 0 | 0 | 9
electric | 22 | 10 | 13
hybrid | 0 | 12 | 0
You could create a calendar table...
CREATE TABLE calendar
(
description VARCHAR2(100 BYTE),
when_start DATE,
when_end DATE
)
then use a pivot query
e.g.
SELECT
vehicle_type,
SUM(jan),SUM(feb),
--add the other months here
SUM(nov),SUM(dece)
FROM
(
SELECT v.vehicle_type,
CASE WHEN c.description='Jan' THEN
count(*)
END AS jan,
case when c.description='Feb' THEN
count(*)
END AS feb,
-- Add the rest of the months here too
CASE WHEN c.description='Nov' THEN
COUNT(*)
END AS nov,
CASE WHEN c.description='Dec' THEN
COUNT(*)
END AS dece
FROM calendar c
INNER JOIN vehicles v ON v.when >= c.when_start AND v.when <= c.when_end
GROUP BY v.vehicle_type
)
GROUP BY vehicle_type
ORDER BY vehicle_type