Oracle: get countries separated by N borders - sql

I would like to get all countries separated by N (1,2,3,4 ...) borders from a specified country.
N should also be specified.
For example I have the table "borders" and "country":
border | neighbor
-----------------
FR | DE
FR | IT
IT | FR
DE | FR
DE | PL
PL | DE
DE | DK
DK | DE
CODE COUNTRYNAME
---- ---------
FR France
DE Germany
RU Russia
IT Italy
PL Poland
DK Denmark
N = 2 & country = France
If I want to get countries separated by 2 borders from France, it should return Poland (FR -> DE -> PL) and Denmark (FR -> DE -> DK)
N = 1 & country = France
If I want to get countries separated by 1 border from France, it should return Germany (FR -> DE) and Italy (FR -> IT)
I can modify borders if needed.
I tried some hierarchical queries without success.
Thanks
BR

Here's a complete enumeration of all possible paths and their lengths, given a starting country and no limitation as to paths being the shortest possible paths between two countries (disclaimer, don't run this on too many countries):
WITH
countries AS (SELECT DISTINCT border country FROM t),
chains (country, path, destination, steps) AS (
SELECT country, country, country, 0
FROM countries
UNION ALL
SELECT chains.country, chains.path || '->' || t.neighbor, t.neighbor, chains.steps + 1
FROM chains
JOIN t ON chains.destination = t.border
AND chains.path NOT LIKE '%' || t.neighbor || '%' -- This prevents cycles
)
SELECT *
FROM chains
ORDER BY country, steps;
The result being:
| COUNTRY | PATH | DESTINATION | STEPS |
|---------|----------------|-------------|-------|
| DE | DE | DE | 0 |
| DE | DE->PL | PL | 1 |
| DE | DE->FR | FR | 1 |
| DE | DE->DK | DK | 1 |
| DE | DE->FR->IT | IT | 2 |
| DK | DK | DK | 0 |
| DK | DK->DE | DE | 1 |
| DK | DK->DE->FR | FR | 2 |
| DK | DK->DE->PL | PL | 2 |
| DK | DK->DE->FR->IT | IT | 3 |
| FR | FR | FR | 0 |
| FR | FR->IT | IT | 1 |
| FR | FR->DE | DE | 1 |
| FR | FR->DE->DK | DK | 2 |
| FR | FR->DE->PL | PL | 2 |
| IT | IT | IT | 0 |
| IT | IT->FR | FR | 1 |
| IT | IT->FR->DE | DE | 2 |
| IT | IT->FR->DE->PL | PL | 3 |
| IT | IT->FR->DE->DK | DK | 3 |
| PL | PL | PL | 0 |
| PL | PL->DE | DE | 1 |
| PL | PL->DE->FR | FR | 2 |
| PL | PL->DE->DK | DK | 2 |
| PL | PL->DE->FR->IT | IT | 3 |
SQLFiddle here.
Store the query in a view and then you can filter on it, e.g.
SELECT * FROM my_view WHERE country = 'FR' AND steps = 2
Side-note on shortest paths:
If you actually did need the shortest paths between two countries, just reuse
that view again picking an arbitrary path when two paths tie (again, this isn't the most efficient solution, don't do this for too many countries!):
SELECT
country,
destination,
MIN(steps) KEEP (DENSE_RANK FIRST ORDER BY steps) AS steps,
MIN(path) KEEP (DENSE_RANK FIRST ORDER BY steps) AS path
FROM paths
WHERE country != destination
GROUP BY country, destination
ORDER BY country, destination
And get:
| COUNTRY | DESTINATION | STEPS | PATH |
|---------|-------------|-------|----------------|
| DE | DK | 1 | DE->DK |
| DE | FR | 1 | DE->FR |
| DE | IT | 2 | DE->FR->IT |
| DE | PL | 1 | DE->PL |
| DK | DE | 1 | DK->DE |
| DK | FR | 2 | DK->DE->FR |
| DK | IT | 3 | DK->DE->FR->IT |
| DK | PL | 2 | DK->DE->PL |
| FR | DE | 1 | FR->DE |
| FR | DK | 2 | FR->DE->DK |
| FR | IT | 1 | FR->IT |
| FR | PL | 2 | FR->DE->PL |
| IT | DE | 2 | IT->FR->DE |
| IT | DK | 3 | IT->FR->DE->DK |
| IT | FR | 1 | IT->FR |
| IT | PL | 3 | IT->FR->DE->PL |
| PL | DE | 1 | PL->DE |
| PL | DK | 2 | PL->DE->DK |
| PL | FR | 2 | PL->DE->FR |
| PL | IT | 3 | PL->DE->FR->IT |
As can be seen in this SQL Fiddle, or again, with a bit more data.

