SQLite difference between latest and second latest row - sql

I have table like this:
create table events(
event_type integer not null,
value integer not null,
time timestamp not null,
unique (event_type ,time)
);
insert into events values
(2, 5, '2015-05-09 12:42:00'),
(4, -42, '2015-05-09 13:19:57'),
(2, 2, '2015-05-09 14:48:39'),
(2, 7, '2015-05-09 13:54:39'),
(3, 16, '2015-05-09 13:19:57'),
(3, 20, '2015-05-09 15:01:09')
I would like to see to a query that for each event_type that has been registered more than once returns the difference between the latest and the second latest value .
Given the above table, I am expecting following output:
event_type value
2 -5
3 4
As I know in SQL Sever/Oracle, this can be achieved using row_number() over (partition by).

You could always simulate ROW_NUMBER:
WITH cte AS
(
SELECT *,
(SELECT COUNT(*) + 1
FROM "events" e1
WHERE e1.event_type = e.event_type
AND e1.time > e.time) AS rn
FROM "events" e
)
SELECT c.event_type, c."value" - c2."value" AS "value"
FROM cte c
JOIN cte c2
ON c.event_type = c2.event_type
AND c.rn = 1 AND c2.rn = 2
ORDER BY event_type, time;
SqlFiddleDemo
Output:
╔═══════════════╦═══════╗
║ event_type ║ value ║
╠═══════════════╬═══════╣
║ 2 ║ -5 ║
║ 3 ║ 4 ║
╚═══════════════╩═══════╝
Identifiers like time/events/value are reserwed words in some SQL dialects.

Related

SQL to Find Max date Value from each group- SQL Server

So I have 2 tables which iam joining using Inner Join.
Table 1 :
Name
Batch_Date
AcctID
Bob
18-08-11
32
Bob
19-08-11
32
Shawn
18-08-11
42
Shawn
20-08-11
42
Paul
18-08-11
36
Paul
19-08-11
36
Table 2
Code
order_Date
AcctID
1
18-08-11
32
0
NULL
32
0
NULL
42
0
NULL
42
1
18-08-11
36
1
18-08-11
36
So I want to get the name, last batch_date , AcctID from the table 1
and code, order date from table 2.
The challenge for me here is as there are multiple rows of same AcctId in table 2, if for any acctid, the date column is not null, I want to select that date and if date column is null for each row, I want to select the null value for date.
SO resulting dataset should look like below:
Name
Batch_Date
AcctID
Code
Order_Date
Bob
19-08-11
32
1
18-08-11
Shawn
20-08-11
42
0
NULL
Paul
19-08-11
36
1
18-08-11
OK, try this
--Set up your sample data in useable form, skip in your actual solution
with cteT1 as (
SELECT *
FROM (VALUES ('Bob', '18-08-11', 32), ('Bob', '19-08-11', 32)
, ('Shawn', '18-08-11', 42), ('Shawn', '20-08-11', 42)
, ('Paul', '18-08-11', 36), ('Paul', '19-08-11', 36)
) as T1 (CustName, BatchDate, AcctID)
), cteT2 as (
SELECT *
FROM (VALUES (1, '18-08-11', 32), (0, NULL, 32), (0, NULL, 42)
, (0, NULL, 42), (1, '18-08-11', 36), (1, '18-08-11', 36)
) as T2 (OrderCode, OrderDate, AcctID)
)
--Set up the solution - tag the newest of each table
, cteTopBatches as (
SELECT ROW_NUMBER() over (PARTITION BY AcctID ORDER BY BatchDate DESC) as BatchNewness, *
FROM cteT1
), cteTopOrders as (
SELECT ROW_NUMBER() over (PARTITION BY AcctID ORDER BY OrderDate DESC) as OrderNewness, *
FROM cteT2 --NOTE: NULLs sort below actual dates, but you could use COALESCE to force a specific value to use, probably in another CTE for readability
)
--Now combine the 2 tables keeping only the newest of each
SELECT T1.AcctID , T1.CustName , T1.BatchDate , T2.OrderCode , T2.OrderDate
FROM cteTopBatches as T1 INNER JOIN cteTopOrders as T2 ON T1.AcctID = T2.AcctID
WHERE T1.BatchNewness = 1 AND T2.OrderNewness = 1

