How to keep related groups together? - postgresql

DB fiddle DB fiddle (updated)
I pay attention to 1-2; 3-4 cases. Notice, that all rows from config_id group still together with all rows of xgroup
Detailed task description:
I have some order details for some resource. For each ordered resource there should be allocated resource. Resource may belongs to each other and belongs to some group. Consider virtual machine VM has: RAM, CPU, HDD.
Currently relation between orders and allocated resources is broken. I try to write query to analyze what is ordered without allocated resource and what is allocated without order.
CREATE TABLE order_detail (
id SERIAL,
order_id INTEGER,
resource_type_id INTEGER,
allocated_resource_id INTEGER,
amount INTEGER
)
CREATE TABLE allocated_resource (
id SERIAL,
group_id INTEGER,
resource_type_id INTEGER,
resource_uuid UUID,
)
This is easily done:
select * from order_detail od
where od.allocated_resource_id IS NULL
SELECT * FROM allocated_resource ar
WHERE NOT EXISTS ( SELECT 1 FROM order_detail od where od.allocated_resource_id = ar.id)
But I need to found to which Order assign that allocated resource. Or which resource allocate for this Order. Or which Order bind to allocated resource.
Data example:
id | order_id | allocated_resource_id | resource_type_id
--------------------------------------------------------
41 | 1 | 1 | 70
42 | 1 | | 71
43 | 1 | | 73
44 | 2 | | 70
45 | 2 | 5 | 71
id | group_id | resource_type_id
--------------------------------
1 | 1 | 70
2 | 1 | 71
3 | 1 | 72
4 | 2 | 70
5 | 2 | 71
6 | 2 | 73
Here I want to get:
id | order_id | allocated_resource_id | resource_type_id | ar.id | ar.group_id | ar.resource_type_id
----------------------------------------------------------------------------------------------------
41 | 1 | 1 | 70 | 1 | 1 | 70
42 | 1 | | 71 |
43 | 1 | | 73 |
| | | | 1 | 1 | 71
| | | | 1 | 1 | 72
44 | 2 | | 70 |
45 | 2 | 5 | 71 | 5 | 1 | 71
| | | | 4 | 1 | 70
| | | | 6 | 1 | 73
Unfortunately order by ar.id or order by od.id will move unbound order details/allocated resource to bottom. I want to keep order details/allocated resources together as in example above.
To resolve task I select all available related groups first:
od_ar_group AS (
SELECT
od.order_id, ar.group_id,
cast( od.order_id AS TEXT ) || '-' || cast( ar.group_id AS TEXT ) AS odar
FROM order_detail od
FULL JOIN allocated_resource ar ON ar.id = od.allocated_resource_id
WHERE od.order_id IS NOT NULL AND ar.group_id IS NOT NULL
GROUP BY od.order_id, ar.group_id
)
Then I attach group info to both tables:
SELECT od.*, odar.odar
FROM order_detail od
LEFT JOIN od_ar_group odar ON odar.order_id = od.order_id
SELECT ar.*, odar.odar
FROM allocated_resource ar
LEFT JOIN od_ar_group odar ON odar.parent_id = ar.parent_id
Finally I can join those groups and sort inside them. So unbound order detail/allocated resource is inside group and not at the bottom of table:
SELECT
CASE WHEN od.odar IS NOT NULL THEN od.odar ELSE ar.odar END AS odar,
od.*, ar.*
FROM od_grouped
FULL JOIN ar_grouped ar ON ar.odar = od.odar
AND ar.id = od.allocated_resource_id
ORDER BY odar, CASE WHEN od.id IS NULL THEN 1 WHEN ar.id IS NULL THEN 2 ELSE 0 END, od.id NULLS LAST
Is there a more easy way to keep related rows inside their group?

Related

Filtering out hierarchical data

