adding and incrementing date and time (Postgresql) - sql

The below is a table that is meant to show when a media will play. so basically it has a start time (starts), the length of the track (clip_length), and when it ends (ends = starts + clip_length), and finally the position of the track.
|starts | ends |position |file_id| clip_length
|2013-08-30 22:00:00 | 2013-08-30 22:03:08 |0 |16 |00:03:08.081768
|2013-08-30 22:03:08 | 2013-08-30 22:06:33 |1 |17 |00:03:25.436485
|2013-08-30 22:06:33 | 2013-08-30 22:09:07 |2 |7 |00:02:33.79968
|2013-08-30 22:09:07 | 2013-08-30 22:12:21 |3 |3 |00:03:14.020273
|2013-08-30 22:12:21 | 2013-08-30 22:15:31 |4 |8 |00:03:10.466689
what i want to do is to add a record, at say position =2 , shown bellow. I have been able to increment the positions, how ever the problem lies with the fact that the times are all messed up.
|starts | ends |position |file_id|clip_length
|2013-08-30 22:00:00 | 2013-08-30 22:03:08 |0 |16 |00:03:08.081768
|2013-08-30 22:03:08 | 2013-08-30 22:06:33 |1 |17 |00:03:25.436485
|2013-08-30 22:06:33 | 2013-08-30 22:09:07 |2 |7 |00:02:33.79968
|2013-08-30 22:06:33 | 2013-08-30 22:11:03 |3 |1 |00:04:30.006958
|2013-08-30 22:09:07 | 2013-08-30 22:12:21 |4 |3 |00:03:14.020273
|2013-08-30 22:12:21 | 2013-08-30 22:15:31 |5 |8 |00:03:10.466689
so it possible to use the first start time.. as point 00, and add clip_length to starts and save in ends, for the first one. then for the second one use the first ends value as the starts and do this recursively till the end (following the positions) .
thanks in advance..

SQL Fiddle
update clip c
set
starts = s.starts,
ends = s.ends
from (
select
starts,
starts + clip_length as ends,
file_id,
position
from (
select
'2013-08-30 22:00:00'::timestamp
+ sum(clip_length) over(order by position)
- clip_length as starts,
clip_length,
file_id,
position
from clip
) s
) s
where c.file_id = s.file_id

Your data model is pretty borked. You're storing at least two pieces of redundant information. You need starts and file_id and any one of clip_length, ends or position; they can each be calculated from each other.
Right now you are storing redundant data, which creates the problem you now have where the data is not internally consistent, it has conflicts within its self.
In this case it sounds like position is trusted and the others aren't. Here's what I'd do:
SELECT
(SELECT min(starts) FROM Sometable)
+ COALESCE(
sum(clip_length) OVER all_rows_but_last,
INTERVAL '0' second
)
AS starts,
(SELECT min(starts) FROM Sometable)
+ COALESCE(
sum(clip_length) OVER all_rows_but_last,
INTERVAL '0' second
) + clip_length
AS ends,
position,
clip_length
FROM Sometable
WINDOW all_rows_but_last AS (ORDER BY position ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING);
see: http://sqlfiddle.com/#!12/ca5fa/1
The principle here is to find the lowest start - the one known to be valid - and ignore the following ends and starts as redundant and useless. Instead, add the running total of the previous durations as the start, and the same running total plus the current interval as the end.
You'll notice that this isn't an update. That's because I think you should actually:
change position to a floating point value or large integer so you don't need to re-number when you insert an entry. If you put something between 1 and 2 give it position 0.5. Or start with 10000, 20000 etc so you can insert 15000.
Get rid of the ends column completely. calculate it from starts + clip_length
Move starts to a separate table where it's stored only once, for the series of segments. Calculate clip start times on the fly from the sum of the lengths of previous clips since the start.
Re-define the current table as a view over the two tables described above.
That's making a lot of guesses about your data and how you're using it, but you haven't said much about what you're doing.