You can aggregate the neighbours of each country into a collection and then use a simple hierarchical query to find the (non-cyclic) paths:
SQL Fiddle
Oracle 11g R2 Schema Setup:
create table borders (border char(2), neighbor char(2));
insert into borders values ('FR','DE');
insert into borders values ('FR','IT');
insert into borders values ('IT','FR');
insert into borders values ('DE','FR');
insert into borders values ('DE','PL');
insert into borders values ('PL','DE');
insert into borders values ('DE','DK');
insert into borders values ('DK','DE');
insert into borders values ('RU','PL');
insert into borders values ('PL','RU');
insert into borders values ('IT','CH');
insert into borders values ('FR','CH');
insert into borders values ('DE','CH');
insert into borders values ('CH','IT');
insert into borders values ('CH','FR');
insert into borders values ('CH','DE');
CREATE TYPE countrylist AS TABLE OF CHAR(2);
Query 1:
WITH neighbors ( country, neighbors ) AS (
SELECT border,
CAST( COLLECT( neighbor ) AS COUNTRYLIST )
FROM borders
GROUP BY border
)
SELECT SYS_CONNECT_BY_PATH( country, '->' ) AS path,
CONNECT_BY_ROOT( country ) AS origin,
country AS destination,
LEVEL - 1 AS path_length
FROM neighbors
WHERE LEVEL - 1 = 4 -- Remove this to find paths of any length
START WITH country = 'FR' -- Remove this to start from any country
CONNECT BY NOCYCLE
country MEMBER OF PRIOR neighbors
Results:
| PATH | ORIGIN | DESTINATION | PATH_LENGTH |
|----------------------|--------|-------------|-------------|
| ->FR->CH->DE->PL->RU | FR | RU | 4 |
| ->FR->IT->CH->DE->DK | FR | DK | 4 |
| ->FR->IT->CH->DE->PL | FR | PL | 4 |
Explain Plan:
-----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Time |
-----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 16 | 608 | 5 | 00:00:01 |
| * 1 | FILTER | | | | | |
| * 2 | CONNECT BY NO FILTERING WITH START-WITH | | | | | |
| 3 | VIEW | | 16 | 608 | 4 | 00:00:01 |
| 4 | SORT GROUP BY | | 16 | 128 | 4 | 00:00:01 |
| 5 | TABLE ACCESS FULL | BORDERS | 16 | 128 | 3 | 00:00:01 |
-----------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
------------------------------------------
* 1 - filter(LEVEL-1=4)
* 2 - filter("COUNTRY"MEMBER OFPRIOR "NEIGHBORS" AND "COUNTRY"='FR')
Query 2 This will get the shortest path (with ties) between pairs of countries:
WITH neighbors ( country, neighbors ) AS (
SELECT border,
CAST( COLLECT( neighbor ) AS COUNTRYLIST )
FROM borders
GROUP BY border
)
SELECT path,
origin,
destination,
path_length
FROM (
SELECT SYS_CONNECT_BY_PATH( country, '->' ) AS path,
CONNECT_BY_ROOT( country ) AS origin,
country AS destination,
LEVEL - 1 AS path_length,
RANK() OVER (
PARTITION BY CONNECT_BY_ROOT( country ), country
ORDER BY LEVEL ASC
) AS path_length_rank
FROM neighbors
WHERE LEVEL > 1
CONNECT BY NOCYCLE
country MEMBER OF PRIOR neighbors
ORDER BY origin, destination
)
WHERE path_length_rank = 1
Results:
| PATH | ORIGIN | DESTINATION | PATH_LENGTH |
|----------------------|--------|-------------|-------------|
| ->CH->DE | CH | DE | 1 |
| ->CH->DE->DK | CH | DK | 2 |
| ->CH->FR | CH | FR | 1 |
| ->CH->IT | CH | IT | 1 |
| ->CH->DE->PL | CH | PL | 2 |
| ->CH->DE->PL->RU | CH | RU | 3 |
| ->DE->CH | DE | CH | 1 |
| ->DE->DK | DE | DK | 1 |
| ->DE->FR | DE | FR | 1 |
| ->DE->FR->IT | DE | IT | 2 |
| ->DE->CH->IT | DE | IT | 2 |
| ->DE->PL | DE | PL | 1 |
| ->DE->PL->RU | DE | RU | 2 |
| ->DK->DE->CH | DK | CH | 2 |
| ->DK->DE | DK | DE | 1 |
| ->DK->DE->FR | DK | FR | 2 |
| ->DK->DE->FR->IT | DK | IT | 3 |
| ->DK->DE->CH->IT | DK | IT | 3 |
| ->DK->DE->PL | DK | PL | 2 |
| ->DK->DE->PL->RU | DK | RU | 3 |
| ->FR->CH | FR | CH | 1 |
| ->FR->DE | FR | DE | 1 |
| ->FR->DE->DK | FR | DK | 2 |
| ->FR->IT | FR | IT | 1 |
| ->FR->DE->PL | FR | PL | 2 |
| ->FR->DE->PL->RU | FR | RU | 3 |
| ->IT->CH | IT | CH | 1 |
| ->IT->FR->DE | IT | DE | 2 |
| ->IT->CH->DE | IT | DE | 2 |
| ->IT->CH->DE->DK | IT | DK | 3 |
| ->IT->FR->DE->DK | IT | DK | 3 |
| ->IT->FR | IT | FR | 1 |
| ->IT->CH->DE->PL | IT | PL | 3 |
| ->IT->FR->DE->PL | IT | PL | 3 |
| ->IT->FR->DE->PL->RU | IT | RU | 4 |
| ->IT->CH->DE->PL->RU | IT | RU | 4 |
| ->PL->DE->CH | PL | CH | 2 |
| ->PL->DE | PL | DE | 1 |
| ->PL->DE->DK | PL | DK | 2 |
| ->PL->DE->FR | PL | FR | 2 |
| ->PL->DE->CH->IT | PL | IT | 3 |
| ->PL->DE->FR->IT | PL | IT | 3 |
| ->PL->RU | PL | RU | 1 |
| ->RU->PL->DE->CH | RU | CH | 3 |
| ->RU->PL->DE | RU | DE | 2 |
| ->RU->PL->DE->DK | RU | DK | 3 |
| ->RU->PL->DE->FR | RU | FR | 3 |
| ->RU->PL->DE->FR->IT | RU | IT | 4 |
| ->RU->PL->DE->CH->IT | RU | IT | 4 |
| ->RU->PL | RU | PL | 1 |
Explain Plan:
---------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Time |
---------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 16 | 32576 | 6 | 00:00:01 |
| * 1 | VIEW | | 16 | 32576 | 6 | 00:00:01 |
| 2 | SORT ORDER BY | | 16 | 608 | 6 | 00:00:01 |
| * 3 | WINDOW SORT PUSHED RANK | | 16 | 608 | 6 | 00:00:01 |
| * 4 | FILTER | | | | | |
| * 5 | CONNECT BY WITHOUT FILTERING | | | | | |
| 6 | VIEW | | 16 | 608 | 4 | 00:00:01 |
| 7 | SORT GROUP BY | | 16 | 128 | 4 | 00:00:01 |
| 8 | TABLE ACCESS FULL | BORDERS | 16 | 128 | 3 | 00:00:01 |
---------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
------------------------------------------
* 1 - filter("PATH_LENGTH_RANK"=1)
* 3 - filter(RANK() OVER ( PARTITION BY ANY,"COUNTRY" ORDER BY LEVEL)<=1)
* 4 - filter(LEVEL>1)
* 5 - filter("COUNTRY"MEMBER OFPRIOR "NEIGHBORS")

