Choose active employes per month with dates formatted dd/mm/yyyy - sql

I'm having a hard time explaining this through writing, so please be patient.
I'm making this project in which I have to choose a month and a year to know all the active employees during that month of the year.. but in my database I'm storing the dates when they started and when they finished in dd/mm/yyyy format.
So if I have an employee who worked for 4 months eg. from 01/01/2013 to 01/05/2013 I'll have him in four months. I'd need to make him appear 4 tables(one for every active month) with the other employees that are active during those months. In this case those will be: January, February, March and April of 2013.
The problem is I have no idea how to make a query here or php processing to achieve this.
All I can think is something like (I'd run this query for every month, passing the year and month as argument)
pg_query= "SELECT employee_name FROM employees
WHERE month_and_year between start_date AND finish_date"
But that can't be done, mainly because month_and_year must be a column not a variable.
Ideas anyone?
UPDATE
Yes, I'm very sorry that I forgot to say I was using DATE as data type.
The easiest solution I found was to use EXTRACT
select * from employees where extract (year FROM start_date)>='2013'
AND extract (month FROM start_date)='06' AND extract (month FROM finish_date)<='07'
This gives me all records from june of 2013 you sure can substite the literal variables for any variable of your preference

There is no need to create a range to make an overlap:
select to_char(d, 'YYYY-MM') as "Month", e.name
from
(
select generate_series(
'2013-01-01'::date, '2013-05-01', '1 month'
)::date
) s(d)
inner join
employee e on
date_trunc('month', e.start_date)::date <= s.d
and coalesce(e.finish_date, 'infinity') > s.d
order by 1, 2
SQL Fiddle
If you want the months with no active employees to show then change the inner for a left join
Erwin, about your comment:
the second expression would have to be coalesce(e.finish_date, 'infinity') >= s.d
Notice the requirement:
So if I have an employee who worked for 4 months eg. from 01/01/2013 to 01/05/2013 I'll have him in four months
From that I understand that the last active day is indeed the previous day from finish.
If I use your "fix" I will include employee f in month 05 from my example. He finished in 2013-05-01:
('f', '2013-04-17', '2013-05-01'),
SQL Fiddle with your fix

Assuming that you really are not storing dates as character strings, but are only outputting them that way, then you can do:
SELECT employee_name
FROM employees
WHERE start_date <= <last date of month> and
(finish_date >= <first date of month> or finish_date is null)
If you are storing them in this format, then you can do some fiddling with years and months.
This version turns the "dates" into strings of the form "YYYYMM". Just express the month you want like this and you can do the comparison:
select employee_name
from employees e
where right(start_date, 4)||substr(start_date, 4, 2) <= 'YYYYMM' and
(right(finish_date, 4)||substr(finish_date, 4, 2) >= 'YYYYMM' or finish_date is null)
NOTE: the expression 'YYYYMM' is meant to be the month/year you are looking for.

First, you can generate multiple date intervals easily with generate_series(). To get lower and upper bound add an interval of 1 month to the start:
SELECT g::date AS d_lower
, (g + interval '1 month')::date AS d_upper
FROM generate_series('2013-01-01'::date, '2013-04-01', '1 month') g;
Produces:
d_lower | d_upper
------------+------------
2013-01-01 | 2013-02-01
2013-02-01 | 2013-03-01
2013-03-01 | 2013-04-01
2013-04-01 | 2013-05-01
The upper border of the time range is the first of the next month. This is on purpose, since we are going to use the standard SQL OVERLAPS operator further down. Quoting the manual at said location:
Each time period is considered to represent the half-open interval
start <= time < end [...]
Next, you use a LEFT [OUTER] JOIN to connect employees to these date ranges:
SELECT to_char(m.d_lower, 'YYYY-MM') AS month_and_year, e.*
FROM (
SELECT g::date AS d_lower
, (g + interval '1 month')::date AS d_upper
FROM generate_series('2013-01-01'::date, '2013-04-01', '1 month') g
) m
LEFT JOIN employees e ON (m.d_lower, m.d_upper)
OVERLAPS (e.start_date, COALESCE(e.finish_date, 'infinity'))
ORDER BY 1;
The LEFT JOIN includes date ranges even if no matching employees are found.
Use COALESCE(e.finish_date, 'infinity')) for employees without a finish_date. They are considered to be still employed. Or maybe use current_date in place of infinity.
Use to_char() to get a nicely formatted month_and_year value.
You can easily select any columns you need from employees. In my example I take all columns with e.*.
The 1 in ORDER BY 1 is a positional parameter to simplify the code. Orders by the first column month_and_year.
To make this fast, create an multi-column index on these expressions. Like
CREATE INDEX employees_start_finish_idx
ON employees (start_date, COALESCE(finish_date, 'infinity') DESC);
Note the descending order on the second index-column.
If you should have committed the folly of storing temporal data as string types (text or varchar) with the pattern 'DD/MM/YYYY' instead of date or timestamp or timestamptz, convert the string to date with to_date(). Example:
SELECT to_date('01/03/2013'::text, 'DD/MM/YYYY')
Change the last line of the query to:
...
OVERLAPS (to_date(e.start_date, 'DD/MM/YYYY')
,COALESCE(to_date(e.finish_date, 'DD/MM/YYYY'), 'infinity'))
You can even have a functional index like that. But really, you should use a date or timestamp column.

