How to convert string to integer in amazon redshift - amazon-redshift

I have a column that holds an id, currently as string. if the id is indeed a number I need to convert it to a real integer and if not, it should be converted to a null value. I would like to run an update query on the table and create a new integer id field.
I was unable to find exactly how to determine if the string is a number
Does any one know?
Thanks
Nir

Since Redshift does not support modifying a column type, it's better to create another table with your desired schema. The way is simply inserting a varchar column value into integer and insert it into a new table.
Here is an example:
dev=> CREATE TABLE table_varchar_id (id varchar(24), val varchar(24));
CREATE TABLE
dev=> INSERT INTO table_varchar_id values ('1111', 'aaaa'),('2222', 'bbbb'),('dummy1', 'cccc'),('dummy2', 'dddd');
INSERT 0 4
dev=> CREATE TABLE table_int_id (id int, val varchar(24));
CREATE TABLE
dev=>
dev=> INSERT INTO table_int_id (
dev(> SELECT
dev(> CASE REGEXP_COUNT(id, '^[0-9]+$')
dev(> WHEN 0 then NULL
dev(> ELSE id::integer
dev(> END as "id",
dev(> val
dev(> FROM
dev(> table_varchar_id
dev(> );
INSERT 0 4
dev=> SELECT * FROM table_varchar_id ORDER BY id;
id | val
--------+------
1111 | aaaa
2222 | bbbb
dummy1 | cccc
dummy2 | dddd
(4 rows)
dev=> SELECT * FROM table_int_id ORDER BY id;
id | val
------+------
1111 | aaaa
2222 | bbbb
| dddd
| cccc
(4 rows)

Related

Apply a sequence based on the value of another column

I would like to create a sequence that starts with 1000 and increments by one unit. Then, apply this sequence to the variable "identification" of my table "person". The number of records in the table "person" is 958, so it should increment to the end by default. In addition, I want the numbering to be based on the Age field sorted in descending order. It's to say: the field 'Identification' has no records, it has NULL values. When I say sort by age, I mean that the one with the youngest age will be assigned the ID number 1000, the second youngest will be assigned 1001 and so on.
I have tried to do something similar to the following but I get no results. I have also tried to put an order by age desc in the middle of the sentence also without result. Any idea to do it only using sequences please?
TABLE PERSON (Name, Surname, City, Identification, Age)
CREATE SEQUENCE seq
START WITH 1000
INCREMENT BY 1
MINVALUE 1000;
ALTER TABLE person
ADD COLUMN identification integer DEFAULT nextval('seq');
A quick example using dummy data:
create table age_seq_test(age int , fld_1 varchar, id integer);
insert into age_seq_test values (10, 'test'), (30, 'test2'), (20, 'test3');
select * from age_seq_test order by age;
age | fld_1 | id
-----+-------+------
10 | test | NULL
30 | test2 | NULL
20 | test3 | NULL
BEGIN;
with t as
(select age, row_number() over (order by age) as rn from age_seq_test)
update
age_seq_test AS ast
set
id = t.rn + 999
from
t
where
ast.age = t.age ;
select * from age_seq_test order by age;
age | fld_1 | id
-----+-------+------
10 | test | 1000
20 | test3 | 1001
30 | test2 | 1002
--COMMIT/ROLLBACK depending on what the SELECT shows.

Efficiently copying a tree modelled with adjacency list in postgres

I have the following table:
CREATE TABLE tree_node (
id serial primary key,
name character varying(255),
parent_id integer references tree (id)
);
The table contains many trees with up to about 1000 nodes.
(I'm able to query a tree and its descendants efficiently with a recursive query).
However, I need to be able to copy a single tree in one operation. Say I have a tree with 3 nodes, ids 1,2,3 (this is potentially a large tree). I would like to make a copy of it i.e. creating new nodes with new ids. (Here the copied tree is ids 4,5,6):
id | name | parent_id
----+-----------------+-----------
1 | NodeA |
2 | NodeA.1 | 1
3 | NodeA.1.1 | 2
4 | NodeA(copy) |
5 | NodeA.1(copy) | 4
6 | NodeA.1.1(copy) | 5
Is there a way to copy a tree and its descendants more efficiently than inserting each tree node separately (because the new parent_id is needed)?
There you go:
\i tmp.sql
CREATE TABLE tree_node (
id serial primary key
, name varchar
, parent_id integer references tree_node (id)
);
INSERT INTO tree_node(name, parent_id) VALUES
( 'Node-A', NULL)
, ( 'Node-A.1', 1)
, ( 'Node-A.1.1', 2)
;
SELECT * FROM tree_node;
-- Find the top value of the sequence
-- and use it as an increment on all the copies
WITH top(val) AS
(select currval('tree_node_id_seq')
)
INSERT INTO tree_node(id, name, parent_id)
SELECT id+top.val
, name|| '(copy)'
, parent_id + top.val
FROM tree_node
CROSS JOIN top
;
SELECT * FROM tree_node;
-- bump the sequence
WITH nxt AS (
select max(id) mx from tree_node
)
SELECT setval('tree_node_id_seq', (select mx FROM nxt) )
;
Output:
DROP SCHEMA
CREATE SCHEMA
SET
CREATE TABLE
INSERT 0 3
id | name | parent_id
----+------------+-----------
1 | Node-A |
2 | Node-A.1 | 1
3 | Node-A.1.1 | 2
(3 rows)
INSERT 0 3
id | name | parent_id
----+------------------+-----------
1 | Node-A |
2 | Node-A.1 | 1
3 | Node-A.1.1 | 2
4 | Node-A(copy) |
5 | Node-A.1(copy) | 4
6 | Node-A.1.1(copy) | 5
(6 rows)
setval
--------
6
(1 row)

Adding days to sysdate based off of values in a column

I am trying to create a manual table based off of a currently built views table.
The structure of this current table is as follows:
ID | Column1 | Column2 | Buffer Days
1 | Asdf | Asdf1 | 91
2 | Qwert | Qwert1 | 11
3 | Zxcv | Zxcv1 | 28
The goal is to add a 4th column after Buffer Days that lists the sys date + the number in buffer days
So the outcome would look like:
ID | Column1 | Column2 | Buffer Days | Lookout Date
1 | Asdf | Asdf1 | 91 | 02-Jan-18
That requirement smells like a virtual column candidate. However, it won't work:
SQL> create table test
2 (id number,
3 column1 varchar2(10),
4 buffer_days number,
5 --
6 lookout_date as (SYSDATE + buffer_days) --> virtual column
7 );
lookout_date as (SYSDATE + buffer_days)
*
ERROR at line 6:
ORA-54002: only pure functions can be specified in a virtual column expression
Obviously, as SYSDATE is a non-deterministic function (doesn't return the same value when invoked).
Why not an "ordinary" column in existing table? Because you shouldn't store values that are calculated using other table columns anyway. For example, good old Scott's EMP table contains SAL and COMM columns. It doesn't (and shouldn't) contain TOTAL_SAL column (as SAL + COMM) because - when SAL and/or COMM changes, you have to remember to update TOTAL as well.
Therefore, a view is what could help here. For example:
SQL> create table test
2 (id number,
3 column1 varchar2(10),
4 buffer_days number
5 );
Table created.
SQL> create or replace view v_test as
2 select id,
3 column1,
4 buffer_days,
5 sysdate + buffer_days lookout_date
6 from test;
View created.
SQL> insert into test (id, column1, buffer_days) values (1, 'asdf', 5);
1 row created.
SQL> select sysdate, v.* from v_test v;
SYSDATE ID COLUMN1 BUFFER_DAYS LOOKOUT_DA
---------- ---------- ---------- ----------- ----------
23.12.2017 1 asdf 5 28.12.2017
SQL>

Collect rating statistics using postgresql

I am currently trying to collect rating statistics from a postgreSql database. Below you can find a simplified example of the database schema I would like to query.
CREATE DATABASE test_db;
CREATE TABLE rateable_object (
id BIGSERIAL PRIMARY KEY,
cdate TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
mdate TIMESTAMP,
name VARCHAR(160) NOT NULL,
description VARCHAR NOT NULL
);
CREATE TABLE ratings (
id BIGSERIAL PRIMARY KEY,
cdate TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
mdate TIMESTAMP,
parent_id BIGINT NOT NULL,
rating INTEGER NOT NULL DEFAULT -1
);
I would now like to collect a statistic for the values in the ratings column. The response should look like this:
+--------------+-------+
| column_value | count |
+--------------+-------+
| -1 | 2 |
| 0 | 45 |
| 1 | 37 |
| 2 | 13 |
| 3 | 5 |
| 4 | 35 |
| 5 | 75 |
+--------------+-------+
My first solution (see below) is very naive and probably not the fastest and simplest one. So my question is, if there is a better solution.
WITH
stars AS (SELECT generate_series(-1, 5) AS value),
votes AS (SELECT * FROM ratings WHERE parent_id = 1)
SELECT
stars.value AS stars, coalesce(COUNT(votes.*), 0) as votes
FROM
stars
LEFT JOIN
votes
ON
votes.rating = stars.value
GROUP BY stars.value
ORDER BY stars.value;
As I would not like to waste your time, I prepared some test data for you:
INSERT INTO rateable_object (name, description) VALUES
('Penguin', 'This is the Linux penguin.'),
('Gnu', 'This is the GNU gnu.'),
('Elephant', 'This is the PHP elephant.'),
('Elephant', 'This is the postgres elephant.'),
('Duck', 'This is the duckduckgo duck.'),
('Cat', 'This is the GitHub cat.'),
('Bird', 'This is the Twitter bird.'),
('Lion', 'This is the Leo lion.');
CREATE OR REPLACE FUNCTION generate_test_data() RETURNS INTEGER LANGUAGE plpgsql AS
$$
BEGIN
FOR i IN 0..1000 LOOP
INSERT INTO ratings (parent_id, rating) VALUES
(
(1 + (10 - 1) * random())::numeric::int,
(-1 + (5 + 1) * random())::numeric::int
);
END LOOP;
RETURN 0;
END;
$$;
SELECT generate_test_data();

Restart primary key numbers of existing rows after deleting most of a big table

I am working with a PostgreSQL 8.4.13 database.
Recently I had around around 86.5 million records in a table. I deleted almost all of them - only 5000 records are left now. I ran:
vacuum full
after deleting the rows and that returned disk space to the OS (thx to suggestion from fellow SO member)
But I see that my id numbers are still stuck at millions. For ex:
id | date_time | event_id | secs_since_1970 | value
---------+-------------------------+----------+-----------------+-----------
61216287 | 2013/03/18 16:42:42:041 | 6 | 1363646562.04 | 46.4082
61216289 | 2013/03/18 16:42:43:041 | 6 | 1363646563.04 | 55.4496
61216290 | 2013/03/18 16:42:44:041 | 6 | 1363646564.04 | 40.0553
61216291 | 2013/03/18 16:42:45:041 | 6 | 1363646565.04 | 38.5694
In an attempt to start the id value at 1 again for the remaining rows, I tried:
cluster mytable_pkey on mytable;
where mytable is the name of my table. But that did not help.
So, my question(s) is/are:
Is there a way to get the index (id value) to start at 1 again?
If I add or update the table with a new record, will it start from 1 or pick up the next highest integer value (say 61216292 in above example)?
My table description is as follows: There is no FK constraint and no sequence in it.
jbossql=> \d mytable;
Table "public.mytable"
Column | Type | Modifiers
-----------------+------------------------+-----------
id | bigint | not null
date_time | character varying(255) |
event_id | bigint |
secs_since_1970 | double precision |
value | real |
Indexes:
"mydata_pkey" PRIMARY KEY, btree (id) CLUSTER
Drop the primary key fisrt and create a temporary sequence.
alter table mytable drop constraint mydata_pkey;
create temporary sequence temp_seq;
Use the sequence to update:
update mytable
set id = nextval('temp_seq');
Recreate the primary key and drop the sequence
alter table mytable add primary key (id);
drop sequence temp_seq;
If there is a foreign key dependency on this table then you will have to deal with it first and the update will be a more complex procedure.
Is your primary key defined using a serial? If so that creates an implicit sequence. You can use ALTER SEQUENCE (see: http://www.postgresql.org/docs/8.2/static/sql-altersequence.html for syntax) to change the starting number back to 1.
Based on the fact that you have some records left (just noticed the 5000 left), you DO NOT want to reset that number to a number before the last ID of the remaining records because then that sequence will generate non-unique numbers. The point of using a sequence is it gives you a transactional way to increment a number and guarantee successive operations get unique incremented numbers.
DROP SCHEMA tmp CASCADE;
CREATE SCHEMA tmp ;
SET search_path=tmp;
--
-- Note: "deferrable initially deferred" appears to be the default
--
CREATE TABLE funky
( id SERIAL NOT NULL PRIMARY KEY DEFERRABLE INITIALLY DEFERRED
, tekst varchar
);
-- create some data with gaps in it
INSERT INTO funky(id, tekst)
SELECT gs, 'Number:' || gs::text
FROM generate_series(1,100,10) gs
;
-- set the sequence to the max occuring id
SELECT setval('funky_id_seq' , mx.mx)
FROM (SELECT max(id) AS mx FROM funky) mx
;
SELECT * FROM funky ;
-- compress the keyspace, making the ids consecutive
UPDATE funky xy
SET id = self.newid
FROM (
SELECT id AS id
, row_number() OVER (ORDER BY id) AS newid
FROM funky
) self
WHERE self.id = xy.id
;
-- set the sequence to the new max occuring id
SELECT setval('funky_id_seq' , mx.mx)
FROM (SELECT max(id) AS mx FROM funky) mx
;
SELECT * FROM funky ;
Result:
CREATE TABLE
INSERT 0 10
setval
--------
91
(1 row)
id | tekst
----+-----------
1 | Number:1
11 | Number:11
21 | Number:21
31 | Number:31
41 | Number:41
51 | Number:51
61 | Number:61
71 | Number:71
81 | Number:81
91 | Number:91
(10 rows)
UPDATE 10
setval
--------
10
(1 row)
id | tekst
----+-----------
1 | Number:1
2 | Number:11
3 | Number:21
4 | Number:31
5 | Number:41
6 | Number:51
7 | Number:61
8 | Number:71
9 | Number:81
10 | Number:91
(10 rows)
WARNING WARNING WARNING WARNING WARNING WARNING ACHTUNG:
Changing key values is generally a terrible idea. Avoid it at all cost.