Alter column type with non-trivial usage of "using" clause - postgresql

Assume we have this table named "mytable":
name [varchar]
eating_habits [int]
Anna
1
Roland
3
Sepp
1
Katrin
2
Lukas
4
Hedwig
3
Now I realize I want to change the colum vegetarian to be specific. I create my own enum type:
CREATE TYPE diet AS ENUM ('vegetarian', 'vegan', 'omni');
What I want is to change the type of the column "eating_habits" to "diet". To do this I also want to map between the types like this
1 --> 'vegan'
2 --> 'vegetarian'
rest --> 'omni'
How would I go about this? I know that one can use the USING clause like this:
ALTER TABLE mytable
ALTER COLUMN eating_habits TYPE diet
USING (
<Expression>
)
But I can't figure out what "Expression" should be, and the vast majority of examples online are trivial casts.

You need a CASE expression:
ALTER TABLE mytable
ALTER COLUMN eating_habits TYPE diet
USING (
case eating_habits
when 1 then 'vegan'::diet
when 2 then 'vegetarian'::diet
else 'omni'::diet
end
)

Related

Array of array in Postgres

In a Postgres DB I have a field field defined like this:
CREATE TABLE t (
id SERIAL PRIMARY KEY,
field character varying(255)[] DEFAULT ARRAY[]::character varying[],
);
There I store values like:
ID FIELD
1 {{lower,0},{greater,10}}
2 {{something_else,7},{lower,5}}
1 - How can I select the lower/greater value? I'd like a query response like this:
ID LOWER
1 0
2 5
2 - How can I filter by those lower/greater values?
Thanks!
It's pretty awkward to do but this accomplishes it. I use PG 9.3 so I don't know if there are better ways to do this in later versions.
SELECT id, (SELECT field[ss][2] FROM generate_subscripts(field, 1) ss WHERE field[ss][1] = 'lower') AS lower
FROM t;
Basically, for each record, generate the subscripts to use as indexes into the main array to access the subarrays. For each, look for an array where the first item is 'lower'. If found, return the value of the second item.

postgres `order by` argument type

