MS-SQL Grouping and counting query - sql

So I have a table and the columns are Product and ProductGroupID.
Every Product can be in 1 or more groups as shown in this example:
+-------+--------------+
|product|ProductGroupId|
+-------+--------------+
| 10 | 2 |
| 10 | 9 |
| 10 | 4 |
| 10 | 7 |
| 20 | 7 |
| 30 | 4 |
| 40 | 1 |
| 50 | 11 |
| 50 | 12 |
| 60 | 2 |
| 70 | 9 |
| 80 | 11 |
| 90 | 12 |
| 100 | 13 |
+-------+--------------+
For every product I need to get it's group or groups and to bring the number of product which are in those groups.
For example product 10 is in groups 2,4,9,7 so I need to count all the products that are in those groups, in this case 5 (for count of products 10,60,70,30,20).
I attach the full desired outcome for this example.
https://i.stack.imgur.com/XTa4R.png
Any suggestion how to do this in ms-sql?
Thnaks!

If I understood it correctly this will work for you.
Schema from your image:
CREATE TABLE #PRODUCTS (PRODUCT INT, PRODUCTGROUP_ID INT)
INSERT INTO #PRODUCTS
SELECT 10,2
UNION ALL
SELECT 10,9
UNION ALL
SELECT 10,4
UNION ALL
SELECT 10,7
UNION ALL
SELECT 20,7
UNION ALL
SELECT 30,4
UNION ALL
SELECT 40,1
UNION ALL
SELECT 50,11
UNION ALL
SELECT 50,12
UNION ALL
SELECT 60,2
UNION ALL
SELECT 70,9
UNION ALL
SELECT 80,11
UNION ALL
SELECT 90,12
UNION ALL
SELECT 100,13
Now do a Self Join with GroupId on condition
SELECT P.PRODUCT, COUNT(DISTINCT G.PRODUCT) Linked_GroupProducts
FROM #PRODUCTS P
INNER JOIN #PRODUCTS G ON P.PRODUCTGROUP_ID = G.PRODUCTGROUP_ID
GROUP BY P.PRODUCT
And the result will be
+---------+----------------------+
| PRODUCT | Linked_GroupProducts |
+---------+----------------------+
| 10 | 5 |
| 20 | 2 |
| 30 | 2 |
| 40 | 1 |
| 50 | 3 |
| 60 | 2 |
| 70 | 2 |
| 80 | 2 |
| 90 | 2 |
| 100 | 1 |
+---------+----------------------+

Related

Oracle SQL - Generate aggregate rows for certain rows using select