Sampling for a SQL Database

I have a column "steak" representing the amount of steak in pounds my firm has bought since day 1 of 2010.
I have another column "c_steak" representing the cumulative sum of pounds of steak.
╔═══╦════════════╦═════════════╗
║ ║ steak ║ c_steak ║
╠═══╬════════════╬═════════════╣
║ 1 ║ 0.2 ║ 0.2 ║
║ 2 ║ 0.2 ║ 0.4 ║
║ 3 ║ 0.3 ║ 0.7 ║
╚═══╩════════════╩═════════════╝
How do I sample the table such that a row is taken once we buy another 100 pounds of steak? (sample ONE row immediately after c_steak reaches 100, 200, 300, 400 etc).
Note(EDIT):
c_steak is float. It may not exactly hit 100, 200, 300....
If c_steak goes like ..., 99.5, 105.3, 107.1, ... then the row corresponding to 105.3 will be sampled.
if c_steak goes like ..., 99, 100.1, 100.2, 100.3, 105..., then the row corresponding to 100.1 will be sampled.
It almost certain you need LAG method. You can try like:
SELECT *
FROM (
SELECT c_steak
,lag(c_steak, 1, 0) OVER (ORDER BY id) lg
FROM myTable
) sub
WHERE cast(sub.c_steak as int) %100 - cast(sub.lg as int)% 100 < 0
The logic is that when you reach a sum of 100, 200 etc, the difference in modulus with the previous value should be negative.
e.g:
80%100 = 80 where as 101%100 = 1
195%100 = 95 where as 205%100 = 5
293%100 = 93 where as 320%100 = 20
etc
This works:
SELECT m2.id,m2.steak,m2.c_steak FROM t1 as m1 inner join t1 as m2 on m2.id = m1.id + 1 WHERE cast(m2.c_steak as int) % 100 < cast(m1.c_steak as int) % 100;
Look here:
DEMO
===========
EDIT (in case id column skips at all):
SELECT distinct m2.id,m2.steak,m2.c_steak FROM t1 as m1 inner join t1 as m2 on m2.id > m1.id WHERE cast(m2.c_steak as int) % 100 < cast(m1.c_steak as int) % 100;
DEMO
===========
You can use the MOD() function. Docs: https://dev.mysql.com/doc/refman/8.0/en/mathematical-functions.html#function_mod
SELECT * FROM Table WHERE MOD(c_steak, 100) = 0;
EDIT:
In response to OPs edit, you can use FLOOR() on c_steak to get an int. Docs: https://dev.mysql.com/doc/refman/8.0/en/mathematical-functions.html#function_floor
SELECT * FROM Table WHERE MOD(FLOOR(c_steak), 100) = 0;
You could have supplied some sample data.
Doing it now :
WITH
-- sample data , this will be in the table
input(id,steak_sold) AS (
SELECT 1,30.07
UNION ALL SELECT 2,30.01
UNION ALL SELECT 3,30.02
UNION ALL SELECT 4,30.03
UNION ALL SELECT 5,30.04
UNION ALL SELECT 6,30.05
UNION ALL SELECT 7,30.06
UNION ALL SELECT 8,30.07
UNION ALL SELECT 9,30.08
UNION ALL SELECT 10,30.09
UNION ALL SELECT 11,30.10
UNION ALL SELECT 12,30.11
UNION ALL SELECT 13,30.12
UNION ALL SELECT 14,30.13
UNION ALL SELECT 15,30.14
UNION ALL SELECT 16,30.15
UNION ALL SELECT 17,30.16
)
-- real WITH clause would begin here: creating running sum myself ....
,
runsum AS (
SELECT
*
, SUM(steak_sold) OVER(ORDER BY id) AS c_steak
FROM input
)
SELECT
*
FROM runsum
-- this running sum is above a certain 100
-- previous (running sum - steak_sold) below that 100
-- integer division by 100 of the two differs
WHERE c_steak//100 <> (c_steak - steak_sold) //100;
-- out id | steak_sold | c_steak
-- out ----+------------+---------
-- out 4 | 30.03 | 120.13
-- out 7 | 30.06 | 210.28
-- out 10 | 30.09 | 300.52
-- out 14 | 30.13 | 420.98
-- out 17 | 30.16 | 511.43
-- out (5 rows)
-- out
-- out Time: First fetch (5 rows): 53.018 ms. All rows formatted: 53.066 ms

