Get all siblings in SQL tree - sql

I have to handle a table PRODUCTS which is created to accommodate tree structure of products. It is done to handle situations when one product can contain several others (e.g. one package product holds several other positions). So, I'm making a function that takes OrderDetails, and it must iterate through all PRODUCTS and list out the child products for each product listed. I am facing an issue that I have to iterate through tree of unknown depth. Please, give me an idea how to do it.
I've implemented it in the table below with the function listed along with it. But in that solution the depth of listing is limited to 1, and what i want to do is to fetch all depth of the tree.
Here is the code:
CREATE OR REPLACE FUNCTION foo()RETURNS text AS
$body$
DECLARE _row RECORD;
_result text := '';
_child_row RECORD;
_count integer := -1;
_marker integer := 1;
BEGIN
FOR _row IN SELECT * FROM tree_products
LOOP
_result := _result || _marker || ' ' || _row.name;
_count := (SELECT count(product_id) FROM tree_products WHERE parent_id = _row.product_id);
IF _count > 0 THEN
FOR _child_row IN SELECT * FROM tree_products WHERE parent_id = _row.product_id
LOOP
_result := _result || ' ' || _child_row.name;
END LOOP;
END IF;
_marker := _marker =1;
END LOOP;
END;
$body$
LANGUAGE plpgsql
UPD Done this usign WITH CTE, but the groupiing problem occured:
CREATE OR REPLACE FUNCTION public.__foo (
)
RETURNS SETOF refcursor AS
$body$
DECLARE _returnvalue refcursor;
_q text;
BEGIN
_q :='
WITH RECURSIVE r_p (product_id, name, parent_id) AS -- 1
(SELECT t_p.product_id, t_p.name , t_p.parent_id -- 2
FROM tree_products t_p
WHERE t_p.product_id = 1
UNION ALL
SELECT t_c.product_id, t_c.name, t_c.parent_id -- 3
FROM r_p t_p, tree_products t_c
WHERE t_c.parent_id = t_p.product_id)
SELECT product_id, name, parent_id -- 4
FROM r_p;';
OPEN _returnvalue FOR EXECUTE (_q);
RETURN NEXT _returnvalue;
END
$body$
LANGUAGE 'plpgsql'
VOLATILE
CALLED ON NULL INPUT
SECURITY INVOKER
COST 100 ROWS 1000;
I want to sibling products be under their respectiveparents, I wonder how to write Grouping statement...
UPD Sorry, the definition of the tree_products is the following:
CREATE TABLE public.tree_products (
product_id INTEGER DEFAULT nextval('ree_products_product_id_seq'::regclass) NOT NULL,
name VARCHAR,
parent_id INTEGER,
CONSTRAINT ree_products_pkey PRIMARY KEY(product_id)
)
WITH (oids = false);
UPD: SAMPLE OUTPUT:
product_id | name | parent_id
---------------------------------------
1 | promo | NULL
3 | fork | 1
4 | spoon | 1
6 | can | 1
10 | big can | 3
11 | small can | 4
12 | large spoon | 6
13 | mega fork | 3
14 | super duper | 6
DESIRED OUTPUT:
product_id | name | parent_id
---------------------------------------
1 | promo | NULL
3 | fork | 1
10 | big can | 3
13 | mega fork | 3
4 | spoon | 1
11 | small can | 4
6 | can | 1
12 | large spoon | 6
14 | super duper | 6
So, the fetched table has structure of the real tree, like the follwing:
- promo
- fork
- big can
- mega fork
- spoon
- small can
- can
- large can
- super duper

This SQLFiddle traverses the tree top-down, keeping an list of parent row numbers in an array, essentially a "parent row position list".
It then sorts the results by the parent-list.
WITH RECURSIVE tree(product_id, name, parentlist) AS (
SELECT product_id, name, ARRAY[ row_number() OVER (ORDER BY product_id) ]
FROM tree_products
WHERE parent_id IS NULL
UNION
SELECT tp.product_id, tp.name, array_append(parentlist, row_number() OVER (ORDER BY tp.product_id))
FROM tree_products tp
INNER JOIN tree t
ON (tp.parent_id = t.product_id)
)
SELECT *
FROM tree
ORDER BY parentlist;

