I want to get the accumulated amount every hour in Oracle - database

`select
order_price,
To_char(to_date(order_date,'YYYY-MM-DD HH24:MI:SS'),'yyyymmdd hh24') as order_date,
SUM(order_price) OVER(ORDER BY To_char(to_date(order_date,'YYYY-MM-DD HH24:MI:SS'),'yyyymmdd
hh24')) as "bth"
from order_tbl
where seller_no=100
order by order_date;`
I got this result.
But the data I want to get is as follows.
20000 / 20220524 15 / 52500
13000 / 20220524 15 / 52500
19500 / 20220524 15 / 52500
19600 / 20220524 16 / 72100
222000 / 20220524 17 / 738700
and even if there is no data...
0 / 20220524 18 / 738700
0 / 20220524 19 / 738700
0 / 20220524 20 / 738700
.
.
.
.
0 / 20220525 10 / 738700
13600 / 20220525 11 / 787300
like this.
I want to get the order_date and bth for every hour even if there is no data.
It's too difficult for me, but how can I do?
i will remove order_price and add distinct later.

You can try this: (read the guide below)
db<>fiddle
WITH all_hour AS (
SELECT LEVEL AS hour
FROM dual
CONNECT BY LEVEL <= 24
),
all_date AS (
SELECT TO_CHAR(DATE'2022-05-24' + LEVEL - 1, 'YYYYMMDD') AS dt
FROM dual
CONNECT BY LEVEL <= (DATE'2022-05-27' - DATE'2022-05-24' + 1)
),
all_date_hour AS (
SELECT dt || ' ' || (CASE WHEN hour < 10 THEN '0' || TO_CHAR(hour) ELSE TO_CHAR(hour) END) AS order_date
FROM all_date
CROSS JOIN all_hour
),
your_order AS (
SELECT
order_price,
TO_CHAR(TO_DATE(order_date,'YYYY-MM-DD HH24:MI:SS'),'YYYYMMDD HH24') AS order_date,
seller_no
FROM order_tbl
),
your_sum AS (
SELECT adh.order_date, SUM(CASE WHEN yo.seller_no = 100 THEN yo.order_price ELSE 0 END) AS bth
FROM all_date_hour adh
LEFT JOIN your_order yo ON adh.order_date = yo.order_date
GROUP BY adh.order_date
)
SELECT order_date, SUM(bth) OVER(ORDER BY order_date) AS bth
FROM your_sum
ORDER BY order_date;
Summary:
(1) Table 1 : all_hour
includes numbers ranging from 1 to 24
(2) Table 2 : all_date
includes dates from '2022-05-24' to '2022-05-27'.
if your prefer range is '2022-01-01' to '2022-12-31', just simply change '2022-05-24'(Line 7 & 9) -> '2022-01-01', and '2022-05-27'(Line 9) -> '2022-12-31'
(3) Table 3 : all_date_hour
includes dates in the format, 'YYYYMMDD HH24', e.g. '20220524 01'
it is a result from cross joining the first and second table
(4) Table 4: your_order
same as your sample table, order_tbl, just reformatting the order_date in the format, 'YYYYMMDD HH24', e.g. '20220524 01'
(5) Table 5: your_sum (NOT ACCUMULATED YET)
simple summation of order_price, group by the order_date
left join is use here so that all dates from all_date_hour is included
any additional conditions can be added inside the case statement (Line 24)
for example, see (Line 24) SUM(CASE WHEN yo.seller_no = 100 AND youradditionalcondition = somecondition THEN yo.order_price ELSE 0 END)
(6) Final Select Query:
accumulated sum is done here using SUM() OVER(ORDER BY *yourexpr*)

