Find relation level from text field - sql

I have a table containing geographical structure of units. There are parent-child relation columns but I want to use the existing text field (instead of recursion) to find the relation level between the items.
(here's a table creation script)
drop table if exists #temp_structure
create table #temp_structure
(org_id int,
parent_org_id int,
org_name nvarchar(255),
search_tree nvarchar(255))
insert into #temp_structure
values
(1,null,'World','| 1 |'),
(2,1,'Europe','| 1 | 2 |'),
(3,1,'North America','| 1 | 3 |'),
(4,1,'South America','| 1 | 4 |'),
(5,1,'Asia','| 1 | 5 |'),
(6,1,'Africa','| 1 | 6 |'),
(7,1,'Australia','| 1 | 7 |'),
(8,2,'Spain','| 1 | 2 | 8 |'),
(9,2,'Germany','| 1 | 2 | 9 |'),
(10,2,'Italy','| 1 | 2 | 10 |'),
(11,2,'France','| 1 | 2 | 11 |'),
(12,8,'Madrid ','| 1 | 2 | 8 | 12 |'),
(13,8,'Barcelona ','| 1 | 2 | 8 | 13 |'),
(14,9,'Berlin','| 1 | 2 | 9 | 14 |'),
(15,9,'Munich','| 1 | 2 | 9 | 15 |'),
(16,10,'Rome','| 1 | 2 | 10 | 16 |'),
(17,10,'Milano','| 1 | 2 | 10 | 17 |'),
(18,11,'Paris','| 1 | 2 | 11 | 18 |'),
(19,11,'Marseille','| 1 | 2 | 11 | 19 |')
The expected result I would like to achieve is presented below (I listed only one 4th level example):
+--------+-------------+------------+
| org_id | search_item | nest_level |
+--------+-------------+------------+
| 1 | 1 | 1 |
| 2 | 2 | 1 |
| 2 | 1 | 2 |
| 3 | 3 | 1 |
| 3 | 1 | 2 |
| 4 | 4 | 1 |
| 4 | 1 | 2 |
| 5 | 5 | 1 |
| 5 | 1 | 2 |
| 6 | 6 | 1 |
| 6 | 1 | 2 |
| 7 | 7 | 1 |
| 7 | 1 | 2 |
| 8 | 8 | 1 |
| 8 | 2 | 2 |
| 8 | 1 | 3 |
| 9 | 9 | 1 |
| 9 | 2 | 2 |
| 9 | 1 | 3 |
| 10 | 10 | 1 |
| 10 | 2 | 2 |
| 10 | 1 | 3 |
| 11 | 11 | 1 |
| 11 | 2 | 2 |
| 11 | 1 | 3 |
| 12 | 12 | 1 |
| 12 | 8 | 2 |
| 12 | 2 | 3 |
| 12 | 1 | 4 |
.....................................
+--------+-------------+------------+
I was able to pull the org_id-search_item relation using STRING_SPLIT, but I still miss the tricky level part (I wonder about enumerating the '|' characters)
SELECT t.org_id
--,substring(replace(search_tree, ' ', ''), 2, len(replace(search_tree, ' ', '')) - 2)
,ss.value as search_item
FROM #temp_structure t
CROSS APPLY string_split(substring(replace(search_tree, ' ', ''), 2, len(replace(search_tree, ' ', '')) - 2),'|') ss

I have not thoroughly tested this, but you could try something like the following:
-- Table mock-up.
DECLARE #temp TABLE ( org_id int, parent_org_id int, org_name nvarchar(255), search_tree nvarchar(255) )
-- Insert sample data...
INSERT INTO #temp VALUES
(1,null,'World','| 1 |'),(2,1,'Europe','| 1 | 2 |'),
(3,1,'North America','| 1 | 3 |'),(4,1,'South America','| 1 | 4 |'),
(5,1,'Asia','| 1 | 5 |'),(6,1,'Africa','| 1 | 6 |'),
(7,1,'Australia','| 1 | 7 |'),(8,2,'Spain','| 1 | 2 | 8 |'),
(9,2,'Germany','| 1 | 2 | 9 |'),(10,2,'Italy','| 1 | 2 | 10 |'),
(11,2,'France','| 1 | 2 | 11 |'),(12,8,'Madrid ','| 1 | 2 | 8 | 12 |');
-- Select data in a nested level...
SELECT
org_id,
search_item,
ROW_NUMBER() OVER ( PARTITION BY org_id ORDER BY org_id, parent_org_id, search_item DESC ) AS nest_level
FROM #temp AS tmp
CROSS APPLY (
SELECT CAST ( [value] AS INT ) AS search_item FROM STRING_SPLIT ( tmp.search_tree, '|' )
WHERE NULLIF ( [value], '' ) IS NOT NULL
) AS tree
ORDER BY
org_id, parent_org_id, search_item DESC;
Returns
+--------+-------------+------------+
| org_id | search_item | nest_level |
+--------+-------------+------------+
| 1 | 1 | 1 |
| 2 | 2 | 1 |
| 2 | 1 | 2 |
| 3 | 3 | 1 |
| 3 | 1 | 2 |
| 4 | 4 | 1 |
| 4 | 1 | 2 |
| 5 | 5 | 1 |
| 5 | 1 | 2 |
| 6 | 6 | 1 |
| 6 | 1 | 2 |
| 7 | 7 | 1 |
| 7 | 1 | 2 |
| 8 | 8 | 1 |
| 8 | 2 | 2 |
| 8 | 1 | 3 |
| 9 | 9 | 1 |
| 9 | 2 | 2 |
| 9 | 1 | 3 |
| 10 | 10 | 1 |
| 10 | 2 | 2 |
| 10 | 1 | 3 |
| 11 | 11 | 1 |
| 11 | 2 | 2 |
| 11 | 1 | 3 |
| 12 | 12 | 1 |
| 12 | 8 | 2 |
| 12 | 2 | 3 |
| 12 | 1 | 4 |
+--------+-------------+------------+

Related

Query with WITH clause and COUNT subquery

In the query below, I don't get the results i would expect. Any insights why? How could i reformulate such query to get the desired results?
Schema (SQLite v3.30)
WITH RECURSIVE
cnt(x,y) AS (VALUES(0,ABS(Random()%3)) UNION ALL SELECT x+1, ABS(Random()%3) FROM cnt WHERE x<10),
i_rnd as (SELECT r1.x, r1.y, (SELECT COUNT(*) FROM cnt as r2 WHERE r2.y<=r1.y) as idx FROM cnt as r1)
SELECT * FROM i_rnd ORDER BY y;
result:
| x | y | idx |
| --- | --- | --- |
| 1 | 0 | 3 |
| 5 | 0 | 6 |
| 8 | 0 | 5 |
| 9 | 0 | 4 |
| 10 | 0 | 2 |
| 3 | 1 | 4 |
| 0 | 2 | 11 |
| 2 | 2 | 11 |
| 4 | 2 | 11 |
| 6 | 2 | 11 |
| 7 | 2 | 11 |
expected result:
| x | y | idx |
| --- | --- | --- |
| 1 | 0 | 5 |
| 5 | 0 | 5 |
| 8 | 0 | 5 |
| 9 | 0 | 5 |
| 10 | 0 | 5 |
| 3 | 1 | 6 |
| 0 | 2 | 11 |
| 2 | 2 | 11 |
| 4 | 2 | 11 |
| 6 | 2 | 11 |
| 7 | 2 | 11 |
In other words, idx should indicate how many rows have y less or equal than the y of row considered.
I would just use:
select cnt.*,
count(*) over (order by y)
from cnt;
Here is a db<>fiddle.
The issue with your code is probably that the CTE is re-evaluated each time it is called, so the values are not consistent -- a problem with volatile functions in CTEs.

sql (oracle) counting number of overlapping intervals

I have the following problem:
Given the following table test in an oracle sql database:
+----+------+-------+------+
| id | name | start | stop |
+----+------+-------+------+
| 1 | A | 1 | 5 |
+----+------+-------+------+
| 2 | A | 2 | 6 |
+----+------+-------+------+
| 3 | A | 5 | 8 |
+----+------+-------+------+
| 4 | A | 9 | 10 |
+----+------+-------+------+
| 5 | B | 3 | 6 |
+----+------+-------+------+
| 6 | B | 4 | 8 |
+----+------+-------+------+
| 7 | B | 1 | 2 |
+----+------+-------+------+
I would like to find the number of overlapping intervals (endpoints included) [start, stop] n_overlap, for all id having the same name, i.e.:
+----+------+-------+------+-----------+
| id | name | start | stop | n_overlap |
+----+------+-------+------+-----------+
| 1 | A | 1 | 5 | 3 |
+----+------+-------+------+-----------+
| 2 | A | 2 | 6 | 3 |
+----+------+-------+------+-----------+
| 3 | A | 4 | 8 | 3 |
+----+------+-------+------+-----------+
| 4 | A | 9 | 10 | 1 |
+----+------+-------+------+-----------+
| 5 | B | 3 | 6 | 2 |
+----+------+-------+------+-----------+
| 6 | B | 4 | 8 | 2 |
+----+------+-------+------+-----------+
| 7 | B | 1 | 2 | 1 |
+----+------+-------+------+-----------+
One method uses a correlated subquery:
select t.*,
(select count(*)
from test t2
where t2.name = t.name and
t2.start < t.end and
t2.end > t.start
) as num_overlaps
from test t;

SQL how to force to display row with 0 if no data available?

My table returns results as following (skips row if HourOfDay does not have data for particular ID)
ID HourOfDay Counts
--------------------------
1 5 5
1 13 10
1 23 3
..........................HourOfDay up till 23
2 9 1
and so on.
What I am trying to achieve is to force showing rows displaying 0 for HoursOfDay, which don't have data, like following:
ID HourOfDay Counts
--------------------------
1 0 0
1 1 0
1 2 0
1......................
1 5 5
1 6 0
1......................
1 23 3
2 0 0
2 1 0
etc.
I have researched around about it. It looks like I can achieve this result if I create an extra table and outer join it. So I have created table variable in SP (as a temp workaround)
DECLARE #Hours TABLE
(
[Hour] INT NULL
);
INSERT INTO #Hours VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)
,(13),(14),(15),(16),(17),(18),(19),(20),(21),(22),(23);
However, no matter how I join it, it does not achieve desired result.
How do I proceed? Do I add extra columns to join on? Completely different approach? Any hint in the right direction is appreciated!
Using a derived table for the distinct Ids cross joined to #Hours, left joined to your table:
select
i.Id
, h.Hour
, coalesce(t.Counts,0) as Counts
from (select distinct Id from t) as i
cross join #Hours as h
left join t
on i.Id = t.Id
and h.Hour = t.HourOfDay
rextester demo: http://rextester.com/XFZYX88502
returns:
+----+------+--------+
| Id | Hour | Counts |
+----+------+--------+
| 1 | 0 | 0 |
| 1 | 1 | 0 |
| 1 | 2 | 0 |
| 1 | 3 | 0 |
| 1 | 4 | 0 |
| 1 | 5 | 5 |
| 1 | 6 | 0 |
| 1 | 7 | 0 |
| 1 | 8 | 0 |
| 1 | 9 | 0 |
| 1 | 10 | 0 |
| 1 | 11 | 0 |
| 1 | 12 | 0 |
| 1 | 13 | 10 |
| 1 | 14 | 0 |
| 1 | 15 | 0 |
| 1 | 16 | 0 |
| 1 | 17 | 0 |
| 1 | 18 | 0 |
| 1 | 19 | 0 |
| 1 | 20 | 0 |
| 1 | 21 | 0 |
| 1 | 22 | 0 |
| 1 | 23 | 3 |
| 2 | 0 | 0 |
| 2 | 1 | 0 |
| 2 | 2 | 0 |
| 2 | 3 | 0 |
| 2 | 4 | 0 |
| 2 | 5 | 0 |
| 2 | 6 | 0 |
| 2 | 7 | 0 |
| 2 | 8 | 0 |
| 2 | 9 | 1 |
| 2 | 10 | 0 |
| 2 | 11 | 0 |
| 2 | 12 | 0 |
| 2 | 13 | 0 |
| 2 | 14 | 0 |
| 2 | 15 | 0 |
| 2 | 16 | 0 |
| 2 | 17 | 0 |
| 2 | 18 | 0 |
| 2 | 19 | 0 |
| 2 | 20 | 0 |
| 2 | 21 | 0 |
| 2 | 22 | 0 |
| 2 | 23 | 0 |
+----+------+--------+

writing SQL query to show result in specific order

I have this table
+----+--------+------------+-----------+
| Id | day_id | subject_id | period_Id |
+----+--------+------------+-----------+
| 1 | 1 | 1 | 1 |
| 2 | 1 | 2 | 2 |
| 8 | 2 | 6 | 1 |
| 9 | 2 | 7 | 2 |
| 15 | 3 | 3 | 1 |
| 16 | 3 | 4 | 2 |
| 22 | 4 | 5 | 1 |
| 23 | 4 | 5 | 2 |
| 24 | 4 | 6 | 3 |
| 29 | 5 | 8 | 1 |
| 30 | 5 | 1 | 2 |
to something like this
| Id | day_id | subject_id | period_Id |
| 1 | 1 | 1 | 1 |
| 8 | 2 | 6 | 1 |
| 15 | 3 | 3 | 1 |
| 22 | 4 | 5 | 1 |
| 29 | 5 | 8 | 1 |
| 2 | 1 | 2 | 2 |
| 2 | 1 | 2 | 2 |
| 16 | 3 | 4 | 2 |
| 23 | 4 | 5 | 2 |
| 30 | 5 | 1 | 2 |
+----+--------+------------+-----------+
SO, I want to choose one period with a different subject each day and doing this for number of weeks. so first subject dose not come until all subject have been chosen.
You can ORDER BY period_id first and then by day_id:
SELECT *
FROM your_table
ORDER BY period_Id, day_Id
LiveDemo

Return Rows as per Quantity of Columns Value In SQL

I have data in a table like below.
Now I want to return rows as per the values of the Quantity column.
Like above data row 1 have the Quantity 4 then in output of select query there will be 4 rows for row 1, similar to for row 2 quantity is 8 then there will be 8 rows.
When I have write query on above data then it should return 26 rows.
Query:
SQLFIDDLEExample
SELECT tbl.*
FROM tbl,
(SELECT number
FROM master..spt_values
WHERE Type = 'P') n
WHERE tbl.Quantity > n.number
Result:
| ROWNO | QUANTITY | SIZE |
|-------|----------|------|
| 1 | 4 | YXS |
| 1 | 4 | YXS |
| 1 | 4 | YXS |
| 1 | 4 | YXS |
| 2 | 8 | 2XL |
| 2 | 8 | 2XL |
| 2 | 8 | 2XL |
| 2 | 8 | 2XL |
| 2 | 8 | 2XL |
| 2 | 8 | 2XL |
| 2 | 8 | 2XL |
| 2 | 8 | 2XL |
| 3 | 7 | XL |
| 3 | 7 | XL |
| 3 | 7 | XL |
| 3 | 7 | XL |
| 3 | 7 | XL |
| 3 | 7 | XL |
| 3 | 7 | XL |
| 4 | 7 | YXS |
| 4 | 7 | YXS |
| 4 | 7 | YXS |
| 4 | 7 | YXS |
| 4 | 7 | YXS |
| 4 | 7 | YXS |
| 4 | 7 | YXS |
Explanation:
Column number from table spt_values contains continuous values from 0 to 2047 for Type 'P'.
These values are joined in, leading to 2048 duplicates returned for each record, and then restricted to return only the number of Quantity via the WHEREclause.