Related

PostgreSQL Calculate product of an array

Write a function that takes an array and returns the product of all the other elements for each element. For example, f([2, 3, 4, 5]) -> [3x4x5, 2x4x5, 2x3x5, 2x3x4] -> [60, 40, 30, 24].
I know to calculate product you can do exp(sum(ln(value))) but am unsure on the rest.
if someone could help it would be appreciated.
While it's doable with with SQL, I think in this case PL/pgSQL might be easier to deal with:
create function multiply_elements(p_input int[])
returns int[]
as
$$
declare
l_result int[];
l_idx1 int;
l_idx2 int;
begin
for l_idx1 in 1..cardinality(p_input) loop
l_result[l_idx1] := 1;
for l_idx2 in 1..cardinality(p_input) loop
if l_idx1 <> l_idx2 then
l_result[l_idx1] := l_result[l_idx1] * p_input[l_idx2];
end if;
end loop;
end loop;
return l_result;
end;
$$
language plpgsql
immutable;
Ok, here is one solution using just SQL Select. It first generates a prep table in a form
rn | num | arr
1 | 2 | {2,3,4,5}
2 | 3 | {2,3,4,5}
3 | 4 | {2,3,4,5}
4 | 5 | {2,3,4,5}
the arrays are then unnested and we generate an intermediate result in the following form (table t)
rn | num | x
1 | 2 | 3
1 | 2 | 4
1 | 2 | 5
2 | 3 | 2
2 | 3 | 4
2 | 3 | 5
...
Such intermediate result is grouped and we calculate the required product. The final step is a creation of an array from rows (the array_agg function)
create aggregate product(integer)
(stype=bigint,sfunc=int84mul,initcond=1);
create function product_array(p_array int[])
returns int[]
as
$$
declare
r int[];
begin
select array_agg(p) into r
from (
select product(x) p
from (
select row_number() over () rn,
num,
p_array arr
from unnest(p_array) num
) prep,
lateral unnest(arr) x
where num != x
group by rn, num
order by rn
) t;
return r;
end;
$$
language plpgsql
immutable;
select product_array(array[2,3,4,5]);
DEMO
Product is computed by an aggregate function product defined at the beginning of the script since there is no such function in PostgreSQL.
The SQL contains the row_number function in order to preserve the order of the elements of the input array since the SQL switch to relations from arrays and back.

Update only dynamically chosen columns