I have a table like below.
|FILE| ID |PARENTID|SHOWCHILD|CAT1|CAT2|CAT3|TOTAL|
|F1 | A1 | P1 | N | 3 | 2 | 6 | 11 |
|F2 | A2 | P2 | N | 4 | 7 | 3 | 14 |
|F3 | A3 | P1 | N | 3 | 1 | 1 | 5 |
|F4 | LG1| | Y | 6 | 3 | 7 | 16 |
|F5 | LG2| | Y | 4 | 7 | 3 | 14 |
Now, Is it possible if I want to find the total (ie) aggregate of cat1, cat2, cat3 & total only for rows which has showChild as 'Y' and add that to the resultset.
|Tot| Res | Res | N | 10 | 10 | 10 | 30 |
Expected final output:
|FILE| ID |PARENTID|SHOWCHILD|CAT1|CAT2|CAT3|TOTAL|
|F1 | A1 | P1 | N | 3 | 2 | 6 | 11 |
|F2 | A2 | P2 | N | 4 | 7 | 3 | 14 |
|F3 | A3 | P1 | N | 3 | 1 | 1 | 5 |
|F4 | LG1| | Y | 6 | 3 | 7 | 16 |
|F5 | LG2| | Y | 4 | 7 | 3 | 14 |
|Tot | Res| Res | N | 10 | 10 | 10 | 30 |
Here I have added the Tot row(last row) after considering only the rows which has showchild as 'Y' and added that to the resultset.
I am trying for a solution without using UNION
Any help on achieving the above results is highly appreciated.
Thank you.
One approach would be to use a union:
WITH cte AS (
SELECT "FILE", ID, PARENTID, SHOWCHILD, CAT1, CAT2, CAT3, TOTAL, 1 AS position
FROM yourTable
UNION ALL
SELECT 'Tot', 'Res', 'Res', 'N', SUM(CAT1), SUM(CAT2), SUM(CAT3), SUM(TOTAL), 2
FROM yourTable
WHERE SHOWCHILD = 'Y'
)
SELECT "FILE", ID, PARENTID, SHOWCHILD, CAT1, CAT2, CAT3, TOTAL
FROM cte
ORDER BY
position,
"FILE";
Demo
You can try using UNION
select FILE,ID ,PARENTID,SHOWCHILD,CAT1,CAT2,CAT3,TOTAL from table1
union
select 'Tot','Res','Res','N',sum(cat1), sum(cat2),sum(cat3), sum(total)
from table1 where SHOWCHILD='Y'
I see you already accepted an answer, but you did ask for a solution that did not involve UNION. One such solution would be to use GROUPING SETS.
GROUPING SETS allow you to specify different grouping levels in your query and generate aggregates at each of those levels. You can use it to generate an output row for each record plus a single "total" row, as per your requirements. The function GROUPING can be used in expressions to identify whether each output row is in one group or the other.
Example, with test data:
with input_data ("FILE", "ID", PARENTID, SHOWCHILD, CAT1, CAT2, CAT3, TOTAL ) AS (
SELECT 'F1','A1','P1','N',3,2,6,11 FROM DUAL UNION ALL
SELECT 'F2','A2','P2','N',4,7,3,14 FROM DUAL UNION ALL
SELECT 'F3','A3','P1','N',3,1,1,5 FROM DUAL UNION ALL
SELECT 'F4','LG1','','Y',6,3,7,16 FROM DUAL UNION ALL
SELECT 'F5','LG2','','Y',4,7,3,14 FROM DUAL )
SELECT decode(grouping("FILE"),1,'Tot',"FILE") "FILE",
decode(grouping("ID"),1,'Res',"ID") "ID",
decode(grouping(parentid),1, 'Res',parentid) parentid,
decode(grouping(showchild),1, 'N',showchild) showchild,
decode(grouping("FILE"),1,sum(decode(showchild,'Y',cat1,0)),sum(cat1)) cat1,
decode(grouping("FILE"),1,sum(decode(showchild,'Y',cat2,0)),sum(cat2)) cat2,
decode(grouping("FILE"),1,sum(decode(showchild,'Y',cat3,0)),sum(cat3)) cat3,
decode(grouping("FILE"),1,sum(decode(showchild,'Y',total,0)),sum(total)) total
from input_data
group by grouping sets (("FILE", "ID", parentid, showchild), ())
+------+-----+-----+----------+-----------+------+------+------+-------+
| FILE | F2 | ID | PARENTID | SHOWCHILD | CAT1 | CAT2 | CAT3 | TOTAL |
+------+-----+-----+----------+-----------+------+------+------+-------+
| F1 | F1 | A1 | P1 | N | 3 | 2 | 6 | 11 |
| F2 | F2 | A2 | P2 | N | 4 | 7 | 3 | 14 |
| F3 | F3 | A3 | P1 | N | 3 | 1 | 1 | 5 |
| F4 | F4 | LG1 | - | Y | 6 | 3 | 7 | 16 |
| F5 | F5 | LG2 | - | Y | 4 | 7 | 3 | 14 |
| Tot | Tot | Res | Res | N | 10 | 10 | 10 | 30 |
+------+-----+-----+----------+-----------+------+------+------+-------+

Get the Id of the matched data from other table. No duplicates of ID from both tables

