How to select path from nested sets excluding subtrees - sql

I have an SQLite database, which contains trees with red and black nodes (not red-black trees though). Trees are stored as nested sets (https://en.wikipedia.org/wiki/Nested_set_model)
Name TreeId Left Right IsBlack
A 1 1 8 1
B 1 2 7 0
C 1 3 6 1
D 1 4 5 1
A 2 1 10 1
B 2 2 5 0
C 2 6 9 1
D 2 3 4 1
D 2 7 8 1
Both B and C nodes with TreeId = 2 point to D node. So D is written twice.
These trees may contain only black or only red nodes
I would like to select all paths for the specified node, that do not contain red nodes, i. e. exclude red nodes and all their subtrees from result
Examples:
For record:
Name TreeId Left Right IsBlack
A 1 1 8 1
the result will be:
Name TreeId Left Right IsBlack
A 1 1 8 1
For record:
Name TreeId Left Right IsBlack
C 1 3 6 1
the result will be:
Name TreeId Left Right IsBlack
C 1 3 6 1
D 1 4 5 1
And, finally, for record:
Name TreeId Left Right IsBlack
A 2 1 10 1
The result will be:
Name TreeId Left Right IsBlack
A 2 1 10 1
C 2 6 9 1
D 2 7 8 1
To keep it simple, let's assume that there is another query, which selects TreeId, Left and Right parameters of the searched node by its name.
So I came up with the following query (for node A):
SELECT Nodes.* FROM Nodes
LEFT JOIN Nodes as n ON Nodes.[TreeId] = n.TreeId AND n.IsBlack = 0 AND Nodes.Left >= n.Left AND Nodes.Right <= n.Right
AND n.Left >= 1 AND n.Right <= 10
WHERE Nodes.TreeId = 2 AND Nodes.Left >= 1 AND Nodes.[Right] <= 10 AND n.Name IS NULL
The query seems to work, but it's terribly slow because of left join even with indexes.
So I was wondering, is there a way to optimize the query in terms of SQLite avoiding left join (for example using inner joins, unions, etc)
P.S. I cannot change the way the data is stored, but I can modify database schema (add new fields).
P.P.S. I understand, that I can query all results, and then filter them on the code side. The other option is to store two types of trees in db: one with red and black nodes, and the other one with black nodes only. However, both of these solutions are last resorts.
Thanks in advance

Okay, I don't know if it'll be faster or not - can't tell with only a few rows of data, but this one uses a recursive CTE to at least get the same results as your examples:
WITH RECURSIVE n AS
(SELECT * FROM nodes WHERE name= ?1 AND treeid = ?2 AND isblack = 1
UNION ALL
SELECT n2.name, n2.treeid, n2.left, n2.right, n2.isblack
FROM n
JOIN nodes AS n2
ON n2.treeid = n.treeid
AND (n2.left = n.left + 1 OR n2.right = n.right - 1)
WHERE n2.isblack = 1)
SELECT * FROM n ORDER BY name
You'll probably want an index on nodes(isblack, treeid, name) (And don't forget to run a PRAGMA optimize once in a while.) Bind/replace ?1 and ?2 with the obvious values for a particular run of the query.

Related

Create View based on join of column name and table value of two tables

