Group the result from DATEDIFF function in SQL - sql

I have this query :
SELECT DATEDIFF(DAY,wj_date,wj_donedate) AS tmpDay
FROM wssjobm , sysbrxces WHERE wj_br = zu_br AND zu_user = 'mbs'
AND wj_date >= '2013/04/01' AND wj_date <= '2013/04/30'
AND wj_status = 'D' AND wj_donedate <= '2013/04/30'
The eg. result :
tmpDay
1
11
5
1
7
2
12
10
2
2
How can i group by the result and count it using query to be like this :
tmpDay count
1 2
2 3
5 1
7 1
10 1
11 1
12 1
thanks!
Extra Question :
May i get the result like this :
tmpDayGroup Count
1- 4 5
5- 8 2
11 -12 3

Try:
SELECT
tmpDay,
COUNT(*) [Count]
FROM
YourTable
Group By tmpDay
For the given example, query should be like:
SELECT
DATEDIFF(DAY,wj_date,wj_donedate) AS tmpDay,
COUNT(*) [Count]
FROM
wssjobm , sysbrxces
WHERE
wj_br = zu_br AND
zu_user = 'mbs' AND
wj_date >= '2013/04/01' AND
wj_date <= '2013/04/30' AND
wj_status = 'D' AND
wj_donedate <= '2013/04/30'
GROUP BY DATEDIFF(DAY,wj_date,wj_donedate)
For grouping the result, try:
;with T as(
select '1' FrmD, '4' ToD union all
select '5' FrmD, '8' ToD union all
select '9' FrmD, '12' ToD
)
select
T.FrmD +'-'+ T.ToD tmpDayGroup,
COUNT(*) [Count]
from T Left Join (
SELECT
DATEDIFF(DAY,wj_date,wj_donedate) AS tmpDay
FROM
wssjobm , sysbrxces
WHERE
wj_br = zu_br AND
zu_user = 'mbs' AND
wj_date >= '2013/04/01' AND
wj_date <= '2013/04/30' AND
wj_status = 'D' AND
wj_donedate <= '2013/04/30'
) T2 on T2.tmpDay between T.FrmD and T.ToD
group by T.FrmD, T.ToD

Related

Query Optimization - To repeat a pattern in Oracle SQL

Introduction: I can do this in MS-Excel, it takes me 1 minute, but I m trying to get this in Oracle SQL
Here is my Code:
SELECT A.*, (CASE WHEN A.r = 1 then 'X1' when A.r = 2 then 'X2' when A.r = 3 then 'X3' when A.r = 4
then 'X4' when A.r = 5 then 'X2' when A.r = 6 then 'X6' end) X FROM
(
Select Rownum r
From dual
Connect By Rownum <= 6 ) A
This is the Output:
Now, what if I have to do it for 25000 numbers, meaning when (rownum <= 25000) currently I have it only for 6, Is there a better method to do this with out case statement?
If you want to repeat this pattern of 6 rows for the remaining rows, then you can do:
select t.*,
(case when mod(rownum, 6) = 5 then 'X2'
else 'X' || (mod(rownum - 1, 6) + 1)
end)
from t;

How to extract and pivot in sql

I have tables like following
I treid to sum score in pivoted style..
product date score
A 2020/8/1 1
B 2018/8/1 2
B 2018/9/1 1
C 2017/9/1 2
I'd like to transform them to the following pivotedone.
The index is YEAR(t.date) and columns = product
date A B C
2017 0 0 2
2018 0 3 0
2019 0 0 0
2020 1 0 0
Are there any effective way to achieve this?
Thanks
We can handle this by joining a calendar table containing all years of interest to your current table, aggregating by year, and then using conditional aggregation to find the sum of scores for each product.
WITH years AS (
SELECT 2017 AS year FROM dual UNION ALL
SELECT 2018 FROM dual UNION ALL
SELECT 2019 FROM dual UNION ALL
SELECT 2020 FROM dual
)
SELECT
y.year,
SUM(CASE WHEN t.product = 'A' THEN t.score ELSE 0 END) AS A,
SUM(CASE WHEN t.product = 'B' THEN t.score ELSE 0 END) AS B,
SUM(CASE WHEN t.product = 'C' THEN t.score ELSE 0 END) AS C
FROM years y
LEFT JOIN yourTable t
ON y.year = EXTRACT(YEAR FROM t."date")
GROUP BY
y.year
ORDER BY
y.year;
Demo
One option would be using PIVOT Clause after determining the year range, and joining outerly with your data source and setting the null scores as zeroes :
WITH years AS
(SELECT MIN(EXTRACT(year from "date")) AS min_year,
MAX(EXTRACT(year from "date")) AS max_year
FROM tab)
SELECT year, NVL(A,0) AS A, NVL(B,0) AS B, NVL(C,0) AS C
FROM (SELECT l.year, product, SUM(score) AS score
FROM tab t --> your original data source
RIGHT JOIN (SELECT level + min_year - 1 AS year
FROM years
CONNECT BY level BETWEEN 1 AND max_year - min_year + 1) l
ON l.year = EXTRACT(year from "date")
GROUP BY l.year, product)
PIVOT (SUM(score) FOR product IN('A' AS "A", 'B' AS "B", 'C' AS "C"))
ORDER BY year;
YEAR A B C
---- - - -
2017 0 0 2
2018 0 3 0
2019 0 0 0
2020 1 0 0
Demo

