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

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;

Related

SQL Server - identify combinations of values and assign combination identifier

I am trying to assign what amounts to a 'combinationid' to rows of my table, based on the values in the two columns below. Each product has a number of customers linked to it. For every combination of customers, I need to create a combination ID.
For example, the combination of customers for product 'a' is the same combination of customers for product 'c' (they both have customers 1, 2 and 3), so products a and c should have the same combination identifier ('customergroup'). However, products should not share the same customergroup if they only share some of the same customers - e.g. product b only has customers 1 and 2 (not 3), so should have a different customergroup to products 'a' and 'c'.
Input:
| productid | customerid |
|-----------|------------|
| a | 1 |
| a | 2 |
| a | 3 |
| b | 1 |
| b | 2 |
| c | 3 |
| c | 2 |
| c | 1 |
| d | 1 |
| d | 3 |
| e | 1 |
| e | 2 |
| f | 1 |
| g | 2 |
| h | 3 |
Desired output:
| productid | customerid | customergroup |
|-----------|------------|---------------|
| a | 1 | 1 |
| a | 2 | 1 |
| a | 3 | 1 |
| b | 1 | 2 |
| b | 2 | 2 |
| c | 3 | 1 |
| c | 2 | 1 |
| c | 1 | 1 |
| d | 1 | 3 |
| d | 3 | 3 |
| e | 1 | 2 |
| e | 2 | 2 |
| f | 1 | 4 |
| g | 2 | 5 |
| h | 3 | 6 |
or just
| productid | customergroupid |
|-----------|-----------------|
| a | 1 |
| b | 2 |
| c | 1 |
| d | 3 |
| e | 2 |
| f | 4 |
| g | 5 |
| h | 6 |
Edit: first version of this did include a description of my attempts. I currently have nested queries that basically give me a column for customer 1, 2, 3 etc and then uses dense rank to get the grouping. The problem is that is not dynamic for different numbers of customers and I did not know where to start for getting a dynamic result as above. Thanks for the replies.
Considering you haven't shown your efforts, or confirmed the version you're using, I've assumed you have the latest ("and greatest") version of SQL Server, which means you have access to STRING_AGG.
This doesn't give the groupings in the same order, but I'm going to also also that doesn't matter, and the grouping is just arbitrary. This gives you the following:
WITH VTE AS(
SELECT *
FROM (VALUES('a',1),
('a',2),
('a',3),
('b',1),
('b',2),
('c',3),
('c',2),
('c',1),
('d',1),
('d',3),
('e',1),
('e',2),
('f',1),
('g',2),
('h',3)) V(productid,customerid)),
Groups AS(
SELECT productid,
STRING_AGG(customerid,',') WITHIN GROUP (ORDER BY customerid) AS CustomerIDs
FROM VTE
GROUP BY productid),
Rankings AS(
SELECT productid,
CustomerIDs,
DENSE_RANK() OVER (ORDER BY CustomerIDs ASC) AS Grouping
FROM Groups)
SELECT V.productid,
V.customerid,
R.Grouping AS customergroupid
FROM VTE V
JOIN Rankings R ON V.productid = R.productid
ORDER BY V.productid,
V.customerid;
db<>fiddle.
If you aren't using SQL Server 2017, I suggest looking up the FOR XML PATH method for string aggregation.
Using Larnu's answer this is how I got the result for 2008:
WITH VTE AS(
SELECT *
FROM (VALUES('a','1'),
('a','2'),
('a','3'),
('b','1'),
('b','2'),
('c','3'),
('c','2'),
('c','1'),
('d','1'),
('d','3'),
('e','1'),
('e','2'),
('f','1'),
('g','2'),
('h','3')) V(productid,customerid)),
Groups AS(
SELECT productid, CustomerIDs = STUFF((SELECT N', ' + customerid
FROM VTE AS p2
WHERE p2.productid = p.productid
ORDER BY customerid
FOR XML PATH(N'')), 1, 2, N'')
FROM VTE AS p
GROUP BY productid),
Rankings AS(
SELECT productid,
CustomerIDs,
DENSE_RANK() OVER (ORDER BY CustomerIDs ASC) AS Grouping
FROM Groups)
SELECT V.productid,
V.customerid,
R.Grouping AS customergroupid
FROM VTE V
JOIN Rankings R ON V.productid = R.productid
ORDER BY V.productid,
V.customerid;
Thanks again for your assistance.

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

