Create buckets in snowflake without specified max value - snowflake-cloud-data-platform

At the moment I'm using this in my query to get order value bins:
case
when (oi.pricePerItemExVat * oia.quantity) >= 0 and (oi.pricePerItemExVat * oia.quantity) <= 10000 then '0-10000'
when (oi.pricePerItemExVat * oia.quantity) >10000 and (oi.pricePerItemExVat * oia.quantity) <= 20000 then '10001-20000'
when (oi.pricePerItemExVat * oia.quantity) >20000 and (oi.pricePerItemExVat * oia.quantity) <= 30000 then '20001-30000'
when (oi.pricePerItemExVat * oia.quantity) >30000 and (oi.pricePerItemExVat * oia.quantity) <= 40000 then '30001-40000'
when (oi.pricePerItemExVat * oia.quantity) >40000 and (oi.pricePerItemExVat * oia.quantity) <=50000 then '40001-50000'
when (oi.pricePerItemExVat * oia.quantity) >50000 then 'over 50000'
end as orderVolumeBins
Instead I want to use this function:
WIDTH_BUCKET( <expr> , <min_value> , <max_value> , <num_buckets> )
Which in my case could be
WIDTH_BUCKET( <volumeBins> , <0> , <50000> , <5> )
But that wouldn't give me the bin with all orders that have volumes above 50000. Does anyone know if there's a variant where this is possible?

But that wouldn't give me the bin with all orders that have volumes above 50000
WIDTH_BUCKET:
When an expression falls outside the range, the function returns:
0 if the expression is less than min_value.
num_buckets + 1 if the expression is greater than or equal to max_value.
For 50,001:
SELECT WIDTH_BUCKET( 50001 , 0 , 50000 , 5 )
-- 6
EDIT:
Can I name the bins in any way? Now I'm getting 1,2,3,4,5,6 instead of 10k-20k etc
SELECT
CASE WIDTH_BUCKET((oi.pricePerItemExVat * oia.quantity), 0, 50000,5)
WHEN 1 THEN '0-10000'
WHEN 2 THEN '10001-20000'
-- ...
WHEN 5 THEN ...
WHEN 6 THEN '>=50000'
END

Related

How to convert HHMMSS to seconds using T-SQL

