How to get the soonest date in relation to another date field - sql

Say I have a date field in one table (table a):
+---------+------------+
| item_id | Date |
+---------+------------+
| 12333 | 10/12/2020 |
+---------+------------+
| 45678 | 10/12/2020 |
+---------+------------+
Then I have another table with another date, and it joins to the table above as so (they join on the primary key of table b):
+-------------+------------+-----------+------------+
| primary_key | date2 | item_id | Date |
| (table b) | (table b) | (table a) | (table a) |
+-------------+------------+-----------+------------+
| 45318 | 10/10/2020 | 12333 | 10/12/2020 |
+-------------+------------+-----------+------------+
| 45318 | 10/13/2020 | 12333 | 10/12/2020 |
+-------------+------------+-----------+------------+
| 45318 | 10/24/2020 | 12333 | 10/12/2020 |
+-------------+------------+-----------+------------+
| 75394 | 10/20/2020 | 45678 | 10/12/2020 |
+-------------+------------+-----------+------------+
You see the last column is from table a. I want to get table b's "date2" column to give me the soonest date after 10/12/2020, and remove the rest.
So for the example of 45318, I want to keep the second line only (the one that is 10/13/2020) since that is the soonest date after 10/12/2020.
If this doesn't make sense, let me know and I will fix it!

One method is apply:
select a.*, b.*. -- or whatever columns you want
from a outer apply
(select top (1) b.*
from b
where b.item_id = a.item_id and
b.date2 >= '2020-10-12'
order by b.date2 asc
) b;

Related

PostgreSQL check if value exists in another table

I'm trying to find a solution in PostgreSQL of how I can add to the output of the query extra column with value if id exists in another table or not:
I need several things:
Do a join between two tables
Add a new column into the result output where I check if exists in the third table or not
My tables:
announcement
author
chapter
announcement table
| id | author_id | date | group_id | ... |
author table
| id | name | email | ... |
chapter table
| id | announcement_id | ... |
This is what I have now. I did a left outer join and it works as I expected:
select announcement.id, announcement.date, author.id as publisher_id, author.name as publisher_name
from announcement
left outer join author
on announcement.author_id = author.id
where announcement.group_id = 123 and announcement.date >= '2022-06-01'::date;
with output:
| id | date | publisher_id | publisher_name |
| 1 | 2020-07-01 | 12 | John |
| 2 | 2020-07-04 | 123 | Arthur |
Now I can't find a solution of how to add an extra column with_chapters to the query response, where I will check if announcement.id exists in chapter table under announcement_id column.
For example, chapter table can have such data:
| id | announcement_id |
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
So we see that some announcements can appear in chapters several times (so i'm looking for at least 1 match). And some announcements doesn't have chapters at all.
Output finally should be like that:
| id | date | publisher_id | publisher_name | with_chapters |
| 1 | 2020-07-01 | 12 | John | true |
| 2 | 2020-07-04 | 123 | Arthur | false |
Thanks a lot for any help :)
While EXISTS (subquery) is usually used in the WHERE clause, it returns an ordinary Boolean and so can be used in the select list.
SELECT blah1, blah2,
EXISTS (select 1 from chapter where chapter.announcement_id=announcement.id) as with_chapter
FROM ...

How to "expand" a SQL join such that each unique value in column A "get" all the unique values for B?

I have a dataset with two columns: id and date. The dates are monthly and span from Mar-21 to Aug-21. I am sure this question could be applied to non-date values, but I think dates are more intuitive for this example.
id | date |
----+--------+--
a | Mar-21 |
a | Apr-21 |
a | Aug-21 | <---- 'a' is missing Jun-21 and Jul-21
b | Mar-21 |
b | May-21 | <---- 'b' is missing Apr-21
b | Jun-21 |
b | Jul-21 |
b | Aug-21 |
And I want this
id | date |
----+--------+--
a | Mar-21 |
a | Apr-21 |
a | May-21 |
a | Jun-21 | <---- 'a' gets Jun-21
a | Aug-21 | <---- ...and now Jul-21
b | Mar-21 |
b | Apr-21 | <---- 'b' gets Apr-21
b | May-21 |
b | Jun-21 |
b | Jul-21 |
b | Aug-21 |
Basically I want to say "I want every single id to get all unique values of date.
Consider below approach
select id, format_date('%b-%y', dt) date
from unnest(generate_date_array('2021-03-01', '2021-08-01', interval 1 month)) dt,
(select distinct id from your_table)
-- order by id, dt
if applied to sample data in your question - output is

