Find overlapping prescriptions - sql-server-2008

I have a bunch of patient prescriptions, each having a certain start date and an end date. I would like to find the instances where a patient has been taking more than one drug in the same drug category for more than 2 days. duration should overlap.
Table structure looks like this:
PatientID StartDate EndDate Drug DrugCategory
1 1/1/2013 1/5/2013 A Cat1
1 1/1/2013 1/4/2013 B Cat1
1 1/10/2013 1/12/2013 C Cat1
2 ....... ........ ............. .........
As seen above, Patient-1 was prescribed 3 drugs in the same category and the first two drug overlapped in duration more than 2 days. So, for this example, I would like the query return the first two records for Patient-1 along with drug name, patientid.
Hope someone can help. This is using SQL Server 2008 R2 btw.

Do you want them as separate rows or as one row? If you want them as separate rows, this should work; otherwise you can pivot the result.
create table want as
select H.* from have H, have V
where H.drug ne V.drug
and H.PatientID=V.PatientID
and H.startDate <= V.startDate
and V.startDate <= H.endDate-2
union select V.* from have H, have V
where H.drug ne V.drug
and H.PatientID=V.PatientID
and H.startDate <= V.startDate
and V.startDate <= H.endDate-2
;
I union the H and V records, I'm sure there's a more efficient way to do that but couldn't easily come up with one. (Just H works for the example given, but for a more proper example where the start dates are not always equal you need the V row as well.)

