LAG of MIN in SQL Analytic - sql-server

I have a table containing employees id, year id, client id, and the number of sales. For example:
--------------------------------------
id_emp | id_year | sales | client id
--------------------------------------
4 | 1 | 14 | 1
4 | 1 | 10 | 2
4 | 2 | 11 | 1
4 | 2 | 17 | 2
For a employee, I want to obtain rows with the minimum sales per year and the minimum sales of the previous year.
One of the queries I tried is the following:
select distinct
id_emp,
id_year,
MIN(sales) OVER(partition by id_emp, id_year) AS min_sales,
LAG(min(sales), 1) OVER(PARTITION BY id_emp, id_year
ORDER BY id_emp, id_year) AS previous
from facts
where id_emp = 4
group by id_emp, id_year, sales;
I get the result:
-------------------------------------
id_emp | id_year | sales | previous
-------------------------------------
4 | 1 | 10 | (null)
4 | 1 | 10 | 10
4 | 2 | 11 | (null)
but I expect to get:
-------------------------------------
id_emp | id_year | sales | previous
-------------------------------------
4 | 1 | 10 | (null)
4 | 2 | 11 | 10

SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE EMPLOYEE_SALES ( id_emp, id_year, sales, client_id ) AS
SELECT 4, 1, 14, 1 FROM DUAL
UNION ALL SELECT 4, 1, 10, 2 FROM DUAL
UNION ALL SELECT 4, 2, 11, 1 FROM DUAL
UNION ALL SELECT 4, 2, 17, 2 FROM DUAL;
Query 1:
SELECT ID_EMP,
ID_YEAR,
SALES AS SALES,
LAG( SALES ) OVER ( PARTITION BY ID_EMP ORDER BY ID_YEAR ) AS PREVIOUS
FROM (
SELECT e.*,
ROW_NUMBER() OVER ( PARTITION BY id_emp, id_year ORDER BY sales ) AS RN
FROM EMPLOYEE_SALES e
)
WHERE rn = 1
Query 2:
SELECT ID_EMP,
ID_YEAR,
MIN( SALES ) AS SALES,
LAG( MIN( SALES ) ) OVER ( PARTITION BY ID_EMP ORDER BY ID_YEAR ) AS PREVIOUS
FROM EMPLOYEE_SALES
GROUP BY ID_EMP, ID_YEAR
Results - Both give the same output:
| ID_EMP | ID_YEAR | SALES | PREVIOUS |
|--------|---------|-------|----------|
| 4 | 1 | 10 | (null) |
| 4 | 2 | 11 | 10 |

You mean like this?
select id_emp, id_year, min(sales) as min_sales,
lag(min(sales)) over (partition by id_emp order by id_year) as prev_year_min_sales
from facts
where id_emp = 4
group by id_emp, id_year;

I believe it is because you are using sales column in your group by statement.
Try to remove it and just use
GROUP BY id_emp,id_year

You could get your desired output using ROW_NUMBER() and LAG() analytic functions.
For example,
Table
SQL> SELECT * FROM t;
ID_EMP ID_YEAR SALES CLIENT_ID
---------- ---------- ---------- ----------
4 1 14 1
4 1 10 2
4 2 11 1
4 2 17 2
Query
SQL> WITH DATA AS
2 (SELECT t.*,
3 row_number() OVER(PARTITION BY id_emp, id_year ORDER BY sales) rn
4 FROM t
5 )
6 SELECT id_emp,
7 id_year ,
8 sales ,
9 lag(sales) over(order by sales) previous
10 FROM DATA
11 WHERE rn =1;
ID_EMP ID_YEAR SALES PREVIOUS
---------- ---------- ---------- ----------
4 1 10
4 2 11 10

Related

MSSQL select where following (sequence) rows with the same column value as current column equale to X