with cte as (
select
'2013-08-30 22:00:00'::timestamp
+ sum(clip_length) over(order by position) as ends,
file_id
from clip
)
update clip as cl set
starts = c.ends - cl.clip_length,
ends = c.ends
from cte as c
where cl.file_id = c.file_id;
=>sql fiddle
Actually, you could live without start and end time in your table at all. You can remove this columns from your table and create function like this:
create or replace function clip_layout(_starts timestamp)
returns table(
starts timestamp, ends timestamp,
"position" int, file_id int,clip_length interval
)
as
$$
with cte as (
select
_starts + sum(clip_length) over(order by position) as ends,
file_id, clip_length, position
from clip
)
select
ends - clip_length as starts,
ends, position,
file_id, clip_length
from cte
$$
language sql;
So you can see start/end times starting from anytime:
select * from clip_layout('2013-08-30 22:00:00'::timestamp)
=>sql fiddle demo

Related

Using ranking functions without order by clause

I have got a PostgreSQL database. There is a query that orders rows by some_date and minutes. The result looks like this:
id|is_smth|some_date |minutes
35|true |2021-11-10|985
36|true |2021-11-19|684
35|true |2021-11-25|605
34|false |null |null
Then, I have tried applying DENSE_RANK() to it, to implement pagination in my application, but it does not work as I need it to, as it has to order the rows by given columns.
What I am trying to achieve is just giving the rank to each row by the criteria specified in the original ORDER BY clause plus the id of the result set without changing their order. So, it would look like this:
id|is_smth|some_date |minutes|rank
35|true |2021-11-10|985 |1
36|true |2021-11-19|684 |2
35|true |2021-11-25|605 |1
34|false |null |null |3
I have to retrieve the first two rows AND all of the associated data with them (in this case it would be all of the other rows with ids 35 and 36). Is it possible?

Find missing entries in a SQL table conditional on criteria

