is it possible to use a function property in a select statement?
I have a forex function in BigQuery like this, but it'd be SO much easier if we can use a rlfm[<column name>] style accessor
Current Approach
CREATE OR REPLACE FUNCTION
reference.fxFromTo(d TIMESTAMP, fromRegion STRING, toRegion STRING, value FLOAT64)
RETURNS FLOAT64 AS (
(SELECT
CASE fromRegion
WHEN 'AUD' THEN value / rlfm.AUD
WHEN 'USD' THEN value / rlfm.USD
WHEN 'EUR' THEN value / rlfm.EUR
WHEN 'SGD' THEN value / rlfm.SGD
WHEN 'CAD' THEN value / rlfm.CAD
WHEN 'GBP' THEN value / rlfm.GBP
WHEN 'NZD' THEN value / rlfm.NZD
ELSE -404
END AS fx
FROM reference.fx_monthly as rlfm
WHERE Date = d)
);
Preferred Approach
CREATE OR REPLACE FUNCTION
reference.fxFromTo(d TIMESTAMP, fromRegion STRING, toRegion STRING, value FLOAT64)
RETURNS FLOAT64 AS (
(SELECT value / rlfm[fromRegion] AS fx
FROM reference.fx_monthly AS rlfm
WHERE Date = d)
);
Instead of rlfm[column_name] notation, you might consider below using regular expression.
CREATE TEMP TABLE fx_monthly AS
SELECT TIMESTAMP '2023-01-01' Date, 10.0 AUD, 20.0 USD, 30.0 EUR, 40.0 SGD, 50.0 CAD, 60.0 GBP, 70.0 NZD
UNION ALL
SELECT TIMESTAMP '2023-01-02' Date, 11.1 AUD, 21.2 USD, 31.2 EUR, 41.34 SGD, 51.43 CAD, 61.42 GBP, 71.43 NZD;
CREATE TEMP FUNCTION fxFromTo (d TIMESTAMP, fromRegion STRING, value FLOAT64)
RETURNS FLOAT64 AS ((
SELECT value / SAFE_CAST(REGEXP_EXTRACT(TO_JSON_STRING(t), FORMAT('"%s":([0-9.]+)', fromRegion)) AS FLOAT64)
FROM (
SELECT * FROM fx_monthly WHERE Date = d
) t
));
SELECT fxFromTo('2023-01-02', 'USD', 100.00);
+-------------------+
| f0_ |
+-------------------+
| 4.716981132075472 |
+-------------------+
Related
I am having an issue with this SQL code. I am trying to divide sum/ by the count of a date. I keep getting the grouping expressions sequence is empty, and 'TableBlank.rep_date' is not an aggregate function error. I have tried with ORDER BY and GROUP BY statements and I'm still getting the error.
SELECT
rep_date,
1 AS AssignRank,
StepCompSum,
'StepCompA' AS FunCat,
CAST( StepCompSum / COUNT( rep_date ) AS double ) AS ParticRate
FROM
TableBlank
WHERE
FunCat = 'StepCompA'
UNION
SELECT
rep_date,
2 AS AssignRank,
StepCompSum,
'StepCompB' AS FunCat,
CAST( StepCompSum / COUNT( rep_date ) AS double ) AS ParticRate
FROM
TableBlank
WHERE
FunCat = 'StepCompB'
UNION
SELECT
rep_date,
3 AS AssignRank,
StepCompSum,
'StepCompC' AS FunCat,
CAST( StepCompSum / COUNT( rep_date ) AS double ) AS ParticRate
FROM
TableBlank
WHERE
FunCat = 'StepCompC'
GROUP BY
rep_date,
StepCompSum
Difficult to know without sample data, but this might work:
SELECT
rep_date
, 2 AS AssignRank
, StepCompSum
, FunCat
, CAST(StepCompSum / (COUNT(rep_date) OVER (PARTITION BY FunCat)) AS double) AS ParticRate
FROM TableBlank
Although, I suspect what you are wanting from the CAST is to get the result to not be an integer. Here's what you'll get:
2 / 3 = 1 (because, integer math)
cast(1 as double) = 1.000
when you probably expect 0.667
You need to either cast the values first, or just include a value of the appropriate type in the expression:
StepCompSum * 1.0 / (COUNT(rep_date) OVER (PARTITION BY FunCat))
I have a question related to my previous one.
What I have is a database that looks like:
category price date
-------------------------
Cat1 37 2019-03
Cat2 65 2019-03
Cat3 34 2019-03
Cat1 45 2019-03
Cat2 100 2019-03
Cat3 60 2019-03
This db has hundred of categories and comes from another one that has different attributes for each observation.
With this code:
WITH table AS
(
SELECT
category, price, date,
substring(date, 1, 4) AS year,
substring(date, 6, 2) as month
FROM
original_table
WHERE
(year = "2019" or year = "2020")
AND (month = "03")
AND product = "XXXXX"
ORDER BY
anno
)
-- I get this from a bigger table, but prefer to make small steps
-- that anyone in the fute can understand where this comes from as
-- the original table is expected to grow fast
SELECT
category,
ROUND(1.0 * next_price/ price - 1, 2) Pct_change,
SUBSTR(Date, 1, 4) || '-' || SUBSTR(next_date, 1, 4) Period,
tipo_establecimiento
FROM
(SELECT
*,
LEAD(Price) OVER (PARTITION BY category ORDER BY year) next_price,
LEAD(year) OVER (PARTITION BY category ORDER BY year) next_date,
CASE
WHEN (category_2>= 35) AND (category_2 <= 61)
THEN 'S'
ELSE 'N'
END 'tipo_establecimiento'
FROM
table)
WHERE
next_date IS NOT NULL AND Pct_change >= 0
ORDER BY
Pct_change DESC
This code gets me a view of the data that looks like:
category Pct_change period
cat1 0.21 2019-2020
cat2 0.53 2019-2020
cat3 0.76 "
This is great! But my next view has to take this one and provide me with a range that shows how many categories are in each range.
It should look like:
range avg num_cat_in
[0.1- 0.4] 0.3 3
This last table is just an example of what I expect
I have been trying with a code that looks like this but i get nothing
WITH table AS (
SELECT category, price, date, substring(date, 1, 4) AS year, substring(date, 6, 2) as month
FROM original_table
WHERE (year= "2019" or year= "2020") and (month= "03") and product = "XXXXX"
order by anno
)
-- I get this from a bigger table, but prefer to make small steps that anyone in the future can understand where this comes from as the original table is expected to grow fast
SELECT category,
ROUND(1.0 * next_price/ price - 1, 2) Pct_change,
SUBSTR(Date, 1, 4) || '-' || SUBSTR(next_date, 1, 4) Period,
tipo_establecimiento
FROM (
SELECT *,
LEAD(Price) OVER (PARTITION BY category ORDER BY year) next_price,
LEAD(year) OVER (PARTITION BY category ORDER BY year) next_date,
CASE
WHEN (category_2>= 35) AND (category_2 <= 61)
THEN 'S'
ELSE 'N'
END 'tipo_establecimiento'
FROM table
)
WHERE next_date IS NOT NULL AND Pct_change>=0
ORDER BY Pct_change DESC
WHERE next_date IS NOT NULL AND Pct_change>=0
)
SELECT
count(CASE WHEN Pct_change> 0.12 AND Pct_change <= 0.22 THEN 1 END) AS [12 - 22],
count(CASE WHEN Pct_change> 0.22 AND Pct_change <= 0.32 THEN 1 END) AS [22 - 32],
count(CASE WHEN Pct_change> 0.32 AND Pct_change <= 0.42 THEN 1 END) AS [32 - 42],
count(CASE WHEN Pct_change> 0.42 AND Pct_change <= 0.52 THEN 1 END) AS [42 - 52],
count(CASE WHEN Pct_change> 0.52 AND Pct_change <= 0.62 THEN 1 END) AS [52 - 62],
count(CASE WHEN Pct_change> 0.62 AND Pct_change <= 0.72 THEN 1 END) AS [62 - 72],
count(CASE WHEN Pct_change> 0.72 AND Pct_change <= 0.82 THEN 1 END) AS [72 - 82]
Thank you!!!
cf. my comment, I'm first assuming that your ranges are not hard-coded and that you wish to evenly split your data across quantiles of Prc_change. What this means is the calculation will figure out the ranges which split your sample as uniformly as possible. In this case, the following would work (where theview is the name of your previous view which calculates percentages):
select
concat('[',min(Pct_change),'-',min(Pct_change),']') as `range`
, avg(Pct_change) as `avg`
, count(*) as num_cat_in
from(
select *
, ntile(5)over(order by Pct_change) as bin
from theview
) t
group by bin
order by bin;
Here is a fiddle.
If on the other hand your ranges are hard-coded, I assume the ranges are in a table such as the one I create:
create table theranges (lower DOUBLE, upper DOUBLE);
insert into theranges values (0,0.2),(0.2,0.4),(0.4,0.6),(0.6,0.8),(0.8,1);
(You have to make sure that the ranges are non-overlapping. By convention I include percentages in the range from the lower bound included to the upper bound excluded, except for the upper bound of 1 which is included.) It is then a matter of left-joining the tables:
select
concat('[',lower,'-',upper,']') as `range`
, avg(Pct_change) as `avg`
, sum(if(Pct_change is null, 0, 1)) as num_cat_in
from theranges left join theview on (Pct_change>=lower and if(upper=1,true,Pct_change<upper))
group by lower, upper
order by lower;
(Note that in the bit that says upper=1, you must change 1 to whatever your highest hard-coded range is; here I am assuming your percentages are between 0 and 1.)
Here is the second fiddle.
The Percent_Failure in the query below is giving results as either 1.00 or 0.00. Can anyone help me understand why it's not giving me the actual decimal?
SELECT
sf.hierarchy_name AS Hierarchy_Name,
cl.cmh_id AS CMH_ID,
cl.facility_name AS Clinic_Name,
sf.billing_city AS City,
sf.billing_state_code AS State,
cl.num_types AS Num_Device_Dypes,
cl.num_ssids AS Num_SSIDs,
SUM(CAST(CASE WHEN (d.mdm_client_version NOT LIKE '1.14%' AND d.type = 'Wallboard')
OR (d.mdm_client_version NOT LIKE '1.14%' AND d.type = 'Tablet')
OR (d.mdm_client_version NOT LIKE '1.14%' AND d.type = 'AndroidMediaPlayer')
OR (d.mdm_client_version NOT LIKE '1.14%' AND d.type = 'InfusionRoomTablet') THEN 1 ELSE 0 END AS INTEGER)) AS Non_Updated,
COUNT(d.asset_id) AS Total_Devices
CAST((Non_Updated) / (Total_Devices) AS DECIMAL (5,4)) AS Percent_Failure
FROM
(SELECT id, clinic_table_id, type, asset_id, mdm_client_version, device_apk_version, ssid
FROM mdm.devices) d
JOIN
(SELECT a.id, a.cmh_id, a.facility_name, COUNT(DISTINCT b.ssid) AS num_ssids, COUNT(DISTINCT b.type) AS num_types
FROM mdm.clinics a
JOIN
(SELECT *
FROM mdm.devices
WHERE status = 'Active'
AND last_seen_at >= (CURRENT_DATE - 2)
AND installed_date <= (CURRENT_DATE - 3)) b
ON a.id = b.clinic_table_id
GROUP BY 1,2,3) cl
ON d.clinic_table_id = cl.id
JOIN
(SELECT x.cmh_id, x.hierarchy_group, x.hierarchy_name, x.billing_city, x.billing_state_code
FROM salesforce.accounts x) sf
ON sf.cmh_id = cl.cmh_id
GROUP BY 1,2,3,4,5,6,7
ORDER BY 10 DESC;
Integer / Integer = Integer. So, you need to cast it before you do the division:
cast (Non_Updated as decimal) / Total_Devices AS Percent_Failure
or shorthand:
Non_Updated::decimal / Total_Devices AS Percent_Failure
I've seen other cute implementations, such as
Non_Updated * 1.0 / Total_Devices AS Percent_Failure
Also, are you sure that total_devices is always non-zero? If not, be sure to handle that.
Try individually cast numerator or denominator as decimal, otherwise the result of the division is considered to be integers.
So you can use :
Non_Updated / CAST(Total_Devices AS DECIMAL)
or
CAST ( Non_Updated AS DECIMAL ) / Total_Devices
cast the numerator to decimal
CAST(Non_Updated) as decimal / Total_Devices
for e.g.
select cast(3 as decimal)/4 ;
I've got a table with items and their prices. What I want to do is change the decimal places of the prices based on a rounding table. If the price ends in .01-.09, the decimal place should end in .94. If it ends in .10-.21, it should end in .95. If it ends in .22-.38, it should end in .96 and so on.
So a price of $5.35 would become $5.96, and $7.12 would become $7.95.
Is there a way of doing this? I'm using Sql Server 2014.
You could use % and CASE:
WITH prices AS (
SELECT CAST(5.35 AS DECIMAL(10,2)) AS p
)
SELECT p,
CAST(CAST(p AS INT) +
CASE WHEN p%1.0 BETWEEN .01 AND .09 THEN .94
WHEN p%1.0 BETWEEN .10 AND .21 THEN .95
WHEN p%1.0 BETWEEN .22 AND .38 THEN .96
ELSE 0 -- default
END AS DECIMAL(10,2)) AS new_price
FROM prices;
Rextester Demo
You could use PARSENAME function to get the decimal part and compare it with your desired ranges.
DECLARE #number decimal(18,2) = 12.11
SELECT CAST(PARSENAME(#number,2) AS DECIMAL(18,2)) +
(CASE WHEN PARSENAME(#number,1) BETWEEN 1 AND 9 THEN 0.94
WHEN PARSENAME(#number,1) BETWEEN 10 AND 21 THEN 0.95
WHEN PARSENAME(#number,1) BETWEEN 22 AND 38 THEN 0.96
ELSE #number END
)
Result
12.95
we all know how to generate a running total column with
SELECT id, date, value, sum(value)
OVER (partition by id order by date) total
FROM dual
ORDER BY date
Which will give me something like
ID DATE VALUE TOTAL
1 1/1/14 0.001 0.001
2 2/1/14 0.003 0.004
3 3/1/14 0.002 0.006
Now I want to generate a "running multiplication" which generated 0.001 * 0.004 * 0.006. I know that if I just want the value for the whole multiplication can be done by something like
SELECT exp(sum(ln(value))) from dual
but this one does not work with the partition in oracle. Maybe someone has an idea?
Edit
The desired result would be (don't mind the numbers, they are just dummies, they will not run into an overflow).
ID DATE VALUE TOTAL
1 1/1/14 0.001 0.001
2 2/1/14 0.003 0,000004
3 3/1/14 0.002 0,000000024
The exp(sum(ln())) approach works as long as you add the analytics for the sum() part, not for the exp(). This would give you the product of the original values:
WITH t AS (
SELECT 1 AS id, DATE '2014-01-01' AS dat, 0.001 AS value FROM dual
UNION ALL SELECT 2, DATE '2014-01-02', 0.003 FROM dual
UNION ALL SELECT 3, DATE '2014-01-03', 0.002 FROM dual
)
SELECT id, dat, value, EXP(SUM(LN(value))
OVER (PARTITION BY null ORDER BY dat))
AS total
FROM t
ORDER BY dat;
ID DAT VALUE TOTAL
---------- --------- ---------- ----------
1 01-JAN-14 .001 .001
2 02-JAN-14 .003 .000003
3 03-JAN-14 .002 .000000006
And this would give you the product of the running total:
WITH t AS (
SELECT 1 AS id, DATE '2014-01-01' AS dat, 0.001 AS value FROM dual
UNION ALL SELECT 2, DATE '2014-01-02', 0.003 FROM dual
UNION ALL SELECT 3, DATE '2014-01-03', 0.002 FROM dual
),
u AS (
SELECT id, dat, value, SUM(value)
OVER (PARTITION BY null ORDER BY dat) AS total
FROM t
)
SELECT id, dat, value, total, EXP(SUM(LN(total))
OVER (PARTITION BY null ORDER BY dat)) AS product
FROM u
ORDER BY dat;
ID DAT VALUE TOTAL PRODUCT
---------- --------- ---------- ---------- ----------
1 01-JAN-14 .001 .001 .001
2 02-JAN-14 .003 .004 .000004
3 03-JAN-14 .002 .006 .000000024
Use your own table instead of the CTE obviously; and if you're trying to get the product/sum over multiple values with an ID when change it to partition by id. Using null is to make this work with your sample data.
Unashamedly riffing off this demonstration of a custom aggregate product() function, which supports windowing, you could create your own analytic function to do the calculation for you:
CREATE OR REPLACE TYPE product_total_impl AS OBJECT
(
product NUMBER,
total NUMBER,
product_total NUMBER,
STATIC FUNCTION ODCIAggregateInitialize(ctx IN OUT product_total_impl) RETURN NUMBER,
MEMBER FUNCTION ODCIAggregateIterate(SELF IN OUT product_total_impl,
VALUE IN NUMBER) RETURN NUMBER,
MEMBER FUNCTION ODCIAggregateMerge(SELF IN OUT product_total_impl,
ctx2 IN product_total_impl) RETURN NUMBER,
MEMBER FUNCTION ODCIAggregateTerminate(SELF IN OUT product_total_impl,
returnvalue OUT NUMBER,
flags IN NUMBER) RETURN NUMBER
);
/
CREATE OR REPLACE TYPE BODY product_total_impl IS
STATIC FUNCTION ODCIAggregateInitialize(ctx IN OUT product_total_impl) RETURN NUMBER IS
BEGIN
ctx := product_total_impl(1, 0, 1);
RETURN ODCIConst.Success;
END ODCIAggregateInitialize;
MEMBER FUNCTION ODCIAggregateIterate(SELF IN OUT product_total_impl,
VALUE IN NUMBER) RETURN NUMBER IS
BEGIN
IF VALUE IS NOT NULL THEN
SELF.product := SELF.product * VALUE;
SELF.total := SELF.total + VALUE;
SELF.product_total := SELF.product_total * SELF.total;
END IF;
RETURN ODCIConst.Success;
END ODCIAggregateIterate;
MEMBER FUNCTION ODCIAggregateMerge(SELF IN OUT product_total_impl,
ctx2 IN product_total_impl) RETURN NUMBER IS
BEGIN
SELF.product := SELF.product * ctx2.product;
SELF.total := SELF.total + ctx2.total;
SELF.product_total := ctx2.product_total * ctx2.total;
RETURN ODCIConst.Success;
END ODCIAggregateMerge;
MEMBER FUNCTION ODCIAggregateTerminate(SELF IN OUT product_total_impl,
returnvalue OUT NUMBER,
flags IN NUMBER) RETURN NUMBER IS
BEGIN
returnvalue := SELF.product_total;
RETURN ODCIConst.Success;
END ODCIAggregateTerminate;
END;
/
CREATE OR REPLACE FUNCTION product_total(x IN NUMBER) RETURN NUMBER
PARALLEL_ENABLE
AGGREGATE USING product_total_impl;
/
Then you can do:
WITH t AS (
SELECT 1 AS id, DATE '2014-01-01' AS dat, 0.001 AS value FROM dual
UNION ALL SELECT 2, DATE '2014-01-02', 0.003 FROM dual
UNION ALL SELECT 3, DATE '2014-01-03', 0.002 FROM dual
)
SELECT id, dat, value,
SUM(value) OVER (PARTITION BY null ORDER BY dat) AS total,
PRODUCT_TOTAL(value) OVER (PARTITION BY null ORDER BY dat) AS product_total
FROM t
ORDER BY dat;
ID DAT VALUE TOTAL PRODUCT_TOTAL
---------- --------- ---------- ---------- -------------
1 01-JAN-14 .001 .001 .001
2 02-JAN-14 .003 .004 .000004
3 03-JAN-14 .002 .006 .000000024
SQL Fiddle with the original product as well.
As before, use your own table instead of the CTE obviously; and if you're trying to get the product/sum over multiple values with an ID when change it to partition by id. Using null is to make this work with your sample data.