Here is my table A.
| Id | GroupId | StoreId | Amount |
| 1 | 20 | 7 | 15000 |
| 2 | 20 | 7 | 1230 |
| 3 | 20 | 7 | 14230 |
| 4 | 20 | 7 | 9540 |
| 5 | 20 | 7 | 24230 |
| 6 | 20 | 7 | 1230 |
| 7 | 20 | 7 | 1230 |
Here is my table B.
| Id | GroupId | StoreId | Credit |
| 12 | 20 | 7 | 1230 |
| 14 | 20 | 7 | 15000 |
| 15 | 20 | 7 | 14230 |
| 16 | 20 | 7 | 1230 |
| 17 | 20 | 7 | 7004 |
| 18 | 20 | 7 | 65523 |
I want to get this result without getting duplicate Id of both table.
I need to get the Id of table B and A where the Amount = Credit.
| A.ID | B.ID | Amount |
| 1 | 14 | 15000 |
| 2 | 12 | 1230 |
| 3 | 15 | 14230 |
| 4 | null | 9540 |
| 5 | null | 24230 |
| 6 | 16 | 1230 |
| 7 | null | 1230 |
My problem is when I have 2 or more same Amount in table A, I get duplicate ID of table B. which should be null. Please help me. Thank you.
I think you want a left join. But this is tricky because you have duplicate amounts, but you only want one to match. The solution is to use row_number():
select . . .
from (select a.*, row_number() over (partition by amount order by id) as seqnum
from a
) a left join
(select b.*, row_number() over (partition by credit order by id) as seqnum
from b
)b
on a.amount = b.credit and a.seqnum = b.seqnum;
Another approach, I think simplier and shorter :)
select ID [A.ID],
(select top 1 ID from TABLE_B where Credit = A.Amount) [B.ID],
Amount
from TABLE_A [A]

How to get values of rows and columns

I have two tables.
Student Table
Property Table
Result Table
How can I get the value of Student Table and the property ID of the column fron the Property table and merge that into the Result table?
Any advice would be helpful.
Update #1:
I tried using Christian Moen 's suggestion, this is what i get.
You need to UNPIVOT the Student's columns first, to get the columns (properties names) in one column as rows. Then join with the Property table based on the property name like this:
WITH UnPivoted
AS
(
SELECT ID, value,col
FROM
(
SELECT ID,
CAST(Name AS NVARCHAR(50)) AS Name,
CAST(Class AS NVARCHAR(50)) AS Class,
CAST(ENG AS NVARCHAR(50)) AS ENG,
CAST(TAM AS NVARCHAR(50)) AS TAM,
CAST(HIN AS NVARCHAR(50)) AS HIN,
CAST(MAT AS NVARCHAR(50)) AS MAT,
CAST(PHY AS NVARCHAR(50)) AS PHY
FROM Student
) AS s
UNPIVOT
(value FOR col IN
([Name], [class], [ENG], [TAM], [HIN], [MAT], [PHY])
)AS unpvt
)
SELECT
ROW_NUMBER() OVER(ORDER BY u.ID,PropertyID) AS ID,
p.PropertyID,
u.Value,
u.ID AS StudID
FROM Property AS p
INNER JOIN UnPivoted AS u ON p.PropertyName = u.col;
For the first ID, I used the ranking function ROW_NUMBER() to generate this sequence id.
This will give the exact results that you are looking for.
Results:
| ID | PropertyID | Value | StudID |
|----|------------|--------|--------|
| 1 | 1 | Jack | 1 |
| 2 | 2 | 10 | 1 |
| 3 | 3 | 89 | 1 |
| 4 | 4 | 88 | 1 |
| 5 | 5 | 45 | 1 |
| 6 | 6 | 100 | 1 |
| 7 | 7 | 98 | 1 |
| 8 | 1 | Jill | 2 |
| 9 | 2 | 10 | 2 |
| 10 | 3 | 89 | 2 |
| 11 | 4 | 99 | 2 |
| 12 | 5 | 100 | 2 |
| 13 | 6 | 78 | 2 |
| 14 | 7 | 91 | 2 |
| 15 | 1 | Trevor | 3 |
| 16 | 2 | 12 | 3 |
| 17 | 3 | 100 | 3 |
| 18 | 4 | 50 | 3 |
| 19 | 5 | 49 | 3 |
| 20 | 6 | 94 | 3 |
| 21 | 7 | 100 | 3 |
| 22 | 1 | Jim | 4 |
| 23 | 2 | 8 | 4 |
| 24 | 3 | 100 | 4 |
| 25 | 4 | 91 | 4 |
| 26 | 5 | 92 | 4 |
| 27 | 6 | 100 | 4 |
| 28 | 7 | 100 | 4 |
Other option is to use of apply if you don't want to go unpivot way
select row_number() over (order by (select 1)) ID, p.PropertyID [PropID], a.Value, a.StuID
from Student s
cross apply
(
values (s.ID, 'Name', s.Name),
(s.ID, 'Class', cast(s.Class as varchar)),
(s.ID, 'ENG', cast(s.ENG as varchar)),
(s.ID, 'TAM', cast(s.TAM as varchar)),
(s.ID, 'HIN', cast(s.HIN as varchar)),
(s.ID, 'MAT', cast(s.MAT as varchar)),
(s.ID, 'PHY', cast(s.PHY as varchar))
) as a(StuID, Property, Value)
join Property p on p.PropertyName = a.Property

