Date Functions Conversions in SQL - sql

Is there a way using TSQL to convert an integer to year, month and days
for e.g. 365 converts to 1year 0 months and 0 days
366 converts to 1year 0 months and 1 day
20 converts to 0 year 0 months and 20 days
200 converts to 0 year 13 months and 9 days
408 converts to 1 year 3 months and 7 days .. etc

I don't know of any inbuilt way in SQL Server 2008, but the following logic will give you all the pieces you need to concatenate the items together:
select
n
, year(dateadd(day,n,0))-1900 y
, month(dateadd(day,n,0))-1 m
, day(dateadd(day,n,0))-1 d
from (
select 365 n union all
select 366 n union all
select 20 n union all
select 200 n union all
select 408 n
) d
| n | y | m | d |
|-------|---|---|----|
| 365 | 1 | 0 | 0 |
| 366 | 1 | 0 | 1 |
| 20 | 0 | 0 | 20 |
| 200 | 0 | 6 | 19 |
| 408 | 1 | 1 | 12 |
Note that zero used in in the DATEDADD function is the date 1900-01-01, hence 1900 is deducted from the year calculation.
Thanks to Martin Smith for correcting my assumption about the leap year.

You could try without using any functions just by dividing integer values if we consider all months are 30 days:
DECLARE #days INT;
SET #days = 365;
SELECT [Years] = #days / 365,
[Months] = (#days % 365) / 30,
[Days] = (#days % 365) % 30;
#days = 365
Years Months Days
1 0 0
#days = 20
Years Months Days
0 0 20

Related

Calculate the days to reach a certain date - PostgreSQL

I need to create a query to calculate the difference in days until a date reach another date. Something like the "how many days until my birthday".
Current_date | Reach_date
2000-01-01 | 2015-01-03
-- Should Return: 2
2015-03-01 | 2021-03-05
-- Should Return: 4
The most similar built-in function I found to solve this problem, was using "age()", but it returns me "year, month and days":
select age(current_date,reach_date) from sample_table;
age
-------------------------
3 years 10 mons 1 day
I also tried to use "extract()" trying to get the difference in days, but it just returns me the part of the age function of the days. At my last sample, instead of it returns me more than 1000 days, it returns me just 1.
Try if this works for you. It checks where it's a leap year to calculate the difference correctly, and then uses different logic to calculate the difference between the dates depending on whether the dates are in the same year or not.
with cte as
(
SELECT *,
CASE WHEN extract(year from CurrentDate)::INT % 4 = 0
and (extract(year from CurrentDate)::INT % 100 <> 0
or extract(year from CurrentDate)::INT % 400 = 0)
THEN TRUE
ELSE FALSE
END AS isLeapYear,
Extract(day from (Reach_date - CurrentDate)) AS diff_in_days
FRoM test
)
SELECT CurrentDate,
Reach_date,
CASE WHEN isLeapYear
THEN
CASE WHEN diff_in_days < 366
THEN diff_in_days
ELSE Extract(day from AGE(Reach_date, CurrentDate))
END
ELSE CASE WHEN diff_in_days < 365
THEN diff_in_days
ELSE Extract(day from AGE(Reach_date, CurrentDate))
END
END AS diff
FROM cte
Test here: SQL Fiddle
SELECT
d_date,
'2021-01-01'::date - '2020-01-01'::date AS diff_2021_minus_2020,
CASE WHEN (date_part('month', d_date)::integer) = 1
AND (date_part('day', d_date)::integer) = 1 THEN
(date_trunc('year', d_date) + interval '1 year')::date - date_trunc('year', d_date)::date
WHEN ((d_date - (date_trunc('year', d_date))::date)) <= 182 THEN
(d_date - (date_trunc('year', d_date))::date)
ELSE
365 - (d_date - (date_trunc('year', d_date))::date)
END AS till_to_birthday
FROM (
VALUES ('2021-12-01'::date),
('2021-06-01'::date),
('2020-01-01'::date),
('2021-01-01'::date),
('2021-09-01'::date),
('2021-11-01'::date),
('2020-06-01'::date)) s (d_date);
returns:
+------------+----------------------+------------------+
| d_date | diff_2021_minus_2020 | till_to_birthday |
+------------+----------------------+------------------+
| 2021-12-01 | 366 | 31 |
| 2021-06-01 | 366 | 151 |
| 2020-01-01 | 366 | 366 |
| 2021-01-01 | 366 | 365 |
| 2021-09-01 | 366 | 122 |
| 2021-11-01 | 366 | 61 |
| 2020-06-01 | 366 | 152 |
+------------+----------------------+------------------+
The behavior that you've got with using age() is because extract() only extract the amount of days but it won't convert months and years into days for you before extraction.
On a SQL Server you could use DATEDIFF() but in Postgre you have to compute it yourself by substracting dates, as shown in this answer.
There's also few examples with all the time units here.

running total starting from a date column

I'm trying to get a running total as of a date. This is the data I have
Date
transaction Amount
End of Week Balance
jan 1
5
100
jan 2
3
100
jan 3
4
100
jan 4
3
100
jan 5
1
100
jan 6
3
100
I would like to find out what the daily end balance is. My thought is to get a running total from each day to the end of the week and subtract it from the end of week balance, like below
Date
transaction Amount
Running total
End of Week Balance
Balance - Running total
jan 1
5
19
100
86
jan 2
3
14
100
89
jan 3
4
11
100
93
jan 4
3
7
100
96
jan 5
1
4
100
97
jan 6
3
3
100
100
I can use
SUM(transactionAmount) OVER (Order by Date)
to get a running total, is there a way to specify that I only want the total of transactions that have taken place after the date?
You can use sum() as a window function, but accumulate in reverse:
select t.*,
(end_of_week_balance -
sum(transactionAmount) over (order by date desc)
)
from t;
If you have this example:
1> select i, sum(i) over (order by i) S from integers where i<10;
2> go
i S
----------- -----------
1 1
2 3
3 6
4 10
5 15
6 21
7 28
8 36
9 45
you can also do:
1> select i, sum(case when i>3 then i else 0 end) over (order by i) S from integers where i<10;
2> go
i S
----------- -----------
1 0
2 0
3 0
4 4
5 9
6 15
7 22
8 30
9 39

How to count employees per hour working in between hours?

How to count employees per hour working in between intime and outtime hours.
I have below table format with intime,outtime of employee .
My Table :
emp_reader_id att_date in_time out_time Shift_In_Time Shift_Out_Time
111 2020-03-01 2020-03-01 08:55:24.000 2020-03-01 10:26:56.000 09:00:00.0000000 10:30:00.0000000
112 2020-03-01 2020-03-01 08:45:49.000 2020-03-01 11:36:14.000 09:00:00.0000000 11:30:00.0000000
113 2020-03-01 2020-03-01 10:58:19.000 2020-03-01 13:36:31.000 09:00:00.0000000 12:00:00.0000000
Need to count the employee in the below format.
Expected Output:
Period Working Employee Count
0 - 1 0
1 - 2 0
2 - 3 0
3 - 4 0
4 - 5 0
5 - 6 0
6 - 7 0
7 - 8 0
8 - 9 2
9 - 10 2
10 - 11 3
11 - 12 2
12 - 13 1
13 - 14 1
14 - 15 0
15 - 16 0
16 - 17 0
17 - 18 0
18 - 19 0
19 - 20 0
20 - 21 0
21 - 22 0
22 - 23 0
23 - 0 0
I tried with below query with my raw data , but it will not work i need from above table
SELECT
(DATENAME(hour, C.DT) + ' - ' + DATENAME(hour, DATEADD(hour, 2, C.DT))) as PERIOD,
Count(C.EVENTID) as Emp_Work_On_Time
FROM
trnevents C
WHERE convert(varchar(50),C.DT,23) ='2020-03-01'
GROUP BY (DATENAME(hour, C.DT) + ' - ' +
DATENAME(hour, DATEADD(hour, 2, C.DT)))
you need to have a list of hours (0 to 23) and then left join to your table.
The following query uses recursive cte to generate that list. You may also use VALUES constructor or TALLY table. Which will gives same effect
; with hours as
(
select hour = 0
union all
select hour = hour + 1
from hours
where hour < 23
)
select convert(varchar(2), h.hour) + ' - ' + convert(varchar(2), (h.hour + 1) % 24) as [Period],
count(t.emp_reader_id) as [Working Employee Count]
from hours h
left join timesheet t on h.hour >= datepart(hour, in_time)
and h.hour <= datepart(hour, out_time)
group by h.hour
Demo : db<>fiddle
Hope that might help but take a look how shift in and shift out are in the code... seems to me its automatic so it could have all you need
SELECT COUNT(Idemp) from aaShiftCountEmp WHERE in_time<'2020-03-01 09:00:00.000' AND out_time>'2020-03-01 10:00:00.000'
this is just example for 9h to 10h but u can make it auto,
btw are u sure that this shoul not show SHIFT ppl cOUNT? i mean u sure 0-1, 1-2 instead of 0-1.30, 1.30-3?? etc?

How to calculate tiered pricing using PostgreSQL

I'm trying to calculate tiered rates for a stay at some lodging. Lets say we have a weekly, half week, and daily rate for a property.
period_name | nights | rate
-------------------------------------
WEEK | 7 | 100
HALFWEEK | 3 | 50
DAY | 1 | 25
How would I query this with a total number of nights and get a break down of what periods qualify, going from longest to shortest? Some examples results
10 nights
We break 10 into (7 days) + (3 days). The 7 days will be at the WEEK rate (100). The 3 days will be at the HALFWEEK rate (50). Here it qualifies for (1 WEEK # 100) + (1 HALFWEEK # 50)
period_name | nights | rate | num | subtotal
----------------------------------------------
WEEK | 7 | 100 | 1 | 100
HALFWEEK | 3 | 50 | 1 | 50
4 nights
We break 4 into (3 days) + (1 day). The 3 days will be at the HALFWEEK rate (50). The 1 day will be at the DAY rate (25). Here it qualifies for (1 HALFWEEK # 50) + (1 DAY # 25)
period_name | nights | rate | num | subtotal
----------------------------------------------
HALFWEEK | 3 | 50 | 1 | 50
DAY | 1 | 25 | 1 | 25
16 nights
We break 16 into (14 days) + (2 days). The 14 days will be at the WEEK rate (multiplied by 2), (100 * 2). The 2 days will be at the DAY rate (2 x 25). Here it qualifies for (2 WEEK # 100) + (2 DAY # 25)
period_name | nights | rate | num | subtotal
----------------------------------------------
WEEK | 7 | 100 | 2 | 200
DAY | 1 | 25 | 2 | 50
I thought about using the lag window function, but now sure how I'd keep track of the days already applied by the previous period.
You can do this with a CTE RECURSIVE query.
http://sqlfiddle.com/#!17/0ac709/1
Tier table (which can be dynamically expanded):
id name days rate
-- --------- ---- ----
1 WEEK 7 100
2 DAYS 1 25
3 HALF_WEEK 3 50
4 MONTH 30 200
Days data:
id num
-- ---
1 10
2 31
3 30
4 19
5 14
6 108
7 3
8 5
9 1
10 2
11 7
Result:
num_id num days total_price
------ --- ----------------------------------------------- -----------
1 10 {"MONTH: 0","WEEK: 1","HALF_WEEK: 1","DAYS: 0"} 150
2 31 {"MONTH: 1","WEEK: 0","HALF_WEEK: 0","DAYS: 1"} 225
3 30 {"MONTH: 1","WEEK: 0","HALF_WEEK: 0","DAYS: 0"} 200
4 19 {"MONTH: 0","WEEK: 2","HALF_WEEK: 1","DAYS: 2"} 300
5 14 {"MONTH: 0","WEEK: 2","HALF_WEEK: 0","DAYS: 0"} 200
6 108 {"MONTH: 3","WEEK: 2","HALF_WEEK: 1","DAYS: 1"} 875
7 3 {"MONTH: 0","WEEK: 0","HALF_WEEK: 1","DAYS: 0"} 50
8 5 {"MONTH: 0","WEEK: 0","HALF_WEEK: 1","DAYS: 2"} 100
9 1 {"MONTH: 0","WEEK: 0","HALF_WEEK: 0","DAYS: 1"} 25
10 2 {"MONTH: 0","WEEK: 0","HALF_WEEK: 0","DAYS: 2"} 50
11 7 {"MONTH: 0","WEEK: 1","HALF_WEEK: 0","DAYS: 0"} 100
The idea:
First I took this query to calculate your result for one value (19):
SELECT
days / 7 as WEEKS,
days % 7 / 3 as HALF_WEEKS,
days % 7 % 3 / 1 as DAYS
FROM
(SELECT 19 as days) s
Here you can see the recursive structure for the module operation terminated by an integer division. Because a more generic version should be necessary I thought about a recursive version. With PostgreSQL WITH RECURSIVE clause this is possible
https://www.postgresql.org/docs/current/static/queries-with.html
So thats the final query
WITH RECURSIVE days_per_tier(row_no, name, days, rate, counts, mods, num_id, num) AS (
SELECT
row_no,
name,
days,
rate,
num.num / days,
num.num % days,
num.id,
num.num
FROM (
SELECT
*,
row_number() over (order by days DESC) as row_no -- C
FROM
testdata.tiers) tiers, -- A
(SELECT id, num FROM testdata.numbers) num -- B
WHERE row_no = 1
UNION
SELECT
days_per_tier.row_no + 1,
tiers.name,
tiers.days,
tiers.rate,
mods / tiers.days, -- D
mods % tiers.days, -- E
days_per_tier.num_id,
days_per_tier.num
FROM
days_per_tier,
(SELECT
*,
row_number() over (order by days DESC) as row_no
FROM testdata.tiers) tiers
WHERE days_per_tier.row_no + 1 = tiers.row_no
)
SELECT
num_id,
num,
array_agg(name || ': ' || counts ORDER BY days DESC) as days,
sum(total_rate_per_tier) as total_price -- G
FROM (
SELECT
*,
rate * counts as total_rate_per_tier -- F
FROM days_per_tier) s
GROUP BY num_id, num
ORDER BY num_Id
The WITH RECURSIVE contains the starting point of the recursion UNION the recursion part. The starting point simply gets the tiers (A) and numbers (B). To order the tiers due to their days I add a row count (C; only necessary if the corresponding ids are not in the right order as in my example. This could happen if you add another tier).
The recursion part takes the previous SELECT result (which is stored in days_per_tier) and calculates the next remainder and integer division (D, E). All other columns are only for holding the origin values (exception the increasing row counter which is responsible for the recursion itself).
After the recursion the counts and rates are multiplied (F) and then grouped by the origin number id which generated the total sum (G)
Edit:
Added the rate function and the sqlfiddle link.
Here what you need to do is first fire an SQL command to retrieve all condition and write down the function for your business logic.
For Example.
I will fire below query into the database.
Select * from table_name order by nights desc
In result, I will get the data sorted by night in descending order that means first will be 7 then 3 then 1.
I will write a function to write down my business logic for example.
Let's suppose I need to find for 11 days.
I will fetch the first record which will be 7 and check it will 11.
if(11 > 7){// execute this if in a loop till it's greater then 7, same for 3 & 1
days = 11-7;
price += price_from_db;
package += package_from_db;
}else{
// goto fetch next record and check the above condition with next record.
}
Note: I write down an algorithm instead of language-specific code.

Calculating datetime intervals taking into an account the current date

I have a sub request which returns this:
item_id, item_datetime, item_duration_in_days
1, '7-dec-2016-12:00', 3
2, '8-dec-2016-11:00', 4
3, '20-dec-2016-05:00', 10
4, '2-jan-2017-14:00', 50
5, '29-jan-2017-22:00', 89
I want to get "item_id" which falls into "now()". For that the algorithm is:
1) var duration_days = interval 'item_duration_in_days[i]'
2) for the very first item:
new_datetime[i] = item_datetime[i] + duration_days
3) for others:
- if a new_datetime from the previous step overlaps with the current item_datetime[i]:
new_datetime[i] = new_datetime[i - 1] + duration_days
- else:
new_datetime[i] = item_datetime[i] + duration_days
4) return an item for each iteration:
{id, item_datetime, new_datetime}
That is, there'll be something like:
item_id item_datetime new_datetime
1 7 dec 2016 10 dec 2016
2 11 dec 2016 15 dec 2016
3 20 dec 2016 30 dec 2016
4 2 jan 2017 22 feb 2017 <------- found because now() == Feb 5
5 22 feb 2017 21 may 2017
How can I do that? I think it should be something like "fold" function. Can it be done via an sql request? Or will have to be an PSQL procedure for intermediate variable storage?
Or please give pointers how to calculate that.
If I understand correctly your task, you need recursive call. Function take first row at first and process each next.
WITH RECURSIVE x AS (
SELECT *
FROM (
SELECT item_id,
item_datetime,
item_datetime + (item_duration_in_days::text || ' day')::interval AS cur_end
FROM ti
ORDER BY item_datetime
LIMIT 1
) AS first
UNION ALL
SELECT item_id,
cur_start,
cur_start + (item_duration_in_days::text || ' day')::interval
FROM (
SELECT item_id,
CASE WHEN item_datetime > prev_end THEN
item_datetime
ELSE
prev_end
END AS cur_start,
item_duration_in_days
FROM (
SELECT ti.item_id,
ti.item_datetime,
x.cur_end + '1 day'::interval AS prev_end,
item_duration_in_days
FROM x
JOIN ti ON (
ti.item_id != x.item_id
AND ti.item_datetime >= x.item_datetime
)
ORDER BY ti.item_datetime
LIMIT 1
) AS a
) AS a
) SELECT * FROM x;
Result:
item_id | item_datetime | cur_end
---------+---------------------+---------------------
1 | 2016-12-07 12:00:00 | 2016-12-10 12:00:00
2 | 2016-12-11 12:00:00 | 2016-12-15 12:00:00
3 | 2016-12-20 05:00:00 | 2016-12-30 05:00:00
4 | 2017-01-02 14:00:00 | 2017-02-21 14:00:00
5 | 2017-02-22 14:00:00 | 2017-05-22 14:00:00
(5 rows)
For seeing current job :
....
) SELECT * FROM x WHERE item_datetime <= now() AND cur_end >= now();
item_id | item_datetime | cur_end
---------+---------------------+---------------------
4 | 2017-01-02 14:00:00 | 2017-02-21 14:00:00
(1 row)