Calculate a field with date from previous rows - sql

I have a table like the one shown in the image below. In this table, I need to calculate two new fields(The Red and Yellow fields), but this fields depends on the previous values. I have to calculate this values in BigQuery/SQL. In Excel, it is very easy, but I don't know how to do it in SQL.
I've tried doing a join with the same table, but previous week, and it works, but for only one "Future Week". (And there are about 100 Future Weeks)
How can I calculate this in BigQuery?
I was thinking in a Cursor.. but as far as I know, there are no cursors in BigQuery
Thanks
This is the example data:
WITH Data as ( Select '2021-01-03' as Week, 1000 as InboundReal, 10000 as StockReal, 1190 as SellReal, 1200 as InboundPpto UNION ALL
Select '2021-01-31',1000, 10000 , 1190 , 1200 UNION ALL Select '2021-02-07',1000, 10000 , 1190 , 1200 UNION ALL
Select '2021-02-14',1000, 10000 , 1200 , 1200 UNION ALL Select '2021-02-21',NULL,NULL,NULL,1200 UNION ALL
Select '2021-02-28',NULL,NULL,NULL,1200 UNION ALL Select '2021-03-07',NULL,NULL,NULL,1200 UNION ALL Select '2021-03-14',NULL,NULL,NULL,1200 )
Select *, NULL as ForecastSell,NULL as StockForecast FROM Data

I don't think this type of problem comes really into the SQL domain.
It is more of an iterative problem, where state of each iteration is maintained and available for the next one immediately (which is not the case in SQL). You can run multiple SQL queries in serial to easily achieve this though. Also, explore scripting options in BigQuery.

Related

Calculate total working hours of employee based swipe in/ swipe out using oracel sql

I was recently given a task to calculate an employee's total office hours based on his card swipe in/swipe out. I have the following data :
id gate_1 gate_2 gate_3 gate_4
100 null null null 9:00
100 null 13:30 null null
100 null null 16:00 null
100 null null 18:00 null
Image
Here, the employee 100 comes in via gate_4 at 9 am and takes a break at 13:30 and goes out using gate_2. Then he comes back at 16:00 using gate_3 and leave office at 18:00 using gate_3. So, how to calculate the total in office timing using this data ?
Thanks in advance.
As has been pointed out your data model is denormalized to not even satisfy 1st normal form. The first step is to correct that (doing so in a query). Then there is no indication as to swipe in or swipe out, therefore it must be assumed that the first swipe time is always in and the ins/outs always alternate properly. Finally there is no indication of multiple days being covered so the assumption is just 1 period. That is a lot of assumptions.
Since an Oracle data type date contains time as well as the date and summing differences is much easier than with timestamps I convert timestamp to date in the first step of normalizing the data. Given all this we arrive at: (See Demo)
with normal (emp_id, inout_tm) as
( select emp_id, cast(gate1 as date)
from emp_gate_time
where gate1 is not null
union all
select emp_id, cast(gate2 as date)
from emp_gate_time
where gate2 is not null
union all
select emp_id, cast(gate3 as date)
from emp_gate_time
where gate3 is not null
union all
select emp_id, cast(gate4 as date)
from emp_gate_time
where gate4 is not null
)
select emp_id, round(24.0*(sum(hours)),1) hours_in_office
from ( select emp_id,(time_out - time_in) hours
from ( select emp_id, inout_tm time_in, rn
, lead(inout_tm) over(partition by emp_id order by inout_tm) time_out
from ( select n.*
, row_number() over(partition by emp_id order by inout_tm) rn
from normal n
)
)
where mod(rn,2) = 1
)
group by emp_id;
Items of Interest:
Subquery Factoring (CTE)
Date Arithmatic - in Hours ...Difference Between Dates in hours ...
Oracle Analytic Functions - Row_number, lead
You have a denormalized structure of your db scheme. You have fields as gate_1, gate_2 and etc. It's wrong way. The better way is following, you should have reference table of gates, for example like this
id|gate_name
--|---------
And your table with data for employee will be looks like this.
id_employee|id_gate|time
Then you can sort data in this table, and then count period of time between two consecutive rows.

Setting a maximum value for a variable. Cognos

I started working in BI and I was given a brain teaser since I came from C# and not SQL/cognus.
I get a number. It can be between 0 and a very large number. When I get it and it's below 1,000 everything is dandy. But if it's bigger than or equal to 1,000 , I should use 1,000 instead.
I am not allowed to use conditions, I need it to be pure math, or if I can't then I should use efficient methods.
I thought it would be easy and just use Min() but that works differently in cognus and SQL apparently.
Use the LEAST() function:
Oracle Setup:
CREATE TABLE data ( value ) AS
SELECT 1 FROM DUAL UNION ALL
SELECT 999 FROM DUAL UNION ALL
SELECT 1000 FROM DUAL UNION ALL
SELECT 1001 FROM DUAL;
Query:
SELECT value, LEAST( value, 1000 ) AS output FROM data
Output:
VALUE OUTPUT
----- ------
1 1
999 999
1000 1000
1001 1000