SQL server table msdb.dbo.sysjobhistory returns run_time and run_duration as INTEGER value formatted as HHMMSS.
How to convert it to seconds?
Example:
163135 (16:31:35) becomes 59495 (seconds)
Meanwhile I figured out this formula:
SELECT DATEDIFF(SECOND, '00:00:00', FORMAT(run_duration, '00:00:00'))
FROM msdb.dbo.sysjobhistory
You can use modulo and integer division to separate the hours, minutes, and seconds, multiply by number of seconds in each result, then sum.
DECLARE #hms int = 163135;
SELECT #hms / 10000 * 3600
+ #hms % 10000 / 100 * 60
+ #hms % 100;
59495
To use this as a view, it's really not any different:
CREATE VIEW dbo.viewname
AS
SELECT <other cols>, run_duration,
run_duration_s = run_duration / 10000 * 3600
+ run_duration % 10000 / 100 * 60
+ run_duration % 100
FROM msdb.dbo.sysjobhistory
WHERE ...
If you don't like math so much, you can treat it like a string:
DECLARE #hms int = 163135;
DECLARE #s char(6) = RIGHT(CONCAT('000000', #hms), 6);
SELECT LEFT(#s, 2) * 60 * 60
+ SUBSTRING(#s, 3, 2) * 60
+ RIGHT(#s, 2);
59495
However, this latter solution may need some tweaking if you could have durations > 99 hours, since now the string will be 7 digits. Maybe safer to use:
DECLARE #hms int = 163135;
DECLARE #s char(24) = RIGHT(CONCAT(REPLICATE('0',24), #hms), 24);
SELECT LEFT(#s, 20) * 60 * 60
+ SUBSTRING(#s, 21, 2) * 60
+ RIGHT(#s, 2);
24 is a ludicrous example, but safe. The job would have had to start in 1990 to hit 10 digits today.

I want to get the accumulated amount every hour in Oracle

`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"

Getting the percent of a number

I think it is some funky rounding issue going on here, but I don't know how the inner sql works. So maybe someone can help.
SELECT COUNT(Id) TotalReferrals,
SUM(CASE WHEN NHSCommunicationNeed1_Id IS NOT NULL THEN 1 ELSE 0 END) NHSCommNeed1Total,
SUM(CASE WHEN NHSCommunicationNeed2_Id IS NOT NULL THEN 1 ELSE 0 END) NHSCommNeed2Total,
SUM(CASE WHEN NHSCommunicationNeed3_Id IS NOT NULL THEN 1 ELSE 0 END)
NHSCommNeed3Total
INTO #Totals
FROM DDS.Referrals
Gives me the following result set
TotalReferrals NHSCommNeed1Total NHSCommNeed2Total NHSCommNeed3Total
1008 1008 508 508
Then when I do this
SELECT
SUM((NHSCommNeed1Total / TotalReferrals) * 100) NHSCommNeed1Percent,
SUM((NHSCommNeed2Total / TotalReferrals) * 100) NHSCommNeed2Percent,
SUM((NHSCommNeed3Total / TotalReferrals) * 100) NHSCommNeed3Percent
FROM #Totals
I get this?
NHSCommNeed1Percent NHSCommNeed2Percent NHSCommNeed3Percent
100 0 0
Can someone explain what is going on and how do I fix it
The result of NHSCommNeed1Total / (TotalReferrals is calculated as integer, therefore it ends up to 0. To enforce making it decimal or double just multiply it with 1.0:
SELECT
SUM((NHSCommNeed1Total / (TotalReferrals * 1.0)) * 100) NHSCommNeed1Percent,
SUM((NHSCommNeed2Total / (TotalReferrals * 1.0)) * 100) NHSCommNeed2Percent,
SUM((NHSCommNeed3Total / (TotalReferrals * 1.0)) * 100) AS NHSCommNeed3Percent
FROM #Totals
Yes sure, no need for two statements, you can use INSERT INTO SELECT:
WITH CTE
AS
(
SELECT COUNT(Id) TotalReferrals,
SUM(CASE WHEN NHSCommunicationNeed1_Id IS NOT NULL THEN 1.0 ELSE 0 END) NHSCommNeed1Total,
SUM(CASE WHEN NHSCommunicationNeed2_Id IS NOT NULL THEN 1.0 ELSE 0 END) NHSCommNeed2Total,
SUM(CASE WHEN NHSCommunicationNeed3_Id IS NOT NULL THEN 1.0 ELSE 0 END)
NHSCommNeed3Total
FROM DDS.Referrals
)
INSERT INTO SomeOtherTable(NHSCommNeed1Total,NHSCommNeed2Total ,NHSCommNeed3Total)
SELECT
SUM((NHSCommNeed1Total / (TotalReferrals * 1.0)) * 100) AS NHSCommNeed1Percent,
SUM((NHSCommNeed2Total / (TotalReferrals * 1.0)) * 100) NHSCommNeed2Percent,
SUM((NHSCommNeed3Total / (TotalReferrals * 1.0)) * 100) AS NHSCommNeed3Percent
FROM CTE;
If you want an all in one statement this should work
SELECT
SUM(CASE WHEN NHSCommunicationNeed1_Id IS NOT NULL THEN 1.0 ELSE 0.0 END) / COUNT(Id) as NHSCommNeed1Percent,
SUM(CASE WHEN NHSCommunicationNeed2_Id IS NOT NULL THEN 1.0 ELSE 0.0 END) / COUNT(Id) as NHSCommNeed2Percent,
SUM(CASE WHEN NHSCommunicationNeed3_Id IS NOT NULL THEN 1.0 ELSE 0.0 END) / COUNT(Id) as NHSCommNeed3Percent
FROM DDS.Referrals

SQL replace duplicate record with value based on condition

I am trying to create a report that shows a single item(store_Product) purchased by store location(store_ID).
There are total 3 types of store_Product: product_A, product_B, and product_C.
There are over a thousand unique store_ID.
My issue is that some unique store_ID have multiple store_Products and I need to shrink the query so it returns only unique store_IDs
These are the rules of condition:
if a store has product_B and any other product, one unique store_ID record would have product_B as the store_Product value
if a store has product_A and product_C, one unique store_ID record would have product_A as the store_Product value
if a store has only one record then store_Product remains unchanged.
So the example below explains what I have on the left and what I want it to look like on the right:
I am using SQL server 2014 building the report in SSRS.
Really appreciate any help!
EDIT: What i have so far:
SELECT [store_ID]
,MIN(CASE WHEN store_product = 'product_B' THEN '1_product_B'
WHEN store_product = 'product_A' THEN '2_product_A'
ELSE '3_product_C' END) AS 'Prob_Group'
,CASE
WHEN (DATEDIFF(MINUTE, Opened_Time, GETDATE())) * 1.0 / 60 < '24'
THEN '0 - 24 HRs'
WHEN (DATEDIFF(MINUTE, Opened_Time, GETDATE())) * 1.0 / 60 >= '24'
AND (DATEDIFF(MINUTE, Opened_Time, GETDATE())) * 1.0 / 60 < '48'
THEN '24 - 48 HRs'
WHEN (DATEDIFF(MINUTE, Opened_Time, GETDATE())) * 1.0 / 60 >= '48'
AND (DATEDIFF(MINUTE, Opened_Time, GETDATE())) * 1.0 / 60 < '72'
THEN '48 - 72 HRs'
ELSE 'Over 168 HRs'
END AS 'Hour_Range'
FROM myTable
WHERE *filters*
GROUP BY [store_ID]
,CASE
WHEN (DATEDIFF(MINUTE, Opened_Time, GETDATE())) * 1.0 / 60 < '24'
THEN '0 - 24 HRs'
WHEN (DATEDIFF(MINUTE, Opened_Time, GETDATE())) * 1.0 / 60 >= '24'
AND (DATEDIFF(MINUTE, Opened_Time, GETDATE())) * 1.0 / 60 < '48'
THEN '24 - 48 HRs'
WHEN (DATEDIFF(MINUTE, Opened_Time, GETDATE())) * 1.0 / 60 >= '48'
AND (DATEDIFF(MINUTE, Opened_Time, GETDATE())) * 1.0 / 60 < '72'
THEN '48 - 72 HRs'
ELSE 'Over 168 HRs'
END
ORDER BY [store_ID]
I'm being very sneaky here:
SELECT store_id,
MIN( CASE WHEN store_Product = "product_B" THEN "1_product_B"
WHEN store_Product = "product_A" THEN "2_product_A"
ELSE "3_product_C"
END) as Product
FROM YourTable
GROUP BY store_id
So if the store has product_B, the result would be 1_product_B, not matter if are product_A and product_C.
Same if not product_B the minimum between 2_product_A and 3_product_C is 2_product_A also works if only has product_A.
After you have the result you can remove the first two characters
WITH cte as (
SELECT store_id,
MIN( CASE WHEN store_Product = "product_B" THEN "1_product_B"
WHEN store_Product = "product_A" THEN "2_product_A"
ELSE "3_product_C"
END) as Product
FROM yourTable
GROUP BY store_id
)
SELECT store_id, RIGHT(Product, LEN(Product) - 2) AS fixProductName
FROM CTE

Round *UP* to the nearest 100 in SQL Server

Is it possible to easily round a figure up to the nearest 100 (or 1000, 500, 200 etc.) in SQL Server?
So:
720 -> 800
790 -> 800
1401 -> 1500
The following should work. After reading your question, I'm not exactly sure what you want 100 to return. For this 100 returns 100.
select floor((X + 99) / 100) * 100;
This gives the following results:
0 -> 0
1 -> 100
99 -> 100
100 -> 100
101 -> 200
For rounding Up to the nearest thousand, try the following:-
select round(YourValue, -3)
One option would be to use the CEILING() function like this:
SELECT CEILING(#value/100.0) * 100
You may need to convert your value to a decimal first depending on its type.
Use CEILING function to round a figure up
DECLARE #Number DECIMAL, #RoundUp DECIMAL
SET #RoundUp = 100
SET #Number = 720
SELECT CEILING(#Number/#RoundUp)*#RoundUp
Try this:
select round(#value , -2);
It is very simple to round a number to any multiple of nearest 10 by using simply the ROUND function
for ex:
SELECT ROUND(number/1000,2)*1000
This will give you the nearest thousandth value.
This will work for the values with decimal also.
select floor((ceiling (#value) + 99) / 100) * 100;
There's no native function that will do this, but there are any number of simple math tricks that will. An example:
DECLARE #Foo int
SET #Foo = 720
print #Foo
print (#Foo + 100) % 100
PRINT #Foo - (#Foo + 100) % 100
You can use this code, assuming your amount is an int. If not you will need to cast, so you get integer division.
If amount % 100 != 0 Then
roundedAmount = ((amount / 100) * 100) + 100
Else
roundedAmount = amount
You might want to package this into a user defined function.
A generic solution - Use MOD to find the last 100th place and then add 100 to the result.
select (720 - MOD(720,100)) + 100 from dual;
If you need the next 80th place, just replace any "100" with "80".
In addition to Gray's answer,
I'd use the following inline function:
CREATE FUNCTION dbo.udf_RoundNearest
(
#Number bigint,
#RoundNearest bigint,
#Direction int
)
RETURNS TABLE AS
RETURN
SELECT CASE WHEN #RoundNearest>=#Number THEN #Number
ELSE
(
(#Number + CASE
WHEN #Direction = 0 --Round Down
THEN 0
ELSE CASE WHEN #Number % #RoundNearest = 0 THEN 0 ELSE #RoundNearest END
END) / #RoundNearest) * #RoundNearest
END Number
Parameter Definition:
#Number - the number you need to round
#RoundNearest 10th, 100th , 1000th etc
#Direction 0-> round down, 1-> round up
using the function:
SELECT * FROM dbo.udf_RoundNearest (1965,100,1) --> 2000
SELECT * FROM dbo.udf_RoundNearest (1359,100,0) --> 1300
SELECT * FROM dbo.udf_RoundNearest (1999,10,0) --1990
SELECT * FROM dbo.udf_RoundNearest (80,100,0) --> 80 (if the #number parameter is less or equal the #RoundNearest parameter the result will be the #number itself
it can also be used as apply it versus a table
such as:
;with tmp (Value) as
(select 1236 union all select 6584 union all select 9999)
select t.*, fn.Number
from tmp t
cross apply dbo.udf_RoundNearest (Value,100,0) fn
/*Result Set
Value Number
1236 1200
6584 6500
9999 9900*/
This worked fine for me.
Round(#value/100, 0) * 100
It works fine for integer value:
#roundUpValue = ((#intValue / 1000) + 1) * 1000
#roundDownValue = (#intValue / 1000) * 1000
For example
declare #intValue as int = 1934
select ((#intValue / 1000) + 1) * 1000 as roundUp
select (#intValue / 1000) * 1000 as roundDown
If you want to round up to the nearest 500 then
select ((#intValue / 500) + 1) * 500 as roundUp
Select round(value/100,0)*100
Whatever number you want to round to, just post that instead of 100 here.
i have create a function in mssql it can help you
CREATE function dbo.roundup
(
#numbr decimal(18,2),
#frac decimal(18,2)
)
RETURNS decimal(18,2)
AS
BEGIN
DECLARE #result decimal(18,2)
set #result = ceiling(#numbr/#frac)*#frac
RETURN #result
END
GO

Resources