I got two tables.
Table Ah:
Country HDI Luck SomeOtherColumn SoOn
1 1 2 x y
2 1 2 b c
3 2 3 v g
4 3 4 e y
5 2 2 b g
6 4 1 n k
and a second
Table Bu
Attribute Value Meaning
Country 1 RichAndLuckyCountry
Country 2 AnotherRichAndLuckyCountry
Country 3 AlsoQuiteRichButStillLuckyCountry
Country 4 NotSoRichAndSadAboutItCountry
Country 5 DoingWellCountry
Country 6 DontWorryBeHappyCountry
HDI 1 Very high
HDI 2 High
HDI 3 Medium
HDI 4 Low
Luck 1 Very high
Luck 2 High
Luck 3 Medium
Luck 4 Low
The resulting View I need would look like this:
Table Result
Country Country_Dissolved HDI HDI_Dissolved Luck Luck_Dissolved SomeOtherColumn SoOn
1 RichAndLuckyCountry 1 Very high 2 High x y
2 AnotherRichAndLuckyCountry 1 Very high 2 High b c
3 AlsoQuiteRichButStillLuckyCountry 2 High 3 Medium v g
4 NotSoRichAndSadAboutItCountry 3 Medium 4 Low e y
5 DoingWellCountry 2 High 2 High b g
6 DontWorryBeHappyCountry 4 Low 1 Very High n k
I only managed to get it done with one column combined with a where clause:
CREATE OR REPLACE VIEW Result AS
SELECT Ah.Country. Bu.Meaning as County_Dissolved
FROM Ah
INNER JOIN Bu
ON Ah.Country = Bu.Value
WHERE Bu.Attribute = 'Country'
I would probably need some command which loop through the column names and joins column names with the values in the attribute column, because the real table has many more possible combinations,so just making multiple SQL statements for every case is no solution.
How can I create a view like the above Result Table?
Dissolve the values of Table Ah with keys in Table Bu.
SQL Fiddle.
Any help would be appreciated.
Thanks in advance!
You need 3 left joins of Ah to Bu:
SELECT Ah.Country,
MAX(CASE WHEN b1.Attribute = 'Country' THEN b1.Meaning END) Country_Dissolved,
Ah.HDI,
MAX(CASE WHEN b2.Attribute = 'HDI' THEN b2.Meaning END) HDI_Dissolved,
Ah.Luck,
MAX(CASE WHEN b3.Attribute = 'Luck' THEN b3.Meaning END) Luck_Dissolved,
Ah.SomeOtherColumn,
Ah.SoOn
FROM Ah
LEFT JOIN Bu b1 ON b1.Value = Ah.Country AND b1.Attribute = 'Country'
LEFT JOIN Bu b2 ON b2.Value = Ah.HDI AND b2.Attribute = 'HDI'
LEFT JOIN Bu b3 ON b3.Value = Ah.Luck AND b3.Attribute = 'Luck'
GROUP BY Ah.Country, Ah.HDI, Ah.Luck, Ah.SomeOtherColumn, Ah.SoOn
ORDER BY Ah.Country
See the demo.
Your query seems to be correct.
My suggestion in this case would be trying
CREATE OR REPLACE VIEW Result AS
SELECT Ah.Country, Bu.Meaning as County_Dissolved
FROM Ah
INNER JOIN Bu
ON Ah.Country = Bu.Value
WHERE Bu.Attribute in ( 'Country','HDI','Luck')
Do a select distinct attribute from Bu and just copy and add as in above query.

Create multiple rows based on 1 column

I currently have a table with a quantity in it.
ID Code Quantity
1 A 1
2 B 3
3 C 2
4 D 1
Is there anyway to write a sql statement that would get me
ID Code Quantity
1 A 1
2 B 1
2 B 1
2 B 1
3 C 1
3 C 1
4 D 1
I need to break out the quantity and have that many number of rows
Thanks
Here's one option using a numbers table to join to:
with numberstable as (
select 1 AS Number
union all
select Number + 1 from numberstable where Number<100
)
select t.id, t.code, 1
from yourtable t
join numberstable n on t.quantity >= n.number
order by t.id
Online Demo
Please note, depending on which database you are using, this may not be the correct approach to creating the numbers table. This works in most databases supporting common table expressions. But the key to the answer is the join and the on criteria.
One way would be to generate an array with X elements (where X is the quantity). So for rows
ID Code Quantity
1 A 1
2 B 3
3 C 2
you would get
ID Code Quantity ArrayVar
1 A 1 [1]
2 B 3 [1,2,3]
3 C 2 [2]
using a sequence function (e.g, in PrestoDB, sequence(start, stop) -> array(bigint))
Then, unnest the array, so for each ID, you get a X rows, and set the quantity to 1. Not sure what SQL distribution you're using, but this should work!
You can use connect by statement to cross join tables in order to get your desired output.
check my solution it works pretty robust.
select
"ID",
"Code",
1 QUANTITY
from Table1, table(cast(multiset
(select level from dual
connect by level <= Table1."Quantity") as sys.OdciNumberList));

Get max value from a joined list paired with another column in DB2