Sum of Absolute Differences - Dynamic base and collection

Long time reader, first time poster. tldr; at bottom
I'm in need of calculating the Sum of Absolute Differences between a Static set of data points and dynamic combination base set.
SQL 2005
Problem:
Think of 10 circular magnets with Serial Numbers : 1,3,6,7,8,11,12,13,18,20
Each Magnet has 7 data points representing gauss measurements taken around the magnet using a gauss meter.
Because these measurements do not vary much WITHIN a given magnet, but can vary greatly BETWEEN two different magnets, I am trying to calculate SUM of absolute differences between a grouping of magnets.
I started by SUMMING the 7 data points and then comparing those SUMS to each other. You can quickly identify which TWO magnets are the closest by sorting by the smallest SAD. SUM(ABS(Magnet1_DataPoints - Magnet2_DataPoints))
The tricky part began when trying to find the N grouping of magnets with the smallest SAD. This turned into a Combinations C(n,r) problem with added complexity. I could have 40 magnets and need to pull a group of 8 with the smallest SAD. I could have 30 and need to pull 4 magnets. So, the dynamic nature of this problem enters as well.
I'm looking for some guidance on how to write this in SQL. So far, I've hit several walls, especially when trying to do it recursively via an SP. The data points were giving me grief when trying to process this through a CTE.
I've found many examples of recursive combination algorithms, but I'm having trouble adapting them to my very specific use-case.
Any and all help is greatly appreciated!
More important details:
1.) Magnet ID's will be unique integers and once consumed, will be flagged as "checked out" with a boolean 0/1 field.
2.) The gauss numbers will be decimal (18,2) to begin with.
3.) It's possible I will need separate procedures for the "quantity/grouping" of magnets if that's easier, i.e., 4,8,12 (most likely those are the increments they will be pulled in).
4.) Magnet/ID order matters. 1,2,3,4 is the same as 2,3,1,4 (identical combinations)
5.) Ideally, the output would be a list representing the grouping and total SAD, i.e. with a request of 3 magnets.
ID1, ID2, ID3, SAD
36, 28, 29, 20.0
36, 28, 31, 22.00
34, 28, 29, 24.00
Table Example as requested:
CREATE TABLE #TmpMagTable ( IDOne int, TopMiddleOne decimal(18,2), TopLeftOne decimal(18,2), TopRightOne decimal(18,2), MiddleOne decimal(18,2), BotLeftOne decimal(18,2), BotRightOne decimal(18,2), BotMiddleOne decimal(18,2), checked int)
INSERT INTO #TmpMagTable
SELECT 1,1,2,3,4,5,6,7,0
UNION ALL SELECT 10,7,5,6,4,2,3,1,0
UNION ALL SELECT 19,4,2,5,7,3,6,1,0
UNION ALL SELECT 28,1,2,2,3,2,2,1,0
UNION ALL SELECT 2,2,3,4,5,6,7,8,0
UNION ALL SELECT 11,8,6,7,5,3,4,2,0
UNION ALL SELECT 20,8,3,6,5,4,7,2,0
UNION ALL SELECT 29,3,2,2,1,2,2,3,0
UNION ALL SELECT 3,3,4,5,6,7,8,9,0
UNION ALL SELECT 12,9,7,8,6,4,5,3,0
UNION ALL SELECT 21,3,4,7,9,5,8,6,0
UNION ALL SELECT 30,5,3,3,2,5,5,4,0
UNION ALL SELECT 4,4,5,6,7,8,9,10,0
UNION ALL SELECT 13,10,8,9,7,5,6,4,0
UNION ALL SELECT 22,7,5,8,4,6,9,10,0
UNION ALL SELECT 31,4,1,3,5,4,3,1,0
UNION ALL SELECT 5,5,6,7,8,9,10,11,0
UNION ALL SELECT 14,11,9,10,8,6,7,5,0
UNION ALL SELECT 23,5,6,9,11,7,10,8,0
UNION ALL SELECT 32,3,4,6,2,4,1,2,0
UNION ALL SELECT 6,6,7,8,9,10,11,12,0
UNION ALL SELECT 15,12,10,11,9,7,8,6,0
UNION ALL SELECT 24,9,7,10,6,8,11,12,0
UNION ALL SELECT 33,5,3,3,6,1,1,2,0
UNION ALL SELECT 7,7,8,9,10,11,12,13,0
UNION ALL SELECT 16,13,11,12,10,8,9,7,0
UNION ALL SELECT 25,7,8,11,13,9,12,10,0
UNION ALL SELECT 34,1,3,3,0,2,1,5,0
UNION ALL SELECT 8,8,9,10,11,12,13,14,0
UNION ALL SELECT 17,14,12,13,11,9,10,8,0
UNION ALL SELECT 26,8,9,12,14,10,13,11,0
UNION ALL SELECT 35,6,1,3,1,0,0,2,0
UNION ALL SELECT 9,9,10,11,12,13,14,15,0
UNION ALL SELECT 18,15,13,14,12,10,11,9,0
UNION ALL SELECT 27,12,10,13,9,11,14,15,0
UNION ALL SELECT 36,1,3,2,5,3,2,1,0
tldr; Need a recursive combination algorithm to sum values of each unique combination