I have modest simple SQL experience (using MS SQL server 2012 here) but this evades me. I wish to output distinct names from a table (previously successfully created from a join) which have some required entries missing, but conditional on the existence of another similar entry. For anyone who has location 90, I want to check they also have locations 10 and 20...
For example, consider this table:
Name |Number |Location
--------|-------|--------
Alice |136218 |90
Alice |136218 |10
Alice |136218 |20
Alice |136218 |40
Bob |121478 |10
Bob |121478 |90
Chris |147835 |20
Chris |147835 |90
Don |138396 |20
Don |138396 |10
Emma |136412 |10
Emma |136412 |20
Emma |136412 |90
Fred |158647 |90
Gay |154221 |90
Gay |154221 |10
Gay |154221 |30
So formally, I would like to obtain the Names (and Numbers) of those entries in the table who:
Have an entry at location 90
AND do not have all the other required location entries - in this case also 10 and 20.
So in the example above
Alice and Emma are not output by this query, they have entries for 90, 10 & 20 (all present and correct, we ignore the location 40 entry).
Don is not output by this query, he does not have an entry for location 90.
Bob and Gay are output by this query, they are both missing location 20 (we ignore Gay's location 30 entry).
Chris is output by this query, he is missing location 10.
Fred is output by this query, he is missing locations 10 & 20.
The desired query output is therefore something like:
Name |Number |Location
--------|-------|--------
Bob |121478 |20
Chris |147835 |10
Fred |158647 |10
Fred |158647 |20
Gay |154221 |20
I've tried a few approaches with left/right joins where B.Key is null, and select from ... except but so far I can't quite get the logical approach correct. In the original table there are hundreds of thousands of entries and only a few tens of valid missing matches. Unfortunately I can't use anything that counts entries as the query has to be locations specific and there are other valid table entries at other locations outside of the desired ones.
I feel that the correct way to do this is something like a left outer join but as the starting table is the output of another join does this require declaring an intermediate table and then outer joining the intermediate table with its self? Note there is no requirement to fill in any gaps or enter items into the table.
Any advice would be very much appreciated.
===Answered and used code pasted here===
--STEP 0: Create a CTE of all valid actual data in the ranges that we want
WITH ValidSplits AS
(
SELECT DISTINCT C.StartNo, S.ChipNo, S.TimingPointId
FROM Splits AS S INNER JOIN Competitors AS C
ON S.ChipNo = C.ChipNo
AND (
S.TimingPointId IN (SELECT TimingPointId FROM #TimingPointCheck)
OR
S.TimingPointId = #TimingPointMasterCheck
)
),
--STEP 1: Create a CTE of the actual data that is specific to the precondition of passing #TimingPointMasterCheck
MasterSplits AS
(
SELECT DISTINCT StartNo, ChipNo, TimingPointId
FROM ValidSplits
WHERE TimingPointId = #TimingPointMasterCheck
)
--STEP 2: Create table of the other data we wish to see, i.e. a representation of the StartNo, ChipNo and TimingPointId of the finishers at the locations in #TimingPointCheck
--The key part here is the CROSS JOIN which makes a copy of every Start/ChipNo for every TimingPointId
SELECT StartNo, ChipNo, Missing.TimingPointId
FROM MasterSplits
CROSS JOIN (SELECT * FROM #TimingPointCheck) AS Missing(TimingPointId)
EXCEPT
SELECT StartNo, ChipNo, TimingPointId FROM ValidSplits
ORDER BY StartNo
Welcome to Stack Overflow.
What you need is a bit challenging, since you want to see data that do not exist.
Thus, we first must create all possible rows, then substract the ones that exist
select ppl_with_90.Name,ppl_with_90.Number,search_if_miss.Location
from
(
select distinct Name,Number
from yourtable t
where Location=90
)ppl_with_90 -- All Name/Numbers that have the 90
cross join (values (10),(20)) as search_if_miss(Location) -- For all the previous, combine them with both 10 and 20
except -- remove the lines already existing
select *
from yourtable
where Location in (10,20)
You need to generate the sets consisting of name, number, 10_and_20 for all rows where location = 90. You can then use your favorite method (left join + null, not exists, not in) to filter the rows that do not exist:
WITH name_number_location AS (
SELECT t.Name, t.Number, v.Location
FROM #yourdata AS t
CROSS JOIN (VALUES (10), (20)) AS v(Location)
WHERE t.Location = 90
)
SELECT *
FROM name_number_location AS r
WHERE NOT EXISTS (
SELECT *
FROM #yourdata AS t
WHERE r.Name = t.Name AND r.Location = t.Location
)

ACCESS: calculate timestamp difference between rows

Here is the data I am working with in MS Access from a system tracking when a agent makes system changes:
|agentid|eventtype|reasoncode|eventdatetimelocal |
|1830 |2 |32762 |01/01/2014 7:11:44 PM|
|1830 |3 |0 |01/01/2014 7:13:46 PM|
|1830 |2 |32762 |01/01/2014 7:14:55 PM|
|1833 |2 |0 |01/01/2014 7:11:35 PM|
|1833 |3 |32762 |01/01/2014 7:13:25 PM|
I need to determine the number of seconds which elapsed between rows by agent. I would also like to preserve the detail of the eventtype and reasoncode.
I tried joining on a subqry but it's not working:
SELECT sub1.agentid,
sub1.eventtype,
sub1.reasoncode,
sub1.eventdatetimelocal,
(sub1.next_timestamp-sub1.eventdatetimelocal) AS duration
FROM (SELECT i.agentid,
eventdatetimelocal,
eventtype,
reasoncode, (SELECT
Min([eventdatetimelocal])
FROM state_detail_tbl
WHERE [eventdatetimelocal] > i.eventdatetimelocal
) AS next_timestamp
FROM state_detail_tbl AS i
WHERE i.eventdatetimelocal BETWEEN #01/01/2014# AND #01/31/2014#
) AS sub1;
You can try this query
SELECT sub1.agentid,
sub1.eventtype,
sub1.reasoncode,
sub1.eventdatetimelocal,
(SELECT TOP 1 sub2.eventdatetimelocal - sub1.eventdatetimelocal
FROM state_detail_tbl AS sub2
WHERE sub1.agentid=sub2.agentid
AND sub2.eventdatetimelocal > sub1.eventdatetimelocal
ORDER BY sub2.eventdatetimelocal) AS duration
FROM state_detail_tbl sub1
WHERE (SELECT TOP 1 eventdatetimelocal
FROM state_detail_tbl AS s3
WHERE sub1.agentid=s3.agentid
AND s3.eventdatetimelocal > sub1.eventdatetimelocal) Is Not Null
AND sub1.eventdatetimelocal BETWEEN #01/01/2014# AND #01/31/2014#
ORDER BY sub1.agentid, sub1.eventdatetimelocal;
I received an error from the query below stating something along the lines of "this query can only return a maximum of one row". But the query along with this reference: Calculating time difference between activity timestamps in a query gave me what I needed which I am listing below for reference. I decided that my initial needs were too broad and so I simplified the query to return the bare minimum data needed to bring the timestamp up to the previous row. I can step another query in with datediff to figure out the seconds. It takes awhile for this to process but it works and can run overnight if required.
SELECT i.agentid, i.eventtype, i.reasoncode, eventdatetimelocal, (SELECT
Min([eventdatetimelocal]) FROM state_detail_subqry WHERE agentid = i.agentid
AND [eventdatetimelocal]>i.[eventdatetimelocal]) AS next_timestamp
FROM state_detail_subqry AS i
ORDER BY agentid, eventdatetimelocal;

Data value "0" has invalid format error in redshift

We are facing a weird problem with one of our query.
Below is the query we are running
INSERT into test
SELECT
member.name as mem_name,
CASE WHEN ( member.dob>0 AND length (member.dob)=8 ) THEN (DATEDIFF(year,to_date("dob",'YYYYMMDD'), to_date(20140716,'YYYYMMDD'))) WHEN ( member.dob=0 ) Then 0 END As Age,
20140716021501
FROM
member
Below is the sample data present in our table.
|name |dob
|Ajitsh |0 |
|rk |51015 |
|s_thiagarajan |19500130 |
|madhav_7 |19700725 |
|1922 |0 |
|rekha |25478 |
|vmkurup |0 |
|ravikris |19620109 |
|ksairaman |0 |
|sruthi |0 |
|rrbha |19630825 |
|sunilsw |0 |
|sunilh |0 |
|venky_pmv |19701207 |
|malagi |0 |
|an752001 |0 |
|edsdf |19790201 |
|anuanand |19730724 |
|fresh |19720821 |
|ampharcopharma |19590127 |
|Nanze |19621123 |
The date of birth is stored in bigint as YYYYMMDD format.
In the data there are some rows, in which date is invalid like 0, 51015.
On some instances this query raises the following error.
INSERT INTO test not successful
An error occurred when executing the SQL command:
INSERT into test
SELECT
member.name as mem_name,
CASE WHEN ( member.dob>0 AND length (member.dob)=8 ) THEN (DATEDIFF(y...
ERROR: Data value "0" has invalid format
Detail:
-----------------------------------------------
error: Data value "0" has invalid format
code: 1009
context: PG ERROR
query: 92776
location: pg_utils.cpp:2731
process: query1_30 [pid=1434]
-----------------------------------------------
Execution time: 3.99s
1 statement failed.
But the strange thing is, it raises the error randomly and not all the time.
Many times it works without any change in query or dataset.
Sometime it also works in second or third attempt.
My doubt is that to_date function is giving this error. But why randomly
and not gives error on every run.
To support my assumption I also tried this small query.
SELECT to_date(20140716,'YYYYMMDD'), to_date(0,'YYYYMMDD');
But this also creates the same scenario. It raises error randomly, while
runs smoothly rest of the times.
If is it fine to ignore this type of values and just convert this to Date format you can follow the below way.
SELECT to_date('20140716','YYYYMMDD'), to_date('0','FMYYYYMMDD');
Here FM suppresses leading zeroes and trailing blanks that would otherwise be added to make the output of a pattern be fixed-width.

Delphi 7 - error when get values using sql operator in

Got no answer on related thread, so i make this question. I've been searched for how to retrieve records value using where clause with multiple values and i got this.
table example :
|ID |PRICE|
|1 |3000 |
|2 |2000 |
|3 |1000 |
|4 |5000 |
|5 |4000 |
SQL query :
DM.Zread.Close;
DM.Zread.SQL.CommaText := 'select PRICE from DVD where ID in (1, 2, 3)';
DM.Zread.Open;
Above gave me an error, when i only put one 1 values which is (1) or (2) it's works fine.
Questions are :
how to straight it, so i could get the values from 3 different
records ?
how to apply it on string values instead ?
SQL is a TStrings subclass. When you set CommaText using the above, you are actually setting your query to:
select PRICE from DVD where ID in (1
2
3)
This obviously won't work.
You want to set the Text property or use Add() method to add separate lines.
Try using CommandText rather than CommaText on your SQL call
DM.Zread.Close;
DM.Zread.SQL.CommaText := 'select PRICE from DVD where ID in (1, 2, 3)';
DM.Zread.Open;
DM.Zread.SQL.CommandText := 'select PRICE from DVD where ID in (1, 2, 3)';