Postgres select concatenated column name - postgresql

For the sake of example, there's five columns in a table month:
month.week1
month.week2
month.week3
month.week4
month.week5
The number of col is determined by a function
EXTRACT(WEEK FROM NOW()) - EXTRACT(WEEK FROM DATE_TRUNC('month', NOW())) + 1
How can I select the column colX? This is what I have so far
SELECT month.week || (
EXTRACT(WEEK FROM NOW())
- EXTRACT(WEEK FROM DATE_TRUNC('month', NOW())) + 1
)::text
FROM month
But that gives me the error
ERROR: column month.week doesn't exist
SQL state: 42703

Use case statement
SELECT
CASE WHEN (week expresion) = 1 THEN month.week1
WHEN (week expresion) = 2 THEN month.week2
WHEN (week expresion) = 3 THEN month.week3
WHEN (week expresion) = 4 THEN month.week4
ELSE month.week5
END as WeekValue
FROM month
OR
SELECT
CASE (week expresion)
WHEN 1 THEN month.week1
WHEN 2 THEN month.week2
WHEN 3 THEN month.week3
WHEN 4 THEN month.week4
ELSE month.week5
END as WeekValue
FROM month

dynamic sql sample:
t=# create table so58(i int,w1 text);
CREATE TABLE
t=# create or replace function so59(_n int) returns table (i int,w text) as $$begin
return query execute format('select i,w%s from so58',_n);
end;
$$ language plpgsql;
CREATE FUNCTION
t=# select * from so59(1);
i | w
---+---
(0 rows)

Related

Create New Rows based on valid to and valid from dates

