copy the value with last non zero value sql - sql

I have data as below, data is order by date.
I need to copy the last non zero value when there is zero. For example 15th August the count is Zero so the count should show 20 as the last count is 20 (on 15th August), same should happen for 16th - 19 August. Now for 21st - 22nd the count should be 14. If no previous value is available let it be zero in case of 14th Aug.
I have also added the result at last.
Date Count
14-Aug-15 0
15-Aug-15 20
16-Aug-15 0
17-Aug-15 0
18-Aug-15 0
19-Aug-15 0
20-Aug-15 14
21-Aug-15 0
22-Aug-15 0
23-Aug-15 10
24-Aug-15 0
25-Aug-15 0
26-Aug-15 0
27-Aug-15 0
28-Aug-15 11
29-Aug-15 0
30-Aug-15 0
31-Aug-15 0
01-Sep-15 0
02-Sep-15 0
03-Sep-15 0
04-Sep-15 0
Result
Date Count
14-Aug-15 0
15-Aug-15 20
16-Aug-15 20
17-Aug-15 20
18-Aug-15 20
19-Aug-15 20
20-Aug-15 14
21-Aug-15 14
22-Aug-15 14
23-Aug-15 10
24-Aug-15 10
25-Aug-15 10
26-Aug-15 10
27-Aug-15 10
28-Aug-15 11
29-Aug-15 11
30-Aug-15 11
31-Aug-15 11
01-Sep-15 11
02-Sep-15 11
03-Sep-15 11
04-Sep-15 11

A simple subquery can get you your result set as desired (with the last current or previous non-zero value).
select [Date],
isnull((select top 1 [Previous].[Count]
from [Table] as [Previous]
where [Previous].[Count] <> 0 and
[Previous].[Date] <= [Table].[Date] and
[Previous].[Id] = [Table].[Id]
order by [Previous].[Date] desc),
0) as [Count]
from [Table]
To update values is very similar :
update [Table] set [Count]= isnull((select top 1 [Previous].[Count]
from [Table] as [Previous]
where [Previous].[Count] <> 0 and
[Previous].[Date] <= [Table].[Date] and
[Previous].[Id] = [Table].[Id]
order by [Previous].[Date] desc),
0) as [Count]

You may use a variable to achieve it
CREATE TABLE tab
(
n VARCHAR(10),
d DATE,
c INT
);
INSERT INTO tab VALUES ('A', '14-Aug-15', 0);
INSERT INTO tab VALUES ('B', '15-Aug-15', 20);
INSERT INTO tab VALUES ('C', '16-Aug-15', 0);
INSERT INTO tab VALUES ('A', '17-Aug-15', 0);
INSERT INTO tab VALUES ('B', '18-Aug-15', 0);
INSERT INTO tab VALUES ('C', '19-Aug-15', 0);
INSERT INTO tab VALUES ('D', '20-Aug-15', 14);
INSERT INTO tab VALUES ('A', '21-Aug-15', 0);
-- for each n value perform the following update
DECLARE #last INT=0;
UPDATE tab
SET #last=c=CASE WHEN c=0 THEN #last ELSE c END
WHERE n = 'B';

I think below code should work for you -
Create table #Source (
id char(1) ,
date smalldatetime,
value tinyint)
go
insert into #Source
select 'A' ,'14-Aug-15' , 0
union all select 'B' , '15-Aug-15' , 20
union all select 'B' , '16-Aug-15' , 0
union all select 'B' , '17-Aug-15' , 0
union all select 'B' , '18-Aug-15' , 0
union all select 'A' , '19-Aug-15' , 0
union all select 'A' , '20-Aug-15' , 14
union all select 'A' , '21-Aug-15' , 0
union all select 'B' , '22-Aug-15' , 0
union all select 'B', '23-Aug-15' , 10
union all select 'B' , '24-Aug-15' , 0
union all select 'B' , '25-Aug-15' , 0
union all select 'B' , '26-Aug-15' , 0
union all select 'C' , '27-Aug-15' , 0
union all select 'C' , '28-Aug-15' , 11
union all select 'C' , '29-Aug-15' , 0
union all select 'C' , '30-Aug-15' , 0
union all select 'C' , '31-Aug-15' , 0
union all select 'C' , '01-Sep-15' , 0
union all select 'B' , '02-Sep-15' , 0
union all select 'B' , '03-Sep-15' , 0
union all select 'A' , '04-Sep-15' , 0
select t1.* , t2.value New_value , RANK() over ( partition by t1.date
order by t2.date desc ) as RANK
into #dest
from #Source t1
left join #Source t2 on t1.id = t2.id and t1.date >= t2.date
and t1.value=0 and t2.value <>0
order by 2
update s
set s.Value = d.New_value
from #Source s join #dest d
on s.id = d.id
and s.date = d.date
and d.RANK = 1 and d.New_value is not null

