Inserting values into array in PostgreSQL - sql

I am trying to insert values into an integer array, used as path to show all ancestors of a particular node.
These values (parent_link integer) are from a table with ID and parent_link. I am trying to traverse the tree-like structure to assemble all parent_link in a path to insert into an integer array belonging to that particular ID. I am trying to do this for every single record in my database. So far I have:
INSERT INTO master_ifis_network (path)
SELECT t2.parent_link
FROM master_ifis_network as t2
WHERE t2.parent_link = (SELECT t1.parent_link
FROM master_ifis_network as t1)
AND t2.link_id = (parent_link)
I get an error saying that I cannot insert an integer where an integer[] is expected.
I have also tried this, which outputs a list of the parent nodes:
SELECT parentX.parent_link FROM [table name] as nodeX, [table name] as parentx
WHERE nodeX.left BETWEEN parentX.left AND parentX.right)
AND nodeX.link_id = [some id]
ORDER BY parentX.left DESC
Any hints or ideas?

Use a recursive CTE, i.e.: WITH RECURSIVE.
And you need an UPDATE, not an INSERT:
WITH RECURSIVE cte AS (
SELECT link_id, ARRAY[parent_link] AS path, 1 AS level
FROM master_ifis_network
UNION ALL
SELECT c.link_id, m.parent_link || c.path, c.level + 1
FROM cte c
JOIN master_ifis_network m ON m.link_id = c.path[1]
)
UPDATE master_ifis_network m
SET path = sub.path
FROM (
SELECT DISTINCT ON (link_id) *
FROM cte
ORDER BY link_id, level DESC
) sub
WHERE m.link_id = sub.link_id;
Related answers:
Tree Structure and Recursion
Recursive query used for transitive closure
There are many others here. Search with above key words.

Here is what I ended up with:
UPDATE master_ifis_network SET path = array(SELECT parentX.parent_link
FROM master_ifis_network AS parentX
WHERE (nodeX.left BETWEEN parentX.left AND parentX.right) AND nodeX.link_id = [some id]
ORDER BY parentX.left DESC)
WHERE link_id = [same id];"

Related

Use Exists with a Column of Query Result?