I've created a function copy_rows_from_table(_tbl regclass) which copies records in a table and gives them a new primary key value. It returns a hstore containing pairs of old_id => new_id. For example for table books my function would create two additional records and return a hstore.
books initially:
id | title | price | author_id | publisher_id
----+---------------+-------+-----------+--------------
1 | The Cyberiad | 15.00 | 23 | 46
2 | The Trial | 10.00 | 12 | 67
books after evaluation of copy_rows_from_table('books'):
id | title | price | author_id | publisher_id
----+---------------+-------+-----------+--------------
1 | The Cyberiad | 15.00 | 23 | 46
2 | The Trial | 10.00 | 12 | 67
3 | The Cyberiad | 15.00 | 23 | 46
4 | The Trial | 10.00 | 12 | 67
returned hstore:
"1"=>"3", "2"=>"4"
It works fine. Now I would like to create a function that copies records from a few tables (passed in an array) and then updates all foreign keys using returned hstore. For example after copying books and authors I want author_id column to be updated in books table. After using my function on books, authors and publishers, if I had a hstore which contains "1"=>"3", "2"=>"4","23"=>"167","12"=>"98","46"=>"87","67"=>"102", my function should update books table in this way:
id | title | price | author_id | publisher_id
----+---------------+-------+-----------+--------------
1 | The Cyberiad | 15.00 | 23 | 46
2 | The Trial | 10.00 | 12 | 67
3 | The Cyberiad | 15.00 | 167 | 87
4 | The Trial | 10.00 | 98 | 102
I came up with something like this:
CREATE OR REPLACE FUNCTION copy_tables(_tbls regclass[])
RETURNS void AS
$func$
DECLARE
_tbl regclass;
_id_pairs hstore;
_table_id_pairs hstore;
_row record;
BEGIN
FOR _tbl IN SELECT _tbls
LOOP
EXECUTE format('SELECT copy_rows_from_table(''%1$s'')', _tbl)
INTO _table_id_pairs;
SELECT COALESCE(_id_pairs, hstore('')) || COALESCE(_table_id_pairs, hstore('')) INTO _id_pairs;
END LOOP;
FOR _tbl IN SELECT _tbls
LOOP
FOR _row IN EXECUTE format('SELECT * FROM %1$s WHERE id = ANY(''%2$s''::uuid[])', _tbl, avals(_id_pairs))
LOOP
EXECUTE (
SELECT format('UPDATE %1$s SET (%2$s) = (%3$s) WHERE id = %4$s'
, _tbl, string_agg(quote_ident(attname), ', '),
string_agg(COALESCE(_id_pairs -> ('_row.' || quote_ident(attname)), '_row.' || quote_ident(attname)), ', '), _row.id)
FROM pg_attribute
WHERE attrelid = _tbl
AND NOT attisdropped
AND attnum > 0
AND attname LIKE '%_id'
);
END LOOP;
END LOOP;
END
$func$
LANGUAGE plpgsql;
But it doesn't quite work. Is there any possibility to update records in a way I explained?
I have finally found a way to do it in PLpgSQL. I just iterate through every column of every record. Here is my working function:
CREATE OR REPLACE FUNCTION copy_tables(_tbls regclass[])
RETURNS void AS
$func$
DECLARE
_id_pairs hstore;
_table_id_pairs hstore;
_row record;
_hs_row record;
BEGIN
FOR I IN array_lower(_tbls, 1)..array_upper(_tbls, 1)
LOOP
EXECUTE format('SELECT copy_rows_from_table(''%1$s'')', _tbls[I])
INTO _table_id_pairs;
SELECT COALESCE(_id_pairs, hstore('')) || COALESCE(_table_id_pairs, hstore('')) INTO _id_pairs;
END LOOP;
FOR I IN array_lower(_tbls, 1)..array_upper(_tbls, 1)
LOOP
FOR _row IN EXECUTE format('SELECT * FROM %1$s WHERE id = ANY(''%2$s''::uuid[])', _tbls[I], avals(_id_pairs))
LOOP
FOR _hs_row IN SELECT kv."key", kv."value" FROM each(hstore(_row)) kv
LOOP
IF _hs_row."value" = ANY(akeys(_id_pairs)) THEN
EXECUTE format('UPDATE %1$s SET %2$s = ''%3$s'' WHERE id = ''%4$s''',
_tbls[I], _hs_row."key", _id_pairs -> _hs_row."value", _row.id);
END IF;
END LOOP;
END LOOP;
END LOOP;
END
$func$
LANGUAGE plpgsql;
PLpgSQL isn't good tool for dynamic updates. Probably there are some few others possibilities, but no one is trivial. The other ways:
Using some more dynamic PL language - PLPythonu, PLPerl
Using extension https://github.com/okbob/pltoolbox - there are functions record_get_fields and record_set_fields

PostgreSQL inheritance: get the record class name

I have a base table value_list with columns code, value, active.
I have some tables vl_a, vl_b, vl_c, etc. inheriting from value_list.
Is there a way, when doing a SELECT * FROM base to know from which class the child comes from.
In other words, I would like to have:
code | value | active | class
-----+-------+---------+--------
1 | c | true | vl_a
3 | g | false | vl_b
5 | d | true | vl_a
7 | f | false | vl_c
2 | u | false | vl_c
2 | q | true | vl_b
8 | a | false | vl_a
Is this possible ?
For more details, here would be the tables:
CREATE TABLE value_list(
code integer NOT NULL,
value character varying(50),
active boolean,
CONSTRAINT pkey PRIMARY KEY (code)
)
CREATE TABLE vl_a() INHERITS (value_list);
CREATE TABLE vl_b() INHERITS (value_list);
CREATE TABLE vl_c() INHERITS (value_list);
dictionary won't let you to, but you can union manually:
select *,'vl_a' from vl_a
union all
select *,'vl_b' from vl_b
union all
select *,'vl_c' from vl_c
well, this gives it:
create or replace function uall() returns table ( code integer ,
value character varying(50),
active boolean,tablename text ) AS $$
declare
_i int;
_r record;
_t text := '';
begin
select distinct string_agg($s$select *,'$s$||table_name||$s$' from $s$||table_name,' union all ') into _t from information_schema.tables where table_name like 'vl_%';
return query execute _t;
end;$$ language plpgsql
;
select * from uall()
I finally found the solution on the Postgres doc.
SELECT p.relname, vl.*
FROM qgep.is_value_list_base vl, pg_class p
WHERE vl.tableoid = p.oid;

