PostgreSQL - Calculation count the elements in the hierarchy - sql

There is a table:
CREATE TABLE product_categories (
id INT NOT NULL PRIMARY KEY,
parent INT NOT NULL,
name varchar(50) NOT NULL,
isProduct boolean NOT NULL
);
Is there any way to calculate count products in the each category?
That is:
INSERT INTO product_categories VALUES (1, NULL, 'Main', 'no');
INSERT INTO product_categories VALUES (2, 1, 'Plant', 'no');
INSERT INTO product_categories VALUES (3, 2, 'Cactus', 'yes');
INSERT INTO product_categories VALUES (4, 2, 'Spruce', 'yes');
INSERT INTO product_categories VALUES (5, 2, 'Birch', 'yes');
INSERT INTO product_categories VALUES (6, 2, 'Pine', 'yes');
INSERT INTO product_categories VALUES (7, 1, 'Stock', 'no');
INSERT INTO product_categories VALUES (8, 7, 'Spade', 'yes');
INSERT INTO product_categories VALUES (9, 7, 'Watering can', 'yes');
and should to receive:
Category | Count
Main | 6
Plant | 4
Stock | 2

You need to use a Recursive Common Table Expression
WITH RECURSIVE Parents AS
( SELECT ID, Parent, Name, IsProduct
FROM product_categories
WHERE Parent IS NOT NULL
UNION ALL
SELECT c.ID, p.Parent, c.Name, c.IsProduct
FROM product_categories c
INNER JOIN Parents p
ON p.ID = c.Parent
)
SELECT pc.Name,
COUNT(*) AS Products,
ARRAY_AGG(p.Name) AS ProductList
FROM product_categories pc
INNER JOIN Parents p
ON p.Parent = pc.ID
WHERE p.IsProduct = 'yes'
GROUP BY pc.Name
Working Example

Related

SQL Server : SELECT query to get DISTINCT and MAX display order value

I have a product table, Category table, and Mapping table. Category saved as a category tree. If a single product has mapped with the last category in a hierarchy of level three. All the levels saved in the mapping table with the same product id.
eg : Assume there is category tre like this Electronic>LapTops>DELL and when product id = 1 assigned to category 'DELL' mapping will save as [1,Electronic],[1,LapTops],[1,DELL]
When I get data with a select query all the category levels appear with the same product Id.
My problem is I need to retrieve data as [productId, ProductName, LastCategortLevel, CategoryName, CategoryId].
Refer actual result below. I just need to pick the highlighted product with the last category level which is the highest category order level.
I can't use another stored procedure or function because it's a small part of a large stored procedure.
The actual database tables are very big. But I have tried to implement the same scenario with small temp tables. see the below queries.
DECLARE #Products TABLE (ProductId INT NOT NULL)
INSERT INTO #Products(ProductId)
SELECT ProductId
FROM (VALUES (1), (2), (3), (4)) as x (ProductId)
DECLARE #Categories TABLE (CategoId INT NOT NULL,
Name VARCHAR(MAX) NOT NULL,
ParentCategoryId INT NOT NULL,
DisplayOrder INT NOT NULL)
-- 1st category tree
INSERT INTO #Categories VALUES (10, 'Electronic', 0, 1)
INSERT INTO #Categories VALUES (11, 'LapTops', 10, 2)
INSERT INTO #Categories VALUES (12, 'DELL', 11, 3)
INSERT INTO #Categories VALUES (13, 'HP', 11, 3)
-- 2st category tree
INSERT INTO #Categories VALUES (14, 'Clothes', 0, 1)
INSERT INTO #Categories VALUES (15, 'T-Shirts', 14, 2)
INSERT INTO #Categories VALUES (16, 'Red', 15, 3)
INSERT INTO #Categories VALUES (17, 'Denim', 14, 2)
INSERT INTO #Categories VALUES (18, 'Levise', 17, 3)
DECLARE #Product_Category_Mappings TABLE(MappingId INT NOT NULL,
ProductId INT NOT NULL,
CategoryId INT NOT NULL)
INSERT INTO #Product_Category_Mappings VALUES (100, 1, 10)
INSERT INTO #Product_Category_Mappings VALUES (101, 1, 11)
INSERT INTO #Product_Category_Mappings VALUES (102, 1, 12)
INSERT INTO #Product_Category_Mappings VALUES (103, 2, 10)
INSERT INTO #Product_Category_Mappings VALUES (104, 2, 11)
INSERT INTO #Product_Category_Mappings VALUES (105, 2, 12)
INSERT INTO #Product_Category_Mappings VALUES (106, 3, 14)
INSERT INTO #Product_Category_Mappings VALUES (107, 3, 15)
INSERT INTO #Product_Category_Mappings VALUES (108, 3, 16)
INSERT INTO #Product_Category_Mappings VALUES (109, 4, 14)
INSERT INTO #Product_Category_Mappings VALUES (110, 4, 17)
INSERT INTO #Product_Category_Mappings VALUES (111, 4, 18)
SELECT *
FROM #Products P
INNER JOIN #Product_Category_Mappings M ON M.ProductId = P.ProductId
INNER JOIN #Categories C ON C.CategoId = M.CategoryId
WHERE M.ProductId = P.ProductId
ORDER BY P.ProductId, C.DisplayOrder
Result of the above script. How I get highlighted rows?
For each ProductId, you want the row with highest DisplayOrder. You can use window functions:
SELECT *
FROM (
SELECT *, ROW_NUMBER() OVER(PARTITION BY P.ProductId ORDER BY C.DisplayOrder DESC) rn
FROM #Products P
INNER JOIN #Product_Category_Mappings M ON M.ProductId = P.ProductId
INNER JOIN #Categories C ON C.CategoId = M.CategoryId
WHERE M.ProductId = P.ProductId
) t
WHERE rn = 1
ORDER BY P.ProductId, C.DisplayOrder

