SQL Server: Swap two lines depending on criteria - sql

Suppose a table named Sales with this data in SQL Server
--------------------------------------------
Id | Customer_Id | Rate | Pid
--------------------------------------------
180 | 374 | 1 | A01
277 | 374 | 0 | NULL
346 | 785 | 1 | D03
476 | 785 | 0 | NULL
1821 | 1234 | 0 | E07
25951 | 1951 | 1 | K73
How update my table to swap Rate and Pid values between lines having same customer_Id, so I can have a result like this:
--------------------------------------------
Id | Customer_Id | Rate | Pid
--------------------------------------------
180 | 374 | 0 | NULL
277 | 374 | 1 | A01
346 | 785 | 0 | NULL
476 | 785 | 1 | D03
1821 | 1234 | 0 | E07
25951 | 1951 | 1 | K73
How can I achieve this?

If you always have at most two records per customer then you can use the following query:
SELECT ID, Customer_Id,
CASE
-- 2 records per Customer_id -> swap
WHEN COUNT(*) OVER (PARTITION BY Customer_id) = 2 THEN
CASE
WHEN ROW_NUMBER() OVER (PARTITION BY Customer_id ORDER BY ID) = 1
THEN LEAD(Rate) OVER (PARTITION BY Customer_id ORDER BY ID)
ELSE LAG(Rate) OVER (PARTITION BY Customer_id ORDER BY ID)
END
-- 1 record per Customer_id -> don't swap
ELSE Rate
END,
CASE
WHEN COUNT(*) OVER (PARTITION BY Customer_id) = 2 THEN
CASE
WHEN ROW_NUMBER() OVER (PARTITION BY Customer_id ORDER BY ID) = 1
THEN LEAD(Pid) OVER (PARTITION BY Customer_id ORDER BY ID)
ELSE LAG(Pid) OVER (PARTITION BY Customer_id ORDER BY ID)
END
ELSE Pid
END
FROM Sales
Demo here
Edit:
If you want to UPDATE then you can wrap the above query in a CTE and do the update on the CTE:
;WITH ToUpdate AS (
SELECT ID, Customer_Id, Rate, Pid,
COUNT(*) OVER (PARTITION BY Customer_id) AS cnt,
CASE
WHEN ROW_NUMBER() OVER (PARTITION BY Customer_id ORDER BY ID) = 1
THEN LEAD(Rate) OVER (PARTITION BY Customer_id ORDER BY ID)
ELSE LAG(Rate) OVER (PARTITION BY Customer_id ORDER BY ID)
END AS NewRate,
CASE
WHEN ROW_NUMBER() OVER (PARTITION BY Customer_id ORDER BY ID) = 1
THEN LEAD(Pid) OVER (PARTITION BY Customer_id ORDER BY ID)
ELSE LAG(Pid) OVER (PARTITION BY Customer_id ORDER BY ID)
END AS NewPid
FROM Sales)
UPDATE ToUpdate
SET Rate = NewRate, Pid = NewPid
WHERE cnt = 2
Demo here

This will do it, with the caveat that you have at most only two records with the same Customer_Id...
update Sales
set Rate =
(
select Rate from Sales sls
where sls.Customer_Id = Sales.Customer_Id
and sls.Rate <> Sales.Rate
)

Related

SQL - get rid of the nested aggregate select