How to substring and join with another table with the substring result

I have 2 tables: errorlookup and errors.
errorlookup has 2 columns: codes and description.
The codes are of length 2.
errors has 2 columns id and errorcodes.
The errorcodes are of length 40 meaning they code store 20 error codes for each id.
I need to display all the description associated with the id by substring the errorcodes and matching with code in errorlookup table.
Sample data for errorlookup:
codes:description
12:Invalid
22:Inactive
21:active
Sample data for errors:
id:errorcodes
1:1221
2:2112
3:1222
I cant use LIKE as it would result in too many errors. I want the errorcodes column to be broken down into strings of length 2 and then joined with the errorlookup.
How can it be done?
If you really cannot alter the tables structure, here's another approach:
Create an auxilary numbers table:
CREATE TABLE numbers
( i INT NOT NULL
, PRIMARY KEY (i)
) ;
INSERT INTO numbers VALUES
( 1 ) ;
INSERT INTO numbers VALUES
( 2 ) ;
--- ...
INSERT INTO numbers VALUES
( 100 ) ;
Then you could use this:
SELECT err.id
, err.errorcodes
, num.i
, look.codes
, look.descriptionid
FROM
( SELECT i, 2*i-1 AS pos --- odd numbers
FROM numbers
WHERE i <= 20 --- 20 pairs
) num
CROSS JOIN
errors err
JOIN
errorlookup look
ON look.codes = SUBSTR(err.errorcodes, pos, 2)
ORDER BY
err.errorcodes
, num.i ;
Test at: SQL-Fiddle
ID ERRORCODES I CODES DESCRIPTIONID
1 1221 1 12 Invalid
1 1221 2 21 Active
3 1222 1 12 Invalid
3 1222 2 22 Inactive
2 2112 1 21 Active
2 2112 2 12 Invalid
I think the cleanest solution is to "normalize" your errocodes table using a PL/SQL function. That way you can keep the current (broken) table design, but still access its content as if it was properly normlized.
create type error_code_type as object (id integer, code varchar(2))
/
create or replace type error_table as table of error_code_type
/
create or replace function unnest_errors
return error_table pipelined
is
codes_l integer;
i integer;
one_row error_code_type := error_code_type(null, null);
begin
for err_rec in (select id, errorcodes from errors) loop
codes_l := length(err_rec.errorcodes);
i := 1;
while i < codes_l loop
one_row.id := err_rec.id;
one_row.code := substr(err_rec.errorcodes, i, 2);
pipe row (one_row);
i := i + 2;
end loop;
end loop;
end;
/
Now with this function you can do something like this:
select er.id, er.code, el.description
from table(unnest_errors) er
join errorlookup el on el.codes = er.code;
You can also create a view based on the function to make the statements a bit easier to read:
create or replace view normalized_errorcodes
as
select *
from table(unnest_errors);
Then you can simply reference the view in the real statement.
(I tested this on 11.2 but I believe it should work on 10.x as well)
I think you're on the right track with LIKE. MySQL has an RLIKE function that allows matching by regular expression (I don't know if it's present in Oracle.) You could use errorlookup.code as a pattern to match against errors.errorcodes. The (..)* pattern is used to prevent things like "1213" from matching, for example, "21".
SELECT *
FROM error
JOIN errorlookup
WHERE errorcodes RLIKE CONCAT('^(..)*',code)
ORDER BY id;
+------+----------+------+
| id | errorcode| code |
+------+----------+------+
| 1 | 11 | 11 |
| 2 | 1121 | 11 |
| 2 | 1121 | 21 |
| 3 | 21313245 | 21 |
| 3 | 21313245 | 31 |
| 3 | 21313245 | 32 |
| 4 | 21 | 21 |
+------+----------+------+