Related

Oracle how to return rows based on condition?

I am trying to return id and name based on flag column. If id has a rows with flag = 1 my query should only return these rows. If it hasn't flag=1 value it should return rows with flag = 0. What is the best way for it ? Here is sample data :
id name flag
5 aa 1
5 bb 0
6 cc 1
10 dd 0
11 ee 1
11 ee 0
Expected output is :
id name flag
5 aa 1
6 cc 1
10 dd 0
11 ee 1
Assuming flag column contains only 0 or 1, select rows whose flag is equal to maximal value of flags of given id:
select id, name, flag
from (
select id, name, flag, max(flag) over (partition by id) as m
from your_table
) x
where x.flag = x.m
You can use the keep dense_rank aggregating function to acheive that like below.
with t (id, name, flag) as (
select 5 , 'aa', 1 from dual union all
select 5 , 'bb', 0 from dual union all
select 6 , 'cc', 1 from dual union all
select 10, 'dd', 0 from dual union all
select 11, 'ee', 1 from dual union all
select 11, 'ee', 0 from dual
)
select id
, max(name)keep(dense_rank last order by id, flag) name
, max(flag)keep(dense_rank last order by id, flag) flag
from t
where flag in (0, 1)
group by id
order by id
;

percentage summary per hour for 24 hours

