Updating order of child record - sql

I've got two tables:
Parent
| id |
Child
| id | owner | ordernr |
owner is a foreign key referencing Parent's id. There is a uniqueness constraint on (owner, ordernr)
Now, there are some gaps in the orders and I'm trying to fix them as follows:
CREATE OR REPLACE VIEW myView AS
(SELECT childid, ordernr, n
FROM (SELECT child.id as childid, ordernr, ROW_NUMBER() OVER ( PARTITION BY parent.id ORDER BY ordernr) AS n
FROM Parent, Child WHERE owner = parent.id)
WHERE ordernr <> n)
UPDATE
(SELECT c.ordernr, n
FROM Child c, myView WHERE childid = c.id) t
SET t.ordernr = t.n
But I get: ORA-01779: cannot modify a column which maps to a non key-preserved table

ORA-01779: cannot modify a column which maps to a non key-preserved table
This error occurs when you try to INSERT or UPDATE columns in a join view which map to a non-key-preserved table.
You could use a MERGE.
For example,
MERGE INTO child c
USING (SELECT n
FROM myview) t
ON (t.childid = c.id)
WHEN matched THEN
UPDATE SET c.ordernr = t.n
/

Related

SQL: Count references to item in same table

I have the following SQL table:
id int(11) NOT NULL AUTO_INCREMENT,
name varchar(200),
parent int(11),
This stores some structured information in a tree:
"id" is the primary (uniq) key of the entries
"name" is some string
"parent" is the "id" of the parent entry (0: root element)
A sample table could be:
id name parent
--+-----------------+----------------
1 root_a 0
2 root_b 0
3 sub_b1 2
4 sub_sub_b1_1 3
5 sub_sub_b1_2 3
This could be a directory with folder ("root_"), sub-folder ("sub_"), sub-sub-folder ("sub_sub_*"), ...
Now I would like to have a SQL query, that returns for each entry how many child entries there are:
SELECT id,name,count(....) as child_count FROM table WHERE ...
For the example table this query shall return:
id name child_count
--+--------------+---------
1 root_a 0
2 root_b 1
3 sub_b1 2
4 sub_sub_b1_1 0
5 sub_sub_b1_2 0
How to perform such a count inside the same table?
Thanks
How about a correlated subquery?
select t.*,
(select count(*)
from t t2
where t2.parent = t.id
) as child_count
from t;
with qry1 as (
select parent,
count(*) as child_count
from table
group by parent
)
select table.id,
table.name,
isnull(qry1.child_count, 0) as child_count
from table
left join qry1
on table.id = qry1.parent
order by table.id
Note: Left Join. Inner join would exclude rows with no children.

Names of nodes at depth d for every descendant leaf

I have a category hierarchy that products are attached to. That category hierarchy is saved as an adjacency list. Products can be attached to any category nodes at any level. The category hierarchy is a tree.
I would like to...
get the name of every level 3 category...
per product...
where that product is attached to any level 3 category node...
or a descendant of a level 3 node.
I know I can materialize the hierarchy, and from that I've been able to satisfy all requirements but the last. I always lose some products or categories.
Given
CREATE TABLE product (p_id varchar PRIMARY KEY);
CREATE TABLE category (c_id varchar PRIMARY KEY, parent_c_id varchar);
CREATE TABLE product_category (
p_id varchar,
c_id varchar,
PRIMARY KEY (p_id, c_id),
FOREIGN KEY (p_id) REFERENCES product (p_id)
ON UPDATE CASCADE ON DELETE CASCADE,
FOREIGN KEY (c_id) REFERENCES category (c_id)
ON UPDATE CASCADE ON DELETE CASCADE
);
INSERT INTO product (p_id) VALUES
('p_01'),
('p_02'),
('p_03'),
('p_04'),
('p_05');
INSERT INTO category (c_id, parent_c_id) VALUES
('c_0_1', NULL),
-- L1
('c_1_1', 'c_0_1'),
('c_1_2', 'c_0_1'),
('c_1_3', 'c_0_1'),
-- L2
('c_2_1', 'c_1_1'),
('c_2_2', 'c_1_1'),
('c_2_3', 'c_1_2'),
('c_2_4', 'c_1_3'),
-- L3
('c_3_1', 'c_2_1'),
('c_3_2', 'c_2_2'),
('c_3_3', 'c_2_3'),
('c_3_4', 'c_2_4'),
-- L4
('c_4_1', 'c_3_1'),
('c_4_2', 'c_3_2'),
('c_4_3', 'c_3_3'),
('c_4_4', 'c_3_4');
INSERT INTO product_category (p_id, c_id) VALUES
-- p_01 explicitly attached to every level in path 1; include.
('p_01', 'c_0_1'),
('p_01', 'c_2_1'),
('p_01', 'c_3_1'),
('p_01', 'c_4_1'),
-- p_02 explicitly attached to desired level in paths 1 and 3; include both.
('p_02', 'c_3_3'),
('p_02', 'c_3_4'),
-- p_03 explicitly attached to super-level in path 3; exclude.
('p_03', 'c_2_4'),
-- p_04 explicitly attached to sub-level in path 1,
-- transitively to desired level in path 1; include.
('p_04', 'c_4_2');
-- p_05 not attached at all.
I would like to end up with something like
p_id | c_id
------+----------------
p_01 | {c_3_1}
p_02 | {c_3_3, c_3_4}
p_04 | {c_3_2}
(3 rows)
but the closest I have gotten is
WITH RECURSIVE category_tree (c_id, parent_c_id, depth, path) AS (
SELECT c_id, parent_c_id, 0 AS depth, ARRAY[]::varchar[]
FROM category
WHERE parent_c_id IS NULL
UNION ALL
SELECT c.c_id, c.parent_c_id, ct.depth + 1, path || c.c_id
FROM category_tree AS ct
INNER JOIN category AS c ON c.parent_c_id = ct.c_id
)
SELECT *
INTO TEMP TABLE t_category_path
FROM category_tree;
SELECT p.p_id, ARRAY_AGG(c_id) category_names
FROM product AS p,
(SELECT DISTINCT t1.c_id, p_id
FROM product_category AS pc
INNER JOIN t_category_path AS t1 ON pc.c_id = t1.c_id
WHERE t1.depth = 3
ORDER BY c_id) x
WHERE p.p_id = x.p_id
GROUP BY p.p_id;
p_id | category_names
------+----------------
p_01 | {c_3_1}
p_02 | {c_3_4,c_3_3}
(2 rows)
The order of categories is irrelevent (I want a set, not a list).
I can tolerate duplicate categories far better than missing categories or products.
I have some liberty to adjust the schema.
> select version();
version
--------------------------------------------------------------------------------------------------------------
PostgreSQL 10.12 on x86_64-redhat-linux-gnu, compiled by gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-39), 64-bit
step-by-step demo:db<>fiddle
WITH RECURSIVE cte AS (
SELECT c_id, parent_c_id, 0 as level, NULL AS level3_category
FROM category
WHERE parent_c_id IS NULL
UNION
SELECT
c.c_id,
cte.parent_c_id,
cte.level + 1,
CASE -- 1
WHEN cte.level + 1 = 3 THEN c.c_id
ELSE cte.level3_category
END
FROM
category c
JOIN
cte
ON c.parent_c_id = cte.c_id
)
SELECT
p_id,
ARRAY_AGG(DISTINCT level3_category) as c_id -- 2
FROM
cte
JOIN
product_category pc
ON cte.c_id = pc.c_id AND cte.level3_category IS NOT NULL
GROUP BY p_id
This CASE clause stores the current name if and only if it is level 3. If it is less, than it returns NULL, if it is greater, it takes the level 3 value.
DISTINCT is allowed in GROUP BY aggregates to eliminate non-distinct values.
You can use exists and not exists with joins to get a particular depth:
select p.p_id, array_agg(pc.c_id)
from products p join
product_category pc
on p.p_id = pc.p_id
where exists (select 1
from category_tree ct join
category_tree ctp
on ct.parent_cid = ctp.cid join
category_tree ctp2
on ctp.parent_cid = ctp2.cid
where ct.cid = pc.c_id
) and
not exists (select 1
from category_tree ct join
category_tree ctp
on ct.parent_cid = ctp.cid join
category_tree ctp2
on ctp.parent_cid = ctp2.cid join
category_tree ctp3
on ctp2.parent_cid = ctp3.cid
where ct.cid = pc.c_id
)
group by p.p_id;

Order parent records by child record count

I have a table that has a foreign key that points to the id column same table.
pages
=====
id integer primary key autoincrement
name text
parent integer
FOREIGN KEY(parent) REFERENCES pages(id)
When I do a select query is it possible to sort the results by the number of children records ?
You could join it with an aggregate query on the child records and sort according to that:
SELECT p.*
FROM pages p
JOIN (SELECT parent, COUNT(*) AS cnt
FROM pages
GROUP BY parent) c ON p.id = c.parent
ORDER BY c.cnt
You could compute the count of children with a correlated subquery and sort according to that:
SELECT id, name
FROM pages
ORDER BY (SELECT count(*)
FROM pages AS p2
WHERE p2.parent = pages.id);

count child records based on parent and date sql

I am tryin to find the number of child records associated to a parent. This is based on 2 columns in the same table (master_ref for parent and ref for child). The difficulty i am finding is to only count the child if they have the same date_entered as the parent. Any help would be much appreciated.
Something like the following should work for you:
select parent.master_ref, COUNT(*)
from nodes parent join
nodes child
on child.ref = parent.master_ref
where parent.date = child.date
group by parent.master_ref;
You need to do a join in order to compare values between the parent and the child.
Assuming you have 3 tables like:
CREATE TABLE Parents (ID INT, date_entered DATE);
CREATE TABLE Children (ID INT, date_entered DATE);
CREATE TABLE Relation (master_ref INT, ref INT);
Following select statement should give you what you want:
SELECT p.ID, COUNT(*)
FROM Parents p
JOIN Relation r
ON p.ID = r.master_ref
JOIN Children c
ON c.ID = r.ref
WHERE c.date_entered = p.date_entered
GROUP BY p.ID
SQLFiddle with that code (without data): http://sqlfiddle.com/#!4/6e7d3/2/0

sql server insert data from table1 to table2 where table1=table3 data

I am trying to insert into Table A, unique data from Table B that matches data in TAble C but I am keep getting the violation of primary key error and not sure what I am doing wrong
Table A - bookfeed
Table B - bookF
Table C - bookStats
INSERT INTO bookFeed
(entryId,feed,entryContent,pubDate,authorName,authorId,age,
sex,locale,pic,fanPage, faceTitle,feedtype,searchterm,clientId,dateadded)
SELECT DISTINCT b.entryId,b.feed,b.entryContent,b.pubDate,b.authorName,
b.authorId,b.age,b.sex,b.locale,b.pic,b.fanPage,b.faceTitle,b.feedtype,
b.searchterm, b.clientId,b.dateadded
FROM bookF as b
INNER JOIN bookStats as a on a.feed = b.feed
WHERE NOT EXISTS (SELECT *
FROM bookFeed as c
WHERE c.entryId = b.entryId)
Table A bookFeed has a primary key on entryId
It looks like in table bookF, there are duplicate records per entryId
If you only want one entryId (limited by PK on bookFeed), you can use this. Adjust the order by in the ROW_NUMBER to suit
insert into bookFeed (
entryId,feed,entryContent,pubDate,authorName,authorId,age,
sex,locale,pic,fanPage,faceTitle,feedtype,searchterm,clientId,dateadded)
select
entryId,feed,entryContent,pubDate,authorName,authorId,age,
sex,locale,pic,fanPage,faceTitle,feedtype,searchterm, clientId,dateadded
from
(
select rn=Row_number() over (partition by b.entryId order by b.entryId),
b.entryId,b.feed,b.entryContent,b.pubDate,b.authorName,b.authorId,b.age,
b.sex,b.locale,b.pic,b.fanPage,b.faceTitle,b.feedtype,b.searchterm, b.clientId,b.dateadded
from bookF as b
inner join bookStats as a on a.feed = b.feed
left join bookFeed as c on c.entryId=b.entryId
where c.entryId is null
) X
where rn=1
UPDATE : Try this for you query and see if it works,look at the data and see if there are duplicates meaning all the entries should have an entry id different than what is currently there -
SELECT b.entryId,b.feed,b.entryContent,b.pubDate,b.authorName,
b.authorId,b.age,b.sex,b.locale,b.pic,b.fanPage,b.faceTitle,b.feedtype,
b.searchterm, b.clientId,b.dateadded
FROM bookF as b
INNER JOIN bookStats as a on a.feed = b.feed
WHERE b.entryId IN (SELECT distinct entryid
FROM bookFeed)
I think you are trying to insert an entry id which is an primary key(check if the value trying to insert is not an duplicate,null or violates any other of primary key constraint)...so either dont try to insert it if it gets populated automatically or in case you are looking to insert it then turn on identity insert on..and try again...
But ideally your id should be calculated (auto increment or something) and never be inserted directly.