SQL: Turning Rows into Columns for Variable Number of Rows - sql

I have two tables whose simplified structure looks like this:
RESPONSES
id
created
ACCESSORY VALUES
id
response_id
sensor_id
value
I want to create a view that flattens all accessory values for a given response into one row over a time period (filtering on response.created). I think I want a Pivot table or Crosstab, but I'm unfamiliar with both and the examples I've found mainly deal with a known number of columns. To complicate things, a given sensor could only appear for part of the time period if a user started or stopped tracking it during the time in question. Ideally I'd hold a NULL in that column for the sensor in any rows when it was not present. Is this possible? If so, am I on the right track or looking in the wrong place?
The SQL to get the data as individual rows looks like
SELECT r.id, a.sensor_id, a.value from results_response r
INNER JOIN results_accessoryvalue a ON r.id = a.response_id
WHERE r.installation_id = 40
AND r.created BETWEEN '2013-04-01' AND '2013-05-01'
ORDER BY r.created
but I'm not having any luck trying to use it in a crosstab because I don't know how to specify dynamic columns.

you should use crosstab with some additons. i had this problem too and solved it for my proposal like this.
first install crosstab extension
the trick i used is to create 2 additional functions. one to get the type information, needed as resultset for crosstab function. look at this:
CREATE OR REPLACE FUNCTION "TEMPORARY.TYPE.FROM.COLUMN"(text, text)
RETURNS text AS
$BODY$
DECLARE
typestring TEXT;
returnrec RECORD;
BEGIN
typestring := '';
FOR returnrec IN EXECUTE $1 LOOP
typestring := typestring||', "'||returnrec."Column"||'" '||$2;
END LOOP;
RETURN typestring;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
Now we can write our second function:
CREATE OR REPLACE FUNCTION "DYNAMIC.CROSSTAB"(text, text, text)
RETURNS text AS
$BODY$
DECLARE
typestring TEXT;
executestring TEXT;
BEGIN
DROP TABLE IF EXISTS "TBL.Crosstab";
SELECT "REPORTING"."TEMPORARY.TYPE.FROM.COLUMN"($2,$3) INTO typestring;
executestring := 'CREATE TEMPORARY TABLE "TBL.Crosstab" AS (SELECT * FROM crosstab('''||$1||''','''||$2||''') AS (row_name DATE'||typestring||'));';
EXECUTE executestring;
RETURN '"TBL.Crosstab"';
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
To create your crosstab simply call:
SELECT * FROM "DYNAMIC.CROSSTAB"(text, text, text);
It returns the name of an temporary table for your session filled with your result. I needed it this way.
Parameter explanation:
First = The query to get your data (must have 3 columns: row_name, cat and value)
Second = The query to get your columns wich returns all your categories (cat)
Third = The columntype for our temporary type
it's not perfect but fit's our needs. we have statements, fetching more than 450 coloumns and some thounds rows at this way. hope it helps

This is an alternative approach using PostgreSQL arrays
create table responses (
id serial not null unique,
x integer,
created timestamp default now()
);
create table accessory (
id serial not null,
responses_id integer references responses (id),
sensor_id integer,
value text,
created timestamp default now()
);
insert into responses (x) values ( 1), (2),(3),(4);
insert into accessory (responses_id , sensor_id, value ) values
( 1, 1, 'A' ), ( 1, 2, 'Ab' ), ( 1, 3, 'Ac' ), ( 1, 4, 'Ad' ),
( 2, 4, 'Ab' ), ( 1, 2, 'bAb' ), ( 3, 3, 'bAc' ), ( 4, 4, 'bAd' );
select *, array(
select value
from accessory
where accessory.responses_id = responses.id
order by accessory.created
) as accessory_values
from responses;
Query result includes an array column with all the accessory values that match response.id

Related

Table variable join equivalent in Oracle

I'm given quite a huge table My_Table and a user-defined collection Picture_Arr as an input usually having 5-10 rows declared as:
TYPE Picture_Rec IS RECORD (
seq_no NUMBER,
task_seq NUMBER);
TYPE Picture_Arr IS TABLE OF Picture_Rec;
In MS SQL I would normally write something like:
DECLARE #Picture_Arr TABLE (seq_no INT, task_seq INT)
SELECT M.*
FROM My_Table M
INNER JOIN #Picture_Arr A
ON M.seq_no = A.seq_no AND M.task_seq = A.task_seq
But I can't get my head around how to re-write the same code in Oracle as Picture_Arr is not a table. As some tutorials state that I could've looped through My_Table and compare keys, but is it efficient in Oracle or is there another way of doing that?
Perhaps this is what you are looking for. It is a bit complicated to understand what is the desired output, and whether the data of the record is stored somewhere or not
create type Picture_Rec as object(
seq_no NUMBER,
task_seq NUMBER);
)
/
create type Picture_Tab as table of Picture_Rec
/
create or replace function get_picture_list
return Picture_Tab
is
l_pic Picture_Tab;
begin
select Picture_Rec ( seqno, taskseq )
bulk collect into l_pic
from your_table; -- the table you have these records
return l_pic;
end;
/
Then you run
SELECT M.*
FROM My_Table M
JOIN TABLE ( get_picture_list() ) p
ON M.seq_no = p.seq_no AND M.task_seq = p.task_seq

Function to select existing value or insert new row

I'm new to working with PL/pgSQL, and I'm attempting to create a function that will either find the ID of an existing row, or will insert a new row if it is not found, and return the new ID.
The query contained in the function below works fine on its own, and the function gets created fine. However, when I try to run it, I get an error stating "ERROR: column reference "id" is ambiguous". Can anybody identify my problem, or suggest a more appropriate way to do this?
create or replace function sp_get_insert_company(
in company_name varchar(100)
)
returns table (id int)
as $$
begin
with s as (
select
id
from
companies
where name = company_name
),
i as (
insert into companies (name)
select company_name
where not exists (select 1 from s)
returning id
)
select id
from i
union all
select id
from s;
end;
$$ language plpgsql;
This is how I call the function:
select sp_get_insert_company('TEST')
And this is the error that I get:
SQL Error [42702]: ERROR: column reference "id" is ambiguous
Detail: It could refer to either a PL/pgSQL variable or a table column.
Where: PL/pgSQL function sp_get_insert_company(character varying) line 3 at SQL statement
As the messages says, id is in there twice. Once in the queries, once in the table definition of the return type. Somehow this clashes.
Try qualifying the column expressions, everywhere.
...
with s as (
select
companies.id
from
companies
where name = company_name
),
i as (
insert into companies (name)
select company_name
where not exists (select 1 from s)
returning companies.id
)
select i.id
from i
union all
select s.id
from s;
...
By qualifying the column expression the DBMS does no longer confuse id of a table with the id in the return type definition.
The next problem will be, that your SELECT has no target. It will tell you to do a PERFORM instead. But I assume you want to return the results. Change the body to
...
RETURN QUERY (
with s as (
select
companies.id
from
companies
where name = company_name
),
i as (
insert into companies (name)
select company_name
where not exists (select 1 from s)
returning companies.id
)
select i.id
from i
union all
select s.id
from s);
...
to do so.
In the function you display there is no need for returns table (id int). It's supposed to always return exactly one integer ID. Simplify to RETURNS int. This also makes ERROR: column reference "id" is ambiguous go away, since we implicitly removed the OUT parameter id (visible in the whole function block).
How to return result of a SELECT inside a function in PostgreSQL?
There is also no need for LANGUAGE plpgsql. Could simply be LANGUAGE sql, then you wouldn't need to add RETURNS QUERY, either. So:
CREATE OR REPLACE FUNCTION sp_get_insert_company(_company_name text)
RETURNS int AS
$func$
WITH s as (
select c.id -- still good style to table-qualify all columns
from companies c
where c.name = _company_name
),
i as (
insert into companies (name)
select _company_name
where not exists (select 1 from s)
returning id
)
select s.id from s
union all
select i.id from i
LIMIT 1; -- to optimize performance
$func$ LANGUAGE sql;
Except that it still suffers from concurrency issues. Find a proper solution for your undisclosed version of Postgres in this closely related answer:
Is SELECT or INSERT in a function prone to race conditions?

Selecting and passing a record as a function argument

It may look like a duplicate of existing questions (e.g. This one) but they only deal with passing "new" arguments, not selecting rows from the database.
I have a table, for example:
CREATE TABLE my_table (
id bigserial NOT NULL,
name text,
CONSTRAINT my_table_pkey PRIMARY KEY (id)
);
And a function:
CREATE FUNCTION do_something(row_in my_table) RETURNS void AS
$$
BEGIN
-- does something
END;
$$
LANGUAGE plpgsql;
I would like to run it on data already existing in the database. It's no problem if I would like to use it from another PL/pgSQL stored procedure, for example:
-- ...
SELECT * INTO row_var FROM my_table WHERE id = 123; -- row_var is of type my_table%rowtype
PERFORM do_something(row_var);
-- ...
However, I have no idea how to do it using an "ordinary" query, e.g.
SELECT do_something(SELECT * FROM my_table WHERE id = 123);
ERROR: syntax error at or near "SELECT"
LINE 1: SELECT FROM do_something(SELECT * FROM my_table ...
Is there a way to execute such query?
You need to pass a scalar record to that function, this requires to enclose the actual select in another pair of parentheses:
SELECT do_something( (SELECT * FROM my_table WHERE id = 123) );
However the above will NOT work, because the function only expects a single column (a record of type my_table) whereas select * returns multiple columns (which is something different than a single record with multiple fields).
In order to return a record from the select you need to use the following syntax:
SELECT do_something( (SELECT my_table FROM my_table WHERE id = 123) );
Note that this might still fail if you don't make sure the select returns exactly one row.
If you want to apply the function to more than one row, you can do that like this:
select do_something(my_table)
from my_table;

Creating a table which can auto update if some column in other table got changed

I have created two tables in postgresql say a and b.
So what i want if i change data in table 'a' data in table 'b' automatically got updated at the same time.
Or if data in table be such that b.salary= 2000+x where x is variable and it will be passed to table.
If any one of the problem get solved i can carry out my work further.
Thanks in Advance
Example:
CREATE TABLE test.x (
value integer
);
CREATE FUNCTION test.value_calc(_arg integer) RETURNS integer AS
$$
DECLARE
C_MODIFIER CONSTANT integer = 1000; -- ;)
BEGIN
RETURN _arg + C_MODIFIER; -- there you can put any logic
END;
$$
LANGUAGE plpgsql IMMUTABLE;
CREATE VIEW test.y AS SELECT x.value, test.value_calc(x.value) AS modified FROM test.x AS x;
INSERT INTO test.x VALUES (543);
SELECT * FROM test.y;
value| modified
-----+----------
543| 1543

RETURN QUERY-Record in PostgreSQL

I am trying to write a PostgreSQL function that inserts data in the database and then receives some data and returns it.
Here is the code:
CREATE OR REPLACE FUNCTION newTask(projectid api.objects.projectid%TYPE, predecessortaskid api.objects.predecessortaskid%TYPE, creatoruserid api.objects.creatoruserid%TYPE, title api.objects.title%TYPE, description api.objects.description%TYPE, deadline api.objects.deadline%TYPE, creationdate api.objects.creationdate%TYPE, issingletask api.tasks.issingletask%TYPE)
RETURNS SETOF api.v_task AS
$$
DECLARE
v_objectid api.objects.objectid%TYPE;
BEGIN
INSERT INTO api.objects(objectid, projectid, predecessortaskid, creatoruserid, title, description, deadline, creationdate) VALUES (DEFAULT, projectid, predecessortaskid, creatoruserid, title, description, deadline, creationdate)
RETURNING objectid INTO v_objectid;
INSERT INTO api.tasks(objectid, issingletask) VALUES (v_objectid, issingletask);
RETURN QUERY (SELECT * FROM api.v_task WHERE objectid = v_objectid);
END;
$$ LANGUAGE plpgsql;
objects and tasks are both tables and v_task is a view, which is a join of the two. The reason why I return data that I just inserted is that there are some triggers doing work on it.
So far so good. I use RETURNS SETOF api.v_task as my return type and RETURN QUERY (...) and therefore expect the result to look like a SELECT from v_task (same columns with same data types). However, what really happens is (output from pgAdmin, same result from my node.js-application):
SELECT newTask( CAST(NULL AS integer), CAST(NULL AS integer), 1, varchar 'a',varchar 'a', cast(NOW() as timestamp(0) without time zone), cast(NOW() as timestamp(0) without time zone), true);
newtask
api.v_task
--------
"(27,,,1,a,a,"2012-03-19 12:15:50","2012-03-19 12:15:50","2012-03-19 12:15:49.629997",,t)"
Instead of several column the output is forced into one, separated by commas.
As I am already using a special record type I can't use the AS keyword to specify the fields of my output.
Calling a table function
To retrieve individual columns from a function returning multiple columns (effectively a composite type or row type), call it with:
SELECT * FROM func();
If you want to, you can also just SELECT some columns and not others. Think of such a function (also called table function) like of a table:
SELECT objectid, projectid, title FROM func();
Alternative here: plain SQL
If you use PostgreSQL 9.1 or later you might be interested in this variant. I use a writable CTE to chain the INSERTs.
One might be tempted to add the final SELECT as another module to the CTE, but that does not work in this case, because the newly inserted values are not visible in the view within the same CTE. So I left that as a separate command - without brackets around the SELECT:
CREATE OR REPLACE FUNCTION new_task (
_projectid api.objects.projectid%TYPE
,_predecessortaskid api.objects.predecessortaskid%TYPE
,_creatoruserid api.objects.creatoruserid%TYPE
,_title api.objects.title%TYPE
,_description api.objects.description%TYPE
,_deadline api.objects.deadline%TYPE
,_creationdate api.objects.creationdate%TYPE
,_issingletask api.tasks.issingletask%TYPE)
RETURNS SETOF api.v_task AS
$func$
DECLARE
_objectid api.objects.objectid%TYPE;
BEGIN
RETURN QUERY
WITH x AS (
INSERT INTO api.objects
( projectid, predecessortaskid, creatoruserid, title
, description, deadline, creationdate)
VALUES (_projectid, _predecessortaskid, _creatoruserid, _title
, _description, _deadline, _creationdate)
RETURNING objectid
)
INSERT INTO api.tasks
(objectid, issingletask)
SELECT x.objectid, _issingletask
FROM x
RETURNING objectid INTO _objectid;
RETURN QUERY
SELECT * FROM api.v_task WHERE objectid = _objectid;
END
$func$ LANGUAGE plpgsql;