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

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 :)

Related

How do I select only the month end date from a table

I am new to SQL, here is my problem.
I have a table with daily dates:
Date:
20190101
20190102
20190103
.
**20190131**
20190201
20190202
20190203
.
**20190228**
20190301
20190302
20190303
.
**20190331**
I want to select only the month-end dates, what would be the code to do that?
thanks
I am using MS SQL Studio.
One method in standard SQL would be:
select t.*
from t
where extract(month from date + interval '1' day) <> extract(month from date);
Date/time functions vary significantly by database, so the exact functions might not match your database. However, the idea is simple: add one day and see if the month changes.
In standard SQL, you could do:
select date
from mytable
where date = date_trunc('month', date) + interval '1' month - interval '1' day
Edit
In SQL Server, you can just use eomonth(). Given a date, this functions returns the corresponding end of month, which you can compare against the date. So:
select date
from mytable
where date = eomonth(date)

SQL to display data of current month & next month from table

I wanted some guidance on producing an SQL query that collects the table information of the current date and also next month without having to type in every day for the current month being October or the next month being November.
Basically I've got a table called WORK, in this table there are SHIFTID, DATEOFSHIFT, and MEMBERSHIPID. I basically need to list the SHIFTID's of shifts where MEMBERSHIPID = null and where DATEOFSHIFT is in November (next month)
Then I need to produce a query for the shift roster showing SHIFTID, DATEOFSHIFT, and MEMBERSHIPID of each shift in this current month.
This is the structure of my database table if needed.
I would recommend:
select w.*
from work w
where w.membershipid is null and
w.dateofshift >= trunc(sysdate, 'Month') + interval '1' month and
w.dateofshift < trunc(sysdate, 'Month') + interval '2' month;
You can also phrase the where as:
where w.membershipid is null and
trunc(w.dateofshift, 'Month') >= trunc(sysdate, 'Month') + interval '1' month
but this makes it hard for Oracle to use an index if an appropriate one is available.
Well from what you've provided, I infer that you want a query to display the information on all those fields for the current month. That is achievable by:
Select SHIFTID, DATEOFSHIFT, MEMBERSHIPID
From WORK
Where Month(DATEOFSHIFT)=MONTH(GETDATE());

Using IF statement in Postgres WHERE clause

I've got a postgres table with user records in it. I want to select all records where the last_visit timestamp is within the last week OR the account was created in the last week. However, if a user hasn't visited yet, the last_visit timestamp is null (yeah, I know-- it's not set to default to the account creation date).
So I need to select all records WHERE either the last_visit is in the last week OR, if that's null, where creation_date is in the last week.
Something like this, although this doesn't work (and neither does a dozen other possible syntaxes I've tried:
SELECT * FROM users
WHERE
IF (users.last_active IS NULL) THEN
value(users.last_visit,timestamp(users.creation_date)) < current timestamp - 7 days
ELSE
users.last_active < current timestamp - 7 days
END IF
Any advice?
You can use coalesce() for this:
where coalesce(last_visit, creation_date) >= current_timestamp - interval '7' day
coalesce() returns the first non-null value from the provided list. So if last_visit is null, creation_date is used. Otherwise last_visit
The easiest way would probably be to coalesce those two values:
SELECT *
FROM users
WHERE COALESCE (last_active, creation_date) <
CURRENT_TIMESTAMP - INTERVAL '7 DAYS'

Pull records ahead of time

I'm trying to develop an 'upcoming listings' puller for my website, however the following query does not seem to be performing as it should.
The SQL:
SELECT * FROM listings WHERE start_date > DATE_SUB( CURDATE(), INTERVAL 3 MONTH )
Rather than pulling listings from 3 months ahead it pulls all listings?
If you want to work with 'future' records, you should use DATE_ADD instead:
SELECT *
FROM listings
WHERE start_date
BETWEEN CURDATE()
AND DATE_ADD(CURDATE(), INTERVAL 3 MONTH);
Note that BETWEEN ... AND clause is inclusive: in other words, you'll have records for start_date equal both to the current's one and the one exactly 3 months after. If that's not the desired outcome, just use separate two conditions:
WHERE start_date > CURDATE()
AND start_date < DATE_ADD(CURDATE(), INTERVAL 3 MONTH);
As it stands now, you collect all the records having start_date set to later than 3 months before the current date. That probably includes the whole dataset.

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

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.