You can use this query - explained later:
select
to_date('01/01/2022 00:00:00','dd/mm/yyyy hh24:mi:ss') + all_hours.sequence_hour/24 order_date_all,
order_price,
To_char(order_date, 'yyyymmdd hh24') as order_date_real,
SUM(order_price) OVER(ORDER BY To_char(order_date, 'yyyymmdd hh24')) as "bth"
from order_tbl ,
(SELECT LEVEL sequence_hour
FROM dual
CONNECT BY LEVEL <= 10) all_hours
where trunc((order_date(+)-to_date('01/01/2022 00:00:00','dd/mm/yyyy hh24:mi:ss'))*24) = all_hours.sequence_hour
and seller_no(+)=100
order by 1;
Explanation:
you don't need to perform To_char(to_date(order_date,'YYYY-MM-DD HH24:MI:SS'),'yyyymmdd hh24') - it means that you take "order_date" which is already date and convert it again to date. It is not needed
I have added maximum number of hours - it is the subquery all_hours. Just for the example it is limited to 10, you can change it to any other value
I have added starting point of time from which you want to display the data "01/01/2022" - change it if you want. Pay attention that it appears in 2 places.
I have added an outer join with order_tbl - pay attention for "(+)" in where conditions. If you want to add additional conditions on order_tbl - remember to add the (+) on the right side of the column as I did with "seller_no"

Related

Increasing a Column value by a % range

I have a table in SQL Server 2012. The following query works great:
SELECT TOP 300 [ObjectID], [tbh_Objects].Title, [Discount], [tbh_Section].Title
FROM [ECom].[dbo].[tbh_Objects]
INNER JOIN [tbh_Section] ON tbh_Objects.SectionID = tbh_Section.SectionID
ORDER BY tbh_Objects.AddedDate DESC
I want to fire a query which increases the discount value to a random % in the range of 5-10 for all 300 rows at once. So for eg: If DIscount of ObjectID=500 is 30, and the random value between 5 and 10 is "6", I want it to become 30+6%of30 for ObjectID=500.
Similarly for Object ID=230, let's say discount is 20 and the random value is 8, I want it as 20+8%of20.
The end result of the Discount should always be a whole number and not a decimal, so automatically rounds off.
Is this possible in SQL Server? How?
You need random integers and a Modulus (%) operator. A possible approach to generate a random integers is using a combination of NEWID() and CHECKSUM() functions. The following simplified example is a possible solution to your problem:
SELECT
Discount,
RandomPercent,
CONVERT(int, (Discount * (100.0 + RandomPercent) / 100)) AS NewDiscount
FROM (
SELECT Discount, (ABS(CHECKSUM(NEWID()) % 6) + 5) AS RandomPercent
FROM (VALUES (30), (20), (50), (70), (11), (21), (13), (15), (1), (6)) v (Discount)
) t
Result:
Discount RandomPercent NewDiscount
----------------------------------
30 7 32
20 5 21
50 6 53
70 10 77
11 9 11
21 9 22
13 8 14
15 10 16
1 6 1
6 5 6
If you need an UPDATE statement:
;WITH UpdateCTE AS (
SELECT TOP 300 o.[Discount]
FROM [ECom].[dbo].[tbh_Objects] o
INNER JOIN [tbh_Section] s ON o.SectionID = s.SectionID
ORDER BY o.AddedDate DESC
)
UPDATE UpdateCTE
SET [Discount] = CONVERT(int, (o.[Discount] * (100.0 + (ABS(CHECKSUM(NEWID()) % 6) + 5)) / 100))
If you want to round the new discounts before the integer conversion, use ROUND():
SET [Discount] = CONVERT(
int,
ROUND(o.[Discount] * (100.0 + (ABS(CHECKSUM(NEWID()) % 6) + 5)) / 100, 0)
)

Accumulative Update for previous records