Using SQL Server 2008 R2.
I'm trying to get an up time percentage for a 24 hour period broken down per hour.
If the column AlertID is 1, then the system is up. If AlertID is anything other number then it's considered down.
Here is what I have that currently isn't working and has a bad performance result but let's just make it work first. The UpTime column is always 0.
SELECT a.dayhour,
( (SELECT Count(*)
FROM commandhistory
WHERE commandid = '4263745C-5603-4E3D-AFB2-CA0E27969D0B'
AND alertid = 1
AND Datepart(hour, recordeddttm) = a.dayhour * 100) / (SELECT
Count(*) FROM commandhistory
WHERE commandid = '4263745C-5603-4E3D-AFB2-CA0E27969D0B'
AND Datepart(hour, recordeddttm) = a.dayhour) ) AS UpTime
FROM (SELECT 0 AS DayHour UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9 UNION SELECT 10 UNION SELECT 11 UNION SELECT 12 UNION SELECT 13 UNION SELECT 14 UNION SELECT 15 UNION SELECT 16 UNION SELECT 17 UNION SELECT 18 UNION SELECT 19 UNION SELECT 20 UNION SELECT 21 UNION SELECT 22 UNION SELECT 23) a
LEFT JOIN commandhistory h
ON a.dayhour = Datepart(hour, recordeddttm)
GROUP BY a.dayhour
ORDER BY a.dayhour
Example Table: (there is other data but i don't care about it for this instance)
uniqueidentifier CommandID
smallint AlertID
datetime RecordedDTTM
AlertID can be 1-5. 1 means it's up, any other number should be treated as down.
how about this:
;with cte as
(
SELECT Datepart(hour, recordeddttm) as DayHour
, sum(case when alertID=1 then 1 else 0 end) as UpTimeCt
, sum(case when alertID <> 1 then 1 else 0 end) as DownTimeCt
, Count(*) AllCt
FROM commandhistory
WHERE commandid = '4263745C-5603-4E3D-AFB2-CA0E27969D0B'
--AND cast(recordeddttm as date) = [some date] --This is optional but limits data set
group by Datepart(hour, recordeddttm)
)
select DayHour
, UpTimeCt
, UpTimeRate = UpTimeCt/AllCt --AllCt might need to be cast
, DownTimeCt
, DownTimeRate = DownTimeCt / AllCt --AllCt might need to be cast
from cte

Counting an item and stuffing the results

is this possible, i have results like below and want to stuff or combine each id
Num id RMA
item1 1 0
item1 1 0
item1 1 0
item1 8 1
item1 8 1
item1 8 1
item1 1 0
item1 1 0
item1 1 0
item2 8 1
item2 8 1
item2 8 1
item2 8 1
item2 8 1
item2 8 1
item2 8 1
item2 1 0
i would like to get the results like below
item1 id1=3,id8=3,id1=3
item2 id8=7,id1=1
If it's SQL server:
You can use a GROUP BY clause:
SELECT Num
,id
,COUNT(RMA) AS Count_RMA
FROM Table
GROUP BY Num
,id
It will output a table that counts each RMA for each unique Num and id combinaison.
Like this:
Num id Count_RMA
item1 1 6
item1 8 3
item2 8 7
item2 1 1
It is not difficult, however, you will need to create some additional steps, see the code bellow:
DECLARE #items TABLE (Num varchar(10) , id INT, RMA int , _group int default(0) )
declare #gIndcator int = 0 , #old_id int = 0
insert #items (num,id,rma) select 'item1' , 1 , 0
insert #items (num,id,rma) select 'item1' , 1 , 0
insert #items (num,id,rma) select 'item1' , 1 , 0
insert #items (num,id,rma) select 'item1' , 8 , 1
insert #items (num,id,rma) select 'item1' , 8 , 1
insert #items (num,id,rma) select 'item1' , 8 , 1
insert #items (num,id,rma) select 'item1' , 1 , 0
insert #items (num,id,rma) select 'item1' , 1 , 0
insert #items (num,id,rma) select 'item1' , 1 , 0
insert #items (num,id,rma) select 'item2' , 8 , 1
insert #items (num,id,rma) select 'item2' , 8 , 1
insert #items (num,id,rma) select 'item2' , 8 , 1
insert #items (num,id,rma) select 'item2' , 8 , 1
insert #items (num,id,rma) select 'item2' , 8 , 1
insert #items (num,id,rma) select 'item2' , 8 , 1
insert #items (num,id,rma) select 'item2' , 8 , 1
insert #items (num,id,rma) select 'item2' , 1 , 0
UPDATE #items
SET #gIndcator = _group = case when #old_id <> id then #gIndcator + 1 else #gIndcator end
, #old_id = id
select * from #items
select distinct
num
, ids = stuff((
select ',id'+convert(varchar(12),b.id) + '=' +convert(varchar(12),b._count)
from (
SELECT num, id , _group, COUNT(1) _count
FROM #items c
where a.Num = c.Num
group by Num, id,_group) b
where a.Num = b.Num
order by b.Num,_group
for xml path (''), type).value('.','nvarchar(max)')
,1,1,'')
from #items a
item1 id1=3,id8=3,id1=3
item2 id8=7,id1=1
It looks something like this:
select t.*,
stuff( (select ',' + cast(id as varchar(max)) + '=' + cast(cnt as varchar(max))
from t tt
where t.num = tt.num
group by id
for xml path ('')
), 1, 1, ''
) as
from (select num from t) t
;WITH CTE(Num , id, RMA)
AS
(
SELECT 'item1', 1 , 0 UNION ALL
SELECT 'item1', 1 , 0 UNION ALL
SELECT 'item1', 1 , 0 UNION ALL
SELECT 'item1', 8 , 1 UNION ALL
SELECT 'item1', 8 , 1 UNION ALL
SELECT 'item1', 8 , 1 UNION ALL
SELECT 'item1', 1 , 0 UNION ALL
SELECT 'item1', 1 , 0 UNION ALL
SELECT 'item1', 1 , 0 UNION ALL
SELECT 'item2', 8 , 1 UNION ALL
SELECT 'item2', 8 , 1 UNION ALL
SELECT 'item2', 8 , 1 UNION ALL
SELECT 'item2', 8 , 1 UNION ALL
SELECT 'item2', 8 , 1 UNION ALL
SELECT 'item2', 8 , 1 UNION ALL
SELECT 'item2', 8 , 1 UNION ALL
SELECT 'item2', 1 , 0
)
,Final
AS
(
SELECT Num,idval,cnt AS COUNT_RMA From
(
SELECT NUM,' id'+CASt(id AS Varchar(2))AS idval,cnt, ROW_NUMBER()OVEr(Partition by NUm,Cnt order by Num)AS Seq
From
(
SELECT NUm,id,COUNT(Id)Over(Partition by Num ,id order by Num)Cnt from CTE
)Dt
)Fnl
where Fnl.Seq=1
)
SELECT DISTINCT num, STUFF((SELECT DISTINCT ', '+CAST(''+CAST(idval AS VARCHAR)+'='+CAST(COUNT_RMA AS VARCHAR) AS VARCHAR(100))
From Final I WHERE O.Num=I.Num foR XML PATH('')),1,1,'') As Requiredvalue
from Final O

SQL - Group by to get sum but also return a row if the sum is 0

I have the following table:
ID BuyOrSell Total
4 B 10
4 B 11
4 S 13
4 S 29
8 B 20
9 S 23
What I am trying to do is to have sum of B and S columns for each ID and if there is not a B or S for an ID have a row with 0, so expected output would be
ID BuyOrSell Total
4 B 21
4 S 42
8 B 20
8 S 0
9 S 23
9 B 0
I have tried this and it is kind of doing what I am after but not exactly:
DECLARE #Temp Table (ID int, BuyOrSell VARCHAR(1), charge Decimal)
INSERT INTO #Temp
SELECT 4, 'B', 10 UNION ALL
SELECT 4, 'B', 11 UNION ALL
SELECT 4, 'S', 13 UNION ALL
SELECT 4, 'S', 29 UNION ALL
SELECT 8, 'B', 20 UNION ALL
SELECT 9, 'S', 23
;With Results AS
(
SELECT ID,
BuyOrSell,
SUM(charge) AS TOTAL
FROM #Temp
Group by ID, BuyOrSell
)
Select t.*,max(
case when BuyOrSell = 'B' then 'Bfound'
end) over (partition by ID) as ref
,max(
case when BuyOrSell = 'S' then 'Sfound'
end) over (partition by ID) as ref
FROM Results t;
Thanks
Try this:
;WITH CTE(ID, BuyOrSell) AS(
SELECT
ID, T.BuyOrSell
FROM #Temp
CROSS JOIN(
SELECT 'B' UNION ALL SELECT 'S'
)T(BuyOrSell)
GROUP BY ID, T.BuyOrSell
)
SELECT
C.ID,
C.BuyOrSell,
Total = ISNULL(SUM(T.charge), 0)
FROM CTE C
LEFT JOIN #Temp T
ON T.ID = C.ID
AND T.BuyOrSell = C.BuyOrSell
GROUP BY C.ID, C.BuyOrSell
ORDER BY C.ID, C.BuyOrSell
#03Usr, despite that your question has been answered, please try this:
SELECT two.ID,
two.BuyOrSell,
ISNULL (one.Total, 0) Total
FROM
(SELECT ID,
BuyOrSell,
SUM (Total) Total
FROM #Temp
GROUP BY ID, BuyOrSell) one
LEFT OUTER JOIN
(SELECT ID,
BuyOrSell
FROM #Temp
GROUP BY ID,
BuyOrSell) two
ON one.ID = two.ID
AND one.BuyOrSell = two.BuyOrSell;
Here is a solution with tricky join:
SELECT t1.ID,
v.l as BuyOrSell,
SUM(CASE WHEN t1.BuyOrSell = v.l THEN t1.charge ELSE 0 END) AS Total
FROM #Temp t1
JOIN (VALUES('B'),('S')) v(l)
ON t1.BuyOrSell = CASE WHEN EXISTS(SELECT * FROM #Temp t2
WHERE t2.ID = t1.ID AND t2.BuyOrSell <> t1.BuyOrSell)
THEN v.l ELSE t1.BuyOrSell END
GROUP BY t1.ID, v.l
ORDER BY t1.ID, v.l
Output:
ID l Total
4 B 21
4 S 42
8 B 20
8 S 0
9 B 0
9 S 23

How to write a query to produce counts for arbitrary value bands?

My table had 3 fields: id and unit. I want to count how many ids have <10, 10-49, 50-100 etc units. The final result should look like:
Category | countIds
<10 | 1516
10 - 49 | 710
50 - 99 | 632
etc.
This is the query that returns each id and how many units it has:
select id, count(unit) as numUnits
from myTable
group by id
How can I build on that query to give me the category, countIds result?
create temporary table ranges (
seq int primary key,
range_label varchar(10),
lower int,
upper int
);
insert into ranges values
(1, '<10', 0, 9),
(2, '10 - 49', 10, 49),
(3, '50 - 99', 50, 99)
etc.
select r.range_label, count(c.numUnits) as countIds
from ranges as r
join (
select id, count(unit) as numUnits
from myTable
group by id) as c
on c.numUnits between r.lower and r.upper
group by r.range_label
order by r.seq;
edit: changed sum() to count() above.
select category_bucket, count(*)
from (select case when category < 10 then "<10"
when category >= 10 and category <= 49 then "10 - 49"
when category >= 50 and category <= 99 then "50 - 99"
else "100+"
end category_bucket, num_units
from my_table)
group by category_bucket
A dynamically grouped solution is much harder.
SELECT id, countIds
FROM (
SELECT id
, 'LESS_THAN_TEN' CATEGORY
, COUNT(unit) countIds
FROM table1
GROUP BY ID
HAVING COUNT(UNIT) < 10
UNION ALL
SELECT id
, 'BETWEEN_10_AND_49' category
, COUNT(unit) countIds
FROM table1
GROUP BY ID
HAVING COUNT(UNIT) BETWEEN 10 AND 49
UNION ALL
SELECT id
, 'BETWEEN_50_AND_99' category
, COUNT(unit) countIds
FROM table1
GROUP BY id
HAVING COUNT(UNIT) BETWEEN 50 AND 99
) x
Giving an example for one range: (10 - 49)
select count(id) from
(select id, count(unit) as numUnits from myTable group by id)
where numUnits >= '10' && numUnits <= '49'
It's not precisely what you want, but you could use fixed ranges, like so:
select ' < ' || floor(id / 50) * 50, count(unit) as numUnits
from myTable
group by floor(id / 50) * 50
order by 1
Try this working sample in SQL Server TSQL
SET NOCOUNT ON
GO
WITH MyTable AS
(
SELECT 00 as Id, 1 Value UNION ALL
SELECT 05 , 2 UNION ALL
SELECT 10 , 3 UNION ALL
SELECT 15 , 1 UNION ALL
SELECT 20 , 2 UNION ALL
SELECT 25 , 3 UNION ALL
SELECT 30 , 1 UNION ALL
SELECT 35 , 2 UNION ALL
SELECT 40 , 3 UNION ALL
SELECT 45 , 1 UNION ALL
SELECT 40 , 3 UNION ALL
SELECT 45 , 1 UNION ALL
SELECT 50 , 3 UNION ALL
SELECT 55 , 1 UNION ALL
SELECT 60 , 3 UNION ALL
SELECT 65 , 1 UNION ALL
SELECT 70 , 3 UNION ALL
SELECT 75 , 1 UNION ALL
SELECT 80 , 3 UNION ALL
SELECT 85 , 1 UNION ALL
SELECT 90 , 3 UNION ALL
SELECT 95 , 1 UNION ALL
SELECT 100 , 3 UNION ALL
SELECT 105 , 1 UNION ALL
SELECT 110 , 3 UNION ALL
SELECT 115 , 1 Value
)
SELECT Category, COUNT (*) CountIds
FROM
(
SELECT
CASE
WHEN Id BETWEEN 0 and 9 then '<10'
WHEN Id BETWEEN 10 and 49 then '10-49'
WHEN Id BETWEEN 50 and 99 then '50-99'
WHEN Id > 99 then '>99'
ELSE '0' END as Category
FROM MyTable
) as A
GROUP BY Category
This will give you the following result
Category CountIds
-------- -----------
<10 2
>99 4
10-49 10
50-99 10