+------------+-------+---------+---------------+
| 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

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

Selecting grouped rows after first two rows SQL Server

This is a bit of a tricky question/situation and my search fu failed me.
Lets say i have the following data
| UID | SharedID | Type | Date |
|-----|----------|------|-----------|
| 1 | 1 | foo | 2/4/2016 |
| 2 | 1 | foo | 2/5/2016 |
| 3 | 1 | foo | 2/8/2016 |
| 4 | 1 | foo | 2/11/2016 |
| 5 | 2 | bar | 1/11/2016 |
| 6 | 2 | bar | 2/11/2016 |
| 7 | 3 | baz | 2/1/2016 |
| 8 | 3 | baz | 2/3/2016 |
| 9 | 3 | baz | 2/11/2016 |
And I would like to ommit a variable number of leading rows (most recent date in this case) and lets say that number is 2 in this example. The resulting table would be something like this:
| UID | SharedID | Type | Date |
|-----|----------|------|-----------|
| 1 | 1 | foo | 2/4/2016 |
| 2 | 1 | foo | 2/5/2016 |
| 7 | 3 | baz | 2/1/2016 |
Is this possible in SQL? Essentially I want to filter on an unknown number of rows which uses the date column as the order by. The goal is to get the oldest types and get a list of UID's in the process.
Sure, it's possible. Use a ROW_NUMBER function to assign a value to each row, partitioning by the SharedID column so that the count restarts every time that ID changes, and select those rows with a value greater than your limit.
WITH cteNumberedRows AS (
SELECT UID, SharedID, Type, Date,
ROW_NUMBER() OVER(PARTITION BY SharedID ORDER BY Date DESC) AS RowNum
FROM YourTable
)
SELECT UID, SharedID, Type, Date
FROM cteNumberedRows
WHERE RowNum > 2;
Not sure if I understand what you mean but something like this?
SELECT * FROM MyTable t1 JOIN MyTable T2 ON t2.id NOT IN (
SELECT TOP 2 UID FROM myTable
WHERE SharedID = t1.sharedID
ORDER BY [Date] DESC
)

Twice Inner Join on same table with Aggregate function

being a novice sql user:
I have a simple table storing some records over night daily. table:
Table: T1
+----+-----+----+-----------+------------+
| Id | A | AB | Value | Date |
+----+-----+----+-----------+------------+
| 1 | abc | I | -48936.08 | 2013-06-24 |
| 2 | def | A | 431266.19 | 2013-06-24 |
| 3 | xyz | I | -13523.90 | 2013-06-24 |
| 4 | abc | A | 13523.90 | 2013-06-23 |
| 5 | xyz | I | -13523.90 | 2013-06-23 |
| 6 | def | A | 13523.90 | 2013-06-22 |
| 7 | def | I | -13523.90 | 2013-06-22 |
+----+-----+----+-----------+------------+
I would like to get all values of columns A,AB, Value for the latest Date on Column A filtered on AB = I
basically the result should look like:
+----+-----+----+-----------+------------+
| Id | A | AB | Value | Date |
+----+-----+----+-----------+------------+
| 1 | abc | I | -48936.08 | 2013-06-24 |
| 3 | xyz | I | -13523.90 | 2013-06-24 |
| 7 | def | I | -13523.90 | 2013-06-22 |
+----+-----+----+-----------+------------+
I have tried to use inner join twice on the same table but failed to come up with correct result.
any help would be appreciated.
thanks :)
This will work with sqlserver 2005+
;WITH a as
(
SELECT id, A,AB, Value, Date
, row_number() over (partition by A order by Date desc) rn
FROM t1
WHERE AB = 'I'
)
SELECT id, A,AB, Value, Date
FROM a WHERE rn = 1
; WITH x AS (
SELECT id
, a
, ab
, "value"
, "date"
, Row_Number() OVER (PARTITION BY a ORDER BY "date" DESC) As row_num
FROM your_table
WHERE ab = 'I'
)
SELECT *
FROM x
WHERE row_num = 1

Resources