I have table that shows these information
Month NewClients OnHoldClients
5-2017 10 2
6-2017 16 4
7-2017 11 1
8-2017 15 6
9-2017 18 7
I am trying to find the accumulative total for each month
which is
(NewClients - OnHoldClients) + Previous Month Total
Something like this
Month NewClients OnHoldClients Total
5-2017 10 2 8
6-2017 16 4 20
7-2017 11 1 30
8-2017 15 6 39
9-2017 18 7 50
the query i tried to build was something like this but I think should be an easier way to do that
UPDATE MyTable
SET Total = (SELECT TOP 1 Total FROM MyTable B WHERE B.Month < A.Month) + NewClients - OnHoldClients
FROM MyTable A
Before we begin, note the mere fact that you're facing such calculative problem is a symptom that maybe you don't have the best possible design. Normally for this purpose calculated values are being stored along the way as the records are inserted. So i'd say you'd better have a total field to begin with and calculate it as records amass.
Now let's get down to the problem at hand. i composed a query which does that nicely but it's a bit verbose due to recursive nature of the problem. However, it yields the exact expected result:
DECLARE #dmin AS date = (SELECT min(mt.[Month]) from dbo.MyTable mt);
;WITH cte(_Month, _Total) AS (
SELECT mt.[Month] AS _Month, (mt.NewClients - mt.OnHoldClients) AS _Total
FROM dbo.MyTable mt
WHERE mt.[Month] = #dmin
UNION ALL
SELECT mt.[Month] AS _Month, ((mt.NewClients - mt.OnHoldClients) + ccc._Total) AS _Total
FROM dbo.MyTable mt
CROSS APPLY (SELECT cc._Total FROM (SELECT c._Total,
CAST((row_number() OVER (ORDER BY c._Month DESC)) AS int) as _Rank
FROM cte c WHERE c._Month < mt.[Month]) as cc
WHERE cc._Rank = 1) AS ccc
WHERE mt.[Month] > #dmin
)
SELECT c._Month, max(c._Total) AS Total
FROM cte c
GROUP BY c._Month
It is a recursive CTE structure that goes about each record all along the way to the initial month and adds up to the final Total value. This query only includes Month and Total fields but you can easily add the other 2 to the list of projection.
Try this
;WITH CTE([Month],NewClients,OnHoldClients)
AS
(
SELECT '5-2017',10,2 UNION ALL
SELECT '6-2017',16,4 UNION ALL
SELECT '7-2017',11,1 UNION ALL
SELECT '8-2017',15,6 UNION ALL
SELECT '9-2017',18,7
)
SELECT [Month],
NewClients,
OnHoldClients,
SUM(MonthTotal)OVER( ORDER BY [Month]) AS Total
FROM
(
SELECT [Month],
NewClients,
OnHoldClients,
SUM(NewClients-OnHoldClients)OVER(PArtition by [Month] Order by [Month]) AS MonthTotal
FROM CTE
)dt
Result,Demo:http://rextester.com/DKLG54359
Month NewClients OnHoldClients Total
--------------------------------------------
5-2017 10 2 8
6-2017 16 4 20
7-2017 11 1 30
8-2017 15 6 39
9-2017 18 7 50

Calculate and display % change for 2 rows in 3rd row