TSQL - Sum on time column with condition

Need to create a query, which will get summary of time during which was bit set ON/OFF.
Example:
╔═══════════════════════════╗
║ TABLE ║
╠════╦════════╦═════╦═══════╣
║ ID ║ TIME ║ BIT ║ VALUE ║
╠════╬════════╬═════╬═══════╣
║ 1 ║ 13:40 ║ 1 ║ 5 ║
║ 2 ║ 13:45 ║ 1 ║ 3 ║
║ 3 ║ 13:50 ║ 1 ║ 1 ║
║ 4 ║ 13:55 ║ 0 ║ 2 ║
║ 5 ║ 14:00 ║ 0 ║ 7 ║
║ 6 ║ 14:05 ║ 1 ║ 3 ║
║ 7 ║ 14:10 ║ 1 ║ 4 ║
║ 8 ║ 14:15 ║ 0 ║ 2 ║
║ 9 ║ 14:20 ║ 1 ║ 2 ║
╚════╩════════╩═════╩═══════╝
I would like to have total summary of TIME (and VALUE - simpler one) when the BIT was SET ON:
13:40 - 13:50 = 10 mins
14:05 - 14:10 = 5 mins
14:20 = no end time, 0 mins
-----------------------------------------
15 mins
Have found:
How to sum up time field in SQL Server = Sort of good question, but there is static time as start (0:00:00) which won't work in this case
Aggregate function over a given time interval = there is aggregation but not conditioned and works on all data
I thought that this could be done as a recursive function (passing last processed datetime), which will pass the last date which was handled, and sum the datetime since the BIT is ON.
SQL query for summing the VALUE (easy one):
SELECT SUM(Value)
FROM Table
WHERE Bit = 1
How should I get total value of minutes (time), during which was the BIT set ON?
EDIT: Query which can be used for testing:
DECLARE #Table TABLE(
ID INT Identity(1,1) PRIMARY KEY,
[TIME] DATETIME NOT NULL,
[BIT] BIT NOT NULL,
[VALUE] INT NOT NULL
);
INSERT INTO #Table([TIME],[BIT],[VALUE]) VALUES('13:40',1,5);
INSERT INTO #Table([TIME],[BIT],[VALUE]) VALUES('13:45',1,3);
INSERT INTO #Table([TIME],[BIT],[VALUE]) VALUES('13:50',1,1);
INSERT INTO #Table([TIME],[BIT],[VALUE]) VALUES('13:55',0,2);
INSERT INTO #Table([TIME],[BIT],[VALUE]) VALUES('14:00',0,7);
INSERT INTO #Table([TIME],[BIT],[VALUE]) VALUES('14:05',1,3);
INSERT INTO #Table([TIME],[BIT],[VALUE]) VALUES('14:10',1,4);
INSERT INTO #Table([TIME],[BIT],[VALUE]) VALUES('14:15',0,2);
INSERT INTO #Table([TIME],[BIT],[VALUE]) VALUES('14:20',1,2);
SELECT * FROM #Table;
Use LEAD function to get time in the next row and to calculate time interval. Then just group result by [bit]
WITH t AS(
SELECT
[time],
DATEDIFF(minute, [time], LEAD([time], 1, null) OVER (ORDER BY [time])) AS interval,
[bit],
[value]
FROM table1)
SELECT [bit], CAST(DATEADD(MINUTE, SUM(interval), '00:00') AS TIME), SUM([value]) FROM t
GROUP BY [bit]
You have two issues: summing up the time and identify the adjacent values. You can handle the second with the difference of row numbers approach. You can handle the former by converting to minutes:
select bit, min(time), max(time),
sum(datediff(minute, 0, time)) as minutes,
sum(value)
from (select t.*,
row_number() over (order by id) as seqnum,
row_number() over (partition by bit order by id) as seqnum_b
from t
) t
group by (seqnum - seqnum_b), bit;
This is a "gaps and islands" problem, with a pretty standard solution. I came up with this, which is pretty much the same as Gordon's, but has an extra step to calculate the intervals. This is the only reason I am posting what is essentially a duplicate answer, I'm not sure that taking the difference in minutes from zero actually works?
DECLARE #table TABLE (id int, [time] TIME, [bit] BIT, value INT);
INSERT INTO #table SELECT 1, '13:40', 1, 5;
INSERT INTO #table SELECT 2, '13:45', 1, 3;
INSERT INTO #table SELECT 3, '13:50', 1, 1;
INSERT INTO #table SELECT 4, '13:55', 0, 2;
INSERT INTO #table SELECT 5, '14:00', 0, 7;
INSERT INTO #table SELECT 6, '14:05', 1, 3;
INSERT INTO #table SELECT 7, '14:10', 1, 4;
INSERT INTO #table SELECT 8, '14:15', 0, 2;
INSERT INTO #table SELECT 9, '14:20', 1, 2;
WITH x AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY [bit] ORDER BY id) AS a_id, ROW_NUMBER() OVER (ORDER BY id) AS b_id FROM #table),
y AS (
SELECT [bit], MIN([time]) AS min_time, MAX([time]) AS max_time, SUM(value) AS value FROM x GROUP BY a_id - b_id, [bit])
SELECT [bit], SUM(value) AS total_value, SUM(DATEDIFF(MINUTE, min_time, max_time)) AS total_minutes FROM y GROUP BY [bit];
Results:
bit total_value total_minutes
0 11 5
1 18 15
As a bonus here is a solution that only solves the actual question, i.e. how much elapsed time is there when the BIT is set to 1:
WITH x AS (SELECT id, id - DENSE_RANK() OVER(ORDER BY id) AS grp FROM #table WHERE [bit] = 1), y AS (SELECT MIN(id) AS range_start, MAX(id) AS range_end FROM x GROUP BY grp)
SELECT SUM(DATEDIFF(MINUTE, t1.[time], t2.[time])) AS minutes_elapsed FROM y INNER JOIN #table t1 ON t1.id = y.range_start INNER JOIN #table t2 ON t2.id = y.range_end;

How to select the equivalent row for another column of the max(column) in group by in SQL Server

I need to make a change in the Sql below to make CreatedOn return the selected record of the Max(Value). You can observe the -- todo line.
Should return: 2/01/2015 and 8/01/2015 as you can see in Query Result,
but the Max(CreatedOn) will select the max and not the referent record
of the Max(Value).
Sql
SET DATEFIRST 1
SELECT
CONCAT(DATEPART(YEAR, CreatedOn),DATEPART(WEEK, CreatedOn)) Week,
MAX(CreatedOn) CreatedOn, -- todo: this should return 2/01/2015 and 8/01/2015
MAX(Value) AS MaxValue
FROM Table1
GROUP BY CONCAT(DATEPART(YEAR, CreatedOn),DATEPART(WEEK, CreatedOn))
Table 1:
╔════╦═══════════╦═══════╗
║ Id ║ CreatedOn ║ Value ║
╠════╬═══════════╬═══════╣
║ 1 ║ 1/01/2015 ║ 1 ║
║ 2 ║ 2/01/2015 ║ 2 ║
║ 3 ║ 8/01/2015 ║ 4 ║
║ 4 ║ 9/01/2015 ║ 2 ║
╚════╩═══════════╩═══════╝
Query Result:
╔════════╦═══════════╦══════════╗
║ Week ║ CreatedOn ║ MaxValue ║
╠════════╬═══════════╬══════════╣
║ 2015 1 ║ 2/01/2015 ║ 2 ║
║ 2015 2 ║ 8/01/2015 ║ 4 ║
╚════════╩═══════════╩══════════╝
*Edited: I need to return 8/01/2015 because it is the correspondent row of the MaxValue (4).
You can use the ROW_NUMBER() over a partition of each week (PARTITION BY Week), ordering by the descending value (ORDER BY Value DESC) to 'rank' each record within the week. Selecting the row with the highest value in each week is then simply the top ranked row in each partition (WHERE Rnk = 1). I've used CTEs to prevent the repetition of the Week calculation.
WITH Weeks AS
(
SELECT CONCAT(DATEPART(YEAR, CreatedOn),DATEPART(WEEK, CreatedOn)) Week,
Id, CreatedOn, Value
FROM Table1
),
Ranked As
(
SELECT Week, CreatedOn, Value,
ROW_NUMBER() OVER (PARTITION BY Week ORDER BY Value DESC) Rnk
FROM Weeks
)
SELECT Week, CreatedOn, Value
FROM Ranked
WHERE Rnk = 1;
SqlFiddle here
Your query is correct, however there is a problem with the date format. You are reading your dates as dd/mm/yyyy while DB is interpreting them as mm/dd/yyyy.
So a quick fix is, use proper format while inserting values like
insert into Table1 ( id , CreatedOn, value)
values (1 , '01/01/2015' , 1 )
insert into Table1 ( id , CreatedOn, value)
values (2 , '01/02/2015' , 2 )
insert into Table1 ( id , CreatedOn, value)
values (3 , '01/09/2015' , 4 )
insert into Table1 ( id , CreatedOn, value)
values (4 , '01/08/2015' , 2)
I tried in this worked. Let me know if you need SQLFiddle of the same.

Recursive SQL Query descending

I use a recursive query to identify all related child categories of a given category-id.
WITH parent AS ( SELECT ParentId, Title, Id
FROM [CocoDb].[dbo].[Categories]
WHERE Id = #CategoryId),tree AS
(
SELECT x.ParentId, x.Id, x.Title
FROM [CocoDb].[dbo].[Categories] x
INNER JOIN parent ON x.ParentId = parent.Id
UNION ALL
SELECT y.ParentId, y.Id, y.Title
FROM [CocoDb].[dbo].[Categories] y
INNER JOIN tree t ON y.ParentId = t.Id
)
SELECT * FROM Tree
My table is:
Categories (ID INT PRIMARY KEY, ParentId INT, Title VARCHAR(MAX))
Question for SQL Server: can I have a query that returns ALL categories?
As example:
Books (Id: 1, Parent: 0)
Horror (Id:2, Parent: 1)
Steven King (Id:3, Parent: 2)
And now if I ask for Steven King (Id 3) I will get all related categories like in this case Books and Horror.
Is that possible with a recursive SQL query?
Best regards
Test Data
DECLARE #Categories TABLE(ID INT PRIMARY KEY, ParentId INT, Title VARCHAR(MAX))
INSERT INTO #Categories
VALUES (1, 0, 'Books'),(2, 1, 'Horro'),(3, 2, 'Steven King')
,(4, 3, 'Value 4'),(5, 4, 'Value 5')
Query
;WITH ParentCte
AS
(
SELECT p.ID ,p.ParentId ,p.Title
FROM #Categories p
WHERE p.Title = 'Steven King'
UNION ALL
SELECT c.ID ,c.ParentId,c.Title
FROM ParentCte cte INNER JOIN #Categories c
ON c.ID = cte.ParentId
)
SELECT *
FROM ParentCte m
Result Set
╔════╦══════════╦═════════════╗
║ ID ║ ParentId ║ Title ║
╠════╬══════════╬═════════════╣
║ 3 ║ 2 ║ Steven King ║
║ 2 ║ 1 ║ Horro ║
║ 1 ║ 0 ║ Books ║
╚════╩══════════╩═════════════╝