Dynamic Date Table - sql

I am creating a Data Model in PowerPivot and am wondering if there is anyway I can create a dynamic date table in SQL. I was able to create one in PowerQuery however there are some bugs in PowerQuery(read only connection) when a table is modified in PowerPivot. What I am looking for is to have a start date of 1/1/2013 (interval is days) and as each new year rolls around rows are added to the date table. Is there anyway to do this?
I am running Postgres
So far I came up with this,
SELECT * FROM dbo.fof_GetDates('1/1/2013', GETDATE())
But I want it to display all dates till end of the year.

The completely dynamic approach would be a query based on generate_series():
SELECT the_date::date
FROM generate_series('2013-01-01 0:0'::timestamp
, date_trunc('year', now()::timestamp)
+ interval '1 year - 1 day'
, interval '1 day') the_date;
Always use ISO 8601 format for dates and timestamps, which works irregardless of locale settings.
A final cast to date (the_date::date), because the function returns timestamp (when fed timestamp arguments).
The expression
date_trunc('year', now()::timestamp) + interval '1 year - 1 day'
calculates the last day of the current year. Alternatively you could use EXTRACT (year FROM now())::text || '-12-31')::date, but that's slower.
You can wrap this into a custom "table-function" (a.k.a. set-returning function) that you can basically use as drop-in replacement for a table name in queries:
CREATE OR REPLACE FUNCTION f_dates_since_2013()
RETURNS SETOF date AS
$func$
SELECT the_date::date
FROM generate_series('2013-01-01 0:0'::timestamp
, date_trunc('year', now()::timestamp)
+ interval '1 year - 1 day'
, interval '1 day') the_date;
$func$ LANGUAGE sql STABLE;
Example:
SELECT * FROM f_dates_since_2013();
Going one step further, you could create a table or - more elegantly - a MATERIALIZED VIEW based on this function (or the underlying query directly):
CREATE MATERIALIZED VIEW my_dates(the_date) AS
SELECT * FROM f_dates_since_2013();
Call:
SELECT * FROM my_dates;
All you have to do now is to schedule a yearly cron job that runs REFRESH MATERIALIZED VIEW at the start of each new year:
REFRESH MATERIALIZED VIEW my_dates;

Related

How to save temporary session variables in PostgreSQL

Say I have a simple SQL statement like this:
SELECT * from birthday WHERE date < now() + INTERVAL '1 day' AND date > now() - INTERVAL '1 day';
but the equations for the "From" and "To" variables would be something more sophisticated than that. And I will be reusing the results of these equations in multiple queries. So, can I store them temporarily as we do in any programming language? I imagine something like:
$from := now() - INTERVAL '1 day';
$to:= now() + INTERVAL '1 day';
SELECT * from birthday WHERE date < $from AND date > $to;
I tried using SELECT INTO as suggested in this question but that's not what I want because it creates a whole database table just to save the variable, which also causes an error when reusing it in a later session even when using the TEMP parameter. It says "relationship already exists"!
I also tried some dollar sign $ syntax and some colon+equal := syntax and none works
SQL is not any programming language. If you want to store the values so you can re-use them in one query, then you can use a CTE:
WITH params as (
SELECT now() - INTERVAL '1 day' as _from,
now() + INTERVAL '1 day' as _to
)
SELECT *
FROM params CROSS JOIN
birthday b
WHERE date < params._to AND date > params._from;
If you want to repeat this across multiple queries, then I would recommend a temporary table:
CREATE TEMPORARY TABLE params AS
SELECT now() - INTERVAL '1 day' as _from,
now() + INTERVAL '1 day' as _to;
SELECT *
FROM params CROSS JOIN
birthday b
WHERE date < params._to AND date > params._from;
You can also encapsulate the code in a procedure/function. Or use some sort of scripting language or language such as Python.

How to get all rows during a day with a specific column value

I have multiple rows in a table with a column named "created" that has a timestamp in the format "1309494407". I need to do a SQL query to get how many rows there are that has a certain value of column called "category" with the value of "book". Basically I need to know how many books are created every day in the database.
How is this accomplished with PostgreSQL?
My schema:
Items(id, category, created)
This is what I've tried so far:
SELECT
*
FROM
my_schema.items
WHERE
(category = 'books' AND TO_TIMESTAMP(created) < NOW() - INTERVAL '1 days')
And it doesn't work I get problem:
function to_timestamp(character varying) does not exist
Basically I need to know how many books are created every day in the database.
Your timestamp looks like a Unix epoch. If so, you can get what you want using simple arithmetic:
SELECT floor(i.created / (24 * 60 * 60)) as dy, COUNT(*)
FROM my_schema.items i
WHERE i.category = 'books'
GROUP BY dy;
If you like, you can convert it to a calendar date using:
SELECT '1970-01-01' + floor(i.created / (24 * 60 * 60)) * interval '1 day' as dy, COUNT(*)
FROM my_schema.items i
WHERE i.category = 'books'
GROUP BY dy;
What a wonder example of why Unix epoch is such a poor choice for storing dates, and a text representation of the number at that. Always store dates/timestamps with the appropriate type. If needed the epoch is easily retrieved from a date, but deriving the date from epoch is - well ugly. And actual timestamp even uglier.
If you happen to to be on version 12 you can at least capture the date in a table column itself, without having to change the code already using the existing column. Create a generated column that derives the actual date. (Guess that would make an interesting round trip, given a date derive the epoch then given the epoch derive the date).
alter table items
add created_date date generated always as
('1970-01-01'::timestamp + (created::integer/ 86400) * interval '1 day')
stored;
Now this query reduces itself to the simple and straight forward:
select i.created_date,count(*)
from my_schema.items i
where i.category = 'books'
group by i.created_date
order by i.created_date;
It also has the additional benefit of making all the Postgres date handling functions available.