Generate group of data based on sum of values

How to calculate the sum and total files.
here is my table
**FileId** **FileSize(MB)**
1 5
2 4
3 1
4 6
5 8
6 1
7 7
8 2
Expected result
BatchNo StartId EndId BatchSize
1 1 3 10
2 4 4 6
3 5 6 9
4 7 8 9
If File Size >= 10 then start new batch
also file count per batch is >= 10 then start new batch
StartId and EndId based on FileId
and BatchNo Is AutoIncrement
You can use recursive query like this
with rdata as
(
select row_number() over (order by fileId) rn, * from data
), rcte as
(
select 1 no, 1 gr, fileSize fileSizeSum , *
from rdata where fileid = 1
union all
select case when fileSizeSum + d.fileSize > 10 or r.no = 10 then 1 else r.no + 1 end gr,
case when fileSizeSum + d.fileSize > 10 or r.no = 10 then r.gr + 1 else r.gr end gr,
case when fileSizeSum + d.fileSize > 10 or r.no = 10 then d.fileSize else d.fileSize + fileSizeSum end fileSizeSum,
d.*
from rcte r
join rdata d on r.rn + 1 = d.rn
)
select r.gr,
min(fileId),
max(fileId),
max(fileSizeSum)
from rcte r
group by r.gr
dbfiddle
Here is another solution, with different approach:
INSERT INTO #batchdetails (FileID, FileSizeTotal, GroupID)
SELECT FileID, (
SELECT SUM(filesize) FROM #filedetails f2
WHERE f1.fileid >= f2.fileid ) AS FileSizeTotal,
1+CONVERT(INT,(
SELECT SUM(filesize) FROM #filedetails f2
WHERE f1.fileid >= f2.fileid
)/(#filesizepergroup+0.1)) AS GroupID
FROM #filedetails f1
SELECT DISTINCT
BatchDetails.GroupID AS BatchNo,
MIN(BatchDetails.FileID) OVER (PARTITION BY BatchDetails.GroupID ORDER BY BatchDetails.GroupID) AS StartID,
MAX(BatchDetails.FileID) OVER (PARTITION BY BatchDetails.GroupID ORDER BY BatchDetails.GroupID) AS EndID,
BatchSizeGroup.BatchSize
FROM #batchdetails BatchDetails
INNER JOIN (
SELECT GroupID, (GroupFileSizeTotal - LAG(GroupFileSizeTotal,1,0) OVER (ORDER BY GroupID)) AS BatchSize
FROM (
SELECT DISTINCT
GroupID,
MAX(FileSizeTotal) OVER (PARTITION BY GroupID ORDER BY GroupID) AS GroupFileSizeTotal
FROM #batchdetails
GROUP BY GroupID, FileSizeTotal
)A
)BatchSizeGroup ON BatchDetails.GroupID = BatchSizeGroup.GroupID
GROUP BY BatchDetails.GroupID, BatchDetails.FileID, BatchSizeGroup.BatchSize
Demo is Here : dbfiddle

SQL query - sum of values by status for date interval

I get crazy because of one query. I have a table like following and I want to get a data - Summa of Values by Status For every Date in interval.
Table
Id Name Value Date Status
1 pro1 2 01.04.14 0
2 pro1 8 02.04.14 1
3 pro2 6 02.04.14 1
4 pro3 0 03.04.14 0
5 pro4 7 03.04.14 0
6 pro4 2 03.04.14 0
7 pro4 4 03.04.14 1
8 pro4 6 04.04.14 1
9 pro4 1 04.04.14 1
For example,
Input: Name = pro4, minDate = 01.02.14, maxDate = 04.09.14
Output:
Date Values sum for 0 Status Values sum for 1 Status
01.04.14 0 0
02.04.14 0 0
03.04.14 9 (=7+2) 4 (only 4 exist)
04.04.14 0 7 (6+1)
In 01.02.14 and 02.04.14 dates, pro4 has not values by status, but I want to show that rows, because I need all dates in that interval. Can anyone help me to create this query?
Edit:
I can not change structure, I have already that table with data. Every day exist in table many times (minimum 1 time)
Thanks in advance.
Assuming you have a row for each date in the table, use conditional aggregation:
select date,
sum(Case when name = 'pro4' and status = 0 then Value else 0 end) as values_0,
sum(case when name = 'pro4' and status = 1 then Value else 0 end) as values_1
from Table t
where date >= '2014-04-01' and date <= '2014-04-09'
group by date
order by date;
If you don't have this list of dates, you can take this approach instead:
with dates as (
select cast('2014-04-01' as date) as thedate
union all
select dateadd(day, 1, thedate)
from dates
where thedate < '2014-04-09'
)
select dates.thedate,
sum(Case when status = 0 then Value else 0 end) as values_0,
sum(case when status = 1 then Value else 0 end) as values_1
from dates left outer join
table t
on t.date = dates.thedate and t.name = 'pro4'
group by dates.thedate;
just an assumption query :
select Distinct date ,case when status = 0 and MAX(date) then SUM(value) ELSE 0 END Status0 ,
case when status = 1 and MAX(date) then SUM(value) ELSE 0 END Status1 from table
To expand my comment the complete query is
WITH [counter](N) AS
(SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1)
, days(N) AS (
SELECT row_number() over (ORDER BY (SELECT NULL)) FROM [counter])
, months (N) AS (
SELECT N - 1 FROM days WHERE N < 13)
, calendar ([date]) AS (
SELECT DISTINCT cast(dateadd(DAY, days.n
, dateadd(MONTH, months.n, '20131231')) AS date)
FROM months
CROSS JOIN days
)
SELECT a.Name
, c.Date
, [Sum of 0] = SUM(CASE Status WHEN 0 THEN Value ELSE 0 END)
, [Sum of 1] = SUM(CASE Status WHEN 1 THEN Value ELSE 0 END)
FROM Calendar c
LEFT JOIN myTable a ON c.Date = a.Date AND a.name = 'pro4'
WHERE c.date BETWEEN '20140201' AND '20140904'
GROUP BY c.Date, a.Name
ORDER BY c.Date
Note that the condition on the name need to be in the JOIN, otherwise you'll get only the date of your table.
If you need multiple years just add another CTE for the count and a dateadd(YEAR,...) in the CTE calendar
This is not really the exact query, but I think you can get that by having a query that looks like:
select date, status, sum(value) from table
where (date between mindate and maxdate) and name = product_name
group by date, status;
this page gives more info.
EDIT
So the above query only gives a part of the answer required by the OP. A LEFT OUTER JOIN of the original table and the result of the above query on thedate and status fields will give the missing info.
e.g.
select x.date, x.status, x.sum_of_values from table as y
left outer join
(select date, status, sum(value) as sum_of_values
from table
where (date between mindate and maxdate) and name = product_name
group by date, status) as x
on y.date= x.date and y.status = x.status
order by x.date;

How to get average of the 'middle' values in a group?

I have a table that has values and group ids (simplified example). I need to get the average for each group of the middle 3 values. So, if there are 1, 2, or 3 values it's just the average. But if there are 4 values, it would exclude the highest, 5 values the highest and lowest, etc. I was thinking some sort of window function, but I'm not sure if it's possible.
http://www.sqlfiddle.com/#!11/af5e0/1
For this data:
TEST_ID TEST_VALUE GROUP_ID
1 5 1
2 10 1
3 15 1
4 25 2
5 35 2
6 5 2
7 15 2
8 25 3
9 45 3
10 55 3
11 15 3
12 5 3
13 25 3
14 45 4
I'd like
GROUP_ID AVG
1 10
2 15
3 21.6
4 45
Another option using analytic functions;
SELECT group_id,
avg( test_value )
FROM (
select t.*,
row_number() over (partition by group_id order by test_value ) rn,
count(*) over (partition by group_id ) cnt
from test t
) alias
where
cnt <= 3
or
rn between floor( cnt / 2 )-1 and ceil( cnt/ 2 ) +1
group by group_id
;
Demo --> http://www.sqlfiddle.com/#!11/af5e0/59
I'm not familiar with the Postgres syntax on windowed functions, but I was able to solve your problem in SQL Server with this SQL Fiddle. Maybe you'll be able to easily migrate this into Postgres-compatible code. Hope it helps!
A quick primer on how I worked it.
Order the test scores for each group
Get a count of items in each group
Use that as a subquery and select only the middle 3 items (that's the where clause in the outer query)
Get the average for each group
--
select
group_id,
avg(test_value)
from (
select
t.group_id,
convert(decimal,t.test_value) as test_value,
row_number() over (
partition by t.group_id
order by t.test_value
) as ord,
g.gc
from
test t
inner join (
select group_id, count(*) as gc
from test
group by group_id
) g
on t.group_id = g.group_id
) a
where
ord >= case when gc <= 3 then 1 when gc % 2 = 1 then gc / 2 else (gc - 1) / 2 end
and ord <= case when gc <= 3 then 3 when gc % 2 = 1 then (gc / 2) + 2 else ((gc - 1) / 2) + 2 end
group by
group_id
with cte as (
select
*,
row_number() over(partition by group_id order by test_value) as rn,
count(*) over(partition by group_id) as cnt
from test
)
select
group_id, avg(test_value)
from cte
where
cnt <= 3 or
(rn >= cnt / 2 - 1 and rn <= cnt / 2 + 1)
group by group_id
order by group_id
sql fiddle demo
in the cte, we need to get count of elements over each group_id by window function + calculate row_number inside each group_id. Then, if this count > 3 then we need to get middle of the group by dividing count by 2 and then get +1 and -1 element. If count <= 3, then we should just take all elements.
This works:
SELECT A.group_id, avg(A.test_value) AS avg_mid3 FROM
(SELECT group_id,
test_value,
row_number() OVER (PARTITION BY group_id ORDER BY test_value) AS position
FROM test) A
JOIN
(SELECT group_id,
CASE
WHEN count(*) < 4 THEN 1
WHEN count(*) % 2 = 0 THEN (count(*)/2 - 1)
ELSE (count(*) / 2)
END AS position_start,
CASE
WHEN count(*) < 4 THEN count(*)
WHEN count(*) % 2 = 0 THEN (count(*)/2 + 1)
ELSE (count(*) / 2 + 2)
END AS position_end
FROM test GROUP BY group_id) B
ON A.group_id=B.group_id
AND A.position >= B.position_start
AND A.position <= B.position_end
GROUP BY A.group_id
Fiddle link: http://www.sqlfiddle.com/#!11/af5e0/56
If you need to calculate the average values ​​for groups then you can do this:
SELECT CASE WHEN NUMBER_FIRST_GROUP <> 0
THEN SUM_FIRST_GROUP / NUMBER_FIRST_GROUP
ELSE NULL
END AS AVG_FIRST_GROUP,
CASE WHEN NUMBER_SECOND_GROUP <> 0
THEN SUM_SECOND_GROUP / NUMBER_SECOND_GROUP
ELSE NULL
END AS AVG_SECOND_GROUP,
CASE WHEN NUMBER_THIRD_GROUP <> 0
THEN SUM_THIRD_GROUP / NUMBER_THIRD_GROUP
ELSE NULL
END AS AVG_THIRD_GROUP,
CASE WHEN NUMBER_FOURTH_GROUP <> 0
THEN SUM_FOURTH_GROUP / NUMBER_FOURTH_GROUP
ELSE NULL
END AS AVG_FOURTH_GROUP
FROM (
SELECT
SUM(CASE WHEN GROUP_ID = 1 THEN 1 ELSE 0 END) AS NUMBER_FIRST_GROUP,
SUM(CASE WHEN GROUP_ID = 1 THEN TEST_VALUE ELSE 0 END) AS SUM_FIRST_GROUP,
SUM(CASE WHEN GROUP_ID = 2 THEN 1 ELSE 0 END) AS NUMBER_SECOND_GROUP,
SUM(CASE WHEN GROUP_ID = 2 THEN TEST_VALUE ELSE 0 END) AS SUM_SECOND_GROUP,
SUM(CASE WHEN GROUP_ID = 3 THEN 1 ELSE 0 END) AS NUMBER_THIRD_GROUP,
SUM(CASE WHEN GROUP_ID = 3 THEN TEST_VALUE ELSE 0 END) AS SUM_THIRD_GROUP,
SUM(CASE WHEN GROUP_ID = 4 THEN 1 ELSE 0 END) AS NUMBER_FOURTH_GROUP,
SUM(CASE WHEN GROUP_ID = 4 THEN TEST_VALUE ELSE 0 END) AS SUM_FOURTH_GROUP
FROM TEST
) AS FOO