I have 2 tables.
One is bom_master:
CHILD
PARENT
1-111
66-6666
2-222
77-7777
2-222
88-8888
3-333
99-9999
Another one is library:
FileName
Location
66-6666_A.step
S:\ABC
77-7777_C~K1.step
S:\DEF
And I want to find out if the child's parents have related files in the library.
Expected Result:
CHILD
PARENT
FileName
1-111
66-6666
66-6666_A.step
2-222
77-7777
77-7777_C~K1.step
Tried below lines but return no results. Any comments? Thank you.
WITH temp_parent_PN(parentPN)
AS
(
SELECT
[PARENT]
FROM [bom_master]
where [bom_master].[CHILD] in ('1-111','2-222')
)
SELECT s.[filename]
FROM [library] s
WHERE EXISTS
(
SELECT
*
FROM temp_parent_PN b
where s.[filename] LIKE '%'+b.[parentPN]+'%'
)
If you have just one level of dependencies use the join solution proposed by dimis164.
If you have deeper levels you could use recursive queries allowed by WITH clause (
ref. WITH common_table_expression (Transact-SQL)).
This is a sample with one more level of relation in bom_master (you could then join the result of the recursive query with library as you need).
DECLARE #bom_master TABLE (Child NVARCHAR(MAX), Parent NVARCHAR(MAX));
INSERT INTO #bom_master VALUES
('1-111', '66-666'),
('2-222', '77-777'),
('3-333', '88-888'),
('4-444', '99-999'),
('A-AAA', '1-111');
WITH
leaf AS ( -- Get the leaf elements (elements without a child)
SELECT Child FROM #bom_master b1
WHERE NOT EXISTS (SELECT * FROM #bom_master b2 WHERE b2.Parent = b1.Child) ),
rec(Child, Parent, Level) AS (
SELECT b.Child, b.Parent, Level = 1
FROM #bom_master b
JOIN leaf l ON l.Child = b.Child
UNION ALL
SELECT rec.Child, b.Parent, Level = rec.Level + 1
FROM rec
JOIN #bom_master b
ON b.Child = rec.Parent )
SELECT * FROM rec
I think you don't have to use exists. The problem is that you need to substring to match the join.
Have a look at this:
SELECT b.CHILD, b.PARENT, l.[FileName]
FROM [bom_master] b
INNER JOIN [library] l ON b.PARENT = SUBSTRING(l.FileName,1,7)

ORA-30926: unable to get a stable set of rows in the source tables in merge query in oracle

I really don't understand why I am getting error with the below query as ORA-30926: unable to get a stable set of rows in the source tables as i have provided distinct clause in my merge query and also BB_TST_HISTORY has appropriate unique constraint.
merge into bb_tst_history h
using (select distinct r.ausuebungsbezeichnung
from bb_tst_rollup r
join ( select root, boerse, security_typ
from bb_export_filter
minus
select root, boerse, security_typ
from bb_tst_exception
) e
on r.root = e.root
and r.security_typ = e.security_typ
and r.boerse = e.boerse
) s
on (s.root = h.root and s.security_typ = h.security_typ and s.boerse = h.boerse)
when matched then
update set h.ausuebungsbezeichnung = s.ausuebungsbezeichnung
when not matched then
insert values (s.ausuebungsbezeichnung);
You must have single record for matching criteria in the USING clause.
so you can use the GROUP BY and MAX in USING clause as follows:
MERGE INTO BB_TST_HISTORY H
USING (
SELECT MAX(R.AUSUEBUNGSBEZEICHNUNG) AS AUSUEBUNGSBEZEICHNUNG,
R.ROOT, R.SECURITY_TYP, R.BOERSE
FROM BB_TST_ROLLUP R
JOIN (
SELECT ROOT, BOERSE, SECURITY_TYP
FROM BB_EXPORT_FILTER
MINUS
SELECT ROOT, BOERSE, SECURITY_TYP
FROM BB_TST_EXCEPTION
) E
ON R.ROOT = E.ROOT
AND R.SECURITY_TYP = E.SECURITY_TYP
AND R.BOERSE = E.BOERSE
GROUP BY R.ROOT, R.SECURITY_TYP, R.BOERSE
) S ON ( S.ROOT = H.ROOT
AND S.SECURITY_TYP = H.SECURITY_TYP AND S.BOERSE = H.BOERSE )
WHEN MATCHED THEN UPDATE
SET H.AUSUEBUNGSBEZEICHNUNG = S.AUSUEBUNGSBEZEICHNUNG
WHEN NOT MATCHED THEN
INSERT VALUES ( S.AUSUEBUNGSBEZEICHNUNG );
This error occurs because you are getting more than one row in your source for the joining columns. Best way to approach this issue is, identifying those records which are causing issue. Below SQL can give you those records -
select root, security_typ, boerse, count(1) from
(select distinct r.AUSUEBUNGSBEZEICHNUNG from BB_TST_ROLLUP r,
(select ROOT, BOERSE, SECURITY_TYP from BB_EXPORT_FILTER minus
select ROOT, BOERSE, SECURITY_TYP from BB_TST_EXCEPTION))
group by root, security_typ, boerse having count(1) > 1;
You need to update additional join condition in 'ON' clause to fix this issue.
Updated Merge Statement -
merge into BB_TST_HISTORY h using
( select ROOT, BOERSE, SECURITY_TYP, AUSUEBUNGSBEZEICHNUNG from (select distinct r.AUSUEBUNGSBEZEICHNUNG from BB_TST_ROLLUP r
join
(select ROOT, BOERSE, SECURITY_TYP from BB_EXPORT_FILTER minus
select ROOT, BOERSE, SECURITY_TYP from BB_TST_EXCEPTION))) e
on r.root=e.root and r.security_typ=e.security_typ and r.boerse=e.boerse ) s
on (s.root=h.root and s.security_typ=h.security_typ and s.boerse=h.boerse)
when matched then update set h.AUSUEBUNGSBEZEICHNUNG = s.AUSUEBUNGSBEZEICHNUNG
when not matched then insert values (s.AUSUEBUNGSBEZEICHNUNG);

Check if a combination of fields already exists in the table

My weakest area of SQL are self JOINS, currently struggling with an issue.
I need to find the latest entry in a table, I'm using a WHERE DATEFIELD IN (SELECT MAX(DATEFIELD) FROM TABLE) to do this. I then need to establish if 3 columns from that already exist in the same TABLE.
My latest attempt looks like this -
SELECT * FROM PART_TABLE
WHERE NOT EXISTS
(
SELECT
t1.DATEFIELD
t1.CODE1
t1.CODE2
t1.CODE3
FROM PART_TABLE t1
INNER JOIN PART_TABLE t2 ON t1.UNIQUE = t2.UNQIUE
)
WHERE t1.DATEFIELD IN
(
SELECT MAX(DATEFIELD)
FROM PARTTABLE
)
)
I think part of the issue is that I can't exclude the unique row from t1 when checking in t2 using this method.
Using MSSQL 2014.
The following query will return the latest record from your table and a bit flag whether a duplicate tuple {Code1, Code2, Code3} exists in it under a different identifier:
select top (1) p.*,
case when exists (
select 0 from dbo.Part_Table t where t.Unique != p.Unique
and t.Code1 = p.Code1 and t.Code2 = p.Code2 and t.Code3 = p.Code3
) then 1
else 0 end as [IsDuplicateExists]
from dbo.Part_Table p
order by p.DateField desc;
You can use this example as a template to address your specific needs, which unfortunately aren't immediately apparent from your explanation.