There is a table Payment, which for example tracks the amount of money user puts into account, simplified as
===================================
Id | UserId | Amount | PayDate |
===================================
1 | 42 | 11 | 01.02.99 |
2 | 42 | 31 | 05.06.99 |
3 | 42 | 21 | 04.11.99 |
4 | 24 | 12 | 05.11.99 |
What is need is to receive a table with balance before payment moment, eg:
===============================================
Id | UserId | Amount | PayDate | Balance |
===============================================
1 | 42 | 11 | 01.02.99 | 0 |
2 | 42 | 31 | 05.06.99 | 11 |
3 | 42 | 21 | 04.11.99 | 42 |
4 | 24 | 12 | 05.11.99 | 0 |
Currently the select statement looks something like
SELECT
Id,
UserId,
Amount,
PaidDate,
(SELECT sum(amount) FROM Payments nestedp
WHERE nestedp.UserId = outerp.UserId AND
nestedp.PayDate < outerp.PayDate) as Balance
FROM
Payments outerp
How can I rewrite this select to get rid of the nested aggregate selection? The database in question is SQL Server 2019.
You need to use cte with some custom logic to handle this type of problem.
WITH PaymentCte
AS (
SELECT ROW_NUMBER() OVER (
PARTITION BY UserId ORDER BY Id
) AS RowId
,Id
,UserId
,PayDate
,Amount
,SUM(Amount) OVER (
PARTITION BY UserId ORDER BY Id
) AS Balance
FROM Payment
)
SELECT X.Id
,X.UserId
,X.Amount
,X.PayDate
,Y.Balance
FROM PaymentCte x
INNER JOIN PaymentCte y ON x.userId = y.UserId
AND X.RowId = Y.RowId + 1
UNION
SELECT X.Id
,X.UserId
,X.Amount
,X.PayDate
,0 AS Balance
FROM PaymentCte x
WHERE X.RowId = 1
This provides the desired output
You can try the following using lag with a cumulative sum
with b as (
select * , isnull(lag(amount) over (partition by userid order by id),0) Amt
from t
)
select Id, UserId, Amount, PayDate,
Sum(Amt) over (partition by userid order by id) Balance
from b
order by Id
Thanks to other participants' leads I came up with a query that (seems) to work:
SELECT
Id,
UserId,
Amount,
PayDate,
COALESCE(sum(Amount) over (partition by UserId
order by PayDate
rows between unbounded preceding and 1 preceding), 0) as Balance
FROM
Payments
ORDER BY
UserId, PayDate
Lots of related examples can be found here

Getting Top 40% users basis sales

I have a table which has columns date, user_id, sales_amount. The table sample is as below
+------------+---------+--------------+
| date | user_id | sales_amount |
+------------+---------+--------------+
| 2020-01-01 | 1 | 27 |
| 2020-01-01 | 2 | 32 |
| 2020-01-01 | 3 | 17 |
| 2020-01-03 | 1 | 19 |
| 2020-01-03 | 2 | 18 |
| 2020-01-03 | 3 | 40 |
| ………….. | ………….. | ………….. |
| ………….. | ………….. | ………….. |
| ………….. | ………….. | ………….. |
+------------+---------+--------------+
I want to get top 40% users basis sales. I would have used something like SELECT TOP 40 PERCENT users after aggregation. But I am not using MS-SQL server, so that method is not applicable.
Something that I know is as below
First get number of rows from below query
SELECT MAX(Rn) AS number_of_rows
FROM(
SELECT *,row_number() OVER(ORDER BY Amt DESC) as Rn
FROM
(SELECT user_id, SUM(AMOUNT) AS Amt
FROM table
GROUP BY user_id) A ) B
Second calculate the 40 % of the above value and get the users
SELECT *
FROM
(SELECT *,row_number() OVER(ORDER BY Amt DESC) as Rn
FROM
(SELECT user_id, SUM(AMOUNT) AS Amt
FROM table
GROUP BY user_id) A ) B
WHERE Rn <= 0.4* (number_of_rows)
Above two steps can be combined as below
SELECT *
FROM
(SELECT *,row_number() OVER(ORDER BY Amt DESC) as Rn
FROM
(SELECT user_id, SUM(AMOUNT) AS Amt
FROM table
GROUP BY user_id) A ) B
WHERE Rn <= 0.4 * (SELECT MAX(Rn) AS number_of_rows
FROM(
SELECT *,row_number() OVER(ORDER BY Amt DESC) as Rn
FROM
(SELECT user_id, SUM(AMOUNT) AS Amt
FROM table
GROUP BY user_id) A ) B)
Is there any optimum way/builtin function to obtain this in hive ?
Yes! You can do both in one step:
SELECT u.*
FROM (SELECT user_id, SUM(AMOUNT) as amt,
ROW_NUMBER() OVER (ORDER BY SUM(AMOUNT) DESC) as seqnum,
COUNT(*) OVER () as cnt
FROM t
GROUP BY user_id
) u
WHERE seqnum <= cnt * 0.4;