Conditionally apply date filter based on column - Oracle SQL

I have a table that looks like this:
| Type | DueDate |
|:----:|:---------:|
| A | 1/1/2019 |
| B | 2/3/2019 |
| C | NULL |
| A | 1/3/2019 |
| B | 9/1/2019 |
| C | NULL |
| A | 3/3/2019 |
| B | 4/3/2019 |
| C | NULL |
| B | 1/6/2019 |
| A | 1/19/2019 |
| B | 8/1/2019 |
| C | NULL |
What I need to accomplish is:
Grab all rows that have Type C. For any other type, only grab them if they have a due date AFTER May 1st 2019.
This is a dummy data -- in actuality, there are 10 or 15 types and about ~125M or so rows.
I have tried SELECT * FROM tblTest WHERE ((Type IN ('A', 'B') AND DueDate > '05-01-2019') OR Type = 'C') but that yields exactly the table above.
Simply changing WHERE DUEDATE >= '05/01/2019' filters outNULL`
How can I edit my WHERE statement to achieve desired results of below?
| Type | DueDate |
|:----:|:--------:|
| C | NULL |
| B | 9/1/2019 |
| C | NULL |
| C | NULL |
| B | 8/1/2019 |
| C | NULL |
SQL FIDDLE for reference
If your date were stored using the correct type, you would simply do:
select t.*
from tbltest
where duedate > date '2019-05-01' or type = 'C';
I would suggest you fix the duedate column to have the correct type. Until that is fixed, you can workaround the problem:
select t.*
from tbltest
where to_date(duedate, 'MM/DD/YYYY') > date '2019-05-01' or type = 'C';
As per the answer by gordon you need to use this in or condition.
If you have more conditions in where clause apart from what is mentioned in question, you need to group the conditions.
select *
from tbltest
where (duedate > DATE '2019-05-01'
or type = 'C') -- group these condition using brackets
And other_condition;
Actually your original query has or condition with all other conditions without any brackets and that yields all the rows in result.
Cheers!!

Measure population on several dates

I want to measure the population of our manucipality (which contains out of several places). I've got two tables in: my first dataset is a calender table with a row for each first day of every month.
My second table contains alle the people that live and have lived in the manucipality.
What I want is the population of each place on every first day of the month from my calender table. I've put some raw data below (just a few records of the persons table because it contains 100.000 records)
Calender table:
+----------+
| Date |
+----------+
| 1-1-2018 |
+----------+
| 1-2-2018 |
+----------+
| 1-3-2018 |
+----------+
| 1-4-2018 |
+----------+
Persons table
+-----+-----------+-----------+---------------+-------+
| BSN | Startdate | Enddate | Date of death | Place |
+-----+-----------+-----------+---------------+-------+
| 1 | 12-1-2000 | null | null | A |
+-----+-----------+-----------+---------------+-------+
| 2 | 10-5-2011 | null | 22-1-2018 | B |
+-----+-----------+-----------+---------------+-------+
| 3 | 16-12-2011| 10-2-2018 | null | B |
+-----+-----------+-----------+---------------+-------+
| 4 | 9-11-2012 | null | null | B |
+-----+-----------+-----------+---------------+-------+
| 5 | 8-9-2013 | null | 27-3-2018 | A |
+-----+-----------+-----------+---------------+-------+
| 6 | 7-10-2017 | 28-3-2018 | null | B |
+-----+-----------+-----------+---------------+-------+
My expected result:
+----------+-------+------------+
| Date | Place | Population |
+----------+-------+------------+
| 1-1-2018 | A | 2 |
+----------+-------+------------+
| 1-1-2018 | B | 4 |
+----------+-------+------------+
| 1-2-2018 | A | 2 |
+----------+-------+------------+
| 1-2-2018 | B | 3 |
+----------+-------+------------+
| 1-3-2018 | A | 2 |
+----------+-------+------------+
| 1-3-2018 | B | 2 |
+----------+-------+------------+
| 1-4-2018 | A | 1 |
+----------+-------+------------+
| 1-4-2018 | B | 1 |
+----------+-------+------------+
What I've done so far but doesnt seems to work:
SELECT a.Place
,c.Date
,(SELECT COUNT(DISTINCT(b.BSN))
FROM Person as b
WHERE b.Startdate < c.Date
AND (b.Enddate > c.Date OR b.Enddate is null)
AND (b.Date of death > c.Date OR b.Date of death is null)
AND a.Place = b.Place) as Population
FROM Person as a
JOIN Calender as c
ON a.Startdate <= c.Date
AND a.Enddate >= c.Date
GROUP BY Place, Date
I hope someone can help finding out the problem. Thanks in advance
First cross join Calender and the places to get the date/place pairs. Then left join the persons on the place and the date. Finally group by date and place to get the count of people for that day and place.
SELECT [ca].[Date],
[pl].[Place],
count([pe].[Place]) [Population]
FROM [Calender] [ca]
CROSS JOIN (SELECT DISTINCT
[pe].[Place]
FROM [Persons] [pe]) [pl]
LEFT JOIN [Persons] [pe]
ON [pe].[Place] = [pl].[Place]
AND [pe].[Startdate] <= [ca].[Date]
AND (colaesce([pe].[Enddate],
[pe].[Date of death]) IS NULL
OR coalesce([pe].[Enddate],
[pe].[Date of death]) > [ca].[Date])
GROUP BY [ca].[Date],
[pl].[Place]
ORDER BY [ca].[Date],
[pl].[Place];
Some notes and assumptions:
If you have a table listing the places use that instead of the subquery aliases [pl]. I just had no other option with the given tables.
I believe the Date of death also implies an Enddate for the same day. You might want to consider a trigger, that sets the Enddate automatically to the Date of death if it isn't null. That would make things easier and probably more consistent.

Filter by value in last row of LEFT OUTER JOIN table

I have a Clients table in PostgreSQL (version 9.1.11), and I would like to write a query to filter that table. The query should return only clients which meet one of the following conditions:
--The client's last order (based on orders.created_at) has a fulfill_by_date in the past.
OR
--The client has no orders at all
I've looked for around 2 months, on and off, for a solution.
I've looked at custom last aggregate functions in Postgres, but could not get them to work, and feel there must be a built-in way to do this.
I've also looked at Postgres last_value window functions, but most of the examples are of a single table, not of a query joining multiple tables.
Any help would be greatly appreciated! Here is a sample of what I am going for:
Clients table:
| client_id | client_name |
----------------------------
| 1 | FirstClient |
| 2 | SecondClient |
| 3 | ThirdClient |
Orders table:
| order_id | client_id | fulfill_by_date | created_at |
-------------------------------------------------------
| 1 | 1 | 3000-01-01 | 2013-01-01 |
| 2 | 1 | 1999-01-01 | 2013-01-02 |
| 3 | 2 | 1999-01-01 | 2013-01-01 |
| 4 | 2 | 3000-01-01 | 2013-01-02 |
Desired query result:
| client_id | client_name |
----------------------------
| 1 | FirstClient |
| 3 | ThirdClient |
Try it this way
SELECT c.client_id, c.client_name
FROM clients c LEFT JOIN
(
SELECT *, ROW_NUMBER() OVER (PARTITION BY client_id ORDER BY created_at DESC) rnum
FROM orders
) o
ON c.client_id = o.client_id
AND o.rnum = 1
WHERE o.fulfill_by_date < CURRENT_DATE
OR o.order_id IS NULL
Output:
| CLIENT_ID | CLIENT_NAME |
|-----------|-------------|
| 1 | FirstClient |
| 3 | ThirdClient |
Here is SQLFiddle demo