MySQL: Get Root Node of Parent-Child Structure

I have a table similar to this:
=================
| Id | ParentId |
=================
| 1 | 0 |
-----+-----------
| 2 | 1 |
-----+-----------
| 3 | 0 |
-----+-----------
| 4 | 3 |
-----+-----------
| 5 | 3 |
-----+-----------
| 6 | 0 |
-----+-----------
| 7 | 6 |
-----+-----------
| 8 | 7 |
-----------------
Given an Id, I need to know its root "node" Id. So,
Given 1, return 1
Given 2, return 1
Given 3, return 3
Given 4, return 3
Given 5, return 3
Given 6, return 6
Given 7, return 6
Given 8, return 7
There is no limit to the levels of the hierarchy. Is there a SQL that can do what I need?
Actually, you can quite easily do this using a function.
Try running the following .sql script on your favorite empty test database.
--
-- Create the `Nodes` table
--
CREATE TABLE `Nodes` (
`Id` INT NOT NULL PRIMARY KEY
,`ParentId` INT NOT NULL
) ENGINE=InnoDB;
--
-- Put your test data into it.
--
INSERT INTO `Nodes` (`Id`, `ParentId`)
VALUES
(1, 0)
, (2, 1)
, (3, 0)
, (4, 3)
, (5, 3)
, (6, 0)
, (7, 6)
, (8, 7);
--
-- Enable use of ;
--
DELIMITER $$
--
-- Create the function
--
CREATE FUNCTION `fnRootNode`
(
pNodeId INT
)
RETURNS INT
BEGIN
DECLARE _Id, _ParentId INT;
SELECT pNodeId INTO _ParentId;
my_loop: LOOP
SELECT
`Id`
,`ParentId`
INTO
_Id
,_ParentId
FROM `Nodes`
WHERE `Id` = _ParentId;
IF _ParentId = 0 THEN
LEAVE my_loop;
END IF;
END LOOP my_loop;
RETURN _Id;
END;
$$
--
-- Re-enable direct querying
--
DELIMITER ;
--
-- Query the table using the function to see data.
--
SELECT
fnRootNode(`Nodes`.`Id`) `Root`
,`Nodes`.`Id`
,`Nodes`.`ParentId`
FROM `Nodes`
ORDER BY
fnRootNode(`Nodes`.`Id`) ASC
;
-- EOF
Output will be:
Root Id ParentId
==== ==== ========
1 1 0
1 2 1
3 3 0
3 4 3
3 5 3
6 6 0
6 7 6
6 8 7
Here is a short query doing what you're asking, assuming your table is called foo and that you want to know the root of <id>:
SELECT f.Id
FROM (
SELECT #id AS _id, (SELECT #id := ParentId FROM foo WHERE Id = _id)
FROM (SELECT #id := <id>) tmp1
JOIN foo ON #id <> 0
) tmp2
JOIN foo f ON tmp2._id = f.Id
WHERE f.ParentId = 0
This is quite difficult to do in MySQL because it doesn't yet support recursive common table expressions.
I'd suggest instead using a nested sets model, or else storing the root node in the row and updating it as the structure changes.
In short: no. Look at regular Bill Karwin's excellent presentation about hierarchical models and it's uses, shortcomings, and how to get around those: http://www.slideshare.net/billkarwin/models-for-hierarchical-data
I used #Kris answer for a while successfully, until I faced an issue where a child node might got deleted (accidentally), as a result the function gets into an infinite loop and hangs the mysql database at all, following is the modified version which works in my case:
DELIMITER $$
CREATE FUNCTION `FindRootNode`(InputValue INT(11)) RETURNS INT(11)
NO SQL
BEGIN
DECLARE ReturnValue, _ParentId INT;
SELECT InputValue INTO _ParentId;
REPEAT
SET ReturnValue = _ParentId;
SELECT IFNULL((SELECT parent_id FROM TableName WHERE id=ReturnValue), 0) INTO _ParentId;
UNTIL _ParentId = 0
END REPEAT;
RETURN ReturnValue;
END $$
DELIMITER ;
Usage1
SELECT CompanyCategoryTestRoot(HERE_COMES_CHILD_NODE_VALUE)