how do i do a select where count = select all sequence rows has the same column value as current column value only if there 3 in sequence (row after row with no holes)
NAME | NUM | DATE
---------------------------------
Name 1 | 1 | '2019-01-07 12:11:11:001'
Name 2 | 1 | '2019-01-07 12:11:12:002'
Name 3 | 3 | '2019-01-07 12:11:13:003'
Name 4 | 2 | '2019-01-07 12:11:14:004'
Name 5 | 2 | '2019-01-07 12:11:15:005'
Name 6 | 2 | '2019-01-07 12:11:16:006'
Name 7 | 4 | '2019-01-07 12:11:17:007'
Name 8 | 5 | '2019-01-07 12:11:18:008'
The results should be where count sequence=3
NAME | NUM | DATE
---------------------------------
Name 4 | 2 | '2019-01-07 12:11:14:004'
Name 5 | 2 | '2019-01-07 12:11:15:005'
Name 6 | 2 | '2019-01-07 12:11:16:006'
because 2 appears 3 times in sequence
You can use the following query:
SELECT [NAME], [NUM], [DATE],
ROW_NUMBER() OVER (ORDER BY [DATE]) -
ROW_NUMBER() OVER (PARTITION BY NUM ORDER BY [DATE]) AS grp
FROM mytable
to get:
NAME NUM DATE grp
----------------------------------------
Name 1 1 2019-01-07 12:11:11 0
Name 2 1 2019-01-07 12:11:12 0
Name 4 2 2019-01-07 12:11:13 3
Name 5 2 2019-01-07 12:11:14 3
Name 6 2 2019-01-07 12:11:15 3
Name 3 3 2019-01-07 12:11:16 2
Name 7 4 2019-01-07 12:11:17 6
Name 8 5 2019-01-07 12:11:18 7
As you can see calculated column grp can be used in order to identify islands of consecutive records having the same NUM value.
You can then wrap the above query in a CTE and do:
;WITH GroupCTE AS (
SELECT [NAME], [NUM], [DATE],
ROW_NUMBER() OVER (ORDER BY [DATE]) -
ROW_NUMBER() OVER (PARTITION BY NUM ORDER BY [DATE]) AS grp
FROM mytable
)
SELECT t.*
FROM GroupCTE AS t
JOIN (SELECT NUM, grp
FROM GroupCTE
GROUP BY NUM, grp
HAVING COUNT(*) = 3) AS g ON t.NUM = g.NUM AND t.grp = g.grp

Limit the rows if same id repeats

I have a table like below
ID | s_id | mark
-----------------------
1 | 2 | 10
2 | 5 | 9
3 | 7 | 8
4 | 2 | 8
5 | 2 | 10
6 | 5 | 7
7 | 3 | 7
8 | 2 | 9
9 | 5 | 8
I need to get SQL query for output like:-
mark column need to be in descending order.
Same s_id should not repeat more than 2 times
if same s_id repeats more than 2 times, ignore the 3rd result
ID | s_id | mark
-----------------------
1 | 2 | 10
2 | 2 | 9
3 | 3 | 7
4 | 5 | 9
5 | 5 | 8
6 | 7 | 8
Assuming you're using SQL Server, you can just use ROW_NUMBER() to assign a row number to each s_id group based on a descending order of the mark column. Then, retain only those records where this row number is 1 or 2.
SELECT
t.ID, t.s_id, t.mark
FROM
(
SELECT ID, s_id, mark, ROW_NUMBER() OVER (PARTITION BY s_id ORDER BY mark DESC) rn
FROM yourTable
) t
WHERE t.rn <= 2
ORDER BY t.s_id;
Note: You'll notice that the record (s_id, mark) = (2, 10) appears twice in my result set. Based on your input data, this is what is generated. If you really intended to also remove duplicate (s_id, mark) pairs, then let us know and a small correction can be added to the query.
Output:
Demo here:
Rextester
try this code.
;WITH cte
AS (
SELECT ROW_NUMBER() OVER (PARTITION BY s_id
ORDER BY ( SELECT 0)) RN,ID,s_id,mark
FROM aaa)
select RN,ID,s_id,mark FROM cte
WHERE RN <= 2
order by s_id,mark desc;

Change the Value on Duplicate Rows

I need assistance on how to code duplicate Line IDs for the same Purchase Order and assign the additional line IDs with a new number. I would like to use Line ID + 100 for the additional duplicate rows. For example if Purchase Order #11 has three Line ID #5s then the first would stay as 5 and the second would be 501 and the third would be 502, however, I can only get a 1, 2 or 3 or if no duplicate just 1. I am not sure what to use to increment. I am hoping some one can assist or guide. Thank you
PurchaseOrderID LineID PackingList NewLineID
11 1 12323 1
11 1 78786 2
11 2 67523 1
11 3 44559 1
11 4 44559 1
11 5 96545 1
11 5 12323 2
11 5 34569 3
The Packing Slip causes the duplicates for the same line ID.
Below is what I am trying to use which is giving me the above NewLineID:
SELECT
PurchaseOrderID,
LineID,
PackingList,
ROW_NUMBER() over
(
partition by PurchaseOrderID, LineID
order by PurchaseOrderID, LineID
) as NewLineID
FROM PurchaseOrderTransactions
Using ROW_NUMBER and CASE:
WITH Cte AS(
SELECT
PurchaseOrderID,
LineID,
PackingList,
RN = ROW_NUMBER() OVER (PARTITION BY PurchaseOrderID, LineID ORDER BY LineID)
FROM PurchaseOrderTransactions
)
SELECT
PurchaseOrderID,
LineID,
PackingList,
NewLineID = CASE
WHEN RN = 1 THEN LineID
ELSE (LineID * 100) + (RN - 1)
END
FROM Cte
Without using a CTE:
SELECT
PurchaseOrderID,
LineID,
PackingList,
NewLineID =
CASE
WHEN ROW_NUMBER() OVER (PARTITION BY PurchaseOrderID, LineID ORDER BY LineID) = 1 THEN LineID
ELSE (LineID * 100) + (ROW_NUMBER() OVER (PARTITION BY PurchaseOrderID, LineID ORDER BY LineID) - 1)
END
FROM PurchaseOrderTransactions
SQL Fiddle
| PurchaseOrderID | LineID | PackingList | NewLineID |
|-----------------|--------|-------------|-----------|
| 11 | 1 | 12323 | 1 |
| 11 | 1 | 78786 | 101 |
| 11 | 2 | 67523 | 2 |
| 11 | 3 | 44559 | 3 |
| 11 | 4 | 44559 | 4 |
| 11 | 5 | 96545 | 5 |
| 11 | 5 | 12323 | 501 |
| 11 | 5 | 34569 | 502 |