How to run PostgreSQL Query every day with updated values?

New to SQL, but trying to learn/do a job for a friend. I have a query set up that returns the number of bookings for the day. Example snippet:
...
WHERE be.event IN ('instant_approve', 'approve') AND TO_CHAR(be.created_at, 'yyyy-mm-dd') BETWEEN '2017-06-26' AND '2017-06-26';
Now, this query is set up for just today. How can I set this up so that tomorrow the query is executed for '2017-06-27' and so on? Is this possible?
Built-in function now() gives you a timestamp of the beginning of your transaction (CURRENT_TIMESTAMP pseudo-constant is its "alias", a part of SQL standard, but I prefer using the function).
Another function, date_trunc() gives you a "truncated" timestamp and you can choose, how to truncate it. E.g., date_trunc('day', now()) will give you the date/time of beginning of the current day.
Also, you can add or subtract intervals easily, so here is an example that gives you the beginning of the current and the next days:
select
date_trunc('day', now()) cur_day_start,
date_trunc('day', now() + interval '1 day') as next_day_start
;
Also, I would not use to_char() or anything else on top of created_at column -- this will not allow Postgres planner use index on top of this field. If you do want to use to_char(), then consider adding a functional index over to_char(created_at, 'yyyy-mm-dd').
Here is how I would retrieve records generated at July 26, 2017:
where
created_at between '2017-06-26' and '2017-06-27'
(implicit type casting from text to timestamps here)
This is equivalent to
where
created_at >= '2017-06-26'
and created_at <= '2017-06-27'
-- so all timestamps generated for July 26, 2017 will match. And for such query Postgres will use a regular index created for created_at column (if any).
"Dynamic" version:
where
created_at between
date_trunc('day', now())
and date_trunc('day', now()) + interval '1 day'
Use current_date built-in function in the between condition and it will work only for today's bookings.
.........
WHERE be.event IN ('instant_approve', 'approve') AND TO_CHAR(be.created_at, 'yyyy-mm-dd') =current_date;

PostgreSQL: SELECT * from table WHERE timestamp IS WITHIN THIS MONTH

For some reason I'm kind of lost on how to archive:
SELECT * FROM table WHERE timestamp IS WITHIN THIS MONTH;
I've looked at https://www.postgresql.org/docs/9.4/static/functions-datetime.html, but are only able to select X days backwards.
I'm running PostgreSQL 9.4
... WHERE date_trunc('month', timestamp)
= date_trunc('month', current_timestamp);
Alternatively:
... WHERE timestamp >= date_trunc('month', current_timestamp)
AND timestamp < date_trunc('month', current_timestamp) + INTERVAL '1 month';
The second version can use an index on timestamp, the first would need one on the expression date_trunc('month', timestamp).
Why don't you just filter the month with between ?
Pass the start of this month as variable1, and the end of this month as variable2...
SELECT * FROM table WHERE
timestamp >= __month_start AND timestamp < __next_month_start
e.g.
SELECT * FROM table
WHERE
(
timestamp >= '20170701'::timestamp
AND
timestamp < '20170801'::timestamp
)
Unlike using functions in the where-clause, this maintains sargability.
What Laurenz Albe suggested will work, however you're going to have a performance penalty because you'll lose cardinality on that field, you either have to index expression you're going to query (Apparently PostgreSQL allows to do that: https://www.postgresql.org/docs/current/static/indexes-expressional.html) or create a separate column to store yyyy-mm values and query it.

PostgreSQL: SELECT integers as DATE or TIMESTAMP

I have a table where I have multiple integer columns: year, month and day. Unfortunately, while the three should have been grouped into one DATE column from the beginning, I am now stuck and now need to view it as such. Is there a function that can do something along the lines of:
SELECT makedate(year, month, day), othercolumn FROM tablename;
or
SELECT maketimestamp(year, month, day, 0, 0), othercolumn FROM tablename;
You can
SELECT format('%s-%s-%s', "year", "month", "day")::date
FROM ...
or use date maths:
SELECT DATE '0001-01-01'
+ ("year"-1) * INTERVAL '1' YEAR
+ ("month"-1) * INTERVAL '1' MONTH
+ ("day"-1) * INTERVAL '1' DAY
FROM ...
Frankly, it's surprising that PostgreSQL doesn't offer a date-constructor like you describe. It's something I should think about writing a patch for.
In fact, a quick look at the sources shows that there's an int date2j(int y, int m, int d) function at the C level already, in src/backend/utils/adt/datetime.c. It just needs to be exposed at the SQL level with a wrapper to convert to a Datum.
OK, now here's a simple makedate extension that adds a single function implemented in C, named makedate. A pure-SQL version is also provided if you don't want to compile and install an extension. I'll submit the C function for the 9.4 commitfest; meanwhile that extension can be installed to provide a fast and simple date constructor:
regress=# SELECT makedate(2012,01,01);
makedate
------------
2012-01-01
(1 row)
PostgreSQL 9.4+
In PostgreSQL 9.4, a function was added to do just this
make_date(year int, month int, day int)
There may be a more elegant method, but this will give you a date.
select to_date(to_char(year * 10000 + month * 100 + day,'00000000'), 'yyyymmdd')
from tablename;
Try something like:
SELECT year * interval '1 year' +
month * interval '1 month' +
day * interval '1 day'
FROM tablename;