How to use Pivot with RowNumber and date - sql-server

I have a SQL Server table like this:
How can I change reading column into 2 columns based on rownumber?
I have tried like this:
WITH pivot_data AS
(
SELECT
date, CurrentMeterSNID,
1 + ((ROW_NUMBER() OVER (PARTITION BY CurrentMeterSNID ORDER BY date desc) - 1) % 2) rownum,
Reading
FROM
INF_Facility_ElectricalRecord
)
SELECT
date, CurrentMeterSNID, [1], [2]
FROM
pivot_data
PIVOT
(MAX(Reading) FOR rownum IN ([1], [2])) AS p;
but the result that I get is:
I get Null record; how can I replace that null value with record from a day after the date?

actually you are not doing PIVOT. You just want to conditionally display the value on different column. For this you use the CASE statement.
For the second requirement : for the NULL value, showing subsequent day value, you can use LEAD() or LAG() window function. This is the else part of the case
select date, CurrentMeterSNID,
[1] = case when rownum2 = 1
then reading
else lead(reading) over(partition by CurrentMeterSNID order by date)
end,
[2] = case when rownum2 = 2
then reading
else lead(reading) over(partition by CurrentMeterSNID order by date)
end
from INF_Facility_ElectricalRecord

As long as you are displaying every date in that query, you can't have what you want.
So you have to pick the max(date) in other words where rownumber will be 1.
WITH pivot_data AS(
SELECT date,CurrentMeterSNID,
1 + ((row_number() over(partition by CurrentMeterSNID ORDER by date desc) - 1) % 2) rownum,
Reading
FROM dbo.Table_1 )
, T2 AS
(
SELECT CurrentMeterSNID, date, [1], [2]
FROM pivot_data PIVOT (max(Reading) FOR rownum IN ([1],[2])) AS p
)
SELECT CurrentMeterSNID, Max(date), MAX([1]), Max([2])
FROM T2
GROUP BY CurrentMeterSNID

Related

UNION Returns NULL When First SELECT Returns Nothing

Hi I have a table: T1 that contains two columns Date and Price
T1
---------------------------
DATE | PRICE |
---------------------------
2018-07-25 |2.00 |
---------------------------
2018-06-20 |3.00 |
---------------------------
2017-05-10 |3.00 |
---------------------------
Here are my requirements:
If a user enters a date that is not in the DB I need to return the last price and date in the table T1.
If a user enters a date that is superior or inferior to one of the dates in the table T1 -- for example if a user enters '2017-05-09' which is not in the table; I have to return the next date above the given date. In this case'2017-05-10'
I am using UNION in my script but it returns empty when one of the SELECT statements returns empty.
I am using a CTE table:
DECLARE #DateEntered DATE
WITH HistoricalCTE (Date, Price, RowNumber) AS (
SELECT R.Date,
R.Price,
ROW_NUMBER() OVER (PARTITION BY R.Date, R.Price ORDER BY Date DESC)
FROM T1 R
WHERE Date = #DateEntered
UNION
SELECT R.Date,
R.Price,
ROW_NUMBER() OVER (PARTITION BY R.Date, R.Price ORDER BY Date DESC)
FROM T1 R
WHERE Date < #DateEntered
UNION
SELECT R.Date,
R.Price,
ROW_NUMBER() OVER (PARTITION BY R.Date, R.Price ORDER BY Date DESC)
FROM T1 R
WHERE Date > #DateEntered
)
The issue is when I enter superior to all the dates in the table T1, I get an empty result because the first select is returning empty. Any idea about how I would solve this?
You might be overcomplicating this. If I read your question correctly, we can just take the smallest value greater than the input, or if that doesn't exist, then just take the max of the table.
WITH cte AS (
SELECT *,
ROW_NUMBER() OVER (ORDER BY Date) rn
FROM T1
WHERE Date > #DateEntered
)
SELECT
CASE WHEN EXISTS (SELECT 1 FROM cte WHERE rn = 1)
THEN (SELECT Date FROM cte WHERE rn = 1)
ELSE (SELECT MAX(Date) FROM T1) END AS Date,
CASE WHEN EXISTS (SELECT 1 FROM cte WHERE rn = 1)
THEN (SELECT Price FROM cte WHERE rn = 1)
ELSE (SELECT Price FROM T1 WHERE Date = (SELECT MAX(Date) FROM T1)) END AS Price;
Demo
All the edge cases seem to be working in the above demo, and you may test any input date against your sample data.