Your BORDERS table contains reciprocal relationships (e.g. FR->DE, DE->FR) . This means you need to handle cycles. This is not straightforward at all, because you want to avoid say FR->DE->PL->DE at three degrees of separation.
I have here a recursive WITH clause, (so Oracle 11gR2 or later) which does this.
with hqry (path, nghbr, prev_bdr, root_bdr, lvl) as (
select b.border
, b.neighbor
, b.border
, b.border
, 1 as lvl
from borders b
where b.border = 'FR'
union all
select hqry.path || '->' || b.border
, b.neighbor
, hqry.nghbr
, hqry.root_bdr
, hqry.lvl + 1
from hqry
join borders b on b.border = hqry.nghbr
where b.neighbor != hqry.root_bdr
and b.neighbor != hqry.prev_bdr
and hqry.lvl < 3 -- this is a nasty kludge, with more time I'd like to fix it
)
SEARCH DEPTH FIRST BY path SET order1
CYCLE path SET cycle TO 1 DEFAULT 0
select path || '->' || nghbr as end_path
from hqry
where hqry.lvl = 3
;
It takes five parameters
path - the previous chain of borders
nghbr - the current neighbor, stating with the root country i.e. France
prev_bdr - the immediate previous border to prevent FR->DE->PL->DE
root_bdr - the originating border to prevent FR->CH->IT->FR
lvl - to track the degrees of separation
Sample output for three degrees of separation:
FR->CH->DE->DK
FR->CH->DE->PL
FR->DE->CH->IT
FR->DE->PL->RU
FR->IT->CH->DE
There is a SQL Fiddle demo here. I added in a couple more countries: Switzerland adds some nasty edges cases.
Obviously the above output shows it doesn't enforce the shortest path algorithm, that is left as an exercise for the reader :) If you're interested in adding this - and you should be, because I think it's key to a robust solution - I suggest you real this post by Lucas Jellema.