I have a table to compare Monthly Sales data on the Year-to-Year basis. This table will get updated every month by replacing the records with sales data for last month-same year and the year before.
Month_Year || Sale_Vol || #Sales || Average_Price
Mar-17 || 1250 || 25 || 50
Mar-18 || 900 || 20 || 45
I need to display % change ((Mar-18)/(Mar-17) - 1)*100 in an additional row as shown below
Month_Year || Sale_Vol || #Sales || Average_Price
Mar-17 || 1250 || 25 || 50
Mar-18 || 900 || 15 || 60
Prcnt_Chng || -28 || -40 || 20
I am not able to figure out how to achieve this. This is what I have written so far but it's not elegant.
SELECT 'Percentage Change',NULL,
100.0 * ((select * from Tbl where [Month_Year] = left(CONVERT(VARCHAR(8),DATEADD(mm,-1,DATEADD(mm,DATEDIFF(mm,0,GETDATE()),0)),112),6))-(select * from Tbl where [Month_Year] = left(CONVERT(VARCHAR(8),DATEADD(mm,-1,DATEADD(mm,DATEDIFF(mm,0,GETDATE())-12,0)),112),6)))/(select * from Tbl where [Month_Year] = left(CONVERT(VARCHAR(8),DATEADD(mm,-1,DATEADD(mm,DATEDIFF(mm,0,GETDATE()),0)),112),6));
And also, I get the error:
Only one expression can be specified in the select list when the subquery is not introduced with EXISTS.
first of all, reason of your error is, counts are mismatched. i.e 100 is single substance, which is out of subquery. But in-side of subuquery, you return more than one column. if you correct that, you'll you get table(may be or not which will your resulting table)
I trying to query for you..!
Edited:
select * from yourtable -- get previous table
union
select 'Prcnt_Chng ',( -- to get calculated row
((((select Sale_Vol from yourtable where Month_Year = 'Mar-18')/ --value of Sale_Vol of mar 18
(select Sale_Vol from yourtable where Month_Year = 'Mar-17'))-1) --value of Sale_Vol of mar 18. divided and sub by 1
100), -- mul by 100
((((select [#Sales] from yourtable where Month_Year = 'Mar-18')/ --value of [#Sales] of mar 18
(select [#Sales] from yourtable where Month_Year = 'Mar-17'))-1) --value of [#Sales] of mar 18. divided and sub by 1
100), -- mul by 100
((((select Average_Price from yourtable where Month_Year = 'Mar-18')/ --value of Average_Price of mar 18
(select Average_Price from yourtable where Month_Year = 'Mar-17'))-1) --value of Average_Price of mar 18. divided and sub by 1
100)) -- mul by 100
Considering that you have only two rows in your table, you could try this query
;with cte as (
select
*, rn = row_number() over (order by Month_Year)
from
myTable
)
select * from myTable
union all
select
'Prcnt_Chng', 100 * b.Sale_Vol / a.Sale_Vol - 100
, 100 * b.[#Sales] / a.[#Sales] - 100
, 100 * b.Average_Price / a.Average_Price - 100
from
cte a
join cte b on a.rn + 1 = b.rn
where a.rn = 1

Select Top 1 In SQL Server

Please help me in select top 1
The data like this
Code Amp Price
-----------------------
00001 10 1000
00002 75-100 1500
00003 50-60 1200
00004 15 1100
Note : datatype for column Amp is VarChar
I want to select with Amp 75 and I want get the price is 1500
So I use this statement:
SELECT TOP 1 *
FROM Cable
WHERE (Amp <= '75')
ORDER BY Amp DESC
but the result price I get is 1200 is record with code : 00003 (wrong), actually I want the result is code : 00002 and the price is 1500
But if I want to select with Amp 76 the result is true with the syntax :
SELECT TOP 1 *
FROM Cable
WHERE (Amp <= '75')
ORDER BY Amp DESC
What is the true select for my case? Please help me
Just about any parse/split function will do, and combined with a Cross Apply, it becomes a small matter
-- Easy to do without a parse function
Declare #Cable table (Code varchar(25),Amp varchar(50),Price int)
Insert Into #Cable values
('00001','10', 1000),
('00002','75-100',1500),
('00003','50-60', 1200),
('00004','15', 1100)
Select Top 1 A.*
From #Cable A
Cross Apply [dbo].[udf-Str-Parse](A.Amp,'-') B
Where RetVal<=76 --<< Notice we are testing for 76
Order By Price Desc
Returns
Code Amp Price
00002 75-100 1500
The UDF if interested
CREATE FUNCTION [dbo].[udf-Str-Parse] (#String varchar(max),#Delimiter varchar(25))
Returns Table
As
Return (
with cte1(N) As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
cte2(N) As (Select Top (IsNull(DataLength(#String),0)) Row_Number() over (Order By (Select NULL)) From (Select N=1 From cte1 a,cte1 b,cte1 c,cte1 d) A ),
cte3(N) As (Select 1 Union All Select t.N+DataLength(#Delimiter) From cte2 t Where Substring(#String,t.N,DataLength(#Delimiter)) = #Delimiter),
cte4(N,L) As (Select S.N,IsNull(NullIf(CharIndex(#Delimiter,#String,s.N),0)-S.N,8000) From cte3 S)
Select RetSeq = Row_Number() over (Order By A.N)
,RetVal = LTrim(RTrim(Substring(#String, A.N, A.L)))
From cte4 A
);
--Orginal Source http://www.sqlservercentral.com/articles/Tally+Table/72993/
--Much faster than str-Parse, but limited to 8K
--Select * from [dbo].[udf-Str-Parse-8K]('Dog,Cat,House,Car',',')
--Select * from [dbo].[udf-Str-Parse-8K]('John||Cappelletti||was||here','||')
If you have to work with this existing datatype and table structure then below query may work for you.
SELECT TOP 1 *
FROM Cable
WHERE (SUBSTRING(Amp,1,IIF((CHARINDEX('-',Amp)-1)>0,(CHARINDEX('-',Amp)-1),0 ) ) <=75)
ORDER BY Amp DESC
The problem is that SQL Server is not going to sort a varchar column like an int.
Sorting issue example:
select *
from (
select '125' as nbr
union all
select '24' as nbr
) as a
order by a.nbr asc
1 is less than 2 (the first character in each nbr), so it will sort thinking that 125 < 24 (not true), even though it looks pretty simple to anyone that 24 should show up first, which is how it would be sorted if the datatype of the column were an int.
What needs to happen is to split the amp column into ranges, or max and min. Using the - as the delimeter, you can use charindex to split the numbers up and cast them as ints instead.
Sample Data Setup:
declare #cable table
(
code char(5) not null
, amp varchar(10) not null
, price int not null
)
insert into #cable
values
('00001','10' ,10000),
('00002','75-100' ,15000),
('00003','50-60' ,12000),
('00004','15' ,11000)
Answer:
declare #amp_nbr int = 75
select top 1 *
from (
select c.code
, cast(iif(charindex('-', c.amp, 0) > 0, left(c.amp, charindex('-', c.amp, 0) - 1), c.amp) as int) as amp_min
, cast(iif(charindex('-', c.amp, 0) > 0, right(c.amp, len(c.amp) - charindex('-', c.amp, 0)), c.amp) as int) as amp_max
, c.price
from #cable as c
) as a
where 1=1
and #amp_nbr between a.amp_min and a.amp_max
order by a.amp_min desc
After that, a simple between constraint in the where clause will do the trick.
Thank's for all answers. I decides to type all data and change it with two field.
Code Amp1 Amp2 Price
00001 10 10 1000
00002 75 100 1500
00003 50 60 1200
00004 15 15 1100
The single value i type same in field Amp2 and then i use the syntax :
SELECT * FROM Cable WHERE (65 BETWEEN Amp1 AND Amp2)
If you are using SQL Server 2008 and later, try something like this:
SELECT TOP 1 *
FROM Cable
WHERE isnumeric(left(Amp, 2)) = 1 and cast(left(Amp, 2) as int) <= 75
and Price = 1500
ORDER BY Amp DESC
Note: This will work only if you have no records with Amp less than 10.

Group by datetime minutes - Show minutes with 0 entries in database?

I'm trying to write a T-SQL query that looks for amount of entries in a table, over a certain period of time, and then group them by minute (entries/minute).
But I cant get it to show the minutes with 0 entries.
My query looks like:
select
[Minute],
Files
from
(
select
(DATEDIFF(MINUTE, '2017-02-02 17:00:00.000', RegTime) / 1) as [Minute] ,
count(*) as Files
from TransferLog
WHERE RegTime BETWEEN '2017-02-02 17:00:00.000' AND '2017-02-03 04:00:00.000'
group BY
(DATEDIFF(MINUTE, '2017-02-02 17:00:00.000', RegTime) / 1)
) x order by [Minute]
Any Ideas?
Edit1. Excepted output would show minutes with no entries in the table. Also, the query is rather unusable when this occurs since I can't know which minute had no data. For example if I want data from 800 minutes but 20 of the minutes had no data written to the table this would only show 780 minutes worth of data. Is there anyway to sort the data or modify the query for this purpose?
Expected output:
Minute | Files
0 685
1 0
2 672
3 0
4 415
5 434
6 746
-
Current Output:
Minute | Files
0 685
1 672
2 415
3 434
4 746
You can create a temp table holding all the minutes you need from 1 to the maximum minute you have (for example using this tecnique), then you can left join your data table with the temp table replacing null values with zeros.
Thus you should have all the minutes listed with your values where you have data and zeros elsewhere.
Here is the code:
declare #max_minute int
declare #dataTable table ([Minute] int, Files int)
--insert your data into a temp table
insert into #dataTable
select [Minute], Files
from
(select
(DATEDIFF(MINUTE, '2017-02-02 17:00:00.000', RegTime) / 1) as [Minute] ,
count(*) as Files
from TransferLog
WHERE RegTime BETWEEN '2017-02-02 17:00:00.000' AND '2017-02-03 04:00:00.000'
group BY
(DATEDIFF(MINUTE, '2017-02-02 17:00:00.000', RegTime) / 1)
) x order by [Minute]
--calculate maximum minute from your data
select #max_minute = max([Minute]) from #dataTable
;
WITH gen AS (
SELECT 1 AS Minute
UNION ALL
SELECT Minute+1 FROM gen WHERE Minute+1<=#max_minute
)
SELECT M.Minute, isnull(D.Files,0) as Files
FROM gen M left join #dataTable D on M.Minute = D.Minute
option (maxrecursion 10000)

Resources