How I can convert Rows to Columns in SQL?

I have a table like this:
Phones
------------------------------------------------------
| CustomerID | PhoneID | PhoneNum |
-----------------------------------------------------
| 1 | 101 | 09811111 |
| 1 | 102 | 09822222 |
| 1 | 103 | 09833333 |
| 2 | 104 | 09844444 |
| 2 | 105 | 09855555 |
-------------------------------------------------
I want query that give me bellow result:
--------------------------------------------------------------------------
| CustomerID | PhoneNum1 | PhoneNum2 | PhoneNum3 |
--------------------------------------------------------------------------
| 1 | 09811111 | 09822222 | 09833333 |
| 2 | 09844444 | 09855555 | NULL |
---------------------------------------------------------------------------
How can I build the result?
We can handle this requirement with the help of ROW_NUMBER and a pivot query:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY CustomerID ORDER BY PhoneID) rn
FROM Phones
)
SELECT
CustomerID,
MAX(CASE WHEN rn = 1 THEN PhoneNum END) AS PhoneNum1,
MAX(CASE WHEN rn = 2 THEN PhoneNum END) AS PhoneNum2,
MAX(CASE WHEN rn = 3 THEN PhoneNum END) AS PhoneNum3
FROM cte
GROUP BY
CustomerID
ORDER BY
CustomerID;
Demo
The query above was very useful. But when I use the Where, the result is not right
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY CustomerID ORDER BY PhoneID) rn
FROM Phones
)
SELECT
CustomerID,
MAX(CASE WHEN rn = 1 THEN PhoneNum END) AS PhoneNum1,
MAX(CASE WHEN rn = 2 THEN PhoneNum END) AS PhoneNum2,
MAX(CASE WHEN rn = 3 THEN PhoneNum END) AS PhoneNum3
FROM cte
where PhoneNum ='09811111'
GROUP BY
CustomerID
ORDER BY
CustomerID;
Result:
--------------------------------------------------------------------------
| CustomerID | PhoneNum1 | PhoneNum2 | PhoneNum3 |
--------------------------------------------------------------------------
| 1 | 09811111 | NULL | NULL |
---------------------------------------------------------------------------
I find a way for my asked
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY CustomerID ORDER BY PhoneID) rn
FROM Phones
)
SELECT
CustomerID,
MAX(CASE WHEN rn = 1 THEN PhoneNum END) AS PhoneNum1,
MAX(CASE WHEN rn = 2 THEN PhoneNum END) AS PhoneNum2,
MAX(CASE WHEN rn = 3 THEN PhoneNum END) AS PhoneNum3
FROM cte
where CustomerID = ( select CustomerID from cte where PhoneNum ='09811111' )
GROUP BY
CustomerID
ORDER BY
CustomerID;

Select Latest 3 records