You could use CONNECT_BY_ROOT to find the neighbors and filter by LEVEL to find the nth neighbor.
SELECT *
FROM (
SELECT border
,connect_by_root(neighbor) AS neighbor
FROM borders
WHERE border = :ctry
AND LEVEL = :n
CONNECT BY NOCYCLE
PRIOR border = neighbor
) WHERE neighbor != :ctry
Demo

Related

How do I get a node by its path_alias? And a path_alias by a node?

I'm trying to get raw data from a Drupal 9 install and I'm just looking for plain SQL to get a node by its path alias:
+--------+-------------+--------------------------------------+----------+-------------+-------------------+--------+
| id | revision_id | uuid | langcode | path | alias | status |
+--------+-------------+--------------------------------------+----------+-------------+-------------------+--------+
| 222385 | 185331 | 1086ef60-2dd5-4fce-a8b4-1fe4eb9fe5af | und | /node/25077 | /category/hello | 1 |
+--------+-------------+--------------------------------------+----------+-------------+-------------------+--------+
What I'm not sure of is how the path_alias relates to the node. How do I get a node by its path and also the path by node? Do one of those ids relate to a pivot table like:
path_alias->pivot(id)->node?
If so what relational table is that?
I'm just looking for raw:
SELECT * FROM node WHERE [id] = 'this-id-from route alias';
I have tried using the path_alias uuid:
SELECT *
FROM node
WHERE uuid = '1086ef60-2dd5-4fce-a8b4-1fe4eb9fe5af';
With no success. I'm not sure what the relation is between the two when I know for certain there is one.
try this solution.
All Entries
SELECT node.nid, path_alias.path, path_alias.alias FROM `node`
LEFT JOIN path_alias ON CONCAT('/node/', node.nid) = path_alias.path;
Results
+-----+---------+-------------------------+
| nid | path | alias |
+-----+---------+-------------------------+
| 1 | /node/1 | /basic-page/test-page-1 |
| 2 | /node/2 | /basic-page/test-page-2 |
| 3 | /node/3 | /article/test-page-3 |
| 4 | /node/4 | /article/test-page-4 |
| 5 | NULL | NULL |
+-----+---------+-------------------------+
Search by Node ID
SELECT node.nid, path_alias.path, path_alias.alias FROM `node`
LEFT JOIN path_alias ON CONCAT('/node/', node.nid) = path_alias.path
WHERE node.nid = 2;
Results
+-----+---------+-------------------------+
| nid | path | alias |
+-----+---------+-------------------------+
| 2 | /node/2 | /basic-page/test-page-2 |
+-----+---------+-------------------------+
Search by Alias
SELECT node.nid, path_alias.path, path_alias.alias FROM `node`
LEFT JOIN path_alias ON CONCAT('/node/', node.nid) = path_alias.path
WHERE path_alias.alias = '/article/test-page-4';
Results
+-----+---------+-------------------------+
| nid | path | alias |
+-----+---------+-------------------------+
| 4 | /node/4 | /article/test-page-4 |
+-----+---------+-------------------------+
With Select ALL
SELECT * FROM `node`
LEFT JOIN path_alias ON CONCAT('/node/', node.nid) = path_alias.path;
Results
+-----+------+---------+--------------------------------------+----------+------+-------------+--------------------------------------+----------+---------+-------------------------+--------+
| nid | vid | type | uuid | langcode | id | revision_id | uuid | langcode | path | alias | status |
+-----+------+---------+--------------------------------------+----------+------+-------------+--------------------------------------+----------+---------+-------------------------+--------+
| 1 | 1 | page | b3276c0c-76c4-4e1c-93e8-198acbb1626b | de | 467 | 467 | bfd389f1-9020-4316-bae8-6186e2c2106a | de | /node/1 | /basic-page/test-page-1 | 1 |
| 2 | 2 | page | a72dd325-c994-4a33-b67b-7cb47dbc6d54 | de | 468 | 468 | f7e4ae6b-eb67-4b55-9578-7cfa56279344 | de | /node/2 | /basic-page/test-page-2 | 1 |
| 3 | 3 | article | 4b57180d-9826-46a9-9d54-b5b8b4fb713b | de | 469 | 469 | ff3d8d47-b710-4eb7-8397-7312bc92a70f | de | /node/3 | /article/test-page-3 | 1 |
| 4 | 4 | article | 858d7100-244b-4e05-b13e-d55966cb86fe | de | 470 | 470 | ffbab5c1-0157-43e8-9aa1-93460cb79b42 | de | /node/4 | /article/test-page-4 | 1 |
| 5 | 5 | page | e0c56917-feac-4dd0-bd08-f5e8f67f40fa | de | NULL | NULL | NULL | NULL | NULL | NULL | NULL |
+-----+------+---------+--------------------------------------+----------+------+-------------+--------------------------------------+----------+---------+-------------------------+--------+
PS: NID 5 shows a node without an path_alias entry

