Need to split entry points with the number of user comes first - sql-server

+------------+-------+---------+---------------+
| STudent_ID | Marks | Subject | EntryPoints |
+------------+-------+---------+---------------+
| 1 | 50 | Maths | 10 |
| 2 | 50 | Maths | 10 |
| 3 | 45 | Maths | 10 |
| 1 | 30 | History | 20 |
| 2 | 30 | History | 20 |
| 3 | 30 | History | 20 |
+------------+-------+---------+---------------+
Expected output:
+------------+-------+---------+---------------+
| student_id | Marks | Subject | TotalPoints |
+------------+-------+---------+---------------+
| 1 | 50 | Maths | 5 |
| 2 | 50 | Maths | 5 |
| 1 | 30 | History | 6.66 |
| 2 | 30 | History | 6.66 |
| 3 | 30 | History | 6.66 |
+------------+-------+---------+---------------+
Total points calculation
For maths Entry points is 10 and number of student scored maximum is 2 so 10/2 = 5
for history Entry points is 20 and number of student scored maximum is 3 so 20/3 = 6.66
Query I tried:
select student_id,marks,subject from
(
select student_id,marks,subject,dense_rank() over ( partition by subject order by marks desc) rn from test
) t
where rn=1
Output:
+------------+-------+---------+
| Student_id | Marks | Subject |
+------------+-------+---------+
| 1 | 50 | Maths |
| 2 | 50 | Maths |
| 1 | 30 | History |
| 2 | 30 | History |
| 3 | 30 | History |
+------------+-------+---------+
I am not getting how to get the total points column in my query