Related

How to calculate the number of each weekday between 2 dates in PostgreSQL?

There is a column with dates. I would like to calculate the number of each weekday (Monday to Sunday) from those dates to present date. On Stack Overflow and otherwise, I found answers that included creating functions, I was hoping there's some inbuilt function that would do it. I found another solution here, which mentions DATEPART('day', start - stop) AS days, but that didn't work. If this is an recent update in PostgreSQL then it won't work because the tool we use at work for PostgreSQL doesn't accept some of the recent updates (like PostgreSQL now accepts negative indexing but the tool doesn't).
What I want:
start_date
day_of_week
no_of_days
2022-04-01
1
10
2022-04-01
2
9
2022-05-15
2
3
2022-06-01
5
1
The start_date is the column of dates, which when subtracted from current_date (the other way around) returns the number of each weekday between those two days. There were 10 Mondays between 1st April 2022 and 6th June 2022 (today), and that's the number I want for each day of the week.
How can I achieve this in PostgreSQL? I am on version 12.8.
This "simple" but optimized solution counts the number of occurrences for every weekday in the interval between start_date and the current date:
WITH cte(start_date) AS (
VALUES
('2022-04-01'::date)
, ('2022-05-15')
, ('2022-06-01')
)
SELECT c.start_date, sub.dow, sub.no_of_days
FROM cte c
CROSS JOIN LATERAL (
SELECT dow, COALESCE(ct, 0) AS no_of_days
FROM (
SELECT EXTRACT('isodow' FROM g)::int AS dow, count(*) AS ct
FROM generate_series(start_date, current_date, interval '1 day') g
GROUP BY 1
) g
RIGHT JOIN generate_series(1, 7) dow USING (dow)
) sub
ORDER BY 1, 2;
db<>fiddle here
The upper bound (current_date) is included.
Every weekday is included, even when no_of_days is 0.
For very old dates (resulting in long intervals), an arithmetic solution will be cheaper than simply counting generated days. A bit more challenging, but not that hard.

Compare date filed with month and year in Postgres

I have a date field in one of my tables and the column name is from_dt. Now I have to compare a month and year combination against this from_dt field and check whether the month has already passed. The current database function uses separate conditions for the month and the year, but this is wrong as it will compare month and year separately. The current code is like this
SELECT bill_rate, currency FROM table_name WHERE
emp_id = employee_id_param
AND EXTRACT(MONTH FROM from_dt) <= month_param
AND EXTRACT(YEAR FROM from_dt) <= year_param
Now the fromt_dt field has value 2021-10-11. If I give month_param as 01 and year_param as 2022, this condition will not work as the month 10 is greater than 1, which I have given. Basically, I need to check whether 01-2022 (Jan 2022) is greater than r equal to 2021-10-01(October 1st, 2021). It would be very much helpful if someone can shed some light here.
If you just want to check whether one date is >= then another:
# select '2022-01-01'::date >= '2021-10-11'::date;
?column?
----------
t
If you want to restrict to year/month then:
select date_trunc('month','2022-01-01'::date) >= date_trunc('month', '2021-10-11'::date);
?column?
----------
t
Where the date_trunc components are:
select date_trunc('month','2022-01-01'::date) ;
date_trunc
------------------------
2022-01-01 00:00:00-08
select date_trunc('month','2021-10-11'::date) ;
date_trunc
------------------------
2021-10-01 00:00:00-07
See Postgres date_trunc for more information.
Assuming the given year_param and month_param are integers you can use the make_date function to create the first of the year_month and date_trunc to get the first on the month from the table. Just compare those values. (See date functions) So:
select bill_rate, currency
from table_name
where emp_id = employee_id_param
and date_trunc('month',from_dt) =
make_date( year_param, month_param, 01);

How to make POSTGRESQL query based on first 2 parameters and latest third parameter

I have DB:
**client_id | Version | Date(year, month, day)**
I need to make query request for last year(or last 12 months) group by Client_id, Version and latest date(!) for them.
For example:
client_id | Version | **LATEST** Date
23 v2 2022-1-25
23 v1 2021-3-23
25 v0 2021-6-23
This is what I have right now:
SELECT client_id, Version, Date
FROM db_table
WHERE date >= '2022-01-01' AND date < NOW()::DATE
GROUP BY client_id, Version, Date
And I'm getting result for EVERY DAY. If I'm removing DATE from group by, its complaining that Date should be in Group by.
I hope I did describe everything properly. I'm new here, so please let me know if I provide not full info.
Thank you for your time.
To get the most recent full year (i.e. not including the partial current day),
SELECT current_date - interval '1 year'; --this returns 2021-02-07 when run today
To consider today as part of the 12 month period, then:
SELECT current_date - interval '1 year - 1 day'; --this returns 2021-02-08 when run today
So, assuming that you want the former option (you can switch it if you like):
SELECT client_id, "Version", MAX("Date") AS latest_date
FROM db_table
WHERE "Date" BETWEEN current_date - interval '1 year' AND current_date
GROUP BY client_id, "Version";
p.s. recommend you don't use "Date" as a column name since it's a reserved word, and avoid using upper case letters in column names :)