Finding the 2 closest corresponding rows to the average of the sum of multiplied values, grouped by a matching title when given an input title

I want to find the average values of a sum of multiplied values grouped by a matching title, in order to give them a corresponding rating, and then find the 2 closest to the input value.
SELECT titleValueAVG / 3 AS average,
title
FROM (
SELECT Sum(a) AS titleValueAVG,
title
FROM (
SELECT value * 1 AS a,
title
FROM Table1
WHERE type = 'A' AND
contesting = 'yes'
UNION ALL
SELECT value * 2,
title
FROM Table1
WHERE type = 'A' AND
contesting = 'no'
UNION ALL
SELECT value * 3,
title
FROM Table1
WHERE type = 'A' AND
contesting = 'undecided'
)
GROUP BY title
ORDER BY title = 'Australia' DESC,
ABS(titleValueAVG) - (
SELECT value * 1 AS a,
title
FROM Table1
WHERE type = 'A' AND
contesting = 'yes' AND title = 'Australia'
UNION ALL
SELECT value * 2,
title
FROM Table1
WHERE type = 'A' AND
contesting = 'no' AND title = 'Australia'
UNION ALL
SELECT value * 3,
title
FROM Table1
WHERE type = 'A' AND
contesting = 'undecided' AND title = 'Australia'
)
) limit 2;
From an example table:
| Title | Type | Competing | Value |
| -------- | -------------| -------------- | -------------- |
| Australia| A | yes | 26 |
| Australia| A | no | 162 |
| Australia| A | undecided | 37 |
| Spain | A | yes | 14 |
| Spain | A | no | 101 |
| Spain | A | undecided | 11 |
| Ireland | A | yes | 124 |
| Ireland | A | no | 62 |
| Ireland | A | undecided | 9 |
| Nigeria | C | yes | 4 |
| Nigeria | C | no | 11 |
| Nigeria | C | undecided | 7 |
| Colombia | A | yes | 26 |
| Colombia | A | no | 12 |
| Colombia | A | undecided | 19 |
| Turkey | A | yes | 29 |
| Turkey | A | no | 145 |
| Turkey | A | undecided | 24 |
| Malta | B | yes | 1 |
| Malta | B | no | 11 |
| Malta | B | undecided | 4 |
| Mexico | A | yes | 74 |
| Mexico | A | no | 19 |
| Mexico | A | undecided | 12 |
| Slovenia | B | yes | 16 |
| Slovenia | B | no | 22 |
| Slovenia | B | undecided | 11 |
| Canada | A | yes | 29 |
| Canada | A | no | 164 |
| Canada | A | undecided | 40 |
| Kenya | C | yes | 8 |
| Kenya | C | no | 12 |
| Kenya | C | undecided | 0 |
So, in this example, I would like to return from an input title 'Australia:
| Title | average |
| -------- | -------------- |
| Australia| 154 |
| Canada | 159 |
| Turkey | 130 |
My attempted solution is above, I've tried multiple ways to organise the order by, which is what I think is the issue, but I can't get it to work at all. If anyone could help me fix this I'd really appreciate it.
Use conditional aggregation:
WITH cte AS (
SELECT title,
ROUND(SUM(value *
CASE competing
WHEN 'yes' THEN 1
WHEN 'no' THEN 2
WHEN 'undecided' THEN 3
END
) / 3.0) average
FROM tablename
WHERE type = 'A'
GROUP BY title
)
SELECT *
FROM cte
ORDER BY title = 'Australia' DESC,
ABS(average - (SELECT average FROM cte WHERE title = 'Australia'))
LIMIT 3;
See the demo.

Output multiple rows after joining and pivoting multiple tables