Please test this thoroughly before using. I have tested it and so far it looks good but not done an exhaustive testing. Would be great if you can test it out further sufficient to your requirements and can point any anomalies in the result if present or take it forward and modify if required.
--Test data:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[Prescriptions](
[PatientID] [int] NULL,
[StartDate] [datetime] NULL,
[EndDate] [datetime] NULL,
[Drug] [varchar](50) NULL,
[DrugCategory] [varchar](50) NULL
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
INSERT [dbo].[Prescriptions] ([PatientID], [StartDate], [EndDate], [Drug], [DrugCategory]) VALUES (1, CAST(0x0000A13A00000000 AS DateTime), CAST(0x0000A13E00000000 AS DateTime), N'D', N'Cat1')
INSERT [dbo].[Prescriptions] ([PatientID], [StartDate], [EndDate], [Drug], [DrugCategory]) VALUES (1, CAST(0x0000A13A00000000 AS DateTime), CAST(0x0000A13B00000000 AS DateTime), N'E', N'Cat1')
INSERT [dbo].[Prescriptions] ([PatientID], [StartDate], [EndDate], [Drug], [DrugCategory]) VALUES (1, CAST(0x0000A13800000000 AS DateTime), CAST(0x0000A13B00000000 AS DateTime), N'F', N'Cat1')
INSERT [dbo].[Prescriptions] ([PatientID], [StartDate], [EndDate], [Drug], [DrugCategory]) VALUES (1, CAST(0x0000A13800000000 AS DateTime), CAST(0x0000A13900000000 AS DateTime), N'G', N'Cat1')
INSERT [dbo].[Prescriptions] ([PatientID], [StartDate], [EndDate], [Drug], [DrugCategory]) VALUES (1, CAST(0x0000A12300000000 AS DateTime), CAST(0x0000A13900000000 AS DateTime), N'Z', N'Cat1')
INSERT [dbo].[Prescriptions] ([PatientID], [StartDate], [EndDate], [Drug], [DrugCategory]) VALUES (1, CAST(0x0000A12300000000 AS DateTime), CAST(0x0000A13A00000000 AS DateTime), N'Y', N'Cat1')
INSERT [dbo].[Prescriptions] ([PatientID], [StartDate], [EndDate], [Drug], [DrugCategory]) VALUES (1, CAST(0x0000A13900000000 AS DateTime), CAST(0x0000A13D00000000 AS DateTime), N'A', N'Cat1')
INSERT [dbo].[Prescriptions] ([PatientID], [StartDate], [EndDate], [Drug], [DrugCategory]) VALUES (1, CAST(0x0000A13900000000 AS DateTime), CAST(0x0000A13C00000000 AS DateTime), N'B', N'Cat1')
INSERT [dbo].[Prescriptions] ([PatientID], [StartDate], [EndDate], [Drug], [DrugCategory]) VALUES (1, CAST(0x0000A14200000000 AS DateTime), CAST(0x0000A14400000000 AS DateTime), N'C', N'Cat1')
Query Used:
SELECT DISTINCT PatientID,StartDate,EndDate,Drug,DrugCategory FROM (
SELECT
DATEDIFF(dd,a.startdate,b.startdate) c1
,DATEDIFF(dd,a.enddate,b.enddate)c2
,DATEDIFF(dd,a.startdate,b.enddate) c3
,DATEDIFF(dd,a.enddate,b.startdate) c4
,DATEDIFF(dd,a.startdate,b.enddate)+DATEDIFF(dd,a.enddate,b.startdate) c34
,a.PatientID
,a.StartDate
,a.EndDate
,a.Drug
,a.DrugCategory
,b.PatientID AS PatientID1
,b.StartDate AS StartDate1
,b.EndDate AS EndDate1
,b.Drug AS Drug1
,b.DrugCategory DrugCategory1
FROM Prescriptions a
,Prescriptions b
WHERE a.patientid=b.patientid
AND a.DrugCategory= b.DrugCategory
and a.drug<>b.drug
)a
WHERE c1*c2*c3*c4 <0
AND c3>2
and c4<=-2
ORDER BY 1,2,3,4
Results:
PatientID StartDate EndDate Drug DrugCategory
----------- ----------------------- ----------------------- -------------------------------------------------- --------------------------------------------------
1 2012-12-10 00:00:00.000 2013-01-02 00:00:00.000 Y Cat1
1 2012-12-31 00:00:00.000 2013-01-03 00:00:00.000 F Cat1
1 2013-01-01 00:00:00.000 2013-01-04 00:00:00.000 B Cat1
1 2013-01-01 00:00:00.000 2013-01-05 00:00:00.000 A Cat1
1 2013-01-02 00:00:00.000 2013-01-06 00:00:00.000 D Cat1
(5 row(s) affected)

what you should be able to do is join the prescription table to itself on the patientID and drugCategory fields where the drugName differs and the startDate OR endDate of the 2nd instance spans the startDate/endDate of the first. Then, determine the overlap range by subtracting the number of days between the max(startDates) and min(endDates). If the overlap is greater than 2 days, return the row:
select *, datediff(d, start_max, end_min) as overlap
from (
SELECT
P.PatientID, P.StartDate, P.EndDate, P.Drug, P.DrugCategory,
P1.StartDate AS p1_start, P1.EndDate AS p1_end, P1.Drug AS p1_drug,
CASE WHEN p.startdate >= P1.startdate THEN p.startdate ELSE P1.startdate END AS start_max,
CASE WHEN p.EndDate <= P1.EndDate THEN p.EndDate ELSE P1.EndDate END AS end_min
FROM
dbo.Prescriptions p INNER JOIN
dbo.Prescriptions AS P1 ON
P.PatientID = P1.PatientID AND
P.DrugCategory = P1.DrugCategory AND
P.Drug <> P1.Drug
WHERE
(P1.StartDate >= P.StartDate AND P1.StartDate <= P.EndDate) OR
(P1.EndDate >= P.StartDate AND P1.EndDate <= P.EndDate)
) t
where
datediff(d, start_max, end_min) > 2

Related

how to get location based on multiple conditions in sql server

How can I get my desired output based on two tables in sql server:
CREATE TABLE [dbo].[Goal](
[Location] [varchar](50) NULL,
[Goal] [int] NULL,
[Month] [date] NULL
)
CREATE TABLE [dbo].[Master](
[Date] [date] NULL,
[Employee] [varchar](50) NULL,
[GP] [int] NULL,
[Location] [varchar](50) NULL
)
INSERT [dbo].[Goal] ([Location], [Goal], [Month]) VALUES (N'MG Road', 50000, CAST(0xB93D0B00 AS Date))
GO
INSERT [dbo].[Goal] ([Location], [Goal], [Month]) VALUES (N'Madiwala', 60000, CAST(0xB93D0B00 AS Date))
GO
INSERT [dbo].[Goal] ([Location], [Goal], [Month]) VALUES (N'Silk Board', 30000, CAST(0xB93D0B00 AS Date))
GO
INSERT [dbo].[Goal] ([Location], [Goal], [Month]) VALUES (N'BTM', 35000, CAST(0xB93D0B00 AS Date))
GO
INSERT [dbo].[Goal] ([Location], [Goal], [Month]) VALUES (N'MG Road', 55000, CAST(0xBA3D0B00 AS Date))
GO
INSERT [dbo].[Goal] ([Location], [Goal], [Month]) VALUES (N'Madiwala', 65000, CAST(0xBA3D0B00 AS Date))
GO
INSERT [dbo].[Goal] ([Location], [Goal], [Month]) VALUES (N'Silk Board', 35000, CAST(0xBA3D0B00 AS Date))
GO
INSERT [dbo].[Goal] ([Location], [Goal], [Month]) VALUES (N'BTM', 35000, CAST(0xBA3D0B00 AS Date))
GO
INSERT [dbo].[Master] ([Date], [Employee], [GP], [Location]) VALUES (CAST(0x283E0B00 AS Date), N'Ram', 2000, N'MG Road')
GO
INSERT [dbo].[Master] ([Date], [Employee], [GP], [Location]) VALUES (CAST(0x283E0B00 AS Date), N'Ram', 1800, N'Silk Board')
GO
INSERT [dbo].[Master] ([Date], [Employee], [GP], [Location]) VALUES (CAST(0x293E0B00 AS Date), N'Sami', 15000, N'BTM')
GO
INSERT [dbo].[Master] ([Date], [Employee], [GP], [Location]) VALUES (CAST(0x2A3E0B00 AS Date), N'Ram', 2500, N'Silk Board')
GO
INSERT [dbo].[Master] ([Date], [Employee], [GP], [Location]) VALUES (CAST(0x2A3E0B00 AS Date), N'Ram', 2500, N'MG Road')
GO
INSERT [dbo].[Master] ([Date], [Employee], [GP], [Location]) VALUES (CAST(0x293E0B00 AS Date), N'Sami', 2000, N'BTM')
GO
INSERT [dbo].[Master] ([Date], [Employee], [GP], [Location]) VALUES (CAST(0x2A3E0B00 AS Date), N'Sami', 19000, N'Madiwala')
GO
INSERT [dbo].[Master] ([Date], [Employee], [GP], [Location]) VALUES (CAST(0x2E3E0B00 AS Date), N'Ram', 30000, N'MG Road')
GO
INSERT [dbo].[Master] ([Date], [Employee], [GP], [Location]) VALUES (CAST(0x2E3E0B00 AS Date), N'Ram', 30000, N'Madiwala')
GO
INSERT [dbo].[Master] ([Date], [Employee], [GP], [Location]) VALUES (CAST(0x2F3E0B00 AS Date), N'Ram', 25000, N'Madiwala')
GO
INSERT [dbo].[Master] ([Date], [Employee], [GP], [Location]) VALUES (CAST(0x2E3E0B00 AS Date), N'Sami', 20000, N'BTM')
GO
INSERT [dbo].[Master] ([Date], [Employee], [GP], [Location]) VALUES (CAST(0x2E3E0B00 AS Date), N'Sami', 15000, N'Silk Board')
GO
INSERT [dbo].[Master] ([Date], [Employee], [GP], [Location]) VALUES (CAST(0x2F3E0B00 AS Date), N'Sami', 15000, N'Silk Board')
based on above data I want output like below :
Employe |Location |TotalGP|Month Goal |Goal %
Ram |MG Road |3800 |50000 |7.6
Ram |MG Road |5000 |50000 |10
Ram |Madiwala |60000 |65000 |92.30769231
Sami |Madiwala |17000 |60000 |28.33333333
Sami |Madiwala |19000 |60000 |31.66666667
Sami |Silk board |35000 |35000 |100
sami |Silk board |15000 |35000 |42.85714286
Ram |Madiwala |25000 |65000 |38.46153846
I tried this:
select employee,date,sum(gp)totalgp
from master
group by employee,date
select * from
(select date,employee,location,gp,ROW_NUMBER() over (partition by employee,date order by gp desc ) as rn
from master
) a
where rn=1
above query not given expected result.
Goal %: totalgp/monthgoal * 100
Month Goal:Month goal should be selected from the month goal table, and it should displaying the goal of the location in which the employee has yielded maximum GP (sum of GP) in a
eg : Ram worked in both MG Road and madiwala, but his sum of GP is more in MG Road so MG road'Goal is diplayed against Ram for April month , but for May month Ram's Goal
Total GP definition:
Eg Ram on 25-04-18 has gained 2000 only in MG road , but in other location on same day he has gained 1800 extra , so we are displaying 3800 for Ram irrespective to the location
Location column definition
Location - The employee might work in one or two locations , but here you should display the location in which he has scored maximum Gp (total Gp of the month in a
eg : Ram worked in both MG Road and madiwala, but his sum of GP is more in MG Road so MG road is diplayed for April month , but for May month Ram locaion is madiwala
Use this query
select date,employee,sum(gp)as totalgp,max(goal) as monthgoal,cast(sum(gp)* 100.0/max(goal) as float) as 'Goal%'
from goal
inner join master on master.location=goal.location
where month(goal.month)=month(master.date)
group by date,employee
The output is like this
date employee totalgp monthgoal Goal%
---------- -------------------------------------------------- ----------- ----------- ----------------------
2018-04-25 Ram 3800 50000 7.6
2018-04-26 Sami 17000 35000 48.571428571428
2018-04-27 Ram 5000 50000 10
2018-04-27 Sami 19000 60000 31.666666666666
2018-05-01 Ram 60000 65000 92.307692307692
2018-05-01 Sami 35000 35000 100
2018-05-02 Ram 25000 65000 38.461538461538
2018-05-02 Sami 15000 35000 42.857142857142
Note :
You have to make the date format same for both the tables

How can I calculate portfolio return in MySQL?

I am stuck on a MySQL problem. I am trying to calculate the return series of a portfolio using:
for(i = startdate+1; i <= enddate; i++) {
return[i]=0;
for(n = 0; n < count(instruments); n++) {
return[i] += price[i,n] / price[i-1, n] * weight[n];
}
}
So, the return of portfolio today is calculated as a sum of price_today/price_yesterday*weight over the instruments in the portfolio.
I created a scribble at http://rextester.com/FUC35243.
If it doesn't work, the code is:
DROP TABLE IF EXISTS x_ports;
DROP TABLE IF EXISTS x_weights;
DROP TABLE IF EXISTS x_prices;
CREATE TABLE IF NOT EXISTS x_ports (id INT NOT NULL AUTO_INCREMENT, name VARCHAR(20), PRIMARY KEY (id));
CREATE TABLE IF NOT EXISTS x_weights (id INT NOT NULL AUTO_INCREMENT, port_id INT, inst_id INT, weight DOUBLE, PRIMARY KEY (id));
CREATE TABLE IF NOT EXISTS x_prices (id INT NOT NULL AUTO_INCREMENT, inst_id INT, trade_date DATE, price DOUBLE, PRIMARY KEY (id));
INSERT INTO x_ports (name) VALUES ('PORT A');
INSERT INTO x_ports (name) VALUES ('PORT B');
INSERT INTO x_weights (port_id, inst_id, weight) VALUES (1, 1, 20.0);
INSERT INTO x_weights (port_id, inst_id, weight) VALUES (1, 2, 80.0);
INSERT INTO x_weights (port_id, inst_id, weight) VALUES (2, 1, 100.0);
INSERT INTO x_prices (inst_id, trade_date, price) VALUES (1, '2018-01-01', 1.12);
INSERT INTO x_prices (inst_id, trade_date, price) VALUES (1, '2018-01-02', 1.13);
INSERT INTO x_prices (inst_id, trade_date, price) VALUES (1, '2018-01-03', 1.12);
INSERT INTO x_prices (inst_id, trade_date, price) VALUES (1, '2018-01-04', 1.12);
INSERT INTO x_prices (inst_id, trade_date, price) VALUES (1, '2018-01-05', 1.13);
INSERT INTO x_prices (inst_id, trade_date, price) VALUES (1, '2018-01-06', 1.14);
INSERT INTO x_prices (inst_id, trade_date, price) VALUES (2, '2018-01-01', 50.23);
INSERT INTO x_prices (inst_id, trade_date, price) VALUES (2, '2018-01-02', 50.45);
INSERT INTO x_prices (inst_id, trade_date, price) VALUES (2, '2018-01-03', 50.30);
INSERT INTO x_prices (inst_id, trade_date, price) VALUES (2, '2018-01-04', 50.29);
INSERT INTO x_prices (inst_id, trade_date, price) VALUES (2, '2018-01-05', 50.40);
INSERT INTO x_prices (inst_id, trade_date, price) VALUES (2, '2018-01-06', 50.66);
# GETTING THE DATES
SET #DtShort='2018-01-01';
SET #DtLong=#DtShort;
SELECT
#DtShort:=#DtLong as date_prev,
#DtLong:=dt.trade_date as date_curent
FROM
(SELECT DISTINCT trade_date FROM x_prices ORDER BY trade_date) dt;
# GETTING RETURN FOR SINGLE DAY
SET #DtToday='2018-01-03';
SET #DtYesterday='2018-01-02';
SELECT
x2.trade_date,
x2.portfolio,
sum(x2.val*x2.weight)/sum(x2.weight) as ret
FROM
(SELECT
x1.trade_date,
x1.portfolio,
sum(x1.weight)/2.0 as weight,
sum(x1.val_end)/sum(x1.val_start) as val,
sum(x1.val_start) as val_start,
sum(x1.val_end) as val_end
FROM
(SELECT
#DtToday as trade_date,
prt.name as portfolio,
wts.inst_id as iid,
wts.weight,
if(prc.trade_date=#DtToday,prc.price*wts.weight,0) as val_start,
if(prc.trade_date=#DtYesterday,prc.price*wts.weight,0) as val_end
FROM
x_ports prt,
x_weights wts,
x_prices prc
WHERE
wts.port_id=prt.id and
prc.inst_id=wts.inst_id and
(prc.trade_date=#DtToday or prc.trade_date=#DtYesterday)) x1
GROUP BY x1.portfolio) x2
GROUP BY x2.portfolio;
I hope to be able to produce a result looking like this:
Date Port A Port B
--------------------------------------------
01/01/2010
02/01/2010 1.005289596 1.004379853
03/01/2010 0.995851496 0.997026759
04/01/2010 0.999840954 0.999801193
05/01/2010 1.003535565 1.002187314
06/01/2010 1.005896896 1.00515873
The return for Port A on the 2/1/2018 should be calculated as 1.13/1.12*20/(20+80) + 50.45/50.23*80/(20+80).
The return for Port B on the 2/1/2018 should be calculated as 50.45/50.23*100/100, or possibly 1.13/1.12*0/(0+100) + 50.45/50.23*100/(0+100).
FYI, in the looping function above, I only calculate at the nominator (or the unscaled weight) so that Port A would be calculated as 1.13/1.12*20+50.45/50.23*80, which I see as the crucial step when calculating the return. The return is then found by dividing it by the sum of the weight.
Though it certainly can be done better, I can get the dates and I can calculate the return of a single day, but I just can't put the two together.
Simulating analytics is no fun! Demo
The math on this doesn't seem right to me; as I'm no where close to your 'looks like results'
I'd like to be able to reuse CurDay but as the version is lower I couldn't use a common table expression.
What this does:
X1 generate the join of the tables
X2 gives us a count of the instruments in a portfolio used later in math
r generates a uservariable on which we can assign rows #rn and #Rn2
CurDay generate a rownumber ordered correctly so we can join
NextDay generates a copy of CurDay so we can join curday to next day on RN+1
Z allows us to do the math and group by current day and prepare for pivot on the portfolio name.
Outer most select allows us to pivot the data so we have date+2 columns
.
SELECT Z.Trade_Date
, sum(case when name = 'Port A' then P_RETURN end) as PortA
, sum(case when name = 'Port B' then P_RETURN end) as PortB
FROM (
## Raw data
SELECT CurDay.*, NextDay.Price/CurDay.Price*CurDay.Weight/CurDay.Inst_Total_Weight as P_Return
FROM (SELECT x1.*, #RN:=#RN+1 rn,x2.inst_cnt, x2.Inst_Total_Weight
FROM (SELECT prt.name, W.port_ID, W.inst_ID, W.weight, prc.trade_Date, Prc.Price
FROM x_ports Prt
INNER JOIN x_weights W
on W.Port_ID = prt.ID
INNER JOIN x_prices Prc
on Prc.INST_ID = W.INST_ID
ORDER BY W.port_id, W.inst_id,trade_Date) x1
CROSS join (SELECT #RN:=0) r
INNER join (SELECT count(*) inst_Cnt, port_ID, sum(Weight) as Inst_Total_Weight
FROM x_weights
GROUP BY Port_ID) x2
on X1.Port_ID = X2.Port_ID) CurDay
LEFT JOIN (SELECT x1.*, #RN2:=#RN2+1 rn2
FROM (SELECT prt.name, W.port_ID, W.inst_ID, W.weight, prc.trade_Date, Prc.Price
FROM x_ports Prt
INNER JOIN x_weights W
on W.Port_ID = prt.ID
INNER JOIN x_prices Prc
on Prc.INST_ID = W.INST_ID
ORDER BY W.port_id, W.inst_id,trade_Date) x1
CROSS join (SELECT #RN2:=0) r
) NextDay
on NextDay.Port_ID = CurDay.Port_ID
and NextDay.Inst_ID = curday.Inst_ID
and NextDay.RN2 = CurDay.RN+1
GROUP BY CurDay.Port_ID, CurDay.Inst_ID, CurDay.Trade_Date) Z
##END RAW DATA
GROUP BY Trade_Date;
+----+---------------------+-------------------+-------------------+
| | Trade_Date | PortA | PortB |
+----+---------------------+-------------------+-------------------+
| 1 | 01.01.2018 00:00:00 | 1,00528959642786 | 1,00892857142857 |
| 2 | 02.01.2018 00:00:00 | 0,995851495829569 | 0,991150442477876 |
| 3 | 03.01.2018 00:00:00 | 0,999840954274354 | 1 |
| 4 | 04.01.2018 00:00:00 | 1,0035355651507 | 1,00892857142857 |
| 5 | 05.01.2018 00:00:00 | 1,00589689563141 | 1,00884955752212 |
| 6 | 06.01.2018 00:00:00 | NULL | NULL |
+----+---------------------+-------------------+-------------------+

Getting max value in a specific peroid of time in MySQL

I have a table like this(tblFuel):
time fuel
2014-11-04 17:11:08 231
2014-11-04 17:34:16 254
2014-11-04 18:03:48 241
2014-11-04 18:41:34 137
2014-11-04 18:43:42 111
Now I expect to show the biggest value of fuel during each 1 hour. For example: max from 17:00:00 to 17:59:59 and so on. And follow the previous requirement, the expected result should:
time fuel
2014-11-04 17:34:16 254
2014-11-04 18:03:48 241
So what should I do to achieve this result?
create table tblFuel (time timestamp, fuel int);
insert into tblFuel values ('2014-11-04 17:11:08', 231);
insert into tblFuel values ('2014-11-04 17:34:16', 254);
insert into tblFuel values ('2014-11-04 18:03:48', 241);
insert into tblFuel values ('2014-11-04 18:41:34', 137);
insert into tblFuel values ('2014-11-04 18:43:42', 111);
select
*
from tblFuel
where concat(date(time), hour(time), fuel) in
(select
concat(date(time), hour(time), max(fuel))
from tblFuel
group by
date(time),
hour(time))
Returns:
time fuel
2014-11-04 17:34:16 254
2014-11-04 18:03:48 241
This query will be helpful.
DECLARE #tblFuel TABLE
(
Val INTEGER,
Time DATETIME
)
INSERT INTO #tblFuel
SELECT '231', '2014-11-04 17:11:08' union All
SELECT '254', '2014-11-04 17:34:16' union All
SELECT '241', '2014-11-04 18:03:48' union All
SELECT '137', '2014-11-04 18:41:34' union All
SELECT '111', '2014-11-04 18:43:42'
SELECT A.Val, A.Time FROM #tblFuel AS A
Inner join
(SELECT MAX(Val) AS VAL,
CONVERT(VARCHAR(20), Time, 110) +' ' + CAST(DATEPART(hour, Time) as varchar(2)) AS Time
FROM #tblFuel GROUP BY CONVERT(VARCHAR(20), Time, 110) +' ' + CAST(DATEPART(hour, Time) as varchar(2))) AS B
ON A.Val = B.val AND CONVERT(VARCHAR(20), A.Time, 110) +' ' + CAST(DATEPART(hour, A.Time) as varchar(2)) = B.Time
Here's one way to do it. It uses the DATE_FORMAT function to group by date and hour.
SELECT ft.time, ft.fuel
FROM fuel_table ft
JOIN
(SELECT DATE_FORMAT(time, '%Y%m%d %H') date_and_hour, MAX(fuel) max_fuel
FROM fuel_table
GROUP BY date_and_hour) AS max_fuel
ON DATE_FORMAT(ft.time, '%Y%m%d %H') = max_fuel.date_and_hour
AND ft.fuel = max_fuel

Bookings system

This is the database schema:
CREATE TABLE `bookings` (
`id` int(2) NOT NULL,
`start` time NOT NULL,
`end` time NOT NULL
)
INSERT INTO `bookings` VALUES(1, '13:00:00', '14:30:00');
INSERT INTO `bookings` VALUES(2, '15:00:00', '16:00:00');
I tried to run the following query, to find the free times:
SELECT free_from, free_until
FROM (SELECT a.end AS free_from,
(SELECT MIN(c.start)
FROM bookings c
WHERE c.start>a.end
) as free_until
FROM bookings a
WHERE NOT EXISTS (SELECT 1
FROM bookings b
WHERE b.start BETWEEN a.end AND a.end + INTERVAL your_duration HOURS
) AND
a.end BETWEEN '10:00:00' AND '18:00:00'
) t
The output generated is:
free_from free_until
14:30:00 15:00:00
16:00:00 NULL
Why is there a NULL in the end? Please help me solve this problem. Expected output should be:
free_from free_until
10:00:00 13:00:00
14:30:00 15:00:00
16:00:00 18:00:00
try this:
first create table and insert records
CREATE TABLE bookings (
id int NOT NULL,
start time NOT NULL,
endtime time NOT NULL
)
INSERT INTO bookings VALUES(1, '13:00:00', '14:30:00');
INSERT INTO bookings VALUES(2, '15:00:00', '16:00:00');
then run this select query
SELECT free_from, free_until
FROM (SELECT a.endtime AS free_from,
(SELECT MIN(c.start)
FROM bookings c
WHERE c.start>a.endtime
) as free_until
FROM bookings a
WHERE a.endtime BETWEEN '10:00:00' AND '18:00:00'
) t where free_until IS NOT NULL;
Note: I changed your column end to endtime
The first row of your output corresponds to the following data set:
a:
1, '13:00:00', '14:30:00'
c:
1, '13:00:00', '14:30:00'
2, '15:00:00', '16:00:00'
for "free_until", c.start must be higher than a.end, which is satisfied for this data set.(for 2nd row of C)
The second row of your output corresponds to the following data set:
a:
2, '15:00:00', '16:00:00'
c:
1, '13:00:00', '14:30:00'
2, '15:00:00', '16:00:00'
for "free_until", c.start>a.end but as you can see, a.end is 16:00 here, and no c.start is higher than this.
that's why null
Modify a.end of the second row to 14:00 and you will not get null value. :D
modify the data in the table:
Delete everything from bookings, then
INSERT INTO `bookings` VALUES(1, '13:00:00', '14:30:00');
INSERT INTO `bookings` VALUES(2, '15:00:00', '14:00:00');

Calculate loan payments & when the payment was satisfed - partial payments allowed

I've been writing SQL queries for years but I'm stuck on this one.
I've got 2 tables in MySQL:
LOANPAYMENTSDUE includes LoanPaymentsDueId, LoanId, AmtDue, DueDate
LOANPAYMENTS includes LoanPaymentsId, LoanId, AmtPaid, PaidDate
The relationship between the tables is the LoanId and not the specific payment that is due. In a perfect world the DueDate = PaidDate and the AmtDue = AmtPaid. However, what is making this complex for me is no relationship between the LoanPaymentsDueId and the LoanPaymentsId. The relationship only exists at the LoanId allowing for partial payments to be made on a single LOANPAYMENTSDUE payment.
I've researched the web trying to find the right query to create a report showing the date that each LOANPAYMENTSDUE was satisfied. This requires calculating the balance as of the LOANPAYMENTSDUE.DueDate because there can be payments missed and a new payment should satisfy the balance of the oldest LOANPAYMENTSDUE payment.
Here is the sample data and table scripts:
CREATE TABLE LOANPAYMENTSDUE (
LoanPaymentsDueId BIGINT(20) NOT NULL AUTO_INCREMENT
, LoanId BIGINT(20)
, AmtDue double NOT NULL
, DueDate date NOT NULL
, PRIMARY KEY (LoanPaymentsDueId)
);
INSERT INTO LOANPAYMENTSDUE (LoanId, AmtDue, DueDate) VALUES (1, 100, '2013-07-15');
INSERT INTO LOANPAYMENTSDUE (LoanId, AmtDue, DueDate) VALUES (1, 100, '2013-08-15');
INSERT INTO LOANPAYMENTSDUE (LoanId, AmtDue, DueDate) VALUES (1, 100, '2013-09-15');
INSERT INTO LOANPAYMENTSDUE (LoanId, AmtDue, DueDate) VALUES (1, 100, '2013-10-15');
INSERT INTO LOANPAYMENTSDUE (LoanId, AmtDue, DueDate) VALUES (1, 100, '2013-11-15');
CREATE TABLE LOANPAYMENTS (
LoanPaymentsId BIGINT(20) NOT NULL AUTO_INCREMENT
, LoanId BIGINT(20)
, AmtPaid double NOT NULL
, PaidDate date NOT NULL
, PRIMARY KEY (LoanPaymentsId)
);
INSERT INTO LOANPAYMENTS (LoanId, AmtPaid, PaidDate) VALUES (1, 100, '2013-07-15'); /* Full pmt on due date */
INSERT INTO LOANPAYMENTS (LoanId, AmtPaid, PaidDate) VALUES (1, 100, '2013-08-10'); /* Full pmt a few days early */
INSERT INTO LOANPAYMENTS (LoanId, AmtPaid, PaidDate) VALUES (1, 100, '2013-09-22'); /* Full pmt a week late */
INSERT INTO LOANPAYMENTS (LoanId, AmtPaid, PaidDate) VALUES (1, 50, '2013-10-18'); /* Partial pmt a few days late */
INSERT INTO LOANPAYMENTS (LoanId, AmtPaid, PaidDate) VALUES (1, 50, '2013-11-07');/* Partial pmt 3 weeks late and satisfies the 10/15/2013 balance on this date */
INSERT INTO LOANPAYMENTS (LoanId, AmtPaid, PaidDate) VALUES (1, 100, '2013-11-22');/* Full pmt a week late and satisfies the 11/15/2013 pmt due */
The report query should simply provide the PAIDDATE when each LOANPAYMENTSDUE was satisfied. Using the table data above the report would be as follows:
LOANID LOANPAYMENTSDUEID AMTDUE DUEDATE PAIDDATE
1 1 100 2013-07-15 2013-07-15
1 2 100 2013-08-15 2013-08-10
1 3 100 2013-09-15 2013-09-22
1 4 100 2013-10-15 2013-11-07
1 5 100 2013-11-15 2013-11-22
You could start with these two queries, that return all of the rows with a running total column:
SELECT
LoanId, DueDate,
CASE WHEN LoanId=#last_LoanId THEN #Due:=#Due+AmtDue
ELSE #Due:=AmtDue END total_due,
#last_LoanId:=LoanId
FROM
LOANPAYMENTSDUE, (SELECT #last_LoanId:=NULL, #Due:=NULL) t;
SELECT
LoanId, PaidDate,
CASE WHEN LoanId=#last_LoanId THEN #Paid:=#Paid+AmtPaid
ELSE #Paid:=AmtPaid END total_paid,
#last_LoanId:=LoanId
FROM
LOANPAYMENTS, (SELECT #last_LoanId:=NULL, #Paid:=NULL) t;
and then you could use a LEFT JOIN on due.LoanId=due.LoanId AND total_due<=total_paid, and a GROUP BY to get the minimum date where the join succeded:
SELECT
ld.LoanId, ld.DueDate, MIN(lp.PaidDate)
FROM
(SELECT
LoanId, DueDate,
CASE WHEN LoanId=#last_LoanId1 THEN #Due:=#Due+AmtDue ELSE #Due:=AmtDue END total_due,
#last_LoanId1:=LoanId
FROM
LOANPAYMENTSDUE, (SELECT #last_LoanId1:=NULL, #Due:=NULL) t1) ld
LEFT JOIN
(SELECT
LoanId, PaidDate,
CASE WHEN LoanId=#last_LoanId2 THEN #Paid:=#Paid+AmtPaid ELSE #Paid:=AmtPaid END total_paid,
#last_LoanId2:=LoanId
FROM
LOANPAYMENTS, (SELECT #last_LoanId2:=NULL, #Paid:=NULL) t2) lp
ON
ld.LoanId=lp.LoanId AND ld.total_due<=lp.total_paid
GROUP BY
ld.LoanId, ld.DueDate
Please see fiddle here.
Assuming that when the amount is paid it's paid in portion or remaining amount in whole, you check based on Total Amount Due and Total Amount Paid by matching those up. Here's the sqlFiddle example of your data and query
SELECT T1.LoanId,
T1.LoanPaymentsDueId,
T1.AmtDue,
T1.DueDate,
T2.PaidDate
FROM
(SELECT
LD.LoanPaymentsDueId,
LD.LoanId,
LD.DueDate,
LD.AmtDue,
(SELECT Sum(AmtDue)
FROM LOANPAYMENTSDUE LD1
WHERE LD1.DueDate <= LD.DueDate
AND LD1.LoanId = LD.LoanId
)as AmtDueTotal
FROM
LOANPAYMENTSDUE LD
)T1,
(SELECT
L.LoanPaymentsId,
L.LoanId,
L.PaidDate,
(SELECT Sum(AmtPaid)
FROM LOANPAYMENTS L1
WHERE L1.PaidDate <= L.PaidDate
AND L1.LoanId = L.LoanId
)as AmtPaidTotal
FROM LOANPAYMENTS L
)T2
WHERE
T1.LoanId = T2.LoanId
AND T1.LoanId = 1
AND T1.AmtDueTotal = T2.AmtPaidTotal;