Get running balance of a work center using partition

I am writing a script that will run on SQL Server 2014.
I have a table of transactions recording transfers from one work center to another. The simplified table is below:
DECLARE #transactionTable TABLE (wono varchar(10),transferDate date
,fromWC varchar(10),toWC varchar(10),qty float)
INSERT INTO #transactionTable
SELECT '0000000123','5/10/2018','STAG','PP-B',10
UNION
SELECT '0000000123','5/11/2018','PP-B','PP-T',5
UNION
SELECT '0000000123','5/11/2018','PP-T','TEST',3
UNION
SELECT '0000000123','5/12/2018','PP-B','PP-T',5
UNION
SELECT '0000000123','5/12/2018','PP-T','TEST',5
UNION
SELECT '0000000123','5/13/2018','PP-T','TEST',2
UNION
SELECT '0000000123','5/13/2018','TEST','FGI',8
UNION
SELECT '0000000123','5/14/2018','TEST','FGI',2
SELECT *,
fromTotal = -SUM(qty) OVER(PARTITION BY fromWC ORDER BY wono, transferdate, fromWC),
toTotal = SUM(qty) OVER(PARTITION BY toWC ORDER BY wono, transferdate, toWC)
FROM #transactionTable
ORDER BY wono, transferDate, fromWC
I want to get a running balance of the fromWC and toWC after each transaction.
Given the records above, the end result should be this:
I believe it is possible to use SUM(qty) OVER(PARTITION BY..., but I am not sure how to write the statement. When I try to get the increase and decrease, each line always results in 0.
How do I write the SUM statement to achieve the desired results?
UPDATE
This image shows each transaction, the resulting WC qty, and highlights the corresponding from and to work centers for each transaction.
For example, looking at the second record on 5/11, 3 were transferred from PP-T to TEST. After the transaction, there were 5 in PP-B, 2 in PP-T, and 3 in TEST.
I can get close, except starting balances:
SELECT wono, transferDate, fromWC, toWC, qty,
SUM( CASE WHEN WC = fromWC THEN RunningTotal ELSE 0 END ) AS FromQTY,
SUM( CASE WHEN WC = toWC THEN RunningTotal ELSE 0 END ) AS ToQTY
FROM( -- b
SELECT *, SUM(Newqty) OVER(PARTITION BY WC ORDER BY wono,transferdate, fromWC, toWC) AS RunningTotal
FROM(-- a
SELECT wono, transferDate, fromWC, toWC, fromWC AS WC, qty, -qty AS Newqty, 'From' AS RecType
FROM #transactionTable
UNION ALL
SELECT wono, transferDate, fromWC, toWC, toWC AS WC, qty, qty AS Newqty, 'To' AS RecType
FROM #transactionTable
) AS a
) AS b
GROUP BY wono, transferDate, fromWC, toWC, qty
My logic assumes that all balances start at 0, therefore "STAG" balance will be -10.
How the query works:
"Unpivot" the input record set into "From" and "To" records with quantities negated for "From" records.
Calculate running totals for each "WC".
Combine "Unpivoted" records back into original shape
Solution 2
WITH CTE
AS(
SELECT *,
ROW_NUMBER() OVER( ORDER BY wono, transferDate, fromWC, toWC ) AS Sequence
FROM #transactionTable
),
CTE2
AS(
SELECT *,
fromTotal = -SUM(qty) OVER(PARTITION BY fromWC ORDER BY Sequence),
toTotal = SUM(qty) OVER(PARTITION BY toWC ORDER BY Sequence)
FROM CTE
)
SELECT a.Sequence, b.Sequence, c.Sequence, a.wono, a.transferDate, a.fromWC, a.toWC, a.qty, a.fromTotal + ISNULL( b.toTotal, 0 ) AS FromTotal, a.toTotal + ISNULL( c.fromTotal, 0 ) AS ToTotal
FROM CTE2 AS a
OUTER APPLY( SELECT TOP 1 * FROM CTE2 WHERE wono = a.wono AND Sequence < a.Sequence AND toWC = a.fromWC ORDER BY Sequence DESC ) AS b
OUTER APPLY( SELECT TOP 1 * FROM CTE2 WHERE wono = a.wono AND Sequence < a.Sequence AND fromWC = a.toWC ORDER BY Sequence DESC ) AS c
ORDER BY a.Sequence
Note: This solution would benefit greatly from an "ID" column, that mirrors transaction order OR at least you will need an index on wono, transferDate, fromWC, toWC

SQL SMS 2008 -Count column ids and count duplicate ids if createddate is greater than 3 months between ids

*Edit (Hopefully to be more clear)
Table below, I would like to count ids and count duplicate ids where the createddate has a gap of 3 months or more for that ID.
Query I have so far...
if object_id('tempdb..#temp') is not null
begin drop table #temp end
select
top 100
a.id, a.CreatedDate
into #temp
from tbl a
where 1=1
--and year(CreatedDate) = '2015'
if object_id('tempdb..#temp2') is not null
begin drop table #temp2 end
select t.id, count(t.id) as Total_Cnt
into #temp2
from #temp t
group by id
select distinct #temp2.Total_Cnt, #temp2.id, #temp.CreatedDate, DENSE_RANK() over (partition by #temp.id order by createddate) RK
from #temp2
inner join #temp on #temp2.id = #temp.id
where 1=1
order by Total_Cnt desc
Results:
Total_cnt id createddate rk
3 1 01-01-2015 1
3 1 03-02-2015 2
3 1 01-02-2015 3
2 2 05-01-2015 1
2 2 05-02-2015 2
1 3 06-01-2015 1
1 4 07-01-2015 1
Count ids and only count duplicate ids when the createddate from the id is greater than 3 months.
Something like this...
Total_cnt id Countwith3monthgap
3 1 2
2 2 1
1 3 1
1 4 1
You can use a cte and ROW_NUMBER to get your order and self join the cte based on the order..
WITH cte AS
( SELECT
*,
ROW_NUMBER() OVER (PARTITION BY ID ORDER BY CreatedDate) Rn
FROM
Test
)
SELECT
c1.ID,
COUNT(CASE WHEN c2.CreatedDate IS NULL THEN 1
WHEN c1.CreatedDate >= DATEADD(month,3,c2.CreatedDate) THEN 1
END)
FROM
cte c1
LEFT JOIN cte c2 ON c1.ID = c2.ID
AND c1.RN = c2.RN + 1
GROUP BY
c1.ID
You also need to use a conditional count where the Previous CreatedDate is null or if the Current CreatedDate is >= the Previous CreatedDate + 3 months
If you happen to be using SQL 2012+ you can also use LAG here to get the same result
SELECT
ID,
COUNT(*)
FROM
(SELECT
ID,
CreatedDate CurrentDate,
LAG(CreatedDate) OVER (PARTITION BY ID ORDER BY CreatedDate) PreviousDate
FROM
Test
) T
WHERE
PreviousDate IS NULL
OR CurrentDate >= DATEADD(month, 3, PreviousDate)
GROUP BY
ID
You can use a lag to get the previous date, Null for the first in the list
SELECT
id,
lag(CreatedDate,1) OVER (PARTITION BY Id ORDER BY CreatedDate) AS PreviousCreateDate,
CreatedDate
FROM #t
You can use that as a subquery and get the difference in months using DATEDIFF
SELECT sub.id,DATEDiff(month, sub.PreviousCreateDate ,sub.CreatedDate)
FROM (SELECT
id,
lag(CreatedDate,1) OVER (PARTITION BY Id ORDER BY CreatedDate) AS PreviousCreateDate,
CreatedDate
FROM #t) sub
WHERE DATEDiff(month, sub.PreviousCreateDate ,sub.CreatedDate) >=3
OR sub.PreviousCreateDate IS NULL
You can then take your totals
SELECT sub.id,COUNT(sub.id) as cnt
FROM (SELECT
id,
lag(CreatedDate,1) OVER (PARTITION BY Id ORDER BY CreatedDate) AS PreviousCreateDate,
CreatedDate
FROM #t) sub
WHERE DATEDIFF(month, sub.PreviousCreateDate ,sub.CreatedDate) >=3
OR sub.PreviousCreateDate IS NULL
GROUP BY sub.id
Note that using datediff the last day of january is three months before the first day of march. That appears to be the logic you were after.
You might want to define your three month gap criteria as
WHERE sub.PreviousCreateDate <= DATEADD(month, -3, sub.CreatedDate)
OR sub.PreviousCreateDate IS NULL
or
WHERE sub.CreatedDate >= DATEADD(month, +3, sub.PreviousCreateDate )
OR sub.PreviousCreateDate IS NULL
I'm guessing that your desired definition of three-month gap doesn't coincide with datediff()'s. Most of the logic here is to look back at the previous date and decide if the gap is big enough to qualify.
When datediff() counts three months difference we still need to make sure the day of month is later than the first one (per example and ID 5). If difference is more than three months then we're good automatically.
But I'm also assuming that you would want to treat the distance from November 30th to February 28th (or 29th in a leap year) as a full three months because the end date falls on the final day of the month. By adjusting the end date by an extra day this is an easy scenario to snag as it will bump the date into the following month and increase the month difference by one as well. If that's not what you want then just remove the dateadd(day, 1, ...) portion and use only the raw CreatedDate value.
You sample data is limited so I'm also making the assumption that the gaps are measure between consecutive dates. If you're wanting to find blocks of runs that don't span more than three months across the set, then that's a different problem and you should clarify with more information.
Since you've indicated that you're probably on SQL Server 2008 you'll have to do without the lag() function. Although the first query could be adjusted for that it's likely easier to go with the second approach at the end.
with diffs as (
select
ID,
row_number() over (partition by ID order by CreatedDate) as RN,
case when
datediff(
month,
lag(CreatedDate, 1) over (partition by ID order by CreatedDate),
CreatedDate
) = 3
and
datepart(
day,
lag(CreatedDate, 1) over (partition by ID order by CreatedDate)
) <= datepart(day, CreatedDate)
or
datediff(
month,
lag(CreatedDate, 1) over (partition by ID order by CreatedDate),
/* adding one day to handle gaps like Nov30 - Feb28/29 and Jan31 - Apr30 */
dateadd(day, 1, CreatedDate)
) >= 4
then 1
else 0
end as GapFlag
from <T> /* <--- your table name here */
), gaps as (
select
ID, RN,
sum(1 + GapFlag) over (partition by ID order by RN) as Counter
from diffs
)
select ID, count(distinct Counter - RN) as "Count"
from gaps
group by ID
The rest of the logic is a typical gaps and islands scenario looking for holes in the sum(1 + GapCount) sequence with the offset of 1 acting pretty much like row_number().
http://sqlfiddle.com/#!6/61b12/3
JamieD77's approach is also valid. I was originally thinking your problem involved more than looking at the rows in sequence. Here's how I would tweak it for the gap definition I've been running with:
with data as (
select ID, CreatedDate, row_number() over (partition by ID order by CreatedDate) as RN
from T
)
select ID, count(*) as "Count"
from data d1 left outer join data d0
on d0.ID = d1.ID and d0.RN = d1.RN - 1 /* connect to the one before */
where
datediff(month, d0.CreatedDate, d1.CreatedDate) = 3
and datepart(day, d0.CreatedDate) <= datepart(day, d0.CreatedDate)
or datediff(month, d0.CreatedDate, dateadd(day, 1, d0.CreatedDate)) >= 4
or d0.ID is null
group by ID
Edit: You have changed the question since yesterday.
Change this line in the first query to include the total count:
...
select count(*) as TotalCnt, ID, count(distinct Counter - RN) as GapCount
...
Second would look like:
with data as (
select ID, CreatedDate, row_number() over (partition by ID order by CreatedDate) as RN
from T
)
select
count(*) as TotalCnt, ID,
count(case when
datediff(month, d0.CreatedDate, d1.CreatedDate) = 3
and datepart(day, d0.CreatedDate) <= datepart(day, d0.CreatedDate)
or datediff(month, d0.CreatedDate, dateadd(day, 1, d0.CreatedDate)) >= 4
or d0.ID is null then 1 end
) as GapCount
from data d1 left outer join data d0
on d0.ID = d1.ID and d0.RN = d1.RN - 1 /* connect to the one before */
where
group by ID

SQL Server 2008 R2 GROUP BY or OVER

I have this table:
ID COLOR TYPE DATE
-------------------------------
1 blue A 2012.02.05
2 white V 2010.10.23
3 white V 2014.03.05
4 black S 2013.02.14
I'd like to select only the ID, but in case of 2nd and 3rd rows I want to select the 3rd row because of its latest DATE value.
I have tried this query but it gives back all the two rows:
SELECT
ID, MAX(DATE) OVER(PARTITION BY COLOR, TYPE)
FROM
TABLE
WHERE
...
How can I select just one column value while I group the rows by other columns, please?
;WITH CTE AS
(
SELECT * , ROW_NUMBER() OVER (PARTITION BY COLOR,[TYPE] ORDER BY [DATE] DESC) rn
FROM TableName
)
SELECT ID
,COLOR
,[TYPE]
,[DATE]
FROM CTE
WHERE rn = 1
OR
SELECT ID
,COLOR
,[TYPE]
,[DATE]
FROM
(
SELECT * , ROW_NUMBER() OVER (PARTITION BY COLOR,[TYPE] ORDER BY [DATE] DESC) rn
FROM TableName
) A
WHERE rn = 1

ROW_NUMBER() without over in SQL

Is there any way to use ROW_NUMBER() in SQL without using OVER, because I want to use sorting.
I have a Grid with multiple sortable columns with configurable rows. In my scenario order by is variable that's why I am not able to put order by using ROWNUM.
select ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) as number from Task order by RuleId
I approached this using a different logic. Instead of checking for every single day and verifying it is a weekday or a holiday I create a table which lists only the business days and then take the nth value.
CREATE OR REPLACE FUNCTION add_n_working_days (
start_date DATE, working_days PLS_INTEGER
) RETURN DATE AS
l_end_date DATE := start_date;
l_counter pls_integer := 0;
BEGIN
SELECT
business_day
INTO l_end_date
FROM
(
WITH
dates AS
(SELECT start_date + level - 1 as dt FROM dual CONNECT BY level < 100)
,weekdates AS
(SELECT dt as weekday FROM dates WHERE TO_CHAR(dt,'fmdy') NOT IN ('sat','sun'))
,business_days AS
(
SELECT weekday as business_day FROM weekdates
MINUS
SELECT holiday FROM so_holidays
)
SELECT business_day, ROW_NUMBER() OVER (ORDER BY 1) as rn from business_days
)
WHERE rn = working_days + 1;
RETURN l_end_date;
END add_n_working_days;
If working_days gets too high then I suggest making that value 100 a variable.
select
a.City,
a.len
from
(
select city,length(city) AS len,
row_number()over(order by length(city) desc, city) as hi,
row_number()over(order by length(city), city) as lo
from
STATION) as a where (a.lo=1 or a.hi=1);

Resources