Recursive query for multiple tables

In a SQL Server table OBJECTS, some objects are derived from another object and it is potentially infinitely many levels deep. Another table contains ATTRIBUTES for objects but it list the attributes for the main(parent) object but not its derived objects. I am searching for a way to get all the objects with a specific attribute(that may or may not be derived)?
I think a Common Table Expression (recursive query) is the way to go but I cant understand how to use it.
DDL:
CREATE TABLE OBJECTS
(
[ID] INT,
[PARENTID] INT,
[ObjectName] VARCHAR(32)
);
INSERT INTO OBJECTS ([ID], [PARENTID], [ObjectName])
VALUES
(1, 0, 'Parent1'),
(2, 1, 'Parent2'),
(3, 1, 'Item1'),
(4, 1, 'Item2'),
(5, 2, 'Item3'),
(6, 0, 'Item4'),
(7, 0, 'Item5');
CREATE TABLE ATTRIBUTES
(
[ID] INT,
[AttributeName] VARCHAR(1)
);
INSERT INTO ATTRIBUTES ([ID], [AttributeName])
VALUES
(1, 'A'),
(1, 'B'),
(2, 'C'),
(2, 'D'),
(3, 'F'),
(6, 'C'),
(7, 'A');
Example question: how to list all objects(both 'native' and derived from parent objects) with a Attribute of 'A'?
Desired output:
ID OBJECTNAME
---------------
1 Parent1
2 Parent2
3 Item1
4 Item2
5 Item3
7 Item5
WITH cte AS (
SELECT
o.id,
o.parentid
FROM objects o
UNION ALL
SELECT
c.id,
o.parentid
FROM objects o
JOIN cte c ON c.parentid = o.id
)
SELECT DISTINCT
a.attributename,
c.id,
o.objectname
FROM cte c
JOIN attributes a ON (a.id = c.id OR a.id = c.parentid)
AND a.attributename = 'A'
LEFT JOIN objects o ON o.id = c.id
ORDER BY 1, 2, 3

postgresql outer join query to get all data from one table

