I'm trying to convert this code from Oracle database to Postgresql, can someone help me with that?
WITH cycles AS
(
SELECT SYS_CONNECT_BY_PATH (child_node_id, ',') AS path, SYS_CONNECT_BY_PATH ( CASE WHEN child_node_id < CONNECT_BY_ROOT child_node_id THEN child_node_id END, ','
) AS less_path
FROM alf_child_ASSOC
WHERE CONNECT_BY_ROOT parent_node_id = child_node_id
CONNECT BY NOCYCLE parent_node_id = PRIOR child_node_id
)
SELECT *
FROM cycles
WHERE LTRIM (less_path, ',') IS NULL ;
Thanks
You can convert your Oracle query from a hierarchical query to a recursive query in the form:
WITH cycles (path, root_id, child_node_id) AS (
SELECT ',' || child_node_id,
parent_node_id AS root_id,
child_node_id
FROM alf_child_assoc
WHERE parent_node_id > child_node_id
UNION ALL
SELECT c.path || ',' || a.child_node_id,
c.root_id,
a.child_node_id
FROM cycles c
INNER JOIN alf_child_assoc a
ON (c.child_node_id = a.parent_node_id)
WHERE a.parent_node_id != c.root_id
)
SELECT path
FROM cycles
WHERE child_node_id = root_id
Oracle db<>fiddle here
Then all you need to do to convert to PostgreSQL is to add the RECURSIVE keyword:
WITH RECURSIVE cycles (path, root_id, child_node_id) AS (
SELECT ',' || child_node_id,
parent_node_id AS root_id,
child_node_id
FROM alf_child_assoc
WHERE parent_node_id > child_node_id
UNION ALL
SELECT c.path || ',' || a.child_node_id,
c.root_id,
a.child_node_id
FROM cycles c
INNER JOIN alf_child_assoc a
ON (c.child_node_id = a.parent_node_id)
WHERE a.parent_node_id != c.root_id
)
SELECT path
FROM cycles
WHERE child_node_id = root_id
Which, for the sample data:
CREATE TABLE alf_child_assoc (
child_node_id NUMERIC(10,0),
parent_node_id NUMERIC(10,0)
);
INSERT INTO alf_child_assoc (child_node_id, parent_node_id) VALUES (2, 1);
INSERT INTO alf_child_assoc (child_node_id, parent_node_id) VALUES (3, 2);
INSERT INTO alf_child_assoc (child_node_id, parent_node_id) VALUES (1, 3);
Outputs:
PATH
,1,2,3
PostgreSQL db<>fiddle here
I found a working solution:
WITH RECURSIVE prev AS (
SELECT alf_child_ASSOC.child_node_id, 1 AS depth, array[child_node_id] as seen, false as cycle
FROM alf_child_ASSOC
UNION ALL
SELECT alf_child_ASSOC.child_node_id, prev.depth + 1, seen || alf_child_ASSOC.child_node_id as seen,
alf_child_ASSOC.child_node_id = any(seen) as cycle
FROM prev
INNER JOIN alf_child_ASSOC on prev.child_node_id = parent_node_id
AND prev.cycle = false
)
SELECT *
FROM prev
WHERE cycle = true;
Exemple of output:
child_node_id | depth | seen | cycle
---------------+-------+-------------------+-------
749254 | 2 | {749254,749254} | t
749675 | 2 | {749675,749675} | t
754208 | 2 | {754208,754208} | t
Related
I have a very big table but as an example I will only provide a very small part of it as following:-
col1 col2 col3 col4
10 2 12
13 4 11
0 1
3 5 111
I know how to find null values in one column. What I want to find is how many null values are there in each column just by writing one query.
Thanks in advance
You can use an aggregate with a filter:
select count(*) filter (where col1 is null) as col1_nulls,
count(*) filter (where col2 is null) as col2_nulls,
count(*) filter (where col3 is null) as col3_nulls,
count(*) filter (where col4 is null) as col4_nulls
from the_table;
I think you can generate this query on the fly. Here is an example of one way you can approach it:
CREATE OR REPLACE FUNCTION null_counts(tablename text)
RETURNS SETOF jsonb LANGUAGE plpgsql AS
$func$
BEGIN
RETURN QUERY EXECUTE 'SELECT to_jsonb(t) FROM (SELECT ' || (
SELECT string_agg('count(*) filter (where ' || a.attname::text || ' is null) as ' || a.attname || '_nulls', ',')
FROM pg_catalog.pg_attribute a
WHERE a.attrelid = tablename::regclass
AND a.attnum > 0
AND a.attisdropped = false
) || ' FROM ' || tablename::regclass || ') as t';
END
$func$;
SELECT null_counts('your_table') AS val;
I am trying to aggregate distinct values from JSONB arrays in a SQL GROUP BY statement:
One dataset has many cfiles and a cfile only ever has one dataset
SELECT * FROM cfiles;
id | dataset_id | property_values (jsonb)
----+------------+-----------------------------------------------
1 | 1 | {"Sample Names": ["SampA", "SampB", "SampC"]}
2 | 1 | {"Sample Names": ["SampA", "SampB", "SampD"]}
3 | 1 | {"Sample Names": ["SampE"]}
4 | 2 | {"Sample Names": ["SampA", "SampF"]}
5 | 2 | {"Sample Names": ["SampG"]}
This query works and returns the correct result I want but it's a mess.
SELECT distinct(datasets.id) as dataset_id,
ARRAY_TO_STRING(
ARRAY(
SELECT DISTINCT * FROM unnest(
STRING_TO_ARRAY(
STRING_AGG(
DISTINCT REPLACE(
REPLACE(
REPLACE(
REPLACE(
cfiles.property_values ->> 'Sample Names', '",' || chr(32) || '"', ';'
), '[' , ''
), '"' , ''
), ']' , ''
), ';'
), ';'
)
) ORDER BY 1 ASC
), '; '
) as sample_names
FROM datasets
JOIN cfiles ON cfiles.dataset_id=datasets.id
GROUP BY datasets.id
dataset_id | sample_names
------------+-----------------------------------
1 | SampA; SampB; SampC; SampD; SampE
2 | SampA; SampF; SampG
Is there a better way to write this query without all the string manipulation?
I tired jsonb_array_elements but it gave me the error subquery uses ungrouped column "cfiles.property_values" from outer query. So then I added cfiles.property_values to the GROUP BY but it no longer grouped just by the dataset_id
Not the result I want:
SELECT DISTINCT datasets.id as dataset_id,
ARRAY_TO_STRING(
ARRAY(
SELECT DISTINCT * FROM jsonb_array_elements(
cfiles.property_values -> 'Sample Names'
) ORDER BY 1 ASC
), '; '
) as sample_names
FROM datasets
JOIN cfiles ON cfiles.dataset_id=datasets.id
GROUP BY datasets.id, cfiles.property_values
dataset_id | sample_names
------------+---------------------------
1 | "SampA"; "SampB"; "SampC"
1 | "SampA"; "SampB"; "SampD"
1 | "SampE"
2 | "SampA"; "SampF"
2 | "SampG"
SQL for creating demo
CREATE TABLE datasets (
id INT PRIMARY KEY
);
CREATE TABLE cfiles (
id INT PRIMARY KEY,
dataset_id INT,
property_values JSONB,
FOREIGN KEY (dataset_id) REFERENCES datasets(id)
);
INSERT INTO datasets values (1),(2);
INSERT INTO cfiles values
(1,1,'{"Sample Names":["SampA", "SampB", "SampC"]}'),
(2,1,'{"Sample Names":["SampA", "SampB", "SampD"]}'),
(3,1,'{"Sample Names":["SampE"]}');
INSERT INTO cfiles values
(4,2,'{"Sample Names":["SampA", "SampF"]}'),
(5,2,'{"Sample Names":["SampG"]}');
jsonb_array_elements is a set returning function and should be used in the FROM clause. Using it in the SELECT list makes things unnecessarily complicated:
select c.dataset_id, string_agg(distinct n.name, '; ' order by n.name)
from cfiles c
cross join jsonb_array_elements_text(c.property_values -> 'Sample Names') as n(name)
group by c.dataset_id
order by c.dataset_id;
Online example
I have the following piece of code:
DROP SCHEMA IF EXISTS s CASCADE;
CREATE SCHEMA s;
CREATE TABLE s.t1 (
id1 BigInt,
id2 BigInt,
CONSTRAINT "pk1" PRIMARY KEY (id1)
)
WITH(OIDS=FALSE);
INSERT INTO s.t1 (id1, id2) VALUES (2, 22);
INSERT INTO s.t1 (id1, id2) VALUES (3, 22);
INSERT INTO s.t1 (id1, id2) VALUES (4, 24);
SELECT EXISTS (SELECT TRUE FROM s.t1 t1 WHERE t1.id1 = 2,
SELECT EXISTS (SELECT TRUE FROM s.t1 t1 WHERE t1.id2 = 22);
Is there a way to check for multiple conditions in a single statement.
I am looking for the values in two separate columns.
The answer I was looking for is something like this:
bool | bool
------+------
t | t
(1 row)
I got the solution:
SELECT EXISTS(SELECT TRUE FROM s.t1 t1 WHERE t1.id1 = 6),
EXISTS(SELECT TRUE FROM s.t1 t1 WHERE t1.id2 = 22);
Output:
exists | exists
--------+--------
f | t
(1 row)
What about
SELECT max(x.t2) as t2,
max(x.t22) as t22
FROM ((SELECT t1.id1 as t2, null as t22 FROM s.t1 t1 WHERE t1.id1 = 2)
UNION
(SELECT null as t2, t1.id2 as t22 FROM s.t1 t1 WHERE t1.id2 = 22)) as x;
Demo
EXISTS yields a Boolean value, which you can use directly:
SELECT
EXISTS (SELECT * FROM s.t1 x1
WHERE x1.id1 = 2) AS ex1
, EXISTS (SELECT * FROM s.t1 x2
WHERE x2.id2 = 22) AS ex2
;
SELECT max( case when id1=2 then 'EXISTS' ELSE 'DOESN''T EXISTS' end) id1_2,
max( case when id2=22 then 'EXISTS' ELSE 'DOESN''T EXISTS' end) id2_22,
max( case when id2=33 then 'EXISTS' ELSE 'DOESN''T EXISTS' end) id2_33
FROM s.t1
demo: http://sqlfiddle.com/#!17/85c47/11
| id1_2 | id2_22 | id2_33 |
|--------|--------|----------------|
| EXISTS | EXISTS | DOESN'T EXISTS |
Input : Keep the column value into next line if word to word space is 3 space and length of the word is >9 .
declare #Table table(CL1 varchar(50))
INSERT INTO #Table
SELECT 'Ohh my GOD'
UNION ALL
SELECT 'hindunewspaer is no1 paper'
select * from #Table
o/p :
CL1
ohh
my god
hindunewpaer
is no1 paper
Used a Split/Parse function. Can be inline if needed.
EDIT - Switch to a Parser which is not limited to 8K because the final
string could easily be larger than 8K
Example
;with cte0 as (
Select Seq=Row_Number() over (Order by (Select null)),RetSeq,RetVal
From #Table A
Cross Apply (
Select RetSeq
,RetVal=case when len(RetVal)>9 then '~~~' else '' end+RetVal+case when len(RetVal)>9 then '~~~' else '' end
From [dbo].[udf-Str-Parse](Replace(CL1,' ','~~~ '),' ')
) B ),
cte1 as ( Select S=Stuff((Select ' '+RetVal From cte0 Order by Seq For XML Path ('')),1,1,'') )
Select CL1 = RetVal
From cte1 A
Cross Apply [dbo].[udf-Str-Parse](A.S,'~~~') B
Order By RetSeq
Returns
CL1
Ohh
my GOD
hindunewspaer
is no1 paper
The Split/Parse Function if Needed
CREATE FUNCTION [dbo].[udf-Str-Parse] (#String varchar(max),#Delimiter varchar(10))
Returns Table
As
Return (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace((Select replace(#String,#Delimiter,'§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
);
--Thanks Shnugo for making this XML safe
--Select * from [dbo].[udf-Str-Parse]('Dog,Cat,House,Car',',')
--Select * from [dbo].[udf-Str-Parse]('John Cappelletti was here',' ')
--Select * from [dbo].[udf-Str-Parse]('this,is,<test>,for,< & >',',')
I am receiving an error when creating a view converted from code at website http://pratchev.blogspot.com/2007/02/passing-variable-to-in-list.html. ERROR: function pg_catalog.substring(text,bigint,integer) does not exist; #7. Appreciate your help.
Code:
WITH recursive Hierarchy(ChildId, SubRepInitials, ParentId, Parents, steps)
AS
(
SELECT salesforceid, salesforceinitials, parentid, CAST('' AS TEXT), 0 as steps
FROM tblbulksalesforce AS FirstGeneration
WHERE parentid IS NULL AND salesforceinitials IS NOT NULL
UNION ALL
SELECT NextGeneration.salesforceid, NextGeneration.salesforceinitials, Parent.ChildId,
CAST(CASE WHEN Parent.Parents = ''
THEN(CAST(NextGeneration.parentid AS TEXT) || ',')
ELSE(Parent.Parents || CAST(NextGeneration.parentid AS TEXT) || ',')
END AS TEXT), Parent.steps +1 as steps
FROM tblbulksalesforce AS NextGeneration
INNER JOIN Hierarchy AS Parent ON NextGeneration.parentid = Parent.ChildId
WHERE NextGeneration.salesforceinitials IS NOT NULL
)
SELECT ISNULL(h.ParentId,h.ChildId) AS ParentId, h.ChildId
, h.SubRepInitials, h.Parents, steps
,Generation0.salesforceinitials AS RepInitials
,parent.salesforceinitials AS RepInitialsParent
FROM Hierarchy AS h
LEFT JOIN tblbulksalesforce AS parent ON parent.RecordID = h.ParentId
LEFT JOIN tblbulksalesforce AS Generation0 ON Generation0.RecordID IN (
(SELECT SUBSTRING(string, 2, strpos(',', string, 2) - 2)
FROM (SELECT SUBSTRING(list, n, character_length(list))
FROM (SELECT ',' || h.Parents || ',') AS L(list),
(SELECT ROW_NUMBER() OVER (ORDER BY parentid)
FROM Hierarchy) AS Nums(n)
WHERE n <= character_length(list)) AS D(string)
WHERE character_length(string) > 1 AND SUBSTRING(string, 1, 1) = ',')
) OR Generation0.RecordID = h.ChildId;
Please see Postgres docs for how to use strpos() and substr(). Note that substring() is a completely different function with a different format for its arguments.
Try this:
WITH recursive Hierarchy(ChildId, SubRepInitials, ParentId, Parents, steps)
AS
(
SELECT salesforceid, salesforceinitials, parentid, CAST('' AS TEXT), 0 as steps
FROM tblbulksalesforce AS FirstGeneration
WHERE parentid IS NULL AND salesforceinitials IS NOT NULL
UNION ALL
SELECT NextGeneration.salesforceid, NextGeneration.salesforceinitials, Parent.ChildId,
CAST(CASE WHEN Parent.Parents = ''
THEN(CAST(NextGeneration.parentid AS TEXT) || ',')
ELSE(Parent.Parents || CAST(NextGeneration.parentid AS TEXT) || ',')
END AS TEXT), Parent.steps +1 as steps
FROM tblbulksalesforce AS NextGeneration
INNER JOIN Hierarchy AS Parent ON NextGeneration.parentid = Parent.ChildId
WHERE NextGeneration.salesforceinitials IS NOT NULL
)
SELECT ISNULL(h.ParentId,h.ChildId) AS ParentId, h.ChildId
, h.SubRepInitials, h.Parents, steps
,Generation0.salesforceinitials AS RepInitials
,parent.salesforceinitials AS RepInitialsParent
FROM Hierarchy AS h
LEFT JOIN tblbulksalesforce AS parent ON parent.RecordID = h.ParentId
LEFT JOIN tblbulksalesforce AS Generation0 ON Generation0.RecordID IN (
(SELECT SUBSTR(string, 2, strpos(',', string) - 2) -- you had a 2 sitting in strpos(',', string, 2) before. I'm not sure what you were trying to do with that.
FROM (SELECT SUBSTR(list, n, character_length(list))
FROM (SELECT ',' || h.Parents || ',') AS L(list),
(SELECT ROW_NUMBER() OVER (ORDER BY parentid)
FROM Hierarchy) AS Nums(n)
WHERE n <= character_length(list)) AS D(string)
WHERE character_length(string) > 1 AND SUBSTR(string, 1, 1) = ',')
) OR Generation0.RecordID = h.ChildId;