What is the argument type for the order by clause in Postgresql?
I came across a very strange behaviour (using Postgresql 9.5). Namely, the query
select * from unnest(array[1,4,3,2]) as x order by 1;
produces 1,2,3,4 as expected. However the query
select * from unnest(array[1,4,3,2]) as x order by 1::int;
produces 1,4,3,2, which seems strange. Similarly, whenever I replace 1::int with whatever function (e.g. greatest(0,1)) or even case operator, the results are unordered (on the contrary to what I would expect).
So which type should an argument of order by have, and how do I get the expected behaviour?
This is expected (and documented) behaviour:
A sort_expression can also be the column label or number of an output column
So the expression:
order by 1
sorts by the first column of the result set (as defined by the SQL standard)
However the expression:
order by 1::int
sorts by the constant value 1, it's essentially the same as:
order by 'foo'
By using a constant value for the order by all rows have the same sort value and thus aren't really sorted.
To sort by an expression, just use that:
order by
case
when some_column = 'foo' then 1
when some_column = 'bar' then 2
else 3
end
The above sorts the result based on the result of the case expression.
Actually I have a function with an integer argument which indicates the column to be used in the order by clause.
In a case when all columns are of the same type, this can work: :
SELECT ....
ORDER BY
CASE function_to_get_a_column_number()
WHEN 1 THEN column1
WHEN 2 THEN column2
.....
WHEN 1235 THEN column1235
END
If columns are of different types, you can try:
SELECT ....
ORDER BY
CASE function_to_get_a_column_number()
WHEN 1 THEN column1::varchar
WHEN 2 THEN column2::varchar
.....
WHEN 1235 THEN column1235::varchar
END
But these "workarounds" are horrible. You need some other approach than the function returning a column number.
Maybe a dynamic SQL ?
I would say that dynamic SQL (thanks #kordirko and the others for the hints) is the best solution to the problem I originally had in mind:
create temp table my_data (
id serial,
val text
);
insert into my_data(id, val)
values (default, 'a'), (default, 'c'), (default, 'd'), (default, 'b');
create function fetch_my_data(col text)
returns setof my_data as
$f$
begin
return query execute $$
select * from my_data
order by $$|| quote_ident(col);
end
$f$ language plpgsql;
select * from fetch_my_data('val'); -- order by val
select * from fetch_my_data('id'); -- order by id
In the beginning I thought this could be achieved using case expression in the argument of the order by clause - the sort_expression. And here comes the tricky part which confused me: when sort_expression is a kind of identifier (name of a column or a number of a column), the corresponding column is used when ordering the results. But when sort_expression is some value, we actually order the results using that value itself (computed for each row). This is #a_horse_with_no_name's answer rephrased.
So when I queried ... order by 1::int, in a way I have assigned value 1 to each row and then tried to sort an array of ones, which clearly is useless.
There are some workarounds without dynamic queries, but they require writing more code and do not seem to have any significant advantages.

How to create a PostgreSQL column that is a set of enum values?

I like that I can create new enum types in PostgreSQL. But what if I want a column value that is a set of enum values. Do I need to implement that manually with an integer column type and bitwise operators, or is there a way to keep using the enums by name?
CREATE TYPE foo AS ENUM ('none', 'loud', 'bright', 'cheap')
CREATE TABLE t (
id serial,
properties [set of foo?]
)
...
SELECT * FROM t;
1 loud
2 loud, cheap
3 bright
4 none
...
You can use an array:
CREATE TYPE foo AS ENUM ('none', 'loud', 'bright', 'cheap');
CREATE TABLE t (
id serial,
properties foo[]
);

use two .nextval in an insert statement

I'm using oracle database and facing a problem where two id_poduct.nextval is creating as error: ORA-00001: unique constraint (SYSTEM.SYS_C004166) violated
It is a primary key. To use all is a requirement. Can I use 2 .nextval in a statement?
insert all
into sale_product values (id_product.nextval, id.currval, 'hello', 123, 1)
into sale_product values (id_product.nextval, id.currval, 'hi', 123, 1)
select * from dual;
insert into sale_product
select id_product.nextval, id.currval, a, b, c
from
(
select 'hello' a, 123 b, 1 c from dual union all
select 'hi' a, 123 b, 1 c from dual
);
This doesn't use the insert all syntax, but it works the same way if you are only inserting into the same table.
The value of id_product.NEXTVAL in the first INSERT is the same as the second INSERT, hence you'll get the unique constraint violation. if you remove the constraint and perform the insert, you'll notice the duplicate values!
The only way is to perform two bulk INSERTS in sequence or to have two seperate sequences with a different range, the latter would require an awful lot of coding and checking.
create table temp(id number ,id2 number);
insert all
into temp values (supplier_seq.nextval, supplier_seq.currval)
into temp values (supplier_seq.nextval, supplier_seq.currval)
select * from dual;
ID ID2
---------- ----------
2 2
2 2
Refrence
The subquery of the multitable insert statement cannot use a sequence
http://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_9014.htm#i2080134

Postgres query error

I have a query in postgres
insert into c_d (select * from cd where ak = '22019763');
And I get the following error
ERROR: column "region" is of type integer but expression is of type character varying
HINT: You will need to rewrite or cast the expression.
An INSERT INTO table1 SELECT * FROM table2 depends entirely on order of the columns, which is part of the table definition. It will line each column of table1 up with the column of table2 with the same order value, regardless of names.
The problem you have here is whatever column from cd with the same order value as c_d of the table "region" has an incompatible type, and an implicit typecast is not available to clear the confusion.
INSERT INTO SELECT * statements are stylistically bad form unless the two tables are defined, and will forever be defined, exactly the same way. All it takes is for a single extra column to get added to cd, and you'll start getting errors about extraneous extra columns.
If it is at all possible, what I would suggest is explicitly calling out the columns within the SELECT statement. You can call a function to change type within each of the column references (or you could define a new type cast to do this implicitly -- see CREATE CAST), and you can use AS to set the column label to match that of your target column.
If you can't do this for some reason, indicate that in your question.
Check out the PostgreSQL insert documentation. The syntax is:
INSERT INTO table [ ( column [, ...] ) ]
{ DEFAULT VALUES | VALUES ( { expression | DEFAULT } [, ...] ) | query }
which here would look something like:
INSERT INTO c_d (column1, column2...) select * from cd where ak = '22019763'
This is the syntax you want to use when inserting values from one table to another where the column types and order are not exactly the same.