I have two tables
CREATE TABLE REFERENCE_VALUES
(
REFERENCE_ID SERIAL,
REFERENCE_OBJ_NAME TEXT,
REFERENCE_VALUE_CODE BIGINT,
DISPLAY_NAME TEXT,
CREATED_ON TIMESTAMP,
MODIFIED_ON TIMESTAMP
);
ALTER TABLE REFERENCE_VALUES
ADD PRIMARY KEY (ID);
ALTER TABLE REFERENCE_VALUES
ADD FOREIGN KEY (REFERENCE_VALUE_CODE)
REFERENCES CATEGORY(ID);
CREATE TABLE CATEGORY
(
ID BIGSERIAL NOT NULL,
CODE TEXT NOT NULL,
NAME TEXT NOT NULL,
PARENT_ID BIGINT,
PATH TEXT,
COMP_ID BIGINT,
CREATED_ON TIMESTAMP,
MODIFIED_ON TIMESTAMP,
);
ALTER TABLE CATEGORY
ADD PRIMARY KEY (ID);
ALTER TABLE CATEGORY
ADD FOREIGN KEY (COMP_ID)
REFERENCES COMPANY_ID(ID);
I have the display names as DISPLAY_NAME in REFERENCE_VALUES table I want to SELECT all values from table CATEGORY but the value of NAME (In CATEGORY table) replaced by value from DISPLAY_NAME in REFERENCE_VALUES and if the value of of DISPLAY_NAME is NULL OR EMPTY i would keep the value of NAME (CATEGORY table).
I have been able to do that with the query below
SELECT C.ID, C.CODE, COALESCE(R.DISPLAY_NAME, C.NAME) as NAME, C.PARENT_ID, C.PATH, C.COMP_ID, C.CREATED_ON, C.MODIFIED_ON
FROM CATEGORY C
LEFT JOIN REFERENCE_VALUES R
ON C.ID = R.REFERENCE_VALUE_CODE
WHERE R.REFERENCE_OBJ_NAME = 'CATEGORY';
but i am getting only two records. How can i get all the records from the category table?
sample data to populate the tables
INSERT INTO CATEGORY VALUES (1, 'CVB', 'COMM VEH', NULL, 'CVB', 1, '2016-05-13 15:50:19.985', NULL);
INSERT INTO CATEGORY VALUES (2, 'LVB', 'AUTO', NULL, 'LVB', 1, '2016-05-13 15:50:19.994', NULL);
INSERT INTO CATEGORY VALUES (3, 'INB', 'INF', NULL, 'INB', 1, '2016-05-13 15:50:19.997', NULL);
INSERT INTO CATEGORY VALUES (4, 'OHB', 'OFF', NULL, 'OHB', 1, '2016-05-13 15:50:20', NULL);
INSERT INTO CATEGORY VALUES (5, 'LUB', 'LUB', NULL, 'LUB', 1, '2016-05-13 15:50:20.002', NULL);
INSERT INTO CATEGORY VALUES (52, 'TRA', 'TIE', 32, 'CVB.HA.TRA', 1, '2016-05-13 15:51:32.605', NULL);
INSERT INTO CATEGORY VALUES (68, 'PF', 'PER', 42, 'LVB.LA.PF', 1, '2016-05-13 15:51:33.117', NULL);
INSERT INTO CATEGORY VALUES (73, 'CE', 'CAR', 32, 'CVB.HA.CE', 1, '2016-05-13 15:51:33.733', NULL);
INSERT INTO CATEGORY VALUES (74, 'KP', 'KP', 32, 'CVB.HA.KP', 1, '2016-05-13 15:51:33.958', NULL);
INSERT INTO CATEGORY VALUES (26, 'RP', 'RING', 11, 'OHB.OH.RP', 1, '2016-05-13 15:51:30.149', NULL);
INSERT INTO CATEGORY VALUES (47, 'CP', 'COMP', 9, 'CVB.CV.CP', 1, '2016-05-13 15:51:31.903', NULL);
INSERT INTO CATEGORY VALUES (48, 'TB', 'TUB', 9, 'CVB.CV.TB', 1, '2016-05-13 15:51:31.905', NULL);
INSERT INTO CATEGORY VALUES (18, 'FB', 'FILT', 11, 'OHB.OH.FB', 1, '2016-05-13 15:51:30.002', NULL);
INSERT INTO REFERENCE_VALUES (ID, REFERENCE_OBJ_NAME, REFERENCE_VALUE_CODE, DISPLAY_NAME) VALUES (1, 'CATEGORY', 6, INDU CHANGED);
INSERT INTO REFERENCE_VALUES (ID, REFERENCE_OBJ_NAME, REFERENCE_VALUE_CODE) VALUES (2, 'CATEGORY', 7);
The WHERE condition filters out rows where R.REFERENCE_OBJ_NAME is null, which is all rows for which there are no matching REFERENCE_VALUE.
Maybe what you are trying to do is only join REFERENCE_VALUES where REFERENCE_OBJ_NAME is 'CATEGORY'. If so you could do it like this:
SELECT C.ID, C.CODE, COALESCE(R.DISPLAY_NAME, C.NAME) as NAME, C.PARENT_ID, C.PATH, C.COMP_ID, C.CREATED_ON, C.MODIFIED_ON
FROM CATEGORY C
LEFT JOIN REFERENCE_VALUES R
ON R.REFERENCE_OBJ_NAME = 'CATEGORY' AND C.ID = R.REFERENCE_VALUE_CODE

COUNT of different tables in GROUP

