Following Table (let's name it validity_period):
-------------------------------
id | valid_from | valid_until
-------------------------------
1 2012-11-12 2012-12-02
2 2012-12-03 NULL
3 2012-12-15 2012-12-21
(valid_from is not nullable; valid_until is nullable, but don't have to be null)
Now I want to find out which entry is valid today (2012-12-19). From the logical sight of view it has to be entry 3, because the entries can overlap each other but only one entry is valid on one day. (On 2012-12-22 it has to be entry 2 which is valid.)
Note that all entries can have a valid_until, but there can't be more than one entry where valid_until is NULL.
How would I perform this in a SQL-Query? (If possible in SQLAlchemy, but I also can translate it myself from raw SQL)
(I'm using PostgreSQL 9.1)
EDIT: Here my final resolution. Thanks to all contributors!
SELECT *
FROM validity_period
WHERE valid_from <= CURRENT_DATE AND
valid_until >= CURRENT_DATE
UNION
SELECT *
FROM validity_period
WHERE valid_from <= CURRENT_DATE AND
valid_until IS NULL
ORDER BY valid_from DESC
LIMIT 1;
You can use a WINDOW function (such as lag or lead) to refer to the previous/next record, and cap the "open" intervals:
DROP SCHEMA tmp CASCADE;
CREATE SCHEMA tmp ;
SET search_path=tmp;
CREATE TABLE daterange
( id INTEGER NOT NULL
, valid_from DATE NOT NULL
, valid_until DATE
);
INSERT INTO daterange (id, valid_from, valid_until) VALUES
(1, '2012-11-12', '2012-12-02' )
,(2, '2012-12-03', NULL)
,(3, '2012-12-15', '2012-12-21' )
,(4, '2012-12-22', NULL )
;
-- The CTE is for convenience; could be a subquery or view
WITH magic AS (
SELECT dr.id
, dr.valid_from
, COALESCE(dr.valid_until , lead(valid_from) OVER ww) AS valid_until
FROM daterange dr
WINDOW ww AS (ORDER BY dr.valid_from )
)
SELECT *
FROM magic ma
WHERE now() BETWEEN ma.valid_from AND ma.valid_until
;
Try something like this:
SELECT *
FROM validity_period
WHERE valid_from <= current_date
AND valid_to >=current_date
UNION
--Gets the last instance where valid_until is null
SELECT *
FROM validity_period
WHERE valid_from <= current_date
AND valid_to IS NULL
ORDER BY id DESC LIMIT 1
Simply remove the DESC if you want the first rather than the last null field
The valid_until is logically redundant in the data you give as an example, and you might get a faster result with this logic if an index on valid_from is present and PostgreSQL will perform a descending index scan:
select *
from validity_period
where valid_from <= current_date
order by valid_from desc
limit 1
Check the explain plan to see if it is an improvement on larger data sets.
Edit: If there can be a situation where the "last" record has a valid until date prior to today, where no rows are to be returned, try:
select most_recent_record.*
from (
select *
from validity_period
where valid_from <= current_date
order by valid_from desc
limit 1) most_recent_record
where coalesce(most_recent_record.valid_until,current_date) <= current_date
That last predicate might read better as:
where not (most_recent_record.valid_until > current_date)
You question is not very clear. Can there be more than one matches with non-null valid_until? Which to pick? What do you want to return? One column? Several? All?
Anyhow, what you posted as solution can be simplified to:
SELECT *
FROM validity_period
WHERE valid_from <= now()::date
AND (valid_until >= now()::date OR valid_until IS NULL) -- parenthesis needed!
ORDER BY valid_from DESC
LIMIT 1;
Related
Hi I am developing a stocks portfolio tracker. As part of this I am pulling all my listed stocks from one website which gives a performance % of 1 week, 1 month, YTD and 1 year. Some of my other investments which I am needing to pull figures for individually only gives the current price as of that day.
I am storing all my data in a mysql database using python (pymysql library currently). What I want to know is how can I do a lookup of a value at the various time periods. I know how I could just do lookup 1 week etc. however sometimes there will be no data on that particular day and I need to just get the either the closest record or the last prior or the next record (not that fussy).
tblHistory with fields rDate, rStockID, rPrice among other fields
Query something like this but varDate1Week is an approximate which won't always have an "EXACT" match
"SELECT rPrice FROM tblHistory WHERE (rStockID = " + str(varStockID) + " AND rDate = " + varDate1Week + ")"
My Sample Code after Luuk's assistance.
import datetime
import pymysql
db = pymysql.connect(host='localhost',user='Test',password='TestPassword',database='dbInvest')
varrDate2 = datetime.datetime.now().strftime("%Y-%m-%d")
cursor2 = db.cursor(pymysql.cursors.DictCursor)
sqllookup = "SELECT rPrice FROM tblHistory WHERE rStockID = 7 and rDate >= DATE_ADD('2021-06-20', INTERVAL - 7 DAY) ORDER BY rDATE LIMIT 1"
cursor2.execute(sqllookup)
Perform1WeekList = cursor2.fetchall()
print ("1 Week Value is " + str(Perform1WeekList[0]))
cursor2.close
The output I get from this is
1 Week Value is {'ClosePrice': Decimal('86.7400')}
Would like to be able to use variable varrDate2 in place of hard coded date. Also to have clean output (ie. 86.7400). I could do it with a split and then a replace on the string but sure there is a better way.
In MySQL you can do:
SELECT
t1.rPrice,
(SELECT t2.rPrice
FROM tblHistory t2
WHERE t2.rStockID = t1.rStockID
and t2.rDate <= DATE_ADD(t1.rDate,INTERVAL -7 DAY)
ORDER BY r2.rDate DESC
LIMIT 1) "1weekago"
(SELECT t3.rPrice
FROM tblHistory t3
WHERE t3.rStockID = t1.rStockID
and t3.rDate <= DATE_ADD(t1.rDate,INTERVAL -30 DAY)
ORDER BY r3.rDate DESC
LIMIT 1) "30daysago"
FROM tblHistory t1
WHERE (t1.rStockID = 1 AND t1.rDate = '2021-06-19')
;
I have a table that logs session activities from the users. These are the fields: id | name | category | total_steps | created_at | training_id | user_id (fk) | completed_at
I want to display a chart in my frontend that will show the activity per day for all users like in this picture:
My backend uses Python and SQL and I'm using MySQL as database. I came up with the sql query like this:
SELECT user_id, COUNT(user_id) as total_logins,
SUM(TIMEDIFF(completed_at,created_at)) as total_time_spent
from metrics_session
where created_at >= '2021-04-01'
AND created_at <= '2021-04-02'
AND completed_at IS NOT NULL
GROUP BY user_id
which for a table with the following data:
it would give me a result like this:
which is correct, however as you can see these would not give me the details break down by each day. I would like a result like this:
(just fictitious values that might not be in accordance to the data present in table, but the point is, when you sum up all the total_logins and total_time_spent it should be the same amount as given by the previous picture with the results. Only difference is the range of dates being break down by each day)
so the solution I initially came up was to call this query looping for each day, and just changing the date parameters used in this part
where created_at >= '2021-04-01'
AND created_at <= '2021-04-02'
Obviously that is not efficient as it would require the same query to be called 30 times for a monthly activity, so I decided it's better to do that manipulation in Python and just have a single simple query selecting all the data from the full range. However I want to improve my SQL skills and I was wondering if there is any way I can do that within SQL?
You need to add the date in order to get all the points you want to paint, and stop to group by user_id, to get the full picture without grouping by users
SELECT
DATE_FORMAT(created_at, '%Y-%m-%d') as day,
count(user_id) as total_logins,
sum(TIMEDIFF(completed_at,created_at)) as total_time_spent
from metrics_session
where created_at >= '2021-04-01'
AND created_at <= '2021-04-02'
AND completed_at IS NOT NULL
GROUP BY DATE_FORMAT(created_at, '%Y-%m-%d')
If you wanted to show this chart to single users, you could add to the query the user_id as another filter in the where clause.
SELECT
DATE_FORMAT(created_at, '%Y-%m-%d') as day,
count(user_id) as total_logins,
sum(TIMEDIFF(completed_at,created_at)) as total_time_spent
from metrics_session
where created_at >= '2021-04-01'
AND created_at <= '2021-04-02'
AND completed_at IS NOT NULL
AND user_id = $your_user_id -- here for user view
GROUP BY DATE_FORMAT(created_at, '%Y-%m-%d')
I try to figure out how to write function with SQLite3 statement which is responsible for informing me about expiry date of any medicine in advance let's suppose 30 days. I did sth like this but it doesn't work properly
l1top = Label(fr,text="Number of serie:")
l1top.grid(row=0,column=0,padx=20,sticky=E,pady=10)
l2top = Label(fr,text="Name of medicine:")
l2top.grid(row=1,column=0,padx=20,sticky=E,pady=10)
l3top = Label(fr,text="Dose")
l3top.grid(row=3,column=0,padx=20,sticky=E,pady=10)
l4top = Label(fr,text="Type of medicine")
l4top.grid(row=4,column=0,padx=20,sticky=E,pady=10)
l5top = Label(fr,text="Packages:")
l5top.grid(row=5,column=0,padx=20,sticky=E,pady=10)
l5top = Label(fr,text="Bottles:")
l5top.grid(row=6,column=0,padx=20,sticky=E,pady=10)
l6top = Label(fr,text="Expiry Date:")
l6top.grid(row=7,column=0,padx=20,sticky=E,pady=10)
def expiry():
conn = sqlite3.connect("pharmacy.db")
cur = conn.cursor()
cur.execute('SELECT date FROM medicine WHERE date <= 30')
matched = [rec[0] for rec in cur]
conn.close()
items = [row for row in tree.get_children() if tree.item(row, 'values')[6] in matched]
tree.selection_set(items)
expiry()
The code above doesn't select properly because it matches only according to days but it does not include the whole date from the widget DateEntry(below). How to rewrite the SQLite statement that it grabs the whole date and matches all products with date which expiry ends in 30 days and highlights on red the last column ([6]) with date.
e6 = DateEntry(fr,width=12,bg="darkblue",fg="white",year=2020,state="readonly",date_pattern="dd/mm/yyyy",textvariable=six)
e6.grid(row=7,column=1,pady=10)
If the format of the column date is DD-MM-YYYY, first you must change it to YYYY-MM-DD, because this is the only valid format for SQLite:
UPDATE medicine
SET date = SUBSTR(date, -4) || '-' || SUBSTR(date, 4, 2) || '-' || SUBSTR(date, 1, 2);
and then use the function DATE() to get the rows where date is between now and now + 30 days:
SELECT date
FROM medicine
WHERE date BETWEEN DATE('now') AND DATE('now', '+30 day')
I want to extract just the date part from a timestamp in PostgreSQL.
I need it to be a postgresql DATE type so I can insert it into another table that expects a DATE value.
For example, if I have 2011/05/26 09:00:00, I want 2011/05/26
I tried casting, but I only get 2011:
timestamp:date
cast(timestamp as date)
I tried to_char() with to_date():
SELECT to_date(to_char(timestamp, 'YYYY/MM/DD'), 'YYYY/MM/DD')
FROM val3 WHERE id=1;
I tried to make it a function:
CREATE OR REPLACE FUNCTION testing() RETURNS void AS '
DECLARE i_date DATE;
BEGIN
SELECT to_date(to_char(val1, "YYYY/MM/DD"),"YYYY/MM/DD")
INTO i_date FROM exampTable WHERE id=1;
INSERT INTO foo(testd) VALUES (i);
END
What is the best way to extract date (yyyy/mm/dd) from a timestamp in PostgreSQL?
You can cast your timestamp to a date by suffixing it with ::date. Here, in psql, is a timestamp:
# select '2010-01-01 12:00:00'::timestamp;
timestamp
---------------------
2010-01-01 12:00:00
Now we'll cast it to a date:
wconrad=# select '2010-01-01 12:00:00'::timestamp::date;
date
------------
2010-01-01
On the other hand you can use date_trunc function. The difference between them is that the latter returns the same data type like timestamptz keeping your time zone intact (if you need it).
=> select date_trunc('day', now());
date_trunc
------------------------
2015-12-15 00:00:00+02
(1 row)
Use the date function:
select date(timestamp_field) from table
From a character field representation to a date you can use:
select date(substring('2011/05/26 09:00:00' from 1 for 10));
Test code:
create table test_table (timestamp_field timestamp);
insert into test_table (timestamp_field) values(current_timestamp);
select timestamp_field, date(timestamp_field) from test_table;
Test result:
Have you tried to cast it to a date, with <mydatetime>::date ?
In postgres simply :
TO_CHAR(timestamp_column, 'DD/MM/YYYY') as submission_date
This works for me in python 2.7
select some_date::DATE from some_table;
Just do select date(timestamp_column) and you would get the only the date part.
Sometimes doing select timestamp_column::date may return date 00:00:00 where it doesn't remove the 00:00:00 part. But I have seen date(timestamp_column) to work perfectly in all the cases. Hope this helps.
CREATE TABLE sometable (t TIMESTAMP, d DATE);
INSERT INTO sometable SELECT '2011/05/26 09:00:00';
UPDATE sometable SET d = t; -- OK
-- UPDATE sometable SET d = t::date; OK
-- UPDATE sometable SET d = CAST (t AS date); OK
-- UPDATE sometable SET d = date(t); OK
SELECT * FROM sometable ;
t | d
---------------------+------------
2011-05-26 09:00:00 | 2011-05-26
(1 row)
Another test kit:
SELECT pg_catalog.date(t) FROM sometable;
date
------------
2011-05-26
(1 row)
SHOW datestyle ;
DateStyle
-----------
ISO, MDY
(1 row)
You can use date_trunc('day', field).
select date_trunc('day', data_gps) as date_description from some_table;
I am using Tweepy API and storing tweets in a sqlite3 database. I am able to get the date and time using a function in Tweepy. it stores the value in the database as
2015-06-06 23:06:19
I then need to determine if the age of this date is greater than 30 days if so it needs to delete the row in the database.
c.execute ("DELETE FROM storedTweets WHERE tweetDate < DATE() - 30")
This is what I have so far but the query isn't deleting entries.
I'm sure this is simple, but I'm new to this. Thanks in advance
DATE()-30 does not return a date:
sqlite> select DATE();
2015-06-06
sqlite> select DATE()-30;
1985
But using the date function,
sqlite> select date('now','-30 days');
2015-05-07
Therefore,
c.execute("DELETE FROM storedTweets WHERE tweetDate < date('now','-30 days')")
Alternatively, you could compute the date in Python:
import datetime as DT
date = DT.date.today()-DT.timedelta(days=30)
c.execute("DELETE FROM storedTweets WHERE tweetDate < ?", [date])