I have a table that has account number, end of month valid from and end of month valid to columns.
What I need is a table that has account number and a column that has all the end of month dates of when the account was live, inclusive of end of month valid to. The Current Table looks like this
New table will need to be like this
I have tried using a calendar table and an CTE table type query but have had no success.
Any help would be great.
This can be achieved using Using multiple comma separated CTEs in a statement
Query
with t0 (i) AS (select 0 union all select 0 union all select 0 union all select 0 union all select 0 union all select 0),
t1 (i) AS (select 0 from t0 a inner join t0 b on a.i = b.i),
n (i) AS (select row_number()over(order by i) from t1),
Account_details (Account_number,valid_from,valid_to,mth,Live_date)As(
select Account_number,valid_from,valid_to, datediff(month,valid_from,valid_to ) mth, valid_from"Live_date"
from tbl1
union all
select Account_number,valid_from,valid_to, datediff(month,valid_from,valid_to ) mth, EOMONTH (dateadd(month,n.i,valid_from)) "Live_date"
from tbl1
inner join n on 1=1 and n.i between 1 and datediff(month,valid_from,valid_to )
)
select *
from Account_details
where Account_details.Account_number =1
order by Account_details.Account_number
Output
CTE Table t0, t1 and n will generate numbers. This is a best way to generate rows without any data.
Then the CTE table Account_details is used to pull data from the table.
Based on sql on the msdn thread how to get month end date between two dates.
DECLARE #Old AS Table (AccountNumber INT, ValidFrom DATE, ValidTo DATE)
DECLARE #New AS Table (AccountNumber INT, LiveDate DATE)
INSERT INTO #old
SELECT 1, '20130630', '20131130' UNION ALL
SELECT 2, '20130630', '20131231' UNION ALL
SELECT 3, '20120430', '20120531' UNION ALL
SELECT 4, '20170331', '20171130'
SELECT TOP 100 * FROM #old
DECLARE #AccountNumber INT, #ValidFrom DATE, #ValidTo DATE
DECLARE #Cursor CURSOR
SET #Cursor = CURSOR FOR
SELECT AccountNumber, ValidFrom, ValidTo
FROM #old
OPEN #Cursor
FETCH NEXT INTO #Cursor FROM #AccountNumber, #ValidFrom, #ValidTo
WHILE ##FETCH_STATUS = 0
BEGIN
;WITH cteEndMonthDates (MonthEndDate)
AS
(
SELECT eomonth(#ValidFrom) AS MonthEndDate
UNION ALL
SELECT eomonth( dateadd(day, 1, MonthEndDate)) AS MonthEndDate
FROM cteEndMonthDates
WHERE MonthEndDate < eomonth(#ValidTo)
)
INSERT INTO #new (AccountNumber, LiveDate)
SELECT #AccountNumber, MonthEndDate
FROM cteEndMonthDates
FETCH NEXT FROM #Cursor INTO #AccountNumber, #ValidFrom, #ValidTo
END
CLOSE #Cursor
DEALLOCATE #Cursor
SELECT * FROM #New
Edit: Or without the cursor
DECLARE #Old AS Table (AccountNumber INT, ValidFrom DATE, ValidTo DATE)
DECLARE #New AS Table (AccountNumber INT, LiveDate DATE)
INSERT INTO #old
SELECT 1, '20130630', '20131130' UNION ALL
SELECT 2, '20130630', '20131231' UNION ALL
SELECT 3, '20120430', '20120531' UNION ALL
SELECT 4, '20170331', '20171130' UNION ALL
SELECT 5, '20180430', '20190131' UNION ALL
SELECT 6, '20160430', '20180531'
SELECT TOP 100 * FROM #old
;WITH cteEndMonthDates (AccountNumber, MonthEndDate)
AS
(
SELECT AccountNumber, eomonth(ValidFrom) AS MonthEndDate
FROM #Old
UNION ALL
SELECT x.AccountNumber, eomonth( dateadd(day, 1, MonthEndDate)) AS MonthEndDate
FROM cteEndMonthDates x
JOIN #Old o ON o.AccountNumber = x.AccountNumber
WHERE MonthEndDate < eomonth(ValidTo)
)
SELECT AccountNumber, MonthEndDate
FROM cteEndMonthDates
order by AccountNumber, MonthEndDate
This should work.
;WITH Span AS (
SELECT
AccountNumber,
ValidFrom AS Valid
FROM dbo.Input
UNION ALL
SELECT
AccountNumber,
DATEADD(DAY, 1, Span.Valid) AS Valid
FROM Span
WHERE DATEADD(DAY, 1, Span.Valid) <= (SELECT ValidTo FROM dbo.Input WHERE AccountNumber = Span.AccountNumber)
)
SELECT * FROM Span
ORDER BY Span.AccountNumber, Span.Valid
OPTION (MAXRECURSION 0);

PGSQL selecting columns on certain conditions

In a table I have 5 columns day1, day2... day5. Records in the table can have all days set to TRUE or few days set to TRUE.
Is there any way in PGSQL to select only those columns of a record which have boolean value as TRUE
Example:
My table is: Course, with columns as Course Name, Day1, Day2, Day3, Day4,Day5 with record set as
English,True,False,True,False,True
German,False,False,True,True,True
French,False,True,False,True,True
What I need to display as result set is:
English,Mon,Wed,Fri
German,Wed,Thu,Fri
French,Tue,Thu,Fri
I believe something like the following should do the job. It's a bit ugly because your schema isn't the most awesome. This should work on Postgres 9+
SELECT course, string_agg(day, ',') as days_of_week
FROM
(
SELECT course, 'Mon' as day FROM yourtable WHERE day1 = 'True'
UNION ALL
SELECT course, 'Tue' as day FROM yourtable WHERE day2 = 'True'
UNION ALL
SELECT course, 'Wed' as day FROM yourtable WHERE day3 = 'True'
UNION ALL
SELECT course, 'Thu' as day FROM yourtable WHERE day4 = 'True'
UNION ALL
SELECT course, 'Fri' as day FROM yourtable WHERE day5 = 'True'
) sub
Function like iif missed suddenly but you could to create it simply:
create or replace function iif(boolean, anyelement, anyelement = null) returns anyelement
language sql
immutable
as $$
select case when $1 is null then null when $1 then $2 else $3 end
$$;
then:
select
course_name,
concat_ws(',', iif(day1,'Mon'), iif(day2,'Tue'), iif(day3,'Wed'), iif(day4,'Thu'), iif(day5, 'Fri'))
from course;

Why won't Postgres filter my date range partitions?

I have a table that uses declarative partitioning (w00t!) to partition tables by date range - one year in my case.
When I query against the table - SELECT * FROM tbl WHERE date > date '2016-01-01', it works exactly as intended; only tables containing newer data are scanned.
When I specify a date using variables or functions (CURRENT_DATE, NOW(), etc), EXPLAIN says it scans every partition.
Things that work as intended:
SELECT * FROM tbl WHERE date > date '2016-01-01'
--
SELECT * FROM tbl WHERE date > '2016-01-01'::date
Things that scan all partitions unnecessarily:
SELECT * FROM tbl WHERE date > CURRENT_DATE
--
SELECT * FROM tbl WHERE date > NOW()
--
SELECT * FROM tbl WHERE date > (NOW() - 365)::date
--
SELECT * FROM tbl WHERE date > (SELECT (NOW()::date - 365)::date AS d)
-- Even CTEs are no dice:
WITH a AS (SELECT CURRENT_DATE AS d)
SELECT * FROM tbl, a WHERE date > a.d
-- Same with JOINs
SELECT w.*
FROM (CURRENT_DATE - 365 as d) a
LEFT JOIN wtf w ON w.date > a.d
..etc
I get the same behavior with other comparison operators - =, <, etc.
The docs say I don't need an idx on the field (which I don't anyways). I added one just in case and it did not help.
Why is this happening, and what can I do to stop it (preferably without adding complication to a simple query)?
Thanks to JustMe for answering this- see the comments on the OP.
The issue lies with when NOW() and CURRENT_TIMESTAMP are evaluated in relation to FROM; it's the same issue you see when you try to filter in a join ala WHERE join_table.a > from_table.b.
Supposing today is Jan 1, 1970, these queries
SELECT * FROM my_stuff WHERE date > NOW()::date;
--
SELECT * FROM my_stuff WHERE date > '1970-01-01'::date;
will necessarily produce an identical resultset but will not necessarily be evaluated in an identical way.
That's why this is happening and unfortunately, there doesn't seem to be a simple way to stop it. A function seems to be the best-ish option:
CREATE OR REPLACE FUNCTION myfunc()
RETURNS setof tbl
LANGUAGE 'plpgsql'
AS $$
DECLARE
n date := CURRENT_DATE - 365;
BEGIN
RETURN query EXECUTE $a$
SELECT * FROM tbl
WHERE date > $1;
$a$ using n;
END $$;
You can test this by changing RETURNS setof tbl to RETURNS setof text and SELECT... to EXPLAIN SELECT...

How to create a table with dates in sequence between range in Hive?

I'm trying to Create a table with column date, And I want to insert date in sequence between Range.
Here's what I have tried:
SET StartDate = '2009-01-01';
SET EndDate = '2016-06-31';
CREATE TABLE DateRangeTable(mydate DATE, qty INT);
INSERT INTO DateRangeTable VALUES (select a.Date, 0
from (
select current_date - INTERVAL (a.a + (10 * b.a) + (100 * c.a)) DAY as Date
from (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as a
cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as b
cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as c
) AS a where a.Date between '2019-01-01' and '2016-06-30');
This is the similar one:
select date_add(t.f1, t.start_r - pe.i) as date_range from (select '2022-01-01' as f1,datediff('2022-01-07','2022-01-01') as start_r,0 as end_r) t lateral view posexplode(split(space(start_r - end_r),' ')) pe as i,s;
You do not need VALUES keyword when using INSERT ... SELECT.
Working example:
set hivevar:start_date=2009-01-01;
set hivevar:end_date=2016-06-31;
CREATE TABLE DateRangeTable(mydate DATE, qty INT);
with date_range as
(--this query generates date range
select date_add ('${hivevar:start_date}',s.i) as dt
from ( select posexplode(split(space(datediff('${hivevar:end_date}','${hivevar:start_date}')),' ')) as (i,x) ) s
)
INSERT INTO TABLE DateRangeTable
select d.dt, 0 qty
from date_range d
where d.dt between '2019-01-01' and '2016-06-30');

Postgresql select date range between two tables

I have two tables that have date fields in them. I want to select data from table 1 where the date is +/- 1 day from any date in table 2.
try something like this :
select * from table1,table2
where table1.date BETWEEN (table2.date - '1 day'::interval)
AND (table2.date + '1 day'::interval)
and ...
If only +/- 1 day, you could use a workaround like this:
select col1, col2, ...
from table1
where date_col in (select distinct date_col
from table2
union all
select distinct (date_col - '1 day'::interval)
from table2
union all
select distinct (date_col + '1 day'::interval)
from table2
);
This has quite good peformance because the subquery only be calculated one time and will be cache for comparing