Running "Group By" Ordinal Counter Based on a "Flip" Column

Usually I'm decent at set-based tsql problems. But this one is beating me.
I've been working 3 days on converting a while-loop procedure into a setbased one. I've gotten to the point below.......but can't make the final jump.
I have the following rows. MyOrdinal will be "in order" ... and a second column (MyMarker) will alternate between having a value and being null. Whenever this "flip" occurs on MyMarker, I would like to increment a "group by" ordinal counter by one. Whenever the "flip" values are non-null or null, these are grouped together as a set.
I've tried several things, but it was too ugly to post. That and since moving to ORM, I don't spend as much time in the tsql anymore.
declare #Holder table ( MyOrdinal int not null , MyMarker int , MyGroupNumber int )
INSERT INTO #Holder (MyOrdinal, MyMarker)
Select 1 , 1
union all Select 2, 2
union all Select 3, null
union all Select 4, 3
union all Select 5, 4
union all Select 6, 5
union all Select 7, 6
union all Select 8, 7
union all Select 9, 8
union all Select 10, 9
union all Select 11, 10
union all Select 12, 11
union all Select 13, 12
union all Select 14, 13
union all Select 15, 14
union all Select 16, 15
union all Select 17, null
union all Select 18, null
union all Select 19, null
union all Select 20, 16
union all Select 21, 17
union all Select 22, 18
union all Select 23, null
union all Select 24, null
union all Select 25, 19
union all Select 26, 20
union all Select 27, null
union all Select 28, 21
Select * from #Holder
Desired Output
| MyOrdinal | MyMarker | MyGroupNumber |
|-----------|----------|---------------|
| 1 | 1 | 1 |
| 2 | 2 | 1 |
| 3 | null | 2 |
| 4 | 3 | 3 |
| 5 | 4 | 3 |
| 6 | 5 | 3 |
| 7 | 6 | 3 |
| 8 | 7 | 3 |
| 9 | 8 | 3 |
| 10 | 9 | 3 |
| 11 | 10 | 3 |
| 12 | 11 | 3 |
| 13 | 12 | 3 |
| 14 | 13 | 3 |
| 15 | 14 | 3 |
| 16 | 15 | 3 |
| 17 | null | 4 |
| 18 | null | 4 |
| 19 | null | 4 |
| 20 | 16 | 5 |
| 21 | 17 | 5 |
| 22 | 18 | 5 |
| 23 | null | 6 |
| 24 | null | 6 |
| 25 | 19 | 7 |
| 26 | 20 | 7 |
| 27 | null | 8 |
| 28 | 21 | 9 |
Try this one:
First, this assigns a same ROW_NUMBER for continuous Non-NULL MyMarker. ROW_NUMBER is NULL for NULL MyMarkers. After that, you want to add a ROW_NUMBER for NULL MyMarkers such that the value is between the previous NON-NULL and the next NON-NULL. Then use DENSE_RANK to finally assign MyGroupNumber:
SQL Fiddle
;WITH Cte AS(
SELECT *,
RN = ROW_NUMBER() OVER(ORDER BY MyOrdinal) - MyMarker + 1
FROM #Holder
),
CteApply AS(
SELECT
t.MyOrdinal,
t.MyMarker,
MyGroupNumber =
CASE
WHEN RN IS NULL THEN x.NewRN
ELSE RN
END
FROM Cte t
OUTER APPLY(
SELECT TOP 1 RN * 1.1 AS NewRN
FROM Cte
WHERE
t.MyOrdinal > MyOrdinal
AND MyMarker IS NOT NULL
ORDER BY MyOrdinal DESC
)x
)
SELECT
MyOrdinal,
MyMarker,
MyGroupNumber = DENSE_RANK() OVER(ORDER BY MyGroupNumber)
FROM CteApply
For Sql Server 2012:
select *, sum(b) over(order by myordinal)
from(select *,
case when (lag(mymarker) over(order by myordinal) is not null
and mymarker is null) or
(lag(mymarker) over(order by myordinal) is null
and mymarker is not null)
then 1 else 0 end as b
from #Holder) t
First you mark rows with 1 where there is a change from null to not null or from not null to null. Other columns are marked as 0. Then running sum of all rows till current.
Fiddle http://sqlfiddle.com/#!6/9eecb/5015
For Sql Server 2008:
with cte1 as (select *,
case when (select max(enddate) from t ti
where ti.ruleid = t.ruleid and ti.startdate < t.startdate) = startdate
then 0 else 1 end as b
from t),
cte2 as(select *, sum(b) over(partition by ruleid order by startdate) as s
from cte1)
select RuleID,
Name,
min(startdate),
case when count(*) = count(enddate)
then max(enddate) else null end from cte2
group by s, ruleid, name
Fiddle http://sqlfiddle.com/#!6/4191d/6

The highest value from list-distinct

Can anyone help me with query, I have table
vendorid, agreementid, sales
12001 1004 700
5291 1004 20576
7596 1004 1908
45 103 345
41 103 9087
what is the goal ?
when agreemtneid >1 then show me data when sales is the highest
vendorid agreementid sales
5291 1004 20576
41 103 9087
Any ideas ?
Thx
Well you could try using a CTE and ROW_NUMBER something like
;WITH Vals AS (
SELECT *, ROW_NUMBER() OVER(PARTITION BY AgreementID ORDER BY Sales DESC) RowID
FROM MyTable
WHERE AgreementID > 1
)
SELECT *
FROM Vals
WHERE RowID = 1
This will avoid you returning multiple records with the same sale.
If that was OK you could try something like
SELECT *
FROM MyTable mt INNER JOIN
(
SELECT AgreementID, MAX(Sales) MaxSales
FROM MyTable
WHERE AgreementID > 1
) MaxVals ON mt.AgreementID = MaxVals.AgreementID AND mt.Sales = MaxVals.MaxSales
SELECT TOP 1 WITH TIES *
FROM MyTable
ORDER BY DENSE_RANK() OVER(PARTITION BY agreementid ORDER BY SIGN (SIGN (agreementid - 2) + 1) * sales DESC)
Explanation
We break table MyTable into partitions by agreementid.
For each partition we construct a ranking or its rows.
If agreementid is greater than 1 ranking will be equal to ORDER BY sales DESC.
Otherwise ranking for every single row in partition will be the same: ORDER BY 0 DESC.
See how it looks like:
SELECT *
, SIGN (SIGN (agreementid - 2) + 1) * sales AS x
, DENSE_RANK() OVER(PARTITION BY agreementid ORDER BY SIGN (SIGN (agreementid - 2) + 1) * sales DESC) AS rnk
FROM MyTable
+----------+-------------+-------+-------+-----+
| vendorid | agreementid | sales | x | rnk |
+----------|-------------|-------+-------+-----+
| 0 | 0 | 3 | 0 | 1 |
| -1 | 0 | 7 | 0 | 1 |
| 0 | 1 | 3 | 0 | 1 |
| -1 | 1 | 7 | 0 | 1 |
| 41 | 103 | 9087 | 9087 | 1 |
| 45 | 103 | 345 | 345 | 2 |
| 5291 | 1004 | 20576 | 20576 | 1 |
| 7596 | 1004 | 1908 | 1908 | 2 |
| 12001 | 1004 | 700 | 700 | 3 |
+----------+-------------+-------+-------+-----+
Then using TOP 1 WITH TIES construction we leave only rows where rnk equals 1.
you can try like this.
SELECT TOP 1 sales FROM MyTable WHERE agreemtneid > 1 ORDER BY sales DESC
I really do not know the business logic behind agreement_id > 1. It looks to me you want the max sales (with ties) by agreement id regardless of vendor_id.
First, lets create a simple sample database.
-- Sample table
create table #sales
(
vendor_id int,
agreement_id int,
sales_amt money
);
-- Sample data
insert into #sales values
(12001, 1004, 700),
(5291, 1004, 20576),
(7596, 1004, 1908),
(45, 103, 345),
(41, 103, 9087);
Second, let's solve this problem using a common table expression to get a result set that has each row paired with the max sales by agreement id.
The select statement just applies the business logic to filter the data to get your answer.
-- CTE = max sales for each agreement id
;
with cte_sales as
(
select
vendor_id,
agreement_id,
sales_amt,
max(sales_amt) OVER(PARTITION BY agreement_id) AS max_sales
from
#sales
)
-- Filter by your business logic
select * from cte_sales where sales_amt = max_sales and agreement_id > 1;
The screen shot below shows the exact result you wanted.

Resources