Table: table_name
create table table_name
(
given_dates timestamp,
set_name varchar
);
Insertion of records:
insert into table_name values('2001-01-01'),('2001-01-05'),('2001-01-10'),
('2001-01-15'),('2001-01-20'),('2001-01-25'),
('2001-02-01'),('2001-02-05'),('2001-02-10'),
('2001-02-15');
Now I want to update set_name for some dates.
For example:
I want to update table like this:
given_dates set_name
----------------------
2001-01-01 s1
2001-01-05 s1
2001-01-10 s2
2001-01-15 s2
2001-01-20
2001-01-25
2001-02-01
2001-02-05
2001-02-10
2001-02-15
Note: The given_dates
and set_name
are pass a parameter because of they are dynamic. I may pass 2 sets
as shown above s1,s2
or may pass 4 sets according to the requirement.
So I need the dynamic case statement for update the set_name
.
Given two parameters:
declare p_dates varchar := '2001-01-01to2001-01-05,2001-01-10to2001-01-15';
declare p_sets varchar := 's1,s2';
Well I can do this by using following static script:
Static Update statement:
update table_name
SET set_name =
CASE
when given_dates between '2001-01-01' and '2001-01-05' then 's1'
when given_dates between '2001-01-10' and '2001-01-15' then 's2'
else ''
end;
The above update statement does the job done but statically.
Like the same way to update table I want to prepare only case statement which should be dynamic which can change as per the parameters (p_dates,p_sets)
changes.
Questions:
p_dates
? (I have to
keyword in between two dates.)p_sets
? (I have ',' comma in between two set_names.)p_dates
and p_sets
?This question relates to Dynamic case statement using SQL Server 2008 R2, which is the same thing but for Microsoft SQL Server.
Clean setup:
CREATE TABLE tbl (
given_date date
, set_name varchar
);
Use a singular term as column name for a single value.
The data type is obviously date
and not a timestamp
.
To transform your text parameters into a useful table:
SELECT unnest(string_to_array('2001-01-01to2001-01-05,2001-01-10to2001-01-15', ',')) AS date_range
, unnest(string_to_array('s1,s2', ',')) AS set_name;
"Parallel unnest" is handy but has its caveats. Postgres 9.4 adds a clean solution, Postgres 10 eventually sanitized the behavior of this. See below.
Prepared statements are only visible to the creating session and die with it. Per documentation:
Prepared statements only last for the duration of the current database session.
PREPARE
once per session:
PREPARE upd_tbl AS
UPDATE tbl t
SET set_name = s.set_name
FROM (
SELECT unnest(string_to_array($1, ',')) AS date_range
, unnest(string_to_array($2, ',')) AS set_name
) s
WHERE t.given_date BETWEEN split_part(date_range, 'to', 1)::date
AND split_part(date_range, 'to', 2)::date;
Or use tools provided by your client to prepare the statement.
Execute n times with arbitrary parameters:
EXECUTE upd_tbl('2001-01-01to2001-01-05,2001-01-10to2001-01-15', 's1,s4');
Functions are persisted and visible to all sessions.
CREATE FUNCTION
once:
CREATE OR REPLACE FUNCTION f_upd_tbl(_date_ranges text, _names text)
RETURNS void
LANGUAGE sql AS
$func$
UPDATE tbl t
SET set_name = s.set_name
FROM (
SELECT unnest(string_to_array($1, ',')) AS date_range
, unnest(string_to_array($2, ',')) AS set_name
) s
WHERE t.given_date BETWEEN split_part(date_range, 'to', 1)::date
AND split_part(date_range, 'to', 2)::date
$func$;
Call n times:
SELECT f_upd_tbl('2001-01-01to2001-01-05,2001-01-20to2001-01-25', 's2,s5');
Old sqlfiddle
Use array parameters (can still be provided as string literals), a daterange
type (both pg 9.3) and the new parallel unnest()
(pg 9.4).
CREATE OR REPLACE FUNCTION f_upd_tbl(_dr daterange[], _n text[])
RETURNS void
LANGUAGE sql AS
$func$
UPDATE tbl t
SET set_name = s.set_name
FROM unnest($1, $2) s(date_range, set_name)
WHERE t.given_date <@ s.date_range
$func$;
<@
being the "element is contained by" operator.
Call:
SELECT f_upd_tbl('{"[2001-01-01,2001-01-05]"
,"[2001-01-20,2001-01-25]"}', '{s2,s5}');
Details: