SQL Server : getting all leafs from tree - sql

I have datatable which contains:
|Parent Key| Component Key|
I need to get all leafs(parent key) for a chosen component key.
For example:
| 1 | 2 |
| 1 | 3 |
| 2 | 4 |
| 2 | 5 |
| 6 | 4 |
| 7 | 6 |
| 8 | 11 |
| 9 | 4 |
| 10 | 12 |
for component key = 4 I want to receive
| 1 |
| 7 |
| 9 |
If selected component key is already a leaf (there is no row where component key == selected component key) I want to return only the selected component key.
Can it be done by select only ?
How to do it in the most efficient way ?

Try something like that:
DECLARE #selected INT = 4;
WITH cte
AS
(
SELECT ParentKey, ComponentKey
FROM Table1
WHERE ComponentKey = #selected
UNION ALL
SELECT Table1.ParentKey, Table1.ComponentKey
FROM Table1
INNER JOIN cte ON Table1.ComponentKey = cte.ParentKey
)
SELECT ParentKey FROM cte
WHERE
ParentKey NOT IN (SELECT ComponentKey FROM Table1)
UNION
SELECT ParentKey FROM Table1
WHERE
ParentKey = #selected
AND ParentKey NOT IN (SELECT ComponentKey FROM Table1)
Replace Table1 with your table name.

Related

SQL recursion + column concatenation

I've got a self referencing table (in SQL Server):
Page
==========
Id: int
RelativeUrl: nvarchar(max)
ParentId: int -> FK to pageId
Example data:
ID | RelativeUrl | ParentId
===============================
1 | /root | null
2 | /test1 | 1
3 | /test2 | 1
4 | /test3 | 1
5 | /test1-1 | 2
6 | /test1-2 | 2
7 | /test1-1-1 | 5
I want to create an sql query to retrieve all pages of the table with FULL relative url.
I thought about using a recursive SQL query, but don't know how to concat the relativeurl to make it a full relative url.
Wanted result:
ID | FullRelativeUrl | ParentId
===================================================
1 | /root | null
2 | /root/test1 | 1
3 | /root/test2 | 1
4 | /root/test3 | 1
5 | /root/test1/test1-1 | 2
6 | /root/test1/test1-2 | 2
7 | /root/test1/test1-1/test1-1-1 | 5
You can use a recursive CTE:
with cte as (
select id, convert(varchar(max), relativeurl) as url, 1 as lev
from page
where parentid is null
union all
select p.id, concat(cte.url, p.relativeurl), lev + 1
from cte join
page p
on p.parentid = cte.id
)
select cte.*
from cte;
Here is a db<>fiddle.

Check if data for update is same as before in SQL Server

I have a table Table1:
ID | RefID | Answer | Points |
----+-------+---------+--------+
1 | 1 | 1 | 5 |
2 | 1 | 2 | 0 |
3 | 1 | 3 | 3 |
4 | 2 | 1 | 4 |
I have a result set in temp table Temp1 with same structure and have update Table1 only if for refID answer and points have changed, otherwise there should be deletion for this record.
I tried:
update table1
set table1.answer = temp1.answer,
table1.points = temp1.points
from table1
join temp1 on table1.refid = temp1.refid
where table1.answer != temp1.answer or table1.points != temp1.points
Here is a fiddle http://sqlfiddle.com/#!18/60424/1/1
However this is not working and don't know how to add the delete condition.
Desired result should be if tables not the same ex. (second row answer 2 points3):
ID | RefID | Answer | Points |
----+-------+---------+--------+
1 | 1 | 1 | 5 |
2 | 1 | 2 | 3 |
3 | 1 | 3 | 3 |
4 | 2 | 1 | 4 |
if they are same all records with refID are deleted.
Explanation when temp1 has this data
ID | RefID | Answer | Points |
----+-------+---------+--------+
12 | 1 | 1 | 5 |
13 | 1 | 2 | 0 |
14 | 1 | 3 | 3 |
EDIT: adding another id column questionid solved the update by adding this also in join.
Table structure is now:
ID | RefID | Qid |Answer | Points |
----+-------+------+-------+--------+
1 | 1 | 10 | 1 | 5 |
2 | 1 | 11 | 2 | 0 |
3 | 1 | 12 | 3 | 3 |
4 | 2 | 11 | 1 | 4 |
SQL for update is: (fiddle http://sqlfiddle.com/#!18/00f87/1/1) :
update table1
set table1.answer = temp1.answer,
table1.points = temp1.points
from table1
join temp1 on table1.refid = temp1.refid and table1.qid = temp1.qid
where table1.answer != temp1.answer or table1.points != temp1.points;
SELECT ID, refid, answer, points
FROM table1
How can I make the deletion case, if data is same ?
You need to add one more condition in the join to exactly match the column.Try this one.
update table1
set table1.answer=temp1.answer,
table1.points=temp1.points
from
table1 join temp1 on table1.refid=temp1.refid and **table1.ID=temp1.ID**
where table1.answer!=temp1.answer or table1.points!=temp1.points
I would first do the delete, and only then the update.
The reason for this is that once you've deleted all the records where the three columns are the same, your update statement becomes simpler - you only need the join, and no where clause:
DELETE t1
FROM table1 AS t1
JOIN temp1 ON t1.refid = temp1.refid
AND t1.qid = temp1.qid
AND t1.answer=temp1.answer
AND t1.points=temp1.points
UPDATE t1
SET answer = temp1.answer,
points = temp1.points
FROM table1 AS t1
JOIN temp1 ON t1.refid=temp1.refid
AND t1.qid = temp1.qid
I think from what i understood that you need to use id instead of refid or both if id is unique

select empty object in jsonb_each in postgres

How to select a empty object Record with jsonb_each function.
because I select some extra field with jsonb_each key and value.
but when a all record jsonb column in empty result is empty.
create table newtest (id SERIAL PRIMARY KEY,foo jsonb);
insert into newtest (foo) values ('{"a":1, "c":2}'), ('{"b":1}'), ('{}');
select * from newtest
ID | foo
-----+----------------
1 | "{"a": 1, "c": 2}"
2 | "{"b": 1}"
3 | "{}"
select id,(jsonb_each(foo)).key AS KEY, (jsonb_each(foo)).value AS value from newtest
Result
ID | key | value
-----+----------------
1 | a | 1
1 | c | 2
2 | b | 1
I need a result like
ID | key | value
-----+----------------
1 | a | 1
1 | c | 2
2 | b | 1
3 |null | null
A lateral left outer join should be the right thing:
SELECT newtest.id, item.key, item.value
FROM newtest
LEFT JOIN LATERAL jsonb_each(newtest.foo) item ON TRUE;
id | key | value
----+-----+-------
1 | a | 1
1 | c | 2
2 | b | 1
3 | |
(4 rows)
This will supply a NULL for missing entries on the right side.

SQL join for selecting two records with common data

I have database with this type of records:
Id | Value | DocId |
------ | ------ | ------|
1 | 10 | null |
2 | -10 | 1 | //this is child of record with id = 1
3 | 15 | null |
4 | -15 | 3 | //this is child of record with id = 3
5 | 7 | null |
6 | -7 | 5 | //this is child of record with id = 5
7 | 16 | null |
So I want to select the records where Id = 1 and Id = DocId, so this should return (because those are the records with Id = 1 and DocId = 1)
Id | Value | DocId |
------ | ------ | ------|
1 | 10 | null |
2 | -10 | 1 |
I know I can use a where clause but I need to do it with Join.
You can select twice from the same table as if they were two separate tables:
select * from TheTable t1, TheTable t2 where t1.id = t2.DocId;
You are showing a result for where id = 1 or docid = 1. So why must you use a join instead? This doesn't seem to make sense. Anyway, here you go:
select t.*
from t
join (select 1 as id) x on x.id in (t.id, t.docid);
You are saying you want the records where the id = 1 or the docId = 1. So in sql you can say almost exactly that:
select * from my_table where id = 1 or docId = 1;

Set-based way to calculate family ranges in SQL?

I have a table that contains parents and 0 or more children for each parent, with a flag indicating which records are parents. All of the members of a given family have the same parent id, and the parent always has the lowest id in a given family. Also, each child has a value associated with it. (Specifically, this is a database of emails and attachments, where each parent is an email and the children are the attachments.)
I have two fields I need to calculate:
Range = {lowest id in family} - {highest id in family} [populated for all members]
Value-list = {delimited list of the values of each child, in id order} [only for parent]
So, given this:
Id | Parent| HasChildren| Value | Range | Value-list
----------------------------------------|-----------
1 | 1 | 1 | | |
2 | 1 | 0 | a | |
3 | 1 | 0 | b | |
4 | 4 | 1 | | |
5 | 4 | 0 | c | |
6 | 6 | 0 | | |
I would like to end up with this:
Id | Parent| HasChildren| Value | Range | Value-list
----------------------------------------|-----------
1 | 1 | 1 | | 1-3 | a;b
2 | 1 | 0 | a | 1-3 |
3 | 1 | 0 | b | 1-3 |
4 | 4 | 1 | | 4-5 | c
5 | 4 | 0 | c | 4-5 |
6 | 6 | 0 | | 6-6 |
How can I do this efficiently? Ideally, I'd like to do this with just set-based logic, without cursors, or even stored procedures. Temporary tables are fine.
I'm working in T-SQL, if that makes a difference, though I'd be curious to see platform agnostic answers.
The following SQLFiddle Solution should do the job for you, however as #Allan mentioned, you might want to revise your database structure.
Using CTE's:
Note: my query uses table1 as name of Your table
with cte as(
select parent
,ValueList= stuff(( select ';' +isnull(t2.Value, '')
from table1 t2
where t1.parent=t2.parent
order by t2.value
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 2, '')
from table1 t1
group by parent
),
cte2 as (select parent
, min(id) as firstID
, max(id) as LastID
from table1
group by parent)
select *
,(select FirstID from cte2 t2 where t2.parent=t1.parent)+'-'+(select LastID from cte2 t2 where t2.parent=t1.parent) as [Range]
,(select ValueList from cte t2 where t1.parent=t2.parent and t1.[haschildren]='1') as [Value -List]
from table1 t1