I have the following tables:
Table I:
etu | nr |
1 2
2 2
2 3
2 1
3 4
3 9
Table A:
etu | rsp | nr
2 8 2
2 7 3
2 3 1
3 2 4
3 6 9
Now what I want to have as a result table is
etu | nr | rsp
2.. 3 7
3.. 9 6
So etu and nr are linked together and if multiple equal etu entries are available only the one with the highest nr is taken and the rsp value is added in the result table. in addition if more etu entries are available in the table I there are .. added to the etu value.
Explain: For the 3 9 6 row: The last row on table I is 3 9 so 3 is the number that is looked for and 9 is the highest number for the 3 rows. So we take that and add the rsp value for that ( 6 ) and we add that to the result table. For the 2 row it is the same 2 3 being the highest 2 row in table I.
I got something like:
select x.etu, x.rsp, y.nr from(
select i.etu etu, max(i.nr) maxnr, a.rsp from i left join a on
i.etu=a.etu and i.nr=a.nr group by etu)t
inner join a x on x.etu=t.etu and x.nr=t.nr inner join y on y.etu=t.etu
and y.nr=t.nr
or
select i.etu, max(i.nr) a.rsp from i left join a on i.etu=a.etu and
i.nr=a.nr grounp by
None even get me close to get the results that I want less add the .. after the etu when having the right result.
The system is DB10.5 Windows.
Thank you for all your help in advance.
Viking
I would use a CTE here like this:
with tmp as (
select i.etu, max(i.nr) as nt, count(*) as cnt
from i
group by i.etu)
select case
when tmp.cnt = 1 then char(a.etu)
else concat(rtrim(char(a.etu)), '..')
end as etu,
a.nr,
a.rsp
from tmp
left outer join a
on a.etu = tmp.etu
and a.nr = tmp.nr
The CTE provides the information necessary to join with a to get the correct response, and append the .. as necessary.

Selecting Data from Same Table that Doesn't Match

I have found several solutions for my type of problem, but I having trouble applying it in my situation.
Essentially I have a Vehicle Table:
License VIN Region
1 1 1
1 2 2
2 3 1
2 3 2
3 4 1
3 4 2
3 5 3
I want to take the license and vin from region 1 and see if the vin matches in all other regions based on the license. If it doesn't I want all the rows that don't match, but if it does match I don't want the row. So a complexity does come in when say I have 3 licenses and region 1 matches one row, but not the other, I want both the unmatched and region 1; however, when I have 3 licenses that all match I don't want any rows including region 1.
So my results in this case would be:
License VIN Region
1 1 1
1 2 2
3 4 1
3 5 3
I am using SQL Server 2005.
I think this is what you're looking for
SELECT DISTINCT a.*
FROM Vehicle AS a
INNER JOIN Vehicle AS b
ON a.License = b.License
WHERE a.VIN != b.VIN
AND a.Region != b.Region
AND (a.Region = 1 OR b.Region = 1)

Efficient SQL to calculate # of shared affiliations

So I have
a table that stores asymmetrical connections between two persons
(like a Twitter follow; not like a Facebook friend) and
a table that stores a person's affiliations to various groups
My task is to find, for each asymmetrical relationship, the number of affiliations shared between the "from person" and the "to person".
I made this brute force solution, but I'm wondering if brighter minds could come up with something more efficient.
select frm01.from_person_id, frm01.to_person_id, count(*) num_affl
from
(
select lnk.from_person_id, lnk.to_person_id, ga.grp_id from_grp_id
from links lnk
left outer join grp_affl ga on lnk.from_person_id = ga.person_id
group by lnk.from_person_id, lnk.to_person_id, grp_id
) frm01
inner join
(
select lnk.from_person_id, lnk.to_person_id, ga.grp_id to_grp_id
from links lnk
left outer join grp_affl ga on lnk.to_person_id = ga.person_id
group by lnk.from_person_id, lnk.to_person_id, grp_id
) to01
on (
frm01.from_person_id = to01.from_person_id
and frm01.to_person_id = to01.to_person_id
and frm01.from_grp_id = to01.to_grp_id
)
group by frm01.from_person_id, frm01.to_person_id;
Using ANSI SQL on Netezza (which doesn't allow correlated subqueries).
TIA!
Edited to add table schema:
table lnk:
from_person_id to_person_id
1 4
2 5
3 6
4 2
5 3
table grp_affl:
person_id grp_id
1 A
1 B
1 C
2 A
3 B
4 C
5 A
5 B
5 C
6 A
expected output:
from_person_id to_person_id num_affl
1 4 1
2 5 1
3 6 0
4 2 0
5 3 1
Persons 1 & 4 have 1 affiliation in common (C), 2 & 5 have A in common, 5 & 3 have B in common. 3 & 6 have nothing in common. Likewise 4 & 2.
You can do this with aggregation and the right joins:
select pairs.from_person, pairs.to_person, count(*)
from links pairs join
grp_affil fromga
on fromga.person_id = pairs.from_person join
grp_affil toga
on toga.person_id = pairs.to_person and
toga.grp_id = fromga.grp_id
group by pairs.from_person, pairs.to_person;
The joins bring in the groups. The last condition only brings in matching groups between the two persons. The final group by counts them.