Postgres function with select - postgresql

I want to display as a function this:
select LoanId, BookId ,(OutDate-InDate)*0.3 as money, OutDate-InDate days
from loans
where (OutDate-InDate)> 30 and OutDate::varchar(4)='2000';
As a input there is only year, as a output should return select.
I tried make function, but it doesn't work.
Any Idea?

I've a table like below
create table loan(loanid int,bookid int,outdate date,indate date);
and some data like this
insert into loan values(1,2,'01-12-2015','01-10-2015'),(1,2,'01-01-2016','08-01-2016');
and I use following select query
select loanid,bookid,(outdate-indate)*0.3 as "money",outdate-indate as "days"
from loan
where (OutDate-InDate)> 30 and extract(year from outdate)='2015'
loanid bookid money days
------ ------ ----- ----
1 2 18.3 61
Now I've a function to do the job that is
create or replace function FN_LOAN_DET(in_year text)
returns table (loanid int,bookid int,money numeric,days int)
as
$$
select loanid,bookid,(outdate-indate)*0.3 as "money",outdate-indate as "days"
from loan
where (OutDate-InDate)> 30 and extract(year from outdate)=$1
$$
language sql
when I call select * from FN_LOAN_DET('2015') I'll get
loanid bookid money days
------ ------ ----- ----
1 2 18.3 61

Related

Converting Daily Snapshots to Ranges in PostgreSQL

I have a very large table with years' worth of daily snapshots, showing what the data looks like each day. For the sake of illustration the table looks something like this:
Part Qty Snapshot
---- ---- --------
A 5 1/1/2015
B 10 1/1/2015
A 5 1/2/2015
B 10 1/2/2015
A 6 1/3/2015
B 10 1/3/2015
A 5 1/4/2015
B 10 1/4/2015
I would like to implement a slowly changing data methodology and collapse this data into a form that would look like this (assume current date is 1/4/15)
Part Qty From Thru Active
---- ---- -------- -------- ------
A 5 1/1/2015 1/2/2015 I
B 10 1/1/2015 1/4/2015 A
A 6 1/3/2015 1/3/2015 I
A 5 1/4/2015 1/4/2015 A
I have a function that runs daily so when I capture the latest snapshot, I convert it to this methodology. This function runs once the data is actually loaded into the table with an active flag of 'C' (current), from the giant table (which is actually in DB2).
This works for me moving forward (once I have all past dates loaded), but I'd like to have a way to do this in one fell swoop, for all existing dates and convert the individual snapshot dates into ranges.
For what it's worth, my current method is to run this function for every possible date value. While it's working, it's quite slow, and I have several years worth of history to process as I loop one day at a time.
Tables:
create table main.history (
part varchar(25) not null,
qty integer not null,
from_date date not null,
thru_date date not null,
active_flag char(1)
);
create table stage.history as select * from main.history where false;
create table partitioned.history_active (
constraint history_active_ck1 check (active_flag in ('A', 'C'))
) inherits (main.history);
create table partitioned.history_inactive (
constraint history_active_ck1 check (active_flag = 'I')
) inherits (main.history);
Function to process a day's worth of new data:
CREATE OR REPLACE FUNCTION main.capture_history(new_date date)
RETURNS null AS
$BODY$
DECLARE
rowcount integer := 0;
BEGIN
-- partitioned.history_active already has a current snapshot for new_date
truncate table stage.history;
insert into stage.history
select
part, qty,
min (from_date), max (thru_date),
case when max (thru_date) = new_date then 'A' else 'I' end
FROM
partitioned.history_active
group by
part_qty;
truncate table partitioned.history_active;
insert into partitioned.history_active
select * from stage.history
where active_flag = 'A';
insert into partitioned.history_inactive
select * from stage.history
where active_flag = 'I';
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;

TSQL Join to get all records from table A for each record in table B?