I have a couple of tables (in a SQL Server 2014 SP2 database) on which I have successfully pivoted (thanks to lptr in Creating SQL pivot where duplicate column names exist). The next problem is that each datasource has one or more datapoints, but I am only getting one of the datapoints, per datasource.
The tables looks like this:
dataSourceSnapshot
| snapshotId | snapshotTime |
-----------------------------------
| 1 | 2021-12-01 07:00 |
| 2 | 2021-12-02 07:00 |
...
datasource
| snapshotId | dsRecordId | dsId | dsName | dsTableDate |
---------------------------------------------------------------
| 1 | 1 | 1 | cpu | 2021-12-01 07:00 |
| 1 | 2 | 2 | mem | 2021-12-01 07:00 |
| 1 | 3 | 3 | cache | 2021-12-01 07:00 |
| 2 | 4 | 1 | cpu | 2021-12-01 07:00 |
| 2 | 5 | 2 | mem | 2021-12-01 07:00 |
| 2 | 6 | 3 | cache | 2021-12-01 07:00 |
...
datasourceProperty
This is a truncated list of datasource properties.
| dsRecordId | dsPropPropertyRecordId | dsPropPropertyName | dsPropPropertyValue |
----------------------------------------------------------------------------------
| 1 | 1 | id | 1 |
| 1 | 2 | description | cpu stats |
| 1 | 3 | name | cpu |
| 1 | 4 | collectInterval | 300 |
| 1 | 5 | group | |
| 1 | 6 | dataPoints | System.Object[] |
| 2 | 7 | id | 2 |
| 2 | 8 | description | memory stats |
| 2 | 9 | name | mem |
| 2 | 10 | collectInterval | 300 |
| 2 | 11 | group | |
| 2 | 12 | dataPoints | System.Object[] |
| 3 | 13 | id | 3 |
| 3 | 14 | description | |
| 3 | 15 | name | cache |
| 3 | 16 | collectInterval | 600 |
| 3 | 17 | group | |
| 3 | 18 | dataPoints | System.Object[] |
...
datapointProperty
This is a truncated list of datapoint properties.
| dsRecordId | dpRecordId | dpPropertyName | dpPropertyValue |
-----------------------------------------------------------------
| 1 | 1 | id | 1 |
| 1 | 2 | datasourceId | 1 |
| 1 | 3 | name | cpuState |
| 1 | 4 | description | |
| 1 | 5 | alertExpr | != 0 0 |
| 1 | 6 | type | 2 |
| 1 | 7 | id | 2 |
| 1 | 8 | datasourceId | 1 |
| 1 | 9 | name | freePercent |
| 1 | 10 | description | CPU utilization |
| 1 | 11 | alertExpr | >= 90 90 |
| 1 | 12 | type | 2 |
| 2 | 13 | id | 3 |
| 2 | 14 | datasourceId | 2 |
| 2 | 15 | name | freePercent |
| 2 | 16 | description | Memory utilization |
| 2 | 17 | alertExpr | >= 90 90 |
| 2 | 18 | type | 2 |
| 3 | 19 | id | 4 |
| 3 | 20 | datasourceId | 3 |
| 3 | 21 | name | state |
| 3 | 22 | description | |
| 3 | 23 | alertExpr | = 1 1 1 |
| 3 | 24 | type | 4 |
...
I am trying to get a row per datasource (but only the most recent instance of that datasource's entry) and per datapoint, which includes all of the datasource properties. Something like this:
| dsRecordId | id | description | name | collectInterval | group | dataPoints | dp_id | dp_datasourceId | dp_name | dp_description | dp_alertExpr | dp_type |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| 1 | 1 | cpu stats | cpu | 300 | | System.Object[] | 1 | 1 | cpuState | | != 0 0 | 2 |
| 1 | 1 | cpu stats | cpu | 300 | | System.Object[] | 2 | 1 | freePercent | | >= 90 90 | 2 |
| 2 | 2 | memory stats | mem | 300 | | System.Object[] | 3 | 2 | freePercent | Memory utilization | >= 90 90 | 2 |
| 3 | 3 | | cache | 600 | | System.Object[] | 4 | 3 | state | | = 1 1 1 | 4 |
I have a bit of T-SQL that will pivot, but because datasources and datapoints both have id, name, and description properties, I have to exclude those properties in the pivot. This query:
DECLARE
#dsPropPropertyColumns NVARCHAR(MAX) = '',
#dpPropertyColumns NVARCHAR(MAX) = '',
#sql NVARCHAR(MAX) = '';
-- select the property names
--- properties in the datasourceProperty table
SELECT #dsPropPropertyColumns = (
SELECT DISTINCT '[' + [dsPropPropertyName] + ']' + ','
FROM dbo.dataSourceSnapshot dss
LEFT OUTER JOIN dbo.datasource ds ON ds.snapshotId = dss.snapshotId
LEFT OUTER JOIN dbo.datasourceProperty dsp ON dsp.dsRecordId = ds.dsRecordId
WHERE dss.snapshotTime = (
SELECT MAX(snapshotTime) FROM dbo.dataSourceSnapshot
)
FOR XML PATH(''), TYPE
).value('.', 'nvarchar(max)')
--- properties in the datapointProperty table
SELECT #dpPropertyColumns = (
SELECT DISTINCT '[' + [dpPropertyName] + ']' + ','
FROM dbo.dataSourceSnapshot dss
LEFT OUTER JOIN dbo.datasource ds ON ds.snapshotId = dss.snapshotId
LEFT OUTER JOIN dbo.datapointProperty dp ON dp.dsRecordId = ds.dsRecordId
WHERE dss.snapshotTime = (
SELECT MAX(snapshotTime) FROM dbo.dataSourceSnapshot
)
FOR XML PATH(''), TYPE
).value('.', 'nvarchar(max)')
-- remove the trailing comma
SET #dsPropPropertyColumns = LEFT(#dsPropPropertyColumns, LEN(#dsPropPropertyColumns) - 1);
SET #dpPropertyColumns = LEFT(#dpPropertyColumns, LEN(#dpPropertyColumns) - 1);
-- construct dynamic SQL
SET #sql ='
SELECT * FROM (
SELECT dsp.dsRecordId, dsp.dsPropPropertyName, dsp.dsPropPropertyValue, CONCAT(''dp_'', dp.dpPropertyName) as dpPropertyName, dp.dpPropertyValue
FROM dbo.snapshot dss
LEFT OUTER JOIN dbo.datasource ds ON ds.snapshotId = dss.snapshotId
LEFT OUTER JOIN dbo.datasourceProperty dsp ON dsp.dsRecordId = ds.dsRecordId
LEFT OUTER JOIN dbo.datapointProperty dp ON dp.dsRecordId = ds.dsRecordId
WHERE dss.snapshotTime = (
SELECT MAX(snapshotTime) FROM dbo.dataSourceSnapshot
)
) t1
PIVOT (
MAX(dsPropPropertyValue) FOR dsPropPropertyName IN ('+ #dsPropPropertyColumns +')
) AS pivot_table
PIVOT (
MAX(dpPropertyValue) FOR dpPropertyName IN ('+ #dpPropertyColumns +')
) AS pivot_table2
ORDER BY id
'
EXECUTE sp_executesql #sql;
Produces this output:
| dsRecordId | id | description | name | collectInterval | group | dataPoints | dp_id | dp_datasourceId | dp_name | dp_description | dp_alertExpr | dp_type |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| 1 | 1 | cpu stats | cpu | 300 | | System.Object[] | 1 | 1 | cpuState | CPU utilization | >= 90 90 | 2 |
| 2 | 2 | memory stats | mem | 300 | | System.Object[] | 3 | 2 | freePercent | Memory utilization | >= 90 90 | 2 |
| 3 | 3 | | cache | 600 | | System.Object[] | 4 | 3 | state | | = 1 1 1 | 4 |
This output shows one row for the "cpu" datasource (which has two datapoints). Confusingly, it also shows the datapoint (dp_) description and alertExpr values for the CPU's freePercent datapoint, with the "cpuState" dp_name.
In Creating SQL pivot where duplicate column names exist, it was suggested to use:
ROW_NUMBER() OVER(partition by dp.dsRecordId, dp.dpPropertyName order by dp.dpRecordId) as dpPropertyName
With this T-SQL:
SELECT dsp.dsRecordId, dsp.dsPropPropertyName, dsp.dsPropPropertyValue, ROW_NUMBER() OVER(partition by dp.dsRecordId, dp.dpPropertyName order by dp.dpRecordId) as dpPropertyName, dp.dpPropertyValue
FROM logicmonitor.dataSourceSnapshot dss
LEFT OUTER JOIN logicmonitor.datasource ds ON ds.dataSourceSnapshotId = dss.dataSourceSnapshotId
LEFT OUTER JOIN logicmonitor.datasourceProperty dsp ON dsp.dsRecordId = ds.dsRecordId
LEFT OUTER JOIN logicmonitor.datapointProperty dp ON dp.dsRecordId = ds.dsRecordId
WHERE dss.dataSourceSnapshotTime = (
SELECT MAX(dataSourceSnapshotTime) FROM logicmonitor.dataSourceSnapshot
)
I get get a table that looks like this:
| dsRecordId | dsPropPropertyName | dsPropPropertyValue | dpPropertyName | dpPropertyValue |
--------------------------------------------------------------------------------------------------
| 1 | id | 1 | 1 | |
| 1 | description | cpu stats | 2 | |
| 1 | name | cpu | 3 | |
That does not look right, so I assume I am mis-using ROW_NUMBER() and OVER().
The question is, how do I get a pivoted row per datapoint, joined to (or joined from?) the datasourceProperty and datasourceSnapshot tables