Sorting SQL Query By Date Column

I'm working on a project with a db that contains a date column for patient visits in the format of %m-%d-yyyy and need to sort so that it only pulls the rows where that date is within the last two weeks. I've tried a few different functions of convert, to_date, and can't seem to get anything to work.
I'm still very new to SQL and I don't know if this is a special case because I'm working with an oracle db
Not the full code, because it has dozens of queries and multiple joins (would that affect the date syntax?) but this is the format I'm trying for...
create table "Visits"
insert into "Visits" (
'John Doe',
'5/24/2021',
'Story about the visit',
'More room for story if needed')
select
"User_Name",
"Visit_Date",
"Visit_Narrative",
"Visit_Narrative_Overflow"
from "Visits"
where "Visits"."Visit_Date" >= TRUNC(SYSDATE) - 14
I'm working on a project with a db that contains a date column for patient visits in the format of %m-%d-yyyy
No, you don't have it in the format mm.dd.yyyy. A DATE data type value is stored within the database as a binary value in 7-bytes (representing century, year-of-century, month, day, hour, minute and second) and this has no format.
need to sort so that it only pulls the rows where that date is within the last two weeks.
You want a WHERE filter:
If you want to have the values that happened in the last 14 days then TRUNCate the current date back to midnight and subtract 14 days:
SELECT visit_date
FROM patient
WHERE visit_date >= TRUNC(SYSDATE) - INTERVAL '14' DAY
or
SELECT visit_date
FROM patient
WHERE visit_date >= TRUNC(SYSDATE) - 14
(or subtract 13 if you want today and 13 days before today.)
If you want it after Monday of last week then TRUNCate to the start of the ISO Week (which is always a Monday) and subtract 7 days:
SELECT visit_date
FROM patient
WHERE visit_date >= TRUNC(SYSDATE, 'IW') - INTERVAL '7' DAY
I ended up figuring it out based on an answer from another forum (linked below);
it appears that my original to_date() was incomplete without a second and operator in my where clause. This code is working perfectly
select
"User_Name",
"Visit_Date",
"Visit_Narrative",
"Visit_Narrative_Overflow"
from "SQLUser"."Visits"
where "SQLUser"."Visits"."Visit_Date" >= to_date('5/10/2021', 'MM/DD/YYYY')
and "SQLUser"."Visits"."Visit_Date" < to_date('5/24/2021', 'MM/DD/YYYY')

How to query checking if a month is between two dates?

I'm looking for query in postgresql to check whether a month is between two dates or not.
I know how to check if a date is between two date or not. Postgres also have a function to do that.
Let's say I have a a_table with rows:
ID | start_date (timestamp) | end_date (timestamp)
1 | 2019-07-20 00:00 | 2020-03-20 00:00
2 | 2019-08-20 00:00 | 2020-08-30 00:00
I have a to return the row that include a month between the start_date and end_date.
Let's say i have a month 2019-08.
So when i count
Select count(*) from a_table
Where [some where clause]
it returns 2 rows, ID 1 and ID 2
AND when i have a month 2020-01 it only return ID 1
You can use date range for this.
It's not clear to me what should happen if the start/end date in the table only covers part of a month.
If you only want to consider the full month, use the "contains" operator
select count(*)
from the_table
where daterange(start_date::date, end_date::date, '[]') #> daterange('2019-08-01'::date, '2019-09-01'::date, '[)');
The <# is the "is contained" operator which tests if the left range (the values from the table) is contained in the right hand range (the month you want to test). The comparison is done with an "open interval", which means '2019-09-01' is excluded from it. The above would not consider rows that do not contain the full August.
If you want to include partial matches as well, use the "overlaps" operator && instead:
select count(*)
from the_table
where daterange(start_date::date, end_date::date, '[]') && daterange('2019-08-01'::date, '2019-09-01'::date, '[)');
You can use to_date() in your where clause. So that your where clause would be like this.
WHERE '2019-08' BETWEEN to_date(start_date, 'YYYY-MM') and to_date(end_date ,'YYYY-MM')
I would recommend writing this as:
WHERE end_date >= TO_DATE('2019-08', 'YYYY-MM') AND
start_date < TO_DATE('2019-08', 'YYYY-MM') + INTERVAL '1 MONTH'
That is, the period includes at least one day of the month, because it starts before the end of the month and ends after the start of the month.
In addition, this has no functions on the columns in the table. So, if an index is available on either column, then it can be used. If you define the start/end as a range, then that provides other opportunities for using indexes.