SQL of a daterange without empty spaces

I need to generate a diagram out of data from a table. This table has the following date:
Timestamp | Value
01-20-2013| 5
01-21-2013| 7
01-22-2013| 3
01-25-2013| 5
As you can see not every date has a value. If I put that into a diagram it looks weird. Dates are used for the X-axis. As 01-23-2013 and 01-24-2013 is missing this values are either not printed in the diagram (looks weird) or the are printed put the line of the diagram goes from 3 directly to 5 and not to 0 as it should.
Is there a way via SQL to select the data so that it looks like this:
Timestamp | Value
01-20-2013| 5
01-21-2013| 7
01-22-2013| 3
01-23-2013| 0
01-24-2013| 0
01-25-2013| 5
Any help is appreciated!
Regards,
Alex
Edit: I had no clue that the database engine was that important. This is running on a MySQL 5 Database (not sure about the complete version string).
There are various ways to do this, depending on the database. Date functions are notoriously database independent.
Here is an approach using a "driver" table with all dates and to use this for a left outer join:
select driver.timestamp, coalesce(t.value, 0) as value
from (select distinct timestamp + n.n as timestamp
from t cross join
(select 0 as n union all select 1 union all select 2 union all select 3
)
) driver left outer join
t;
This version assumes that there are gaps of no more than three days.
In some databases, you can construct the list of dates using a recursive CTE. Such an approach would handle gaps of any size.

SQL statement, selecting data from specific range of hours

Intro: I have an database with table which contains column with hours, for example
08:30:00
08:45:00
09:30:00
12:20:00
...
18:00:00
the datatype in this column is "time".
Question: is it possible with SQL to select all hours [ for eg. 10:00:00, 10:30:00, 11:00:00 etc. ] from range 08:00:00 to 18:00:00, which are not on the list?
edit:
Well, after thinking it out, the solution which You [ thanks! ] is not entirely perfect [ well it is perfect with details I gave u :) ]. My application is allowing users to make an appointments which takes half an hour [ in this version of app ]. The column with time I gave u in my first topic is quite optimistic, because I didn't put cover meetings for hours such as 11:45:00 or 13:05:00, which will end at 12:15:00 and 13:35:00, so I don't really know from the top which hours I would be able to put in this extra table.
First, create a table T_REF with a column COL_HR with dates that have
all the hours you want to report if not found in the original table.
In MYSQL doing something like this should work
SELECT DATE_FORMAT(COLUMN_NAME, '%H') FROM T_REF
MINUS
SELECT DATE_FORMAT(COLUMN_NAME, '%H') FROM ORIG_TABLE
This will get and compare only the hours and report the ones that are not
in your table (ORIG_TABLE).
Ugly, but possible:
CREATE TABLE thours (h time);
INSERT INTO thours VALUES
('08:30:00'), ('08:45:00'), ('09:30:00'),
('12:20:00'), ('18:00:00');
CREATE VIEW hrange AS
SELECT 8 as h UNION ALL SELECT 9 UNION ALL SELECT 10 UNION ALL
SELECT 11 UNION ALL SELECT 12 UNION ALL SELECT 13 UNION ALL
SELECT 14 UNION ALL SELECT 15 UNION ALL SELECT 16 UNION ALL
SELECT 17 UNION ALL SELECT 18;
CREATE VIEW hours AS
SELECT cast(concat(cast(h as char), ':00:00') as time) AS h FROM hrange
UNION ALL
SELECT cast(concat(cast(h as char), ':30:00') as time)
FROM hrange WHERE h < 18
ORDER BY 1;
SELECT h.h AS ranged, t.h AS scheduled
FROM hours h
LEFT join thours t ON h.h = t.h;
If you'll add WHERE t.h IS NULL to the query, you'll get a list of wanted hours.
I created views as MySQL cannot dynamically generate series.
Try out here.
Are you only expecting to find every hour and half hour? Without knowing which times you're expecting to find or not find, I'd suggest the following:
Create a separate table with all possible times in it and then run a query to find which times don't exist in the original table.