I have a table that holds a list of classes available at a school. Each class can have a number of sessions. And each class can have pupils assigned to it.
What I need to do is get a count of all sessions for each class, as well as the number of students attending the class. I have done the first bit, but if I join to the pupil allocation table, my counts will be wrong.
I have conjured up some fake SQL that you can use.
I'm stuck with efficiently getting a count from the Pupil link table.
DECLARE #Class TABLE
(
ClassID INT NOT NULL,
ClassName VARCHAR(20) NOT NULL
)
INSERT INTO #Class VALUES (1, 'English')
INSERT INTO #Class VALUES (2, 'Maths')
DECLARE #ClassSession TABLE
(
ClassSessionID INT NOT NULL,
ClassID INT NOT NULL,
Description VARCHAR(100) NOT NULL
)
INSERT INTO #ClassSession VALUES (1, 1, 'Basic English')
INSERT INTO #ClassSession VALUES (2, 1, 'Advanced English')
INSERT INTO #ClassSession VALUES (3, 1, 'Amazing English')
INSERT INTO #ClassSession VALUES (4, 2, 'Basic English')
INSERT INTO #ClassSession VALUES (5, 2, 'Basic English')
DECLARE #ClassPupil TABLE
(
ClassPupilID INT NOT NULL,
ClassID INT NOT NULL,
PupilID INT NOT NULL -- FK to the Pupils table.
)
INSERT INTO #ClassPupil VALUES (1, 1, 1000)
INSERT INTO #ClassPupil VALUES (2, 1, 1001)
INSERT INTO #ClassPupil VALUES (3, 1, 1002)
INSERT INTO #ClassPupil VALUES (4, 1, 1003)
INSERT INTO #ClassPupil VALUES (5, 1, 1004)
INSERT INTO #ClassPupil VALUES (6, 2, 1005)
INSERT INTO #ClassPupil VALUES (7, 2, 1006)
INSERT INTO #ClassPupil VALUES (8, 2, 1007)
SELECT ClassName, COUNT(*) AS Sessions, '??' AS NumerOfPupils
FROM #Class c
INNER JOIN #ClassSession cs
ON cs.ClassID = c.ClassID
GROUP BY c.ClassID, c.ClassName
It can maybe be done with a sub query? Is that the best way?
You have two independent dimensions for each class. You need to aggregat them separately:
SELECT c.ClassName, cs.Sessions, cp.Pupils
FROM #Class c INNER JOIN
(SELECT ClassId, COUNT(*) as sessions
FROM #ClassSession cs
GROUP BY ClassId
) cs
ON cs.ClassID = c.ClassID INNER JOIN
(SELECT ClassId, COUNT(*) as pupils
FROM #ClassPupil cp
GROUP BY ClassId
) cp
ON cp.ClassId = c.ClassId;
Another method is to use CROSS APPLY to get the count of pupils:
SELECT
ClassName, COUNT(*) AS Sessions, cp.NumberOfPupils
FROM #Class c
INNER JOIN #ClassSession cs
ON cs.ClassID = c.ClassID
CROSS APPLY (
SELECT COUNT(*) AS NumberOfPupils
FROM #ClassPupil
WHERE
ClassID = c.ClassID
) cp
GROUP BY c.ClassID, c.ClassName, cp.NumberOfPupils
SELECT ClassName, COUNT(distinct cs.ClassSessionID) AS Sessions, /*'??'*/ count( distinct cp.PupilID) AS NumerOfPupils
FROM #Class c
INNER JOIN #ClassSession cs
ON cs.ClassID = c.ClassID
inner join #ClassPupil cp on c.ClassID=cp.ClassID
GROUP BY /*c.ClassID,*/ c.ClassName
count(distinct...) solves (works around) the problem.
Generally, this is (A -> B, C) issue.

SQL CTE counting childs recursion

I'd like (using cte) to count children in table in that way to have at parent level number of all children including theirs children. Is there any sample available?
CREATE TABLE t_parent (id INT NOT NULL PRIMARY KEY, parentID INT NOT NULL)
INSERT
INTO t_parent
VALUES (1, 0)
INSERT
INTO t_parent
VALUES (2, 1)
INSERT
INTO t_parent
VALUES (3, 1)
INSERT
INTO t_parent
VALUES (4, 2)
INSERT
INTO t_parent
VALUES (5, 1)
INSERT
INTO t_parent
VALUES (6, 5)
INSERT
INTO t_parent
VALUES (7, 5);
WITH q AS
(
SELECT id, parentId
FROM t_parent
UNION ALL
SELECT p.id, p.parentID
FROM q
JOIN t_parent p
ON p.id = q.parentID
)
SELECT id, COUNT(*)
FROM q
GROUP BY
id