How can I compare 2 tables in MS-SQL?

How can I compare 2 tables with the same rows, but different data?
The tables are something like this:
1. Table price_old:
|-----------------------|
| id | price1 | price2 |
|-----------------------|
| 1 | 12 | 12 |
|-----------------------|
| 2 | 12 | 55 |
------------------------|
| 3 | 12 | 40 |
-------------------------
The tables are something like this:
2. Table price_old:
|-----------------------|
| id | price1 | price2 |
|-----------------------|
| 1 | 12 | 12 |
|-----------------------|
| 2 | 13 | 40 |
------------------------|
| 3 | 10 | 40 |
-------------------------
The Result should look like this:
3. Table Result:
|----------------------------------------------------------|
| id | price1_old | price1_new | price2_old | price2_new |
|----------------------------------------------------------|
| 2 | 12 | 13 | 55 | 40 |
|----------------------------------------------------------|
| 3 | 13 | 10 | 40 | 40 |
Try this, joining on the id and filtering out occurrences where at least one price is different:
SELECT
old.id,
old.price1 as price1_old,
old.price2 as price2_old,
new.price1 as price1_new,
new.price2 as price2_new
FROM price_old as old
LEFT JOIN price_new as new on old.id=new.id
WHERE old.price1<>new.price1
OR old.price2<>new.price2
This is might be an approach:
SELECT 'TableName' AS `set`, r.*
FROM robot r
WHERE ROW(r.col1, r.col2, …) NOT IN
(
SELECT *
FROM TableName2
)
UNION ALL
SELECT 'TableName2' AS `set`, t.*
FROM tbd_robot t
WHERE ROW(t.col1, t.col2, …) NOT IN
(
SELECT *
FROM TableName1
)

How to return smallest value inside the resultset as a separate column in SQL?

I've been struggling with the following SQL query.
My resultset is now:
| Id | Customer | Sales |
| 1 | 1 | 10 |
| 2 | 1 | 20 |
| 3 | 2 | 30 |
| 4 | 2 | 40 |
What I'd like to do is to add additional column that shows the smallest sale for that customer:
| Id | Customer | Sales | SmallestSale |
| 1 | 1 | 10 | 10 |
| 2 | 1 | 20 | 10 |
| 3 | 2 | 30 | 30 |
| 4 | 2 | 40 | 30 |
As the select query to get those three columns is now rather complex I'd like to avoid subqueries.
Any ideas?
Mika
Assuming your RDBMS supports windowed aggregates
SELECT Id,
Customer,
Sales,
MIN(Sales) OVER (PARTITION BY Customer) AS SmallestSale
FROM YourTable
select s.Id, s.Customer, s.Sales, sm.SmallestSale
from Sales s
inner join (
select Customer, min(sales) as SmallestSale
from Sales
group by Customer
) sm on s.Customer = sm.Customer