Using SQL Server 2014. I have data that lists a Unique Identifier, a Sale Data and a Sale Price. I would like to extract into a VIEW the last 3 sales for each unique Id.
Example of data:
+------+-----------+------------+-------------+
| ID | UNIQUE_ID | SaleDate | SalePrice |
+------+-----------+------------+-------------+
| 8210 | 1-5 | 2015-09-29 | 0 |
| 8211 | 1-6 | 2016-11-01 | 485672 |
| 8212 | 1-7 | 1994-06-24 | 120000 |
| 8213 | 1-1 | 1996-09-06 | 170000 |
| 8214 | 1-1 | 2000-01-28 | 265000 |
| 8215 | 1-1 | 2013-10-02 | 305000 |
| 8216 | 1-1 | 2015-11-20 | 1425000 |
| 8217 | 1-3 | 1994-01-12 | 1 |
| 8218 | 1-3 | 2001-04-30 | 1 |
| 8219 | 1-3 | 2004-09-30 | 0 |
+------+-----------+------------+-------------+
The result in the view would list each Unique ID and then 6 fields:
SaleDate1
SalePrice1
SaleDate2
SalePrice2
SaleDate3
SalePrice3
Any hints appreciated.
You can use row_number() :
SELECT t.*
FROM (SELECT t.*,
ROW_NUMBER() OVER (PARTITION BY UNIQUE_ID ORDER BY SaleDate DESC, SalePrice DESC) AS Seq
FROM table t
) t
WHERE Seq <= 3;
You can use a window function to filter data and then conditional aggregation to get the 6 columns you need:
declare #tmp table(ID int, UNIQUE_ID varchar(50), SaleDate date, SalePrice int)
insert into #tmp values
(8210, '1-5','2015-09-29', 0 )
,(8211, '1-6','2016-11-01', 485672 )
,(8212, '1-7','1994-06-24', 120000 )
,(8213, '1-1','1996-09-06', 170000 )
,(8214, '1-1','2000-01-28', 265000 )
,(8215, '1-1','2013-10-02', 305000 )
,(8216, '1-1','2015-11-20', 1425000)
,(8217, '1-3','1994-01-12', 1 )
,(8218, '1-3','2001-04-30', 1 )
,(8219, '1-3','2004-09-30', 0 )
SELECT t.UNIQUE_ID
,max(case when t.Seq = 1 then SaleDate else null end) as SaleDate1
,sum(case when t.Seq = 1 then SalePrice else null end) as SalePrice1
,max(case when t.Seq = 2 then SaleDate else null end) as SaleDate2
,sum(case when t.Seq = 2 then SalePrice else null end) as SalePrice2
,max(case when t.Seq = 3 then SaleDate else null end) as SaleDate3
,sum(case when t.Seq = 3 then SalePrice else null end) as SalePrice3
FROM (SELECT x.*,
ROW_NUMBER() OVER (PARTITION BY UNIQUE_ID
ORDER BY SaleDate DESC, SalePrice DESC) AS Seq
FROM #tmp x
) t
WHERE t.Seq < 4
group by t.UNIQUE_ID
Results:
The following query return the 3 most recent sold rows of each item
select * from
(
select UNIQUE_ID,SaleDate,SalePrice,rank() over (partition by UNIQUE_ID order by SaleDate desc) as rnk
from salestable
) where rnk<4

Select ONLY row with max(id) in SQL SERVER

I have a table A :
ID | ProductCatId | ProductCode | Price
1 | 1 | PROD0001 | 2
2 | 2 | PROD0005 | 2
3 | 2 | PROD0005 | 2
4 | 3 | PROD0008 | 2
5 | 5 | PROD0009 | 2
6 | 7 | PROD0012 | 2
I want to select ID,ProductCatId,ProductCode,Price with condition :
"if ProductCatId exists same value ,so get ProductCatId with max(ID)", like :
ID | ProductCatId | ProductCode | Price
1 | 1 | PROD0001 | 2
3 | 2 | PROD0005 | 2
4 | 3 | PROD0008 | 2
5 | 5 | PROD0009 | 2
6 | 7 | PROD0012 | 2
Go for window function and row_number()
select ID , ProductCatId , ProductCode , Price
from (
select ID , ProductCatId , ProductCode , Price, row_number() over (partition by ProductCatId order by ID desc) as rn
from myTable
) as t
where t.rn = 1
select
top 1 with ties
ID,ProductCatId,ProductCode,Price
from
table
order by
row_number() over (partition by productcatid order by id desc)
may use row_number():
select t.*
from (select t.*,
row_number() over (partition by ProductCatId order by ID desc) as seqnum
from #Table t
) t
where seqnum = 1
order by ID;
You can try this,
Select Max(ID),ProductCatId,ProductCode,price
From TableName
Group By ProductCatId,ProductCode,price
A little shorter:
SELECT DISTINCT
max(ID) OVER (PARTITION BY ProductCatId,
ProductCode,
Price) AS ID,
ProductCatId,
ProductCode,
Price,
FROM myTable