I have two tables:
PeriodId Period (Periods Table)
-------- -------
1 Week 1
2 Week 2
3 Week 3
EmpId PeriodId ApprovedDate (Worked Table)
----- -------- ------------
1 1 Null
1 2 2/28/2013
2 2 2/28/2013
I am trying to write a query that results in this:
EmpId Period Worked ApprovedDate
----- -------- --------- ------------
1 Week 1 Yes Null
1 Week 2 Yes 2/28/2013
1 Week 3 No Null
2 Week 1 No Null
2 Week 2 Yes 2/28/2013
2 Week 3 No Null
The idea is that I need each Period from the Periods table for each Emp. If there was no record in the Worked table then the 'No' value is placed Worked field.
What does the TSQL look like to get this result?
(Note: if it helps I also have access to an Employee table that has EmpId and LastName for each employee. For performance reasons I'm hoping not to need this but if I do then so be it.)
You should be able to use the following:
select p.empid,
p.period,
case
when w.PeriodId is not null
then 'Yes'
else 'No' End Worked,
w.ApprovedDate
from
(
select p.periodid, p.period, e.empid
from periods p
cross join (select distinct EmpId from worked) e
) p
left join worked w
on p.periodid = w.periodid
and p.empid = w.empid
order by p.empid
See SQL Fiddle with Demo

PostgreSql - Adding YEar and Month to Table

I am creating a Customer table and i want one of the attributes to be Expiry Date of credit card.I want the format to be 'Month Year'. What data type should i use? i want to use date but the format is year/month/day. Is there any other way to restrict format to only Month and year?
You can constrain the date to the first day of the month:
create table customer (
cc_expire date check (cc_expire = date_trunc('month', cc_expire))
);
Now this fails:
insert into customer (cc_expire) values ('2014-12-02');
ERROR: new row for relation "customer" violates check constraint "customer_cc_expire_check"
DETAIL: Failing row contains (2014-12-02).
And this works:
insert into customer (cc_expire) values ('2014-12-01');
INSERT 0 1
But it does not matter what day is entered. You will only check the month:
select
date_trunc('month', cc_expire) > current_date as valid
from customer;
valid
-------
t
Extract year and month separately:
select extract(year from cc_expire) "year", extract(month from cc_expire) "month"
from customer
;
year | month
------+-------
2014 | 12
Or concatenated:
select to_char(cc_expire, 'YYYYMM') "month"
from customer
;
month
--------
201412
Use either
char(5) for two-digit years, or
char(7) for four-digit years.
Code below assumes two-digit years, which is the form that matches all my credit cards. First, let's create a table of valid expiration dates.
create table valid_expiration_dates (
exp_date char(5) primary key
);
Now let's populate it. This code is just for 2013. You can easily adjust the range by changing the starting date (currently '2013-01-01'), and the "number" of months (currently 11, which lets you get all of 2013 by adding from 0 to 11 months to the starting date).
with all_months as (
select '2013-01-01'::date + (n || ' months')::interval months
from generate_series(0, 11) n
)
insert into valid_expiration_dates
select to_char(months, 'MM') || '/' || to_char(months, 'YY') exp_date
from all_months;
Now, in your data table, create a char(5) column, and set a foreign key reference from it to valid_expiration_dates.exp_date.
While you're busy with this, think hard about whether "exp_month" might be a better name for that column than "exp_date". (I think it would.)
As another idea you could essentially create some brief utilities to do this for you using int[]:
CREATE OR REPLACE FUNCTION exp_valid(int[]) returns bool LANGUAGE SQL IMMUTABLE as
$$
SELECT $1[1] <= 12 AND (select count(*) = 2 FROM unnest($1));
$$;
CREATE OR REPLACE FUNCTION first_invalid_day(int[]) RETURNS date LANGUAGE SQL IMMUTABLE AS
$$
SELECT (to_date($1[2]::text || $1[1]::text, CASE WHEN $1[2] < 100 THEN 'YYMM' ELSE 'YYYYMM' END) + '1 month'::interval)::date;
$$;
These work:
postgres=# select exp_valid('{04,13}');
exp_valid
-----------
t
(1 row)
postgres=# select exp_valid('{13,04}');
exp_valid
-----------
f
(1 row)
postgres=# select exp_valid('{04,13,12}');
exp_valid
-----------
f
(1 row)
Then we can convert these into a date:
postgres=# select first_invalid_day('{04,13}');
first_invalid_day
-------------------
2013-05-01
(1 row)
This use of arrays does not violate any normalization rules because the array as a whole represents a single value in its domain. We are storing two integers representing a single date. '{12,2}' is December of 2002, while '{2,12}' is Feb of 2012. Each represents a single value of the domain and is therefore perfectly atomic.

How to select total time from three time columns (SQL Server 2008)

I have table with columns of time(0) datatypes:
Name TimeOne TimeTwo TimeThree
------ ---------------- ---------------- ----------------
Sarah 06:45:00 03:30:00 NULL
John 06:45:00 NULL NULL
How to make SELECT statement so that the "CalculatedTotal" column is total from TimeOne, TimeTwo and TimeThree per row?
What I'd like to have is select query like this:
SELECT
Name,
TimeOne,
TimeTwo,
TimeThree,
TimeOne + TimeTwo + TimeThree As CalculatedTotal
FROM
MyTable
I'd like to get back resultset like this:
Name TimeOne TimeTwo TimeThree CalculatedTotal
------ ---------------- ---------------- ---------------- ---------------
Sarah 06:45:00 03:30:00 NULL 10:15:00
John 06:45:00 NULL NULL 06:45:00
Just using plus operator in select statement gives you an error:
Operand data type time is invalid for add operator.
You could determine the number of seconds for each time value, add them together and convert back to time:
select TimeOne, TimeTwo, TimeThree,
cast(dateadd(s, isnull(datediff(s, 0, TimeOne), 0) + isnull(datediff(s, 0, TimeTwo), 0) + isnull(datediff(s, 0, TimeThree), 0), 0) as time(0)) as CalculatedTotal
from MyTable
Try the below script.
select convert(time, CONVERT(datetime, '00:08:00.000') + CONVERT(datetime, '00:07:00.000'))
select convert(time,cast('00:08:00.000'as datetime)+cast('00:07:00.000' as datetime) ) as 'T'
I belive you can use the SUM() function
SELECT
Name,
TimeOne,
TimeTwo,
TimeThree,
SUM(TimeOne+TimeTwo+TimeThree) As CalculatedTotal
FROM
MyTable
But equally you might need to convert each to seconds first using the SECOND() function, then use SEC_TO_TIME() on the result.
Edit: I've just had a look at the manual:
The SUM() and AVG() aggregate functions do not work with temporal
values. (They convert the values to numbers, which loses the part
after the first nonnumeric character.) To work around this problem,
you can convert to numeric units, perform the aggregate operation, and
convert back to a temporal value. Examples:
SELECT SEC_TO_TIME(SUM(TIME_TO_SEC(time_col))) FROM tbl_name;
SELECT FROM_DAYS(SUM(TO_DAYS(date_col))) FROM tbl_name;

Getting a range of dates for PostgreSQL

I would like to get the entries for a range of dates, using PostgreSQL.
For example...
beginyear endyear name
--------- ------- -----
950 1100 jason
1300 1400 andrew
I would like to get all people who lived in 1050. Could someone please help me to write this query.
select name
from yourtable
where 1050 between beginyear and endyear;
SELECT * FROM mytable
WHERE beginyear <= 1050 AND 1050 <= endyear