Save row in %ROWTYPE and delete it efficiently afterwards?

This is how I do it currently:
DECLARE tmp message%ROWTYPE;
BEGIN;
SELECT * INTO tmp FROM [...]
DELETE FROM message m WHERE m.id = tmp.id;
END;
I'm afraid that the db will do two queries here: One for doing the SELECT and one for the DELETE. In case this is true - can I make this more efficient somehow? After all the row that should be deleted was already found in the SELECT query.
N.b. I'm eventually storing something from the SELECT query and return it from the function. The above is just simplified.
delete from message m
using (
select *
from ...
) s
where m.id = s.id
returning s.*
For simple cases you do not even need a subquery.
If your secret first query only involves the same table:
DELETE FROM message m
WHERE m.id = <something>
RETURNING m.*; -- or what you need from the deleted row.
If your secret first query involves one or more additional tables:
DELETE FROM message m
USING some_tbl s
WHERE s.some_column = <something>
AND m.id = s.id
RETURNING m.id, s.foo; -- you didn't define ...
Solution for actual query (after comments)
An educated guess, to delete the oldest row (smallest timestamp) from each set with identical id:
DELETE FROM message m
USING (
SELECT DISTINCT ON (id)
id, timestamp
FROM message
WHERE queue_id = _queue_id
AND source_client_id = _source_client_id
AND (target_client_id IN (-1, _client_id))
ORDER BY id, timestamp
) sub
WHERE m.id = sub.id
AND m.timestamp = sub.timestamp
RETURNING m.content
INTO rv;
Or, if (id, timestamp) is UNIQUE, NOT EXISTS is probably faster:
DELETE FROM message m
WHERE queue_id = _queue_id
AND source_client_id = _source_client_id
AND target_client_id IN (-1, _client_id)
AND NOT EXISTS (
SELECT 1
FROM message
WHERE queue_id = _queue_id
AND source_client_id = _source_client_id
AND target_client_id IN (-1, _client_id)
AND id = m.id
AND timestamp < m.timestamp
) sub
WHERE m.id = sub.id
RETURNING m.content
INTO rv;
More about DISTINCT ON and selecting "the greatest" from each group:
Select first row in each GROUP BY group?
If performance is your paramount objective, look to the last chapter ...
Aside: timestamp is a basic type name in Postgres and a reserved word in standard SQL. Don't use it as identifier.
Solution in comment below, audited:
DELETE FROM message m
USING (
SELECT id
FROM message
WHERE queue_id = _queue_id
AND target_client_id IN (client_id, -1)
ORDER BY timestamp
LIMIT 1
) AS tmp
WHERE m.id = tmp.id
RETURNING m.content
INTO rv;
INTO ... only makes sense inside a plpgsql function, of course.
An index on (queue_id, timestamp) makes this fast - possibly even a partial index with the condition WHERE target_client_id IN (client_id, -1) (depends).

ITVF with With-Statement possible?

IS it possible to return the table of a With-Statement from an inline-table valued function?
my With-Statement looks like this
WITH ret AS(
SELECT t.ID
FROM SelfReferencingTable
WHERE ID = #PartnerID
UNION ALL
SELECT t.ID
FROM (SelfReferencingTable) t INNER JOIN
ret r ON t.ParentID = r.ID
)
Yes, you can. You just have to place the common table expression (what you refer to as a WITH statement) in the appropriate place:
create function TT()
RETURNS TABLE
AS
RETURN (With Aardvark as (select * from sysobjects) --TODO - Remove *, use column names
select * from Aardvark)
;