Sort entity by multiple children values in EF Core - ef-core-3.1

So how would one go about sorting entities based on the composite value of multiple children? Here is an example model:
public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
public string ProductGroup { get; set; }
public ICollection<Option> Options { get; set; }
}
public class Option
{
public int OptionId { get; set; }
public int ProductId { get; set; }
public Product Product { get; set; }
public string Name { get; set; }
public int NamePosition { get; set; }
public string Value { get; set; }
public int ValuePosition { get; set; }
}
And example data that looks like this (assume that we can't control the order in which products or options are added, so the primary keys are not useful for ordering):
insert into Product (Name, ProductGroup)
select 'Long, Thin Red Drapes', 'Red Drapes' -- product id 1
union all
select 'Short, Thin Red Drapes', 'Red Drapes' -- product id 2
union all
select 'Long, Wide Red Drapes', 'Red Drapes' -- product id 3
union all
select 'Short, Wide Red Drapes', 'Red Drapes' -- product id 4
union all
select 'Long, Thin White Drapes', 'White Drapes' -- product id 5
union all
select 'Short, Thin White Drapes', 'White Drapes' -- product id 6
union all
select 'Long, Wide White Drapes', 'White Drapes' -- product id 7
union all
select 'Short, Wide White Drapes', 'White Drapes' -- product id 8
go
insert into Options (ProductId, Name, NamePosition, Value, ValuePosition)
select 1, 'Length', 1, '84 inches', 2
union all
select 1, 'Width', 2, '25 inches', 1
union all
select 2, 'Length', 1, '64 inches', 1
union all
select 2, 'Width', 2, '25 inches', 1
union all
select 3, 'Length', 1, '84 inches', 2
union all
select 3, 'Width', 2, '42 inches', 2
union all
select 4, 'Length', 1, '64 inches', 1
union all
select 4, 'Width', 2, '42 inches', 2
union all
select 5, 'Length', 1, '84 inches', 2
union all
select 5, 'Width', 2, '25 inches', 1
union all
select 6, 'Length', 1, '64 inches', 1
union all
select 6, 'Width', 2, '25 inches', 1
union all
select 7, 'Length', 1, '84 inches', 2
union all
select 7, 'Width', 2, '42 inches', 2
union all
select 8, 'Length', 1, '64 inches', 1
union all
select 8, 'Width', 2, '42 inches', 2
go
We would want to sort the products by their length values, then by their width values, so the sorted ProductId list for 'Red Drapes' would be: { 2, 4, 1, 3 }.
It seems like there is an OrderBy, ThenBy solution here, using the sorted name positions as ordinals or something, but I can't seem to find it. So far the only solution I can wrap my head around is to build a special sorting trick in the controller that would make a composite value based on all of the children, along the lines of:
ProductId 1 = Long, Thin Red Drapes = Length:2, Width:1 = Composite "21"
ProductId 2 = Short, Thin Red Drapes = Length:1, Width:1 = Composite "11"
ProductId 3 = Long, Wide Red Drapes = Length:2, Width:2 = Composite "22"
ProductId 4 = Short, Wide Red Drapes = Length:1, Width:2 = Composite "12"
Then sorting by this composite value, we get { "11", "12", "21", "22" }, or the desired { 2, 4, 1, 3 } sequence.
But this feels awfully awkward, and would break if a product came along with 10+ possible option values.
This query would do the trick, aside from duplicating the items by the number of options:
select i.Name, o.NamePosition, o.ValuePosition
from SaleableItem i
join SaleableOption o on o.SaleableItemId = i.SaleableItemId
where i.ProductGroup = 'Red Drapes'
order by o.NamePosition, o.ValuePosition
Thanks for any comments or suggestions!

Related

Postresql: How do I UPDATE null values in column with values from another table?

Using PostgreSQL 9.2.4 ... I have three tables:
sales:
inventoryid, batchno, quantity
00001, A40-0007, 1
00002, NULL, 1
00003, C20-0039, 2
parts:
partid, name
8, A40
5, B30
3, C20
inventory:
inventoryid, partid, batcho
00001, 8, A40-0007
00002, 5, NULL
00003, 3, C20-0039
How do I UPDATE all of the NULL values in sales.batchno with the matching part name? In the end, I want the sales table to look like this:
sales:
inventoryid, batchno, quantity
00001, A40-0007, 1
00002, B30, 1
00003, C20-0039, 2
You can use update ... from:
update sales s
set batchno = p.name
from inventory i
inner join parts p on p.partid = i.partid
where i.inventoryid = s.inventoryid and s.batchno is null
Note that your design has duplicated information, so it does not look optimal.

Convert jsonb in PostgreSQL to rows without cycle

ffI have a json array stored in my postgres database. The first table "Orders" looks like this:
order_id, basket_items_id
1, {1,2}
2, {3}
3, {1,2,3,1}
Second table "Items" looks like this:
item_id, price
1,5
2,3
3,20
Already tried to load data with multiple sql and select of different jsonb record, but this is not a silver bullet.
SELECT
sum(price)
FROM orders
INNER JOIN items on
orders.basket_items_id = items.item_id
WHERE order_id = 3;
Want to get this as output:
order_id, basket_items_id, price
1, 1, 5
1, 2, 3
2, 3, 20
3, 1, 5
3, 2, 3
3, 3, 20
3, 1, 5
or this:
order_id, sum(price)
1, 8
2, 20
3, 33
demo:db<>fiddle
SELECT
o.order_id,
elems.value::int as basket_items_id,
i.price
FROM
orders o, jsonb_array_elements_text(basket_items_id) as elems
LEFT JOIN items i
ON i.item_id = elems.value::int
ORDER BY 1,2,3
jsonb_array_elements_text expands the jsonb array into one row each element. With this you are able to join against your second table directly
Since the expanded array gives you text elements you have to cast them into integers using ::int
Of course you can GROUP and SUM aggregate this as well:
SELECT
o.order_id,
SUM(i.price)
FROM
orders o, jsonb_array_elements_text(basket_items_id) as elems
LEFT JOIN items i
ON i.item_id = elems.value::int
GROUP BY o.order_id
ORDER BY 1
Is your orders.basket_items_id column of type jsonb or int[]?
If the type is jsonb you can use json_array_elements_text to expand the column:
SELECT
o.order_id,
o.basket_item_id,
items.price
FROM
(
SELECT
order_id,
jsonb_array_elements_text(basket_items_id)::int basket_item_id
FROM
orders
) o
JOIN
items ON o.basket_item_id = items.item_id
ORDER BY
1, 2, 3;
See this DB-Fiddle.
If the type is int[] (array of integers), you can run a similar query with the unnest function:
SELECT
o.order_id,
o.basket_item_id,
items.price
FROM
(
SELECT
order_id,
unnest(basket_items_id) basket_item_id
FROM
orders
) o
JOIN
items ON o.basket_item_id = items.item_id
ORDER BY
1, 2, 3;
See this DB-fiddle

Limit query by count distinct column values

I have a table with people, something like this:
ID PersonId SomeAttribute
1 1 yellow
2 1 red
3 2 yellow
4 3 green
5 3 black
6 3 purple
7 4 white
Previously I was returning all of Persons to API as seperate objects. So if user set limit to 3, I was just setting query maxResults in hibernate to 3 and returning:
{"PersonID": 1, "attr":"yellow"}
{"PersonID": 1, "attr":"red"}
{"PersonID": 2, "attr":"yellow"}
and if someone specify limit to 3 and page 2(setMaxResult(3), setFirstResult(6) it would be:
{"PersonID": 3, "attr":"green"}
{"PersonID": 3, "attr":"black"}
{"PersonID": 3, "attr":"purple"}
But now I want to select people and combine then into one json object to look like this:
{
"PersonID":3,
"attrs": [
{"attr":"green"},
{"attr":"black"},
{"attr":"purple"}
]
}
And here is the problem. Is there any possibility in postgresql or hibernate to set limit not by number of rows but to number of distinct people ids, because if user specifies limit to 4 I should return person1, 2, 3 and 4, but in my current limiting mechanism I will return person1 with 2 attributes, person2 and person3 with only one attribute. Same problem with pagination, now I can return half of a person3 array attrs on one page and another half on next page.
You can use row_number to simulate LIMIT:
-- Test data
CREATE TABLE person AS
WITH tmp ("ID", "PersonId", "SomeAttribute") AS (
VALUES
(1, 1, 'yellow'::TEXT),
(2, 1, 'red'),
(3, 2, 'yellow'),
(4, 3, 'green'),
(5, 3, 'black'),
(6, 3, 'purple'),
(7, 4, 'white')
)
SELECT * FROM tmp;
-- Returning as a normal column (limit by someAttribute size)
SELECT * FROM (
select
"PersonId",
"SomeAttribute",
row_number() OVER(PARTITION BY "PersonId" ORDER BY "PersonId") AS rownum
from
person) as tmp
WHERE rownum <= 3;
-- Returning as a normal column (overall limit)
SELECT * FROM (
select
"PersonId",
"SomeAttribute",
row_number() OVER(ORDER BY "PersonId") AS rownum
from
person) as tmp
WHERE rownum <= 4;
-- Returning as a JSON column (limit by someAttribute size)
SELECT "PersonId", json_object_agg('color', "SomeAttribute") AS attributes FROM (
select
"PersonId",
"SomeAttribute",
row_number() OVER(PARTITION BY "PersonId" ORDER BY "PersonId") AS rownum
from
person) as tmp
WHERE rownum <= 3 GROUP BY "PersonId";
-- Returning as a JSON column (limit by person)
SELECT "PersonId", json_object_agg('color', "SomeAttribute") AS attributes FROM (
select
"PersonId",
"SomeAttribute"
from
person) as tmp
GROUP BY "PersonId"
LIMIT 4;
In this case, of course, you must use a native query, but this is a small trade-off IMHO.
More info here and here.
I'm assuming you have another Person table. With JPA, you should do the query on Person table(one side), not on the PersonColor(many side).Then the limit will be applied on number of rows of Person then
If you don't have the Person table and can't modify the DB, what you can do is use SQL and Group By PersonId, and concatenate colors
select PersonId, array_agg(Color) FROM my_table group by PersonId limit 2
SQL Fiddle
Thank you guys. After I realize that it could not be done with one query I just do sth like
temp_query = select distinct x.person_id from (my_original_query) x
with user specific page/per_page
and then:
my_original_query += " AND person_id in (temp_query_results)

Summarizing Only Rows with given criteria

all!
Given the following table structure
DECLARE #TempTable TABLE
(
idProduct INT,
Layers INT,
LayersOnPallet INT,
id INT IDENTITY(1, 1) NOT NULL,
Summarized BIT NOT NULL DEFAULT(0)
)
and the following insert statement which generates test data
INSERT INTO #TempTable(idProduct, Layers, LayersOnPallet)
SELECT 1, 2, 4
UNION ALL
SELECT 1, 2, 4
UNION ALL
SELECT 1, 1, 4
UNION ALL
SELECT 2, 2, 4
I would like to summarize only those rows (by the Layers only) with the same idProduct and which will have the sum of layers equal to LayersOnPallet.
A picture is worth a thousand words:
From the picture above, you can see that only the first to rows were summarized because both have the same idProduct and the sum(layers) will be equal to LayersOnPallet.
How can I achieve this? It's there any way to do this only in selects (not with while)?
Thank you!
Perhaps this will do the trick. Note my comments:
-- your sample data
DECLARE #TempTable TABLE
(
idProduct INT,
Layers INT,
LayersOnPallet INT,
id INT IDENTITY(1, 1) NOT NULL,
Summarized BIT NOT NULL DEFAULT(0)
)
INSERT INTO #TempTable(idProduct, Layers, LayersOnPallet)
SELECT 1, 2, 4 UNION ALL
SELECT 1, 2, 4 UNION ALL
SELECT 1, 1, 4 UNION ALL
SELECT 2, 2, 4;
-- an intermediate temp table used for processing
IF OBJECT_ID('tempdb..#processing') IS NOT NULL DROP TABLE #processing;
-- let's populate the #processing table with duplicates
SELECT
idProduct,
Layers,
LayersOnPallet,
rCount = COUNT(*)
INTO #processing
FROM #tempTable
GROUP BY
idProduct,
Layers,
LayersOnPallet
HAVING COUNT(*) > 1;
-- Remove the duplicates
DELETE t
FROM #TempTable t
JOIN #processing p
ON p.idProduct = t.idProduct
AND p.Layers = t.Layers
AND p.LayersOnPallet = t.LayersOnPallet
-- Add the new, updated record
INSERT #TempTable
SELECT
idProduct,
Layers * rCount,
LayersOnPallet, 1
FROM #processing;
DROP TABLE #processing; -- cleanup
-- Final output
SELECT idProduct, Layers, LayersOnPallet, Summarized
FROM #TempTable;
Results:
idProduct Layers LayersOnPallet Summarized
----------- ----------- -------------- ----------
1 4 4 1
1 1 4 0
2 2 4 0

TSQL recursive CTE threaded sorting

I have the following table:
ID parentID name
1 0 car1
2 1 tire
3 2 rubber
4 0 car2
5 2 nut
6 3 black
To help with testing...
CREATE TABLE #TT (ID int
,ParentID int
,Name varchar(25)
)
INSERT #TT
SELECT 1,0,'car1' UNION ALL
SELECT 2,1,'tire' UNION ALL
SELECT 3,2,'rubber' UNION ALL
SELECT 4,0,'car2' UNION ALL
SELECT 5,2,'nut' UNION ALL
SELECT 6,3,'black'
I'm trying to create a "threaded" hierarchy, but I want to list the child nodes under their parents like so:
ID parentID name
1 0 car1
2 1 tire
3 2 rubber
6 3 black
5 2 nut
4 0 car2
If I use a recursive CTE like this one...
;WITH Features
AS
(
SELECT *
FROM #TT
WHERE ParentID = 0
UNION ALL
SELECT F.*
FROM #TT AS F
INNER JOIN Features
ON F.ParentID = Features.ID
)
SELECT *
FROM Features
I end up with this...
ID parentID name
1 0 car1
4 0 car2
2 1 tire
3 2 rubber
5 2 nut
6 3 black
any ideas? Thank you in advance.
You can build a tree path as you go along, and order it by that
Something like
DECLARE #TT TABLE(ID int, ParentID int, Name varchar(25))
INSERT #TT
SELECT 1,0,'car1' UNION ALL
SELECT 2,1,'tire' UNION ALL
SELECT 3,2,'rubber' UNION ALL
SELECT 4,0,'car2' UNION ALL
SELECT 5,2,'nut' UNION ALL
SELECT 6,3,'black'
SELECT *
FROM #TT
;WITH Features AS (
SELECT *,
CAST(ID AS VARCHAR(MAX)) + '/' AS TreePath
FROM #TT
WHERE ParentID = 0
UNION ALL
SELECT tt.*,
f.TreePath + CAST(tt.ID AS VARCHAR(10)) + '/'
FROM #TT tt INNER JOIN
Features f ON tt.ParentID = f.ID
)
SELECT *
FROM Features
ORDER BY TreePath
Try adding an ORDER BY clause such as:
SELECT * FROM Features
ORDER BY parentID