I need help with a problem I am facing processing hierarchical data.
Schema of the tables that maintain hierarchical data:
Category table:
| ID | Label |
Mapping table:
| ID | QualifierID | ItemID | ParentID |
Step 1: Wrote a simple self-join query to trasnform above mappings:
WITH category_masterlist AS (
SELECT id,
label
FROM Category
)
select id, id as itemid, label, NULL as parentId from [Category] where categoryLevel = 1
UNION
select itemid as id, itemId, (select label from category_masterlist where id = cm.itemid) Label, parentId
from [CategoryMapping] cm
Step 2: Wrote a self-join query using common table expression to return mapping data as follows:
WITH CategoryCTE(ParentID, ID, Label, CategoryLevel) AS
(
SELECT ParentID, ItemID, Label, 0 AS CategoryLevel
FROM [view_TreeviewCategoryMapping]
WHERE ParentID IS NULL
UNION ALL
SELECT e.ParentID, e.ItemID, e.Label, CategoryLevel + 1
FROM [view_TreeviewCategoryMapping] AS e
INNER JOIN CategoryCTE AS d
ON e.ParentID = d.ID
)
SELECT distinct ParentID, ID, Label, CategoryLevel
FROM CategoryCTE
| ID | Label | ParentID | CategoryLevel |
--------------------------------------------------------------------------------
| 90 | Satellite | NULL | 0 |
| 91 | Concrete | NULL | 0 |
| 92 | ETC | NULL | 0 |
| 93 | Chisel | NULL | 0 |
| 94 | Steel | NULL | 0 |
| 96 | Wood | NULL | 0 |
| 97 | MIC Systems | 90 | 1 |
| 97 | MIC Systems | 91 | 1 |
| 99 | Foundations | 91 | 1 |
| 100 | Down Systems | 91 | 1 |
| 101 | Side Systems | 91 | 1 |
| 102 | Systems | 91 | 1 |
| 98 | DWG | 92 | 1 |
| 97 | MIC Systems | 93 | 1 |
| 97 | MIC Systems | 94 | 1 |
| 99 | Foundations | 94 | 1 |
| 100 | Down Systems | 94 | 1 |
| 101 | Side Systems | 94 | 1 |
| 102 | Systems | 94 | 1 |
| 97 | MIC Systems | 95 | 1 |
| 98 | DWG | 95 | 1 |
| 102 | Systems | 95 | 1 |
| 103 | Project Management| 95 | 1 |
| 104 | Software | 95 | 1 |
| 99 | Foundations | 96 | 1 |
| 119 | Fronts | 97 | 2 |
| 121 | Technology | 98 | 2 |
| 112 | Root Systems | 98 | 2 |
| 112 | Root Systems | 99 | 2 |
| 137 | Closed Systems | 112 | 3 |
| 203 | Support | 121 | 3 |
Step 3: I would like to filter above results so that only categories that are mapped completely are returned. Completed mapping is a mapping that has children at level=3. For example, below is what I am looking for based on above resultset:
| ID | Label | ParentID | CategoryLevel |
--------------------------------------------------------------------------------
| 96 | Wood | NULL | 0 |
| 92 | ETC | NULL | 0 |
| 98 | DWG | 92 | 1 |
| 99 | Foundations | 96 | 1 |
| 121 | Technology | 98 | 2 |
| 112 | Root Systems | 98 | 2 |
| 112 | Root Systems | 99 | 2 |
| 137 | Closed Systems | 112 | 3 |
| 203 | Support | 121 | 3 |
Step 4: Ultimately, end user should be presented with a tree view control as follows:
Root
|
|---Wood
| |---Foundations
| |---Root Systems
| |---Closed Systems
|
|---ETC
| |---DWG
| |---Technology
| |---Support
| |---Root Systems
| |---Closed Systems
Please note, a category can have multiple parents. For example, Root Systems has two parents - DWG and Foundations. Did I get the schema correct for category and mapping table especially for the case when a category can have multiple parents?
How can I filter out categories that are not mapped completely from Step 2 to Step 3? That is the hurdle I am unable to cross. Any pointers? I can filter them out at the application level but would really love to filter them out at database level.
I am open to suggestions and recommendations that will help me achieve my goal. I also want a confirmation that the schema I am using is the most efficient one.
Thank you!
Here is a working option that uses the datatype hierarchyID
The nesting is option and really for illustration.
Example
Declare #Top int = null --<< Sets top of Hier Try 94
;with cteP as (
Select ID
,ParentID
,Label
,HierID = convert(hierarchyid,concat('/',ID,'/'))
From YourTable
Where IsNull(#Top,-1) = case when #Top is null then isnull(ParentID ,-1) else ID end
Union All
Select ID = r.ID
,Pt = r.ParentID
,Label = r.Label
,HierID = convert(hierarchyid,concat(p.HierID.ToString(),r.ID,'/'))
From YourTable r
Join cteP p on r.ParentID = p.ID)
Select Lvl = HierID.GetLevel()
,ID
,ParentID
,Label = replicate('|----',HierID.GetLevel()-1) + Label -- Nesting Optional ... For Presentation
,HierID_String = HierID.ToString()
From cteP A
Order By A.HierID
Results
Now if #Top was set to 94

Lederboards in PostgreSQL and get 2 next and previous rows

We use Postgresql 14.1
I have a sample data that contains over 50 million records.
base table:
+------+----------+--------+--------+--------+
| id | item_id | battles| wins | damage |
+------+----------+--------+--------+--------+
| 1 | 255 | 35 | 52.08 | 1245.2 |
| 2 | 255 | 35 | 52.08 | 1245.2 |
| 3 | 255 | 35 | 52.08 | 1245.3 |
| 4 | 255 | 35 | 52.08 | 1245.3 |
| 5 | 255 | 35 | 52.09 | 1245.4 |
| 6 | 255 | 35 | 52.08 | 1245.3 |
| 7 | 255 | 35 | 52.08 | 1245.3 |
| 8 | 255 | 35 | 52.08 | 1245.7 |
| 1 | 460 | 18 | 47.35 | 1010.1 |
| 2 | 460 | 27 | 49.18 | 1518.9 |
| 3 | 460 | 16 | 50.78 | 1171.2 |
+------+----------+--------+--------+--------+
We need to get the target row number and 2 next and 2 previous rows as quickly as possible.
Indexed columns:
id
item_id
Sorting:
damage (DESC)
wins (DESC)
battles (ASC)
id (ASC)
At the example, we need to find the row number and +- 2 rows where id = 4 and item_id = 255. The result table should be:
+------+----------+--------+--------+--------+------+
| id | item_id | battles| wins | damage | rank |
+------+----------+--------+--------+--------+------+
| 5 | 255 | 35 | 52.09 | 1245.4 | 2 |
| 3 | 255 | 35 | 52.08 | 1245.3 | 3 |
| 4 | 255 | 35 | 52.08 | 1245.3 | 4 |
| 6 | 255 | 35 | 52.08 | 1245.3 | 5 |
| 7 | 255 | 35 | 52.08 | 1245.3 | 6 |
+------+----------+--------+--------+--------+------+
How can I do this with Row number windows function?
Is there is any way optimize in query to make it faster because other columns have no indexes?
CREATE OR REPLACE FUNCTION find_top(in_id integer, in_item_id integer) RETURNS TABLE (
r_id int,
r_item_id int,
r_battles int,
r_wins real,
r_damage real,
r_rank bigint,
r_eff real,
r_frags int
) AS $$
DECLARE
center_place bigint;
BEGIN
SELECT place INTO center_place FROM
(SELECT
id, item_id,
ROW_NUMBER() OVER (ORDER BY damage DESC, wins DESC, battles, id) AS place
FROM
public.my_table
WHERE
item_id = in_item_id
AND battles >= 20
) AS s
WHERE s.id = in_id;
RETURN QUERY SELECT
s.place, pt.id, pt.item_id, pt.battles, pt.wins, pt.damage
FROM
(
SELECT * FROM
(SELECT
ROW_NUMBER () OVER (ORDER BY damage DESC, wins DESC, battles, id) AS place,
id, item_id
FROM
public.my_table
WHERE
item_id = in_item_id
AND battles >= 20) x
WHERE x.place BETWEEN (center_place - 2) AND (center_place + 2)
) s
JOIN
public.my_table pt
ON pt.id = s.id AND pt.item_id = s.item_id;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION find_top(in_id integer, in_item_id integer) RETURNS TABLE (
r_id int,
r_item_id int,
r_battles int,
r_wins real,
r_damage real,
r_rank bigint,
r_eff real,
r_frags int
) AS $$
BEGIN
RETURN QUERY
SELECT c.*, B.ord -3 AS row_number
FROM
( SELECT array_agg(id) OVER w AS id
, array_agg(item_id) OVER w AS item_id
FROM public.my_table
WINDOW w AS (ORDER BY damage DESC, wins DESC, battles, id ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING)
) AS a
CROSS JOIN LATERAL unnest(a.id, a.item_id) WITH ORDINALITY AS b(id, item_id, ord)
INNER JOIN public.my_table AS c
ON c.id = b.id
AND c.item_id = b.item_id
WHERE a.item_id[3] = in_item_id
AND a.id[3] = in_id
ORDER BY b.ord ;
END ; $$ LANGUAGE plpgsql;
test result in dbfiddle

posgresql selecting two different data as two columns from one column

I need to select two id's from stockcurrent as two different columns (id1,id2), first where points.id = '244' and second where points.id ='191'. But result facing last where clause and filling only one column based on that statement.
I think I've faced a similar problem as in that case: Two SELECT statements as two columns
The only difference is that in the case above his last where clause is in range but mine is not. In my opinion, it is the reason why my statement is not working:
select
(case when po.id='244' then st.id end) id1,
(case when po.id='191' then st.id end) id2
from stockcurrent st
inner join points po on po.id = st.point
where po.id ='244';
My result:
Expected result:
So I need to find a solution to fill both columns with id's not only one which in that case giving me the result(s) of '244'. Thanks in advance.
Example of stockcurrent table:
+-------+-------+
| id | point |
+-------+-------+
| 23414 | 191 |
| 12493 | 191 |
| 16121 | 170 |
| 24325 | 191 |
| 51232 | 244 |
| 11255 | 244 |
| 56572 | 244 |
| 16123 | 170 |
+-------+-------+
Example of points table:
+-----+------+------+
| id | comp | type |
+-----+------+------+
| 191 | 96 | 2 |
| 307 | 96 | 1 |
| 244 | 97 | 0 |
| 311 | 98 | 0 |
| 170 | 109 | 0 |
+-----+------+------+
Change the query to:
select
(case when po.id='244' then st.id end) id1,
(case when po.id='191' then st.id end) id2
from stockcurrent st
inner join points po on po.id = st.point
where po.id in ('244', '191');

Flattenning the Left Join outcome in PostgreSQL

I have eventtags, filtervalues.So I have something like:
eventtags:
event_id, key_id, value_id, event_date
filtervalues:
value_id, key,value, counts_seen
Let's say I've 2 events reporting with multiple key, value pairs in eventtags table
event_id | key_id | value_id | event_date
---------+--------+----------+-----------
1 | 20 | 32 | xx-xx-xxxx
1 | 21 | 34 | xx-xx-xxxx
2 | 20 | 35 | yy-yy-yyyy
2 | 21 | 39 | yy-yy-yyyy
Corresponding filter_value table is having data as below
values_id | key | value | counts_seen
----------+-------+-------+----------
32 | type | staff | 52
34 | tag | tag1 | 13
35 | type | user | 10
39 | tag | tag2 | 35
Now based on this I tried below query to consolidate the data from two tables
SELECT t.event_id as Event_Id,
DATE (t.event_date) as Event_Date,
v.key as Keys,
v.value as Values
FROM eventtags t
LEFT JOIN filtervalues as v ON t.value_id = v.id
This results in something like this
Event_Id | Keys | Values | Event_Date
---------+--------+----------+-----------
1 | type | staff | xx-xx-xxxx
1 | tag | tag1 | xx-xx-xxxx
2 | type | user | yy-yy-yyyy
2 | tag | tag2 | yy-yy-yyyy
I want the data to be in the below format
Event_Id | type | tag | Event_Date
---------+--------+---------+-----------
1 | staff | tag1 | xx-xx-xxxx
2 | user | tag2 | yy-yy-yyyy
What changes do I need to make on the query above to obtain this format?
Note: I cannot use Pivots since the system I'm working on, doesn't support them.
Any help is much appreciated
Try this for your scenario without pivot(crosstab):
SELECT t.event_id as Event_Id,
max(v.value) filter (where v.key='type') as "type",
max(v.value) filter (where v.key='tag') as "tag",
DATE (t.event_date) as Event_Date
FROM eventtags t
LEFT JOIN filtervalues as v ON t.value_id = v.id
group by t.event_id,t.event_date
DEMO
above will work only for PostgreSQL 9.4 and above.

T-SQL 2012- How can I pair down query and check the returned values

I have a table which captures various events and returns data as below
+----+-----------+--------------+-----------------+
| ID | EventType | SystemUserID | OrganisationID |
+----+-----------+--------------+-----------------+
| 23 | 8 | 11 | 1 |
| 44 | 7 | 456 | 304 |
| 80 | 1 | 48 | 4042 |
| 89 | 2 | 5673 | 183 |
+----+-----------+--------------+-----------------+
EventType 8 is captured each time a user visits an organisation.
I have another table named OrganisationHasContact as such
+----+----------------+--------------+
| ID | OrganisationID | SystemUserID |
+----+----------------+--------------+
| 23 | 1 | 11 |
| 44 | 304 | 456 |
| 80 | 4042 | 48 |
| 89 | 183 | 5673 |
+----+----------------+--------------+
I have wrote a query in order to find the total number of visits each organisation has had but excluding SystemUsers who are associated to that organisation
SELECT
OrganisationID AS [OrganisationID],
ORG.Name AS [Compayny Name],
Count(OrganisationID) AS [Visits]
FROM [Audit].[EventData]
LEFT OUTER JOIN Employed.Organisation AS ORG ON [EventData].OrganisationID = ORG.ID
Where Not Exists(
Select * From [Audit].[EventData] AS A2
Inner join Employed.OrganisationHasContact AS OHC On [EventData].OrganisationIDID = OHC.OrganisationID
Where OHC.SystemUserID = [EventData].SystemUserID
)
AND
EventTypeID = 8
Group BY [EventData].OrganisationID,
ORG.Name
Order By Visits Desc
To test the above query was returning correct values (449 visits) I did the following
First I ran this query to get all SystemUserId's associated to Org 1741
Select * from Employed.OrganisationHasContact
Where OrganisationID = 1741
Then with the returned SystemUserID's I ran this query
SELECT Distinct
OrganisationID AS [OrganisationID],
ORG.Name AS [Compayny Name],
Count(OrganisationID) AS [Visits]
FROM [Audit].[EventData]
LEFT OUTER JOIN Employed.Organisation AS ORG ON [EventData].OrganisationID = ORG.ID
Where
EventTypeID = 8
AND SystemUserID NOT IN (35, 4602, 48, 4603, 7704)
Group BY OrganisationID,
ORG.Name
Order By Visits Desc
This Returned 446 visits for Org 1741, my original query returns 449 visits.
What could be causing the difference?
Can I improve my original query somehow?
Is there a better way to test this?
Thanks