I am assuming that the max score is the max among all the marks with the same subject.
SELECT
Student_ID
,Marks
,T.Subject
,CONVERT(decimal(18, 2),
CONVERT(float, EntryPoints)/
CONVERT(float, COUNT(*) OVER(PARTITION BY T.Subject))) as 'TotalPoints'
FROM #T T
INNER JOIN (SELECT DISTINCT Subject, MAX(Marks) OVER(PARTITION BY Subject)
as Max_Marks FROM #T) Scores
ON T.Subject = Scores.Subject
WHERE Marks = Scores.Max_Marks
ORDER BY Marks DESC, Student_ID

You can use Group by with your query for getting the count of subject, then join the result with the same query.
as next:-
Select a.student_id,a.marks,a.subject,
Cast(a.EntryPoints as decimal ) / cast(b.CountPerSubject as decimal) TotalPoints
from
(
select student_id,marks,subject ,EntryPoints
from
(
select student_id,marks,
subject, EntryPoints,
dense_rank() over ( partition by subject order by marks desc) rn
from test
) t
where rn=1
) a
Left Join
(
select subject, count(subject) CountPerSubject
from
(
select student_id,marks,subject ,EntryPoints
from
(
select student_id,marks,
subject, EntryPoints,
dense_rank() over ( partition by subject order by marks desc) rn
from test
) t
where rn=1 ) c
group by subject) b
on a.subject = b.subject

Related

Getting Top 10 based on column value

I have a code that output a long list of the sum of count of work orders per name and sorts it by total, name and count:
;with cte as (
SELECT [Name],
[Emergency],
count([Emergency]) as [CountItem]
FROM tableA
GROUP BY [Name], [Emergency])
select Name,[Emergency],[Count],SUM([CountItem]) OVER(PARTITION BY Name) as Total from cte
order by Total desc, Name, [CountItem] desc
but I only want to get the top 10 Names with the highest total like the one below:
+-------+-------------------------------+-------+-------+
| Name | Emergency | Count | Total |
+-------+-------------------------------+-------+-------+
| PLB | No | 7 | 15 |
| PLB | No Hot Water | 4 | 15 |
| PLB | Resident Locked Out | 2 | 15 |
| PLB | Overflowing Tub | 1 | 15 |
| PLB | No Heat | 1 | 15 |
| GG | Broken Lock - Exterior | 6 | 6 |
| BOA | Broken Lock - Exterior | 2 | 4 |
| BOA | Garage Door not working | 1 | 4 |
| BOA | Resident Locked Out | 1 | 4 |
| 15777 | Smoke Alarm not working | 3 | 3 |
| FP | No air conditioning | 2 | 3 |
| FP | Flood | 1 | 3 |
| KB | No electrical power | 2 | 3 |
| KB | No | 1 | 3 |
| MEM | Noise Complaint | 3 | 3 |
| ANG | Parking Issue | 2 | 2 |
| ALL | Smoke Alarm not working | 2 | 2 |
| AAS | No air conditioning | 1 | 2 |
| AAS | Toilet - Clogged (1 Bathroom) | 1 | 2 |
+-------+-------------------------------+-------+-------+
Note: I'm not after unique values. As you can see from the example above it gets the top 10 names from a very long table.
What I want to happen is assign a row id for each name so all PLB above will have a row id of 1, GG = 2, BOA = 3, ...
So on my final select I will only add the where clause where row id <= 10. I already tried ROW_NUMBER() OVER(PARTITION BY Name ORDER BY Name) but it's assigning 1 to every unique Name it encounters.
You may try this:
;with cte as (
SELECT [Name],
[Emergency],
count([Emergency]) as [CountItem]
FROM tableA
GROUP BY [Name], [Emergency]),
ct as (
select Name,[Emergency],[Count],SUM([CountItem]) OVER(PARTITION BY PropertyName) as Total from cte
),
ctname as (
select dense_rank() over ( order by total, name ) as RankName, Name,[Emergency],[Count], total from ct )
select * from ctname where rankname < 11

Lead/Lag syntax help - how do you order the columns?

Thanks for your help/advice. I'm unclear about the 1,0 within the LAG expression - what is that and why isn't mine working?
Do I have to do two Order by for both lead and lag?
Select
* Customer,
Prod,
day,
current sold,
date,
lag[current sold,1,0] OVER(PARTITION BY customer ORDER BY date DESC) as Previous Day,
lead[current sold,1,0] OVER(PARTITION BY customer ORDER BY date DESC) as Next Day
From
table1
Result:
| PROD | DAY | CURRENT SOLD | date customer |
+-------+-----+--------------+-----------------------
| SHIRT | M | 2 | 1-2018 A
| SHIRT | T | 9 | 2-2018 B
| SHIRT | W | 0 | 12-2018 C
| SHIRT | TH | 6 | 11-2018 D
| SHIRT | F | 7 | 3-2018 E
+-------+-----+--------------+--+----------------
+-------+-----+--------------+---------------+-----------+--+--------------
| PROD | DAY | CURRENT SOLD | PREVIOUS SOLD | NEXT SOLD | date |customer
+-------+-----+--------------+---------------+-----------+--+---------------
| SHIRT | M | 2 | | 9 | 1-2018 |A
| SHIRT | T | 9 | 2 | 0 | 2-2018 |B
| SHIRT | W | 0 | 9 | 6 | 12-2018|C
| SHIRT | TH | 6 | 0 | 7 | 11-2018|D
| SHIRT | F | 7 | 6 | | 3-2018 |E
+-------+-----+--------------+---------------+-----------+--+---------------
you can use LAG for previous sales and LEAD for next sales. I prepared sample with your example :
--DROP TABLE #Tbl;
--DROP TABLE #Days;
CREATE TABLE #Tbl
(
Prod VARCHAR(10)
,[DayName] VARCHAR(2)
,CurrentSold INT
);
CREATE TABLE #Days
(
DayNumber INT
,[DayName] VARCHAR(2)
);
INSERT INTO #Days
VALUES (1,'M'),(2,'T'),(3,'W'),(4,'TH'),(5,'F');
INSERT INTO #Tbl
VALUES ('SHIRT','M',2)
,('SHIRT','T',9)
,('SHIRT','W',0)
,('SHIRT','TH',6)
,('SHIRT','F',7);
SELECT T.Prod
,T.DayName
,T.CurrentSold
,LAG(CurrentSold, 1,0) OVER (ORDER BY DayNumber) AS PreviousSold
,LEAD(CurrentSold, 1,0) OVER (ORDER BY DayNumber) AS PreviousSold
FROM #Tbl T
INNER JOIN #Days D ON T.DayName = D.DayName;

Getting duplicates with additional information

I've inherited a database and I'm having trouble constructing a working SQL query.
Suppose this is the data:
[Products]
| Id | DisplayId | Version | Company | Description |
|---- |----------- |---------- |-----------| ----------- |
| 1 | 12345 | 0 | 16 | Random |
| 2 | 12345 | 0 | 2 | Random 2 |
| 3 | AB123 | 0 | 1 | Random 3 |
| 4 | 12345 | 1 | 16 | Random 4 |
| 5 | 12345 | 1 | 2 | Random 5 |
| 6 | AB123 | 0 | 5 | Random 6 |
| 7 | 12345 | 2 | 16 | Random 7 |
| 8 | XX45 | 0 | 5 | Random 8 |
| 9 | XX45 | 0 | 7 | Random 9 |
| 10 | XX45 | 1 | 5 | Random 10 |
| 11 | XX45 | 1 | 7 | Random 11 |
[Companies]
| Id | Code |
|---- |-----------|
| 1 | 'ABC' |
| 2 | '456' |
| 5 | 'XYZ' |
| 7 | 'XYZ' |
| 16 | '456' |
The Versioncolumn is a version number. Higher numbers indicate more recent versions.
The Company column is a foreign key referencing the Companies table on the Id column.
There's another table called ProductData with a ProductId column referencing Products.Id.
Now I need to find duplicates based on the DisplayId and the corresponding Companies.Code. The ProductData table should be joined to show a title (ProductData.Title), and only the most recent ones should be included in the results. So the expected results are:
| Id | DisplayId | Version | Company | Description | ProductData.Title |
|---- |----------- |---------- |-----------|------------- |------------------ |
| 5 | 12345 | 1 | 2 | Random 2 | Title 2 |
| 7 | 12345 | 2 | 16 | Random 7 | Title 7 |
| 10 | XX45 | 1 | 5 | Random 10 | Title 10 |
| 11 | XX45 | 1 | 7 | Random 11 | Title 11 |
because XX45 has 2 "entries": one with Company 5 and one with Company 7, but both companies share the same code.
because 12345 has 2 "entries": one with Company 2 and one with Company 16, but both companies share the same code. Note that the most recent version of both differs (version 2 for company 16's entry and version 1 for company 2's entry)
ABC123 should not be included as its 2 entries have different company codes.
I'm eager to learn your insights...
Based on your sample data, you just need to JOIN the tables:
SELECT
p.Id, p.DisplayId, p.Version, p.Company, d.Title
FROM Products AS p
INNER JOIN Companies AS c ON p.Company = c.Id
INNER JOIN ProductData AS d ON d.ProductId = p.Id;
But if you want the latest one, you can use the ROW_NUMBER():
WITH CTE
AS
(
SELECT
p.Id, p.DisplayId, p.Version, p.Company, d.Title,
ROW_NUMBER() OVER(PARTITION BY p.DisplayId,p.Company ORDER BY p.Id DESC) AS RN
FROM Products AS p
INNER JOIN Companies AS c ON p.Company = c.Id
INNER JOIN ProductData AS d ON d.ProductId = p.Id
)
SELECT *
FROM CTE
WHERE RN = 1;
sample fiddle
| Id | DisplayId | Version | Company | Title |
|----|-----------|---------|---------|----------|
| 5 | 12345 | 1 | 2 | Title 5 |
| 7 | 12345 | 2 | 16 | Title 7 |
| 10 | XX45 | 1 | 5 | Title 10 |
| 11 | XX45 | 1 | 7 | Title 11 |
If i understood you correctly, you can use CTE to find all the duplicated rows from your table, then you can just use SELECT from CTE and even add more manipulations.
WITH CTE AS(
SELECT Id,DisplayId,Version,Company,Description,ProductData.Title
RN = ROW_NUMBER()OVER(PARTITION BY DisplayId, Company ORDER BY p.Id DESC)
FROM dbo.YourTable1
)
SELECT *
FROM CTE
Try this:
SELECT b.ID,displayid,version,company,productdata.title
FROM
(select A.ID,a.displayid,version,a.company,rn,a.code, COUNT(displayid) over (partition by displayid,code) cnt from
(select Prod.ID,displayid,version,company,Companies.code, Row_number() over (partition by displayid,company order by version desc) rn
from Prod inner join Companies on Prod.Company = Companies.id) a
where a.rn=1) b inner join productdata on b.id = productdata.id where cnt =2
You have to first get the current version and then you see how many times the DisplayID + Code show-up. Then based on that you can select only the ones that have a count greater than one. You can then INNER JOIN ProductData on the final query to get the Title.
WITH
MaxVersion AS --Get the current versions
(
SELECT
MAX(Version) AS Version,
DisplayID,
Company
FROM
#TmpProducts
GROUP BY
DisplayID,
Company
)
,CTE AS
(
SELECT
p.DisplayID,
c.Code,
COUNT(*) AS RowCounter
FROM
#TmpProducts p
INNER JOIN
#TmpCompanies c
ON
c.ID = p.Company
INNER JOIN
MaxVersion mv
ON
mv.DisplayID = p.DisplayID
AND mv.Version = p.Version
AND mv.Company = p.Company
GROUP BY
p.DisplayID,
c.Code
)
SELECT
p.*
FROM
#TmpProducts p
INNER JOIN
CTE c
ON
c.DisplayID = p.DisplayID
INNER JOIN
MaxVersion mv
ON
mv.DisplayID = p.DisplayID
AND mv.Company = p.Company
AND mv.Version = p.Version
WHERE
c.RowCounter > 1

SQL Server query for next row value where previous row value

This query gives me Event values from 1 to 20 within an hour, how to add to that if a consecutive Event value is >=200 as well?
SELECT ID, count(Event) as numberoftimes
FROM table_name
WHERE Event >=1 and Event <=20
GROUP BY ID, DATEPART(HH, AtHour)
HAVING DATEPART(HH, AtHour) <= 1
ORDER BY ID desc
In this dummy 24h table:
+----+-------+--------+
| ID | Event | AtHour |
+----+-------+--------+
| 1 | 1 | 11:00 |
| 1 | 4 | 11:01 |
| 1 | 1 | 11:02 |
| 1 | 20 | 11:03 |
| 1 | 200 | 11:04 |
| 1 | 1 | 13:00 |
| 1 | 1 | 13:05 |
| 1 | 2 | 13:06 |
| 1 | 500 | 13:07 |
| 1 | 39 | 13:10 |
| 1 | 50 | 13:11 |
| 1 | 2 | 13:12 |
+----+-------+--------+
I would like to select IDs with Event with values with range between 1 and 20 followed immediately by value greater than or equal to 200 within an hour.
Expected result should be something like that:
+----+--------+
| ID | AtHour |
+----+--------+
| 1 | 11 |
| 1 | 13 |
| 2 | 11 |
| 2 | 14 |
| 3 | 09 |
| 3 | 12 |
+----+--------+
or just how many times it has happened for unique ID instead of which hour.
Please excuse me I am still rusty with post formatting!
CREATE TABLE data (Id INT, Event INT, AtHour SMALLDATETIME);
INSERT data (Id, Event, AtHour) VALUES
(1,1,'2017-03-16 11:00:00'),
(1,4,'2017-03-16 11:01:00'),
(1,1,'2017-03-16 11:02:00'),
(1,20,'2017-03-16 11:03:00'),
(1,200,'2017-03-16 11:04:00'),
(1,1,'2017-03-16 13:00:00'),
(1,1,'2017-03-16 13:05:00'),
(1,2,'2017-03-16 13:06:00'),
(1,500,'2017-03-16 13:07:00'),
(1,39,'2017-03-16 13:10:00')
;
; WITH temp as (
SELECT rownum = ROW_NUMBER() OVER (PARTITION BY id ORDER BY AtHour)
, *
FROM data
)
SELECT a.id, DATEPART(HOUR, a.AtHour) as AtHour, COUNT(*) AS NumOfPairs
FROM temp a JOIN temp b ON a.rownum = b.rownum-1
WHERE a.Event BETWEEN 1 and 20 AND b.Event >= 200
AND DATEDIFF(MINUTE, a.AtHour, b.AtHour) <= 60
GROUP BY a.id, DATEPART(HOUR, a.AtHour)
;

Find max and min of a column and update the first column sql server

Based on the product and product key, update the column ord_by. There should be only one min and max for a product and product_key .
E.g: Table
+-------------+---------+-------+--------+
| Product_key | product | price | ord_by |
+-------------+---------+-------+--------+
| 1 | ABC | 10 | |
| 1 | ABC | 10 | |
| 1 | ABC | 20 | |
| 1 | ABC | 100 | |
| 1 | ABC | 100 | |
| 2 | EFG | 20 | |
| 2 | EFG | 40 | |
| 3 | ABC | 100 | |
+-------------+---------+-------+--------+
Expected output:
+-------------+---------+-------+--------+
| Product_key | product | price | ord_by |
+-------------+---------+-------+--------+
| 1 | ABC | 10 | Min |
| 1 | ABC | 10 | Mid |
| 1 | ABC | 20 | Mid |
| 1 | ABC | 100 | Mid |
| 1 | ABC | 100 | Max |
| 2 | EFG | 20 | Min |
| 2 | EFG | 40 | Max |
| 3 | ABC | 100 | None |
+-------------+---------+-------+--------+
My try :
;WITH ord_cte
AS (
SELECT product
,product_key
,max(price) as max_price
,min(price) as min_price
FROM t_prod_ord
group by product,product_key
)
UPDATE t1
SET ord_by = case
when t2.max_price =t2.min_price then 'none'
when t2.max_price=t1.price then 'max'
when t2.min_price=t1.price then 'min'
else 'mid' end
FROM t_prod_ord t1
INNER JOIN ord_cte t2 ON t1.product_key = t2.product_key and t1.product=t2.product
using this query it is updating more than one max and min value for column ord_by.
Generate row number for each Product_key order by Price in both ASC and DESC order. Then use the row number in CASE statement to find the Min/Max values
Count() Over() aggregate window function will help you find the total count of each Product_key which we can use it for finding None
Here is one way
;WITH cte
AS (SELECT *,
Row_number()OVER(PARTITION BY Product_key ORDER BY price) AS Min_KEY,
Row_number()OVER(PARTITION BY Product_key ORDER BY price DESC) AS Max_KEY,
Count(1)OVER(partition BY Product_key) AS cnt
FROM Yourtable)
SELECT Product_key,
product,
price,
CASE
WHEN cnt = 1 THEN 'None'
WHEN Min_KEY = 1 THEN 'Min'
WHEN Max_Key = 1 THEN 'Max'
ELSE 'Mid'
END
FROM cte
Another way to do with out cte...
SELECT [Product_key],
[product],
[price],
CASE
WHEN Max(RN)
OVER(
PARTITION BY PRODUCT_KEY, PRODUCT
)=1 AND RN=1 THEN 'NONE'
WHEN Min(RN)
OVER(
PARTITION BY PRODUCT_KEY, PRODUCT
) = RN THEN 'MIN'
WHEN Max(RN)
OVER(
PARTITION BY PRODUCT_KEY, PRODUCT
) = RN THEN 'MAX'
ELSE 'MID'
END ORDER_BY
FROM (SELECT *,
Row_number()
OVER(
PARTITION BY PRODUCT_KEY, PRODUCT
ORDER BY PRICE) RN
FROM TABLE1)Z

Resources