Stored procedure calculate taxable amount - mysql

I have been working with calculating annual taxable amount of a staff over changing salary rates.
salary_assigned_date | salary
-------------------------------
2011-12-06 5000
2012-01-05 10000
2012-02-10 15000
2012-04-08 20000
2012-08-01 28000
Now, my taxable amount for year 2012 in terms of months should be like this:
I have assumed no. of days in a month as 30.
month | taxable_amount
-----------------------------------------------
01 833.33 + 8333.33 /* Since salary has been changed
at 6th of month,
for 5 days,
taxable amount = 5000/30*5
=> 833.33
and for remaining 25 days
= 10000/30*25=> 8333.33
and same case for remaining months.*/
02 3000 + 10500
03 15000
04 4666.67 + 15333.33
05 20000
06 20000
07 20000
08 933.33 + 27066.67
09 28000
10 28000
11 28000
12 28000
I tried to write a stored procedure in order to calculate the taxable amount but I could not accomplish this.
Can someone help on this ?

you need a sql statement that joins a record in the table to the record in the table that has the next salary value... you also need to use a CTE (or whatever **MySQL equivalent* exists ) to generate all the months where no salary change occurs. * [Thanks to #Neville's comment]
Excuse the SQL server syntax, I am not going to look up the MySQL equivalents for you... the intent should be clear. I know MySQL has it's own functions equivalent to SQL servers' date functions getdate(), DateDiff(), DateAdd(), and Day().
With Dates(dt) As
( Select min(salary_assigned_date)
From yourTable
Union All
Select DateAdd(month,1, dt)
from dates
where dt < getdate()) -- replace getdate() with parameter for max date to calculate
-- If MySQL has no equivalent to CTE, you need to generate a temp table with
-- these dates in it and use that instead of the [Dates] construction
Select t.dt, t.salary/30.0 * (day(t.dt)-1) +
+ n.salary/30.0 * (31 - day(t.dt))
From Dates d
join yourTable t On t.salary_assigned_date =
(Select Min(salary_assigned_date)
From test where salary_assigned_date >= d.dt)
join yourTable n On n.salary_assigned_date =
(Select Min(salary_assigned_date)
From test where salary_assigned_date > d.dt)
Select t.salary/30.0 * (day(t.salary_assigned_date)-1) +
+ n.salary/30.0 * (31 - day(t.salary_assigned_date))
From table t
join table n On n.salary_assigned_date =
(Select Min(salary_assigned_date) From table
Where salary_assigned_date > t.salary_assigned_date)

Related

group by year on multiple date columns mysql

I have table as following:
hours | ... | task_assigned | task_deadline | task_completion
----------------------------------------------------------------
123 | ... | 2019-08-01 | - | -
234 | ... | - | 2018-08-01 | 2019-08-01
145 | ... | 2017-08-01 | 2017-08-01 | 2018-01-01
I want to calculate total hours for each year, i.e. grouping by year.
Currently I'm only taking into account task_completion field.
If there's no value in task_completion field, the record is not included in SUM calculation.
To elaborate further, say for year 2019, row 1 and 1 both should be considered. Hence the total hours should be 123 + 234 = 357.
And for year 2018, row 2 and 3.
Similarly, for year 2017, row 3.
SELECT YEAR(task_completion) as year, ROUND(SUM(total_hours), 2) as hours
FROM task
GROUP BY year
HAVING year BETWEEN '$year_from' AND '$year_to'
The resultset:
year | hours
--------------------
2017 | <somevalue>
2018 | <somevalue>
2019 | <somevalue>
How can I include other two date fields too?
You want to consider each row once for each of its years. Use UNION to get these years:
select year, round(sum(total_hours), 2) as hours
from
(
select year(task_assigned) as year, total_hours from task
union
select year(task_deadline) as year, total_hours from task
union
select year(task_completion) as year, total_hours from task
) years_and_hours
group by year
having year between $year_from and $year_to
order by year;
If you want to consider a row with one year twice or thrice also as often in the sum, then change UNION to UNION ALL.
Basically, you want to unpivot the data. I will assume that the - represents a NULL value and your dates are real dates.
select year(dte) as year, sum(total_hours) as hours
from ((select task_assigned as dte, total_hours
from task
) union all
(select task_deadline, total_hours
from task
) union all
(select task_completion, total_hours
from task
)
) d
where dte is not null
group by year(dte)
order by year(dte);
Based on your sample data, the round() is not necessary so I removed it.
If you want to filter for particular years, the filtering should be in a where clause -- so it filters the data before aggregation.
Change the where to:
where year(dte) >= ? and year(dte) <= ?
or:
where dte >= ? and dte <= ?
to pass in the dates.
The ? are for parameter placeholders. Learn how to use parameters rather than munging query strings.
This answer is no langer valid with the updated request.
If I understand correctly, you want to use task_assigned if the task_completion is still null. Use COALEASCE for this.
SELECT
YEAR(COALESCE(task_completion, task_assigned)) as year,
ROUND(SUM(total_hours), 2) as hours
FROM task
GROUP BY year
HAVING year BETWEEN $year_from AND $year_to
ORDER BY year;
(I don't think you actually want to use task_deadline, too, for how could a task get completed before getting assigned first? If such can occur, then include it in the COALESCE expression. Probably: COALESCE(task_completion, task_assigned, task_deadline)` then.)

How to pass parameters in subquery mysql?

So, I have a mysql table with user id(id) and date of transaction(dot) that looks like:
id dot
-------------------------------
101 2015-06-12 12:18:42 UTC
102 2015-06-12 12:18:40 UTC
103 2015-06-12 12:18:42 UTC
101 2015-07-12 12:18:42 UTC
and so on.
(Output for this data should be:
Year Month Num of users
-----------------------------
2015 06 0
2015 07 2
)
It logs all the transactions that are made. For each month m, I want to find out the count of users by month and year who transacted in m-1 month but not in m month. The results need to be grouped by year and month. Ideally, table should look like (http://sqlfiddle.com/#!9/b80f49/1)
Year Month Num of users
-----------------------------
2015 05 0
2015 06 2
2015 07 1
2015 08 4
Now for a single month(E.g. 05/2015), I can hardcode:
SELECT "2015" AS Year,"05" AS Month, "COUNT(DISTINCT id) FROM table WHERE
MONTH(dot)=4 AND YEAR(dot)=2015
AND id NOT IN
(SELECT id FROM table WHERE MONTH(dot)=5 AND YEAR(dot)=2015)
To group the count of users using GROUP BY, the query would look like:
SELECT YEAR(dot) as Year,MONTH(dot),COUNT(DISTINCT id) as Month FROM table
WHERE id NOT IN(SELECT id FROM table
WHERE DATEDIFF(dot_parent,dot_this_table)<30 AND DATEDIFF(dot_parent,dot_this_table)>=0)
Here dot_parent is the dot of the parent query and dot_this_table is the dot of the subquery. Now the problem here is that I can't pass the dot_parent inside the subquery. Is there a way to do that or frame the query in another way such that its logical structure remains similar, since I would have to make similar queries for multiple date ranges.
You must query the same table thrice: once for the months to show, once to find the users in the previous months, once for user matches in the months in question. You'd select distinct users per month, as you are not interested in whether a user had more than one transaction in a month or not.
Here is the complete query:
select
this_month.year,
this_month.month,
count(prev_month_users.user) - count(this_month_users.user) as users
from
(
select distinct year(timing) as year, month(timing) as month
from transactions
) this_month
left join
(
select distinct
year(timing) as year, month(timing) as month, id as user,
year(date_add(timing, interval 1 month)) as next_month_year,
month(date_add(timing, interval 1 month)) as next_month_month
from transactions
) prev_month_users
on prev_month_users.next_month_year = this_month.year
and prev_month_users.next_month_month = this_month.month
left join
(
select distinct year(timing) as year, month(timing) as month, id as user
from transactions
) this_month_users
on this_month_users.user = prev_month_users.user
and this_month_users.year = prev_month_users.next_month_year
and this_month_users.month = prev_month_users.next_month_month
group by this_month.year, this_month.month;
Result:
year month users
2015 5 0
2015 6 2
2015 7 1
2015 8 3
Note that I show three users for August (users 101, 102, 104). User 101 had two transactions in July, but it is still three users who had transactions in July but not in August.
Here is your SQL fiddle back: http://sqlfiddle.com/#!9/b80f49/13

Check if instances have occurred minimum once, every year in a specific range

In MySQL I'm tasked with a big dataset, with data from 1970 to 2010.
I want to check for consistency: check if each instance occurs minimum one time per year. I took a snippet from 1970-1972 as example to demonstrate my problem.
input:
id year counts
-- ---- ---------
1 1970 1
1 1971 1
2 1970 3
2 1971 8
2 1972 1
3 1970 4
expected:
id 1970-1972
-- ----------
1 no
2 yes
3 no
I though about counting within the date range and then taking those out who had 3 counts: 1970, 1971, 1972. The following query doesn't force the check on each point in the range though.
select id, count(*)
from table1
WHERE (year BETWEEN '1970' AND '1972') AND `no_counts` >= 1
group by id
What to do?
You can use GROUP BY with CASE / inline if.
Using CASE. SQL Fiddle
select id,CASE WHEN COUNT(distinct year) = 3 THEN 'yes'ELSE 'No' END "1970-72"
from abc
WHERE year between 1970 and 1972
GROUP BY id
Using inline IF. SQL Fiddle
select id,IF( COUNT(distinct year) = 3,'yes','No') "1970-72"
from abc
WHERE year between 1970 and 1972
GROUP BY id
You can use a having clause with distinct count:
select `id`
from `table1`
where `year` between '1970' and '1972'
group by id
having count(distinct `year`) = 3
Do you expect this?
select id, count(*)
from table1
WHERE (year BETWEEN '1970' AND '1972')
group by id
having count(distinct year) = 3

Given a table with time periods, query for a list of sum per day

Let's say I have a table that says how many items of something are valid between two dates.
Additionally, there may be multiple such periods.
For example, given a table:
itemtype | count | start | end
A | 10 | 2014-01-01 | 2014-01-10
A | 10 | 2014-01-05 | 2014-01-08
This means that there are 10 items of type A valid 2014-01-01 - 2014-01-10 and additionally, there are 10 valid 2014-01-05 - 2014-01-08.
So for example, the sum of valid items at 2014-01-06 are 20.
How can I query the table to get the sum per day? I would like a result such as
2014-01-01 10
2014-01-02 10
2014-01-03 10
2014-01-04 10
2014-01-05 20
2014-01-06 20
2014-01-07 20
2014-01-08 20
2014-01-09 10
2014-01-10 10
Can this be done with SQL? Either Oracle or MySQL would be fine
The basic syntax you are looking for is as follows:
For my example below I've defined a new table called DateTimePeriods which has a column for StartDate and EndDate both of which are DATE columns.
SELECT
SUM(NumericColumnName)
, DateTimePeriods.StartDate
, DateTimePeriods.EndDate
FROM
TableName
INNER JOIN DateTimePeriods ON TableName.dateColumnName BETWEEN DateTimePeriods.StartDate and DateTimePeriods.EndDate
GROUP BY
DateTimePeriods.StartDate
, DateTimePeriods.EndDate
Obviously the above code won't work on your database but should give you a reasonable place to start. You should look into GROUP BY and Aggregate Functions. I'm also not certain of how universal BETWEEN is for each database type, but you could do it using other comparisons such as <= and >=.
There are several ways to go about this. First, you need a list of dense dates to query. Using a row generator statement can provide that:
select date '2014-01-01' + level -1 d
from dual
connect by level <= 15;
Then for each date, select the sum of inventory:
with
sample_data as
(select 'A' itemtype, 10 item_count, date '2014-01-01' start_date, date '2014-01-10' end_date from dual union all
select 'A', 10, date '2014-01-05', date '2014-01-08' from dual),
periods as (select date '2014-01-01' + level -1 d from dual connect by level <= 15)
select
periods.d,
(select sum(item_count) from sample_data where periods.d between start_date and end_date) available
from periods
where periods.d = date '2014-01-06';
You would need to dynamically set the number of date rows to generate.
If you only needed a single row, then a query like this would work:
with
sample_data as
(select 'A' itemtype, 10 item_count, date '2014-01-01' start_date, date '2014-01-10' end_date from dual union all
select 'A', 10, date '2014-01-05', date '2014-01-08' from dual)
select sum(item_count)
from sample_data
where date '2014-01-06' between start_date and end_date;

Mysql query with criteria value > 10 for consecutive date period of 3 hours

Consider a table with id,date datetime,value double, I have data in the table every minute.
I'm trying to use mysql to identify "events" where value > 10 continuously for more than 3 hours.
At the time I am using the query:
select date from table where value > 10;
Then I manually read where the dates are continuously.
Example of "event":
Date - value
2000/01/01 00:00 - 5
2000/01/01 01:00 - 5
2000/01/01 02:00 - 5
2000/01/01 03:00 - 11
2000/01/01 04:00 - 11
2000/01/01 05:00 - 11
2000/01/01 06:00 - 5
2000/01/01 07:00 - 5
2000/01/01 08:00 - 5
2000/01/01 09:00 - 11
2000/01/01 10:00 - 11
2000/01/01 11:00 - 5
In this case there is one "event" between 03:00 and 05:00.
In MySQL, you can assign variables in a SELECT statement while retrieving data. This functionality helps in solving many problems where one would "normally" use windowing functions (which MySQL doesn't have). It can also help in yours. Here's a solution I ended up with:
SET #startdate = CAST(NULL AS datetime);
SET #granularity = 60; /* minutes */
SET #minduration = 180; /* minutes */
SET #minvalue = 10;
SELECT
t.Date,
t.Value
FROM (
SELECT
StartDate,
MAX(Date) AS EndDate
FROM (
SELECT
Date,
Value,
CASE
WHEN Value > #minvalue OR #startdate IS NOT NULL
THEN IFNULL(#startdate, Date)
END AS StartDate,
#startdate := CASE
WHEN Value > #minvalue
THEN IFNULL(#startdate, Date)
END AS s
FROM (
SELECT Date, Value FROM YourTable
UNION ALL
SELECT MAX(Date) + INTERVAL #granularity MINUTE, #minvalue FROM YourTable
) s
ORDER BY Date
) s
WHERE StartDate IS NOT NULL
GROUP BY StartDate
) s
INNER JOIN YourTable t ON t.Date >= s.StartDate AND t.Date < s.EndDate
WHERE s.EndDate >= s.StartDate + INTERVAL #minduration MINUTE
;
Three of the four variables used here are merely script arguments, and only one, #startdate, actually gets both assigned and checked in the query.
Basically, the query iterates over the rows, marking those where the value is greater than a specific minimum (#minvalue), eventually producing a list of time ranges during which values matched the condition. Actually, in order to calculate the ending bounds correctly, non-matching rows that immediately follow groups of the matching ones are also included in the respective groups. Because of that, an extra row is being added to the original dataset, where Date is calculated off the latest Date plus the specified #granularity of timestamps in your table and Value is just #minvalue.
Once obtained, the list of ranges is joined back to the original table to retrieve the detail rows that fall in between the ranges' bounds, the ranges that are not long enough (as specified by #minduration) being filtered out along the way.
If you run this solution on SQL Fiddle, you will see the following output:
DATE VALUE
------------------------------ -----
January, 01 2000 03:00:00-0800 11
January, 01 2000 04:00:00-0800 11
January, 01 2000 05:00:00-0800 11
which, I understand, is what you would expect.
select count(*) from table where DATE_SUB(CURDATE(),INTERVAL 3 HOUR) < `date`
select count(*) from table where DATE_SUB(CURDATE(),INTERVAL 3 HOUR) < `date` AND `value` > 10
Then compare the result, if not same, then is not continuously.
Wild guess:
select * from
(select event, MAX(date) as date from table where value > 10 group by event) maxs
inner join
(select event, MIN(date) as date from table where value > 10 group by event) mins
on maxs.event = mins.event
where (time_to_sec(timediff(maxes.date, mins.date)) / 3600) > 3