How to update table 2 from the inserted data in table 1?

Can you help me on what query I to to update one table with data from another.
I have 2 tables for example:
tbl_med_take
| id | name | med | qty |
---------------------------------
| 1 | jayson | med2 | 3 |
| 2 | may | med2 | 4 |
| 3 | jenny. | med3 | 6 |
| 4 | joel. | med3 | 4 |
tbl_med
| id | med | stocks |
-----------------------------
| 1 | med1 | 20 |
| 2 | med2 |. 17 |
| 3 | med3 | 24 |
The output that I want in tbl_med:
tbl_med
| id | med | stocks |
-----------------------------
| 1 | med1 | 20 |
| 2 | med2 |. 10 |
| 3 | med3 | 14 |
First get the total consumed from med_tbl_take using
select med,sum(quantity) as total from tbl_med_take group by med
Then you can left join with your med_tbl and subtract.
select m.id,m.med,(m.stocks-ISNULL(n.total,0)) from tbl_med m
left join
(select med,sum(quantity) as total from tbl_med_take group by med) n
on m.med=n.med
CHECK DEMO HERE

sort a table while keeping the hierarchy of rows

I have a table which represents the hierarchy of departments:
+-----------+--------------+--------------+--------------+-----------+-------+
| Top Dept. | 2-tier Dept. | 3-tire Dept. | 4-tier Dept. | name | tier |
+-----------+--------------+--------------+--------------+-----------+-------+
| 00 | | | | abc | 0 |
| | 00-01 | | | bcd | 1 |
| | | 00-01-01 | | cde | 2 |
| | | 00-01-02 | | abc | 2 |
| | 00-02 | | | aef | 1 |
| | | 00-02-01 | | qwe | 2 |
| | | 00-02-03 | | abc | 2 |
| | | | 00-02-03-01 | abc | 3 |
+-----------+--------------+--------------+--------------+-----------+-------+
now I want to sort the rows which are in the same tier by their names while keeping the hierarchy overall, That's what I expect:
+-----------+--------------+--------------+--------------+-----------+-------+
| Top Dept. | 2-tier Dept. | 3-tire Dept. | 4-tier Dept. | name | tier |
+-----------+--------------+--------------+--------------+-----------+-------+
| 00 | | | | abc | 0 |
| | 00-02 | | | aef | 1 |
| | | 00-02-03 | | abc | 2 |
| | | 00-02-01 | | qwe | 2 |
| | 00-01 | | | def | 1 |
| | | 00-01-02 | | abc | 2 |
| | | 00-01-01 | | cde | 2 |
| | | | 00-02-03-01 | abc | 3 |
+-----------+--------------+--------------+--------------+-----------+-------+
the missing data means null, I'm using Oracle DB, can anyone help me?
EDIT: Actually, it's a simple version of this sql, I've tried to add a new column which concats the values of the first four columns and then order by it and by name, but it did't work.
Update: This appears to be working... SQL Fiddle
All that was really needed from my original comment was to amend name to department in that order in both selects. This allows the engine to sort by name first, while maintaining the hierarchy.
WITH cte(Dept, superiorDept, name, depth, sort)AS (
SELECT
Dept,
superiorDept,
name,
0,
name|| dept
FROM hierarchy h
WHERE superiorDept IS NULL
UNION ALL
SELECT
h2.Dept,
h2.superiorDept,
h2.name,
cte.depth + 1,
cte.sort || h2.name ||h2.dept
FROM hierarchy h2
INNER JOIN cte ON h2.superiorDept = cte.Dept
)
SELECT
CASE WHEN depth = 0 THEN Dept END AS 一级部门,
CASE WHEN depth = 1 THEN Dept END AS 二级部门,
CASE WHEN depth = 2 THEN Dept END AS 三级部门,
CASE WHEN depth = 3 THEN Dept END AS 四级部门,
name,
depth,
sort
FROM cte
ORDER BY sort, name