Why this runtime error?
ERROR: column reference "arrive" is ambiguous
LINE 6: case when ( (cast('05:00' as time) >= arrive)
DETAIL: It could refer to either a PL/pgSQL variable or a table column.
The thing is, the case statement is in a query, and I'm selecting from a table that does have a column called "arrive". I have not declared a variable called arrive. Why can't PG simply check to see that there's no such variable declared, and conclude that the reference must be to the column?
I am attached the code below. The only ostensible conflict I see is in the definition of the outgoing table returned by this function, in which there's a column called "arrive", and the use of the column name in the final select against the temporary table TT.
CREATE or replace function GetHourlyView
(v_whichDate date)
returns TABLE
(
stagecoach,
arrive time,
depart time,
t5am int,t6am int,t7am int,t8am int,t9am int,t10am int, t11am int,
t12pm int, t1pm int, t2pm int t3pm int,t4pm int,t5pm int,t6pm int,
t7pm int, t8pm int, t9pm int, t10pm int, t11pm int
)
as $body$
declare v_dow int := date_part('dow',v_whichDate);
begin
drop table if exists TT;
create temp table TT
(stagecoach varchar(25),
arrive time,
depart time,
t5am int,t6am int,t7am int,t8am int,t9am int,t10am int, t11am int,
t12pm int, t1pm int, t2pm int t3pm int,t4pm int,t5pm int,t6pm int,
t7pm int, t8pm int, t9pm int, t10pm int, t11pm int
) without OIDS on commit drop;
insert into TT
select * from
GetDailySchedule( v_whichDate);
-- (arrive=depart) means 'cancelled'
delete from TT where TT.arrive=TT.depart;
return QUERY
select
TT.stagecoach,
arrive,
depart,
case when ( (cast('05:00' as time) >= arrive) and (cast('05:00' as time) < depart )) then 1 else 0 end as t5am,
case when ( (cast('06:00' as time) >= arrive) and (cast('06:00' as time) < depart )) then 1 else 0 end as t6am,
<snip>
.
.
.
case when ( (cast('23:00' as time) >= arrive) and (cast('23:00' as time) < depart )) then 1 else 0 end as t11pm
from TT
;
drop table TT;
end
$body$
LANGUAGE 'plpgsql'
It's rather simple really: function parameters are visible everywhere inside the function body (except for dynamic SQL). That's true for all parameters: IN, OUT, INOUT, VARIADIC and any column name used in a RETURNS TABLE clause.
You also had a couple of other minor errors.
Avoid conflicts by table-qualifying column names: tt.arrive instead of just arrive.
Missing type for stagecoach in RETURNS TABLE.
Don't single-quote plpgsql at the end. It's an identifier.
I would also advise to adjust your syntax style. You are living in opposite world. The convention is to upper case SQL key words and lower case identifiers, not the other way round. Remember that PostgreSQL automatically casts unquoted identifiers to lower case.
All this aside, your function can be largely simplified to a plain SQL query. I wrapped it into an SQL function:
CREATE OR REPLACE FUNCTION gethourlyview(v_whichdate date)
RETURNS TABLE (
stagecoach text, arrive time, depart time
, t5am int, t6am int, t7am int, t8am int, t9am int, t10am int, t11am int
, t12pm int, t1pm int, t2pm int, t3pm int, t4pm int, t5pm int, t6pm int
, t7pm int, t8pm int, t9pm int, t10pm int, t11pm int
) AS
$body$
SELECT tt.stagecoach
,tt.arrive -- "depart" would cause conflict
,tt.depart
,CASE WHEN '05:00'::time >= tt.arrive
AND '05:00'::time < tt.depart THEN 1 ELSE 0 END -- AS t5am
,...
,CASE WHEN '23:00'::time >= tt.arrive
AND '23:00'::time < tt.depart THEN 1 ELSE 0 END -- AS t11pm
FROM getdailyschedule($1) tt
WHERE tt.arrive IS DISTINCT FROM tt.depart;
$body$ LANGUAGE sql;
No need to create a temporary table. You can do it all in one statement.
Related
I want to generate random numbers in PostgreSQL just like I have done in MySQL like below. I want to do so in a Postgres function.
MySQL:
DROP PROCEDURE IF EXISTS Generate_random;
DELIMITER $$
CREATE PROCEDURE Generate_random()
BEGIN
Drop table if exists aa_dev.`Agents`;
CREATE TABLE aa_dev.`Agents`(AgentID int PRIMARY KEY);
SET #first = 1;
SET #last = 1000;
WHILE(#first <= #last) Do
INSERT INTO aa_dev.`Agents` VALUES(FLOOR(RAND()*(2900000-2800000+1)+2800000))
ON DUPLICATE KEY UPDATE AgentID = FLOOR(RAND()*(2900000-2800000+1)+2800000);
IF ROW_COUNT() = 1 THEN
SET #first = #first + 1;
END IF;
END WHILE;
END$$
DELIMITER ;
CALL Generate_random();
I have so far generated random numbers in Postgres but they are getting repeated in the column. Please tell me how can I achieve the above MySQL code in PostgreSQL.
drop function if exists aa_dev.rand_cust(low INT, high INT, total INT);
CREATE OR REPLACE FUNCTION aa_dev.rand_cust(low INT ,high INT, total INT)
RETURNS TABLE (Cust_id int) AS
$$
declare
counter int := 0;
rand int := 0;
begin
------------------- Creating a customer table with Cust_id----------------------------
DROP TABLE IF EXISTS aa_dev.Customer;
CREATE TABLE IF NOT EXISTS aa_dev.Customer (
Cust_id INT
);
--------------------- Loop to insert random -----------------------
while counter < total loop
rand = floor(random()* (high-low + 1) + low);
Insert into aa_dev.Customer (Cust_id) values(rand);
counter := counter + 1;
end loop;
return query
select *
from aa_dev.customer;
end
$$
LANGUAGE plpgsql;
select * from aa_dev.rand_cust(1, 50, 100);
For Postgres you've asked for 100 numbers between 1 and 50 - there will naturally be duplicates!
The MySQL code has a much wider range of possible values (100000) and only 1000 of them are sampled. Also the MySQL code generates random numbers until there is no key error, i.e. there are no duplicates in the column.
So, for Postgres, you could try checking for duplicates and retrying if found. Making the column unique will prevent duplicate insertion, but you have to handle it.
Also, a sample size that is larger than the number of values is required. Be careful with the retries, don't replicate the MySQL example. If the sample size is smaller than the required count, the loop will never terminate.
Update
Here is a function that will generate unique random numbers within a range and populate a table with them:
DROP FUNCTION IF EXISTS rand_cust (low INT, high INT, total INT);
CREATE OR REPLACE FUNCTION rand_cust (low INT, high INT, total INT)
RETURNS TABLE (Cust_id INT)
AS
$$
BEGIN
------------------- Creating a customer table with Cust_id----------------------------
DROP TABLE IF EXISTS Customer;
CREATE TABLE IF NOT EXISTS Customer(Cust_id INT);
RETURN query
INSERT INTO Customer(Cust_id)
SELECT *
FROM generate_series(low, high)
ORDER BY random() LIMIT total
RETURNING -- returns the id's you generated
Customer.Cust_id;
END $$
LANGUAGE plpgsql;
SELECT *
FROM rand_cust(1000, 2000, 100); -- 100 unique numbers between 1000 and 2000 inclusive
Note that this will not be able to generate more numbers than the sample size, e.g. you can't generate 100 numbers between 1 and 50, only a maximum of 50. That's a consequence of the uniqueness requirement. The LIMIT clause will not cause errors, but you could add code to check that (hi - low) >= total before attempting the query.
If you'd prefer a simple function to generate n random unique numbers:
DROP FUNCTION IF EXISTS sample(low INT, high INT, total INT);
CREATE OR REPLACE FUNCTION sample(low INT, high INT, total INT)
RETURNS TABLE (Cust_id INT)
AS
$$
BEGIN
RETURN query
SELECT *
FROM generate_series(low, high)
ORDER BY random() LIMIT total;
END $$
LANGUAGE plpgsql;
-- create a table of unique random values
SELECT INTO Customer FROM sample(100, 200, 10);
As said before, you have a range between 1 and 50 and you want to create 100 records. That will never be unique. And your query doesn't ask for unique values anyway, so even with a million records you can have duplicates.
But, your code can be much simpler as well, without a loop and just a single query:
DROP FUNCTION IF EXISTS aa_dev.rand_cust ( low INT, high INT, total INT );
CREATE OR REPLACE FUNCTION aa_dev.rand_cust ( low INT, high INT, total INT )
RETURNS TABLE ( Cust_id INT )
AS
$$
BEGIN
------------------- Creating a customer table with Cust_id----------------------------
DROP TABLE IF EXISTS aa_dev.Customer;
CREATE TABLE IF NOT EXISTS aa_dev.Customer ( Cust_id INT );
--------------------- No Loop to insert random -----------------------
RETURN query
INSERT INTO aa_dev.Customer ( Cust_id )
SELECT FLOOR ( random( ) * ( high - low + 1 ) + low ) -- no uniqueness!
FROM generate_series(1, total) -- no loop needed
RETURNING -- returns the id's you generated
Customer.Cust_id;
END $$
LANGUAGE plpgsql;
SELECT
*
FROM
aa_dev.rand_cust ( 1, 50, 100 );
I have a Postgres function which is returning a table:
CREATE OR REPLACE FUNCTION testFunction() RETURNS TABLE(a int, b int) AS
$BODY$
DECLARE a int DEFAULT 0;
DECLARE b int DEFAULT 0;
BEGIN
CREATE TABLE tempTable AS SELECT a, b;
RETURN QUERY SELECT * FROM tempTable;
DROP TABLE tempTable;
END;
$BODY$
LANGUAGE plpgsql;
This function is not returning data in row and column form. Instead it returns data as:
(0,0)
That is causing a problem in Coldfusion cfquery block in extracting data. How do I get data in rows and columns when a table is returned from this function? In other words: Why does the PL/pgSQL function not return data as columns?
To get individual columns instead of the row type, call the function with:
SELECT * FROM testfunction();
Just like you would select all columns from a table.
Also consider this reviewed form of your test function:
CREATE OR REPLACE FUNCTION testfunction()
RETURNS TABLE(a int, b int)
LANGUAGE plpgsql AS
$func$
DECLARE
_a int := 0;
_b int := 0;
BEGIN
CREATE TABLE tempTable AS SELECT _a, _b;
RETURN QUERY SELECT * FROM tempTable;
DROP TABLE tempTable;
END
$func$;
In particular:
The DECLARE key word is only needed once.
Avoid declaring parameters that are already (implicitly) declared as OUT parameters in the RETURNS TABLE (...) clause.
Don't use unquoted CaMeL-case identifiers in Postgres. It works, unquoted identifiers are cast to lower case, but it can lead to confusing errors. See:
Are PostgreSQL column names case-sensitive?
The temporary table in the example is completely useless (probably over-simplified). The example as given boils down to:
CREATE OR REPLACE FUNCTION testfunction(OUT a int, OUT b int)
LANGUAGE plpgsql AS
$func$
BEGIN
a := 0;
b := 0;
END
$func$;
Of course you can do this by putting the function call in the FROM clause, like Eric Brandstetter correctly answered.
However, this is sometimes complicating in a query that already has other things in the FROM clause.
To get the individual columns that the function returns, you can use this syntax:
SELECT (testfunction()).*
Or to get only the column called "a":
SELECT (testfunction()).a
Place the whole function, including the input value(s) in parenteses, followed by a dot and the desired column name, or an asterisk.
To get the column names that the function returns, you'll have to either:
check the source code
inspect the result of the function first, like so : SELECT * FROM testfunction() .
The input values can still come out of a FROM clause.
Just to illustrate this, consider this function and test data:
CREATE FUNCTION funky(a integer, b integer)
RETURNS TABLE(x double precision, y double precision) AS $$
SELECT a*random(), b*random();
$$ LANGUAGE SQL;
CREATE TABLE mytable(a integer, b integer);
INSERT INTO mytable
SELECT generate_series(1,100), generate_series(101,200);
You could call the function "funky(a,b)", without the need to put it in the FROM clause:
SELECT (funky(mytable.a, mytable.b)).*
FROM mytable;
Which would result in 2 columns:
x | y
-------------------+-------------------
0.202419687062502 | 55.417385618668
1.97231830470264 | 63.3628275180236
1.89781916560605 | 1.98870931006968
(...)
I have table with a lot of columns. I want to create a function which returns all these column, with an additional column. Is there a way to do this type-safe (that is, without returning a record) without having to repeat all column names and types?
For example:
create table t
(
t1 int,
t2 int,
t3 text,
t4 boolean
);
create function extra_t() returns table(t1 int, t2 int, t3 text, t4 boolean, extra text) as
$$
select t.*, 'example'::text from t;
$$ language sql
It's quite annoying that I have to repeat t1 int, t2 int, t3 text, t4 boolean in the function definition.
I am with you in your struggle, and I don't know of a good way to do this.
That said, this is a hacky way to do this. It does shift the burden of specifying individual fields in the return type to the actual function text, but it does make the whole thing a bit more digestible, in my opinion.
create table t2 (
extra_text text
) inherits (t);
create or replace function extra_t() returns setof t2 as
$$
select t.*, 'example'::text from t;
$$ language sql;
Like I said, it's not a good way. It's just a way.
this function doesnt work if i use it in Sum operator.It says" Operand data type nvarchar is sum operator.invalid for".What is the easisest solution for it?I use this function in many places that gives result numeric or nvarchar.Thanks for your help.
USE [DSS]
GO
/****** Object: UserDefinedFunction [dbo].[DECODE] Script Date: 03/22/2011 09:30:20 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[DECODE]
(#Val1 nvarchar(20),#Val2 nvarchar(10),#Val3 nvarchar(10),#Val4 nvarchar(10))
RETURNS nvarchar(10)
AS
BEGIN
RETURN
(
Select case #Val1 When #Val2 then #Val3 else #Val4 end
)
END
Sum works on ints. Change datatype to int and it should work for you.
CREATE FUNCTION [dbo].[DECODE]
(#Val1 int,#Val2 int,#Val3 int,#Val4 int)
RETURNS int
AS
BEGIN
RETURN
(
Select case #Val1 When #Val2 then #Val3 else #Val4 end
)
END
Or cast the result to int in the sum
select sum(cast(dbo.decode('1','1','3','4') as int))
is there any way of accomplishing something like the following:
CREATE FUNCTION GetQtyFromID
(
#oricod varchar(15),
#ccocod varchar(15),
#ocmnum int,
#oinnum int,
#acmnum int,
#acttip char(2),
#unisim varchar(15)
)
AS
RETURNS DECIMAL(18,8)
BEGIN
DECLARE #Result decimal(18,8)
DECLARE #SQLString nvarchar(max);
DECLARE #ParmDefinition nvarchar(max);
--I need to execute a query stored in a cell which returns the calculated qty.
--i.e of AcuQry: select #cant = sum(smt) from table where oricod = #oricod and ...
SELECT #SQLString = AcuQry
FROM OinActUni
WHERE (OriCod = #oricod) AND (ActTipCod = #acttip) AND (UniSim = #unisim) AND (AcuEst > 0)
SET #ParmDefinition = N'
#oricod varchar(15),
#ccocod varchar(15),
#ocmnum int,
#oinnum int,
#acmnum int,
#cant decimal(18,8) output';
EXECUTE sp_executesql #SQLString, #ParmDefinition,
#oricod = #oricod,
#ccocod = #ccocod,
#ocmnum = #ocmnum,
#oinnum = #oinnum,
#acmnum = #acmnum,
#cant = #result OUTPUT;
RETURN #Result
END
The problem with this approach is that it is prohibited to execute sp_excutesql in a function...
What I need is to do something like:
select id, getQtyFromID(id) as qty
from table
The main idea is to execute a query stored in a table cell, this is because the qty of something depends on it's unit. the unit can be days or it can be metric tons, so there is no relation between the units, therefore the need of a specific query for each unit.
What about using an if then or case expression in a stored procedure to check the unit, then perform specific calculations based on the type of unit?