MySQL Custom calculate views - mysql

I am having difficulty trying to combine a set of queries I am using. I currently am having to execute multiple queries with different years and CID numbers. (CID is 1-5, Years are CurrentYear to CurrentYear-6)
Current SQL Statement:
SELECT
1 as CID,
2015 as `Year`,
( SELECT SUM( legacy_reports_payments.AmountPaid ) * -1 AS Payments FROM legacy_reports_payments
WHERE DatePaid >= "2015-01-01 00:00:00" AND DatePaid < "2015-02-01 00:00:00"
AND CID = 1) As PaymentsJan,
( SELECT SUM( legacy_reports_payments.AmountPaid ) * -1 AS Payments FROM legacy_reports_payments
WHERE DatePaid >= "2015-02-01 00:00:00" AND DatePaid < "2015-03-01 00:00:00"
AND CID = 1) As PaymentsFeb,
( SELECT SUM( legacy_reports_payments.AmountPaid ) * -1 AS Payments FROM legacy_reports_payments
WHERE DatePaid >= "2015-03-01 00:00:00" AND DatePaid < "2015-04-01 00:00:00"
AND CID= 1) As PaymentsMar,
...
This returns:
CID | Year | PaymentsJan | PaymentsFeb | ...
1 | 2015 | 3000.00 | 3000.00 | ...
I want to create a single view that can show all of the calculated information, instead of running several queries like the one above.
I would like to have the view look more like this:
CID | Year | PaymentsJan | PaymentsFeb | ...
1 | 2015 | 3000.00 | 3000.00 | ...
2 | 2015 | 2000.00 | 2000.00 | ...
3 | 2015 | 5000.00 | 5000.00 | ...
1 | 2014 | 1000.00 | 3000.00 | ...
2 | 2014 | 3000.00 | 4000.00 | ...
3 | 2014 | 2000.00 | 5000.00 | ...
Can anyone provide any insight on the best way to handle this?

You could build a temporary table with the Years and CIDs you need and then join your query on that to do all of them at one. That many sub queries will be slow, but better than doing each one manually.
create table #Dates
(
controlCID int
,controlYear varchar(4)
)
DECLARE #year int = YEAR( getDate() )
DECLARE #numYears int = 6
DECLARE #numCID int = 5
DECLARE #CIDcounter int
while (#numYears > 0)
BEGIN
SET #CIDcounter = #numCID
while (#CIDcounter > 0)
BEGIN
insert into #Dates values ( #CIDcounter, (#year - #numYears + 1))
SET #CIDcounter = #CIDcounter - 1
END
SET #numYears = #numYears -1
END
select controlCID
,controlYear
--,( SELECT SUM( legacy_reports_payments.AmountPaid ) * -1 AS Payments FROM legacy_reports_payments
--WHERE p.DatePaid >= (controlYear+'-01-01 00:00:00') AND p.DatePaid < (controlYear+'-02-01 00:00:00')
--AND p.CID = controlCID) As PaymentsJan,
--etc and so on...
from #Dates
order by controlYear desc, controlCID
drop table #Dates
Just use the year and CID from the table in the sub queries and you should be good to go.

Related

How to select most recent record while using self join in SQL Server 2014

+-----------+-------------+-------------+----------+
| Item Code | Vendor Code | Direct Cost | Date |
+-----------+-------------+-------------+----------+
| 6994 | 1441 | 3.86 | 20150512 |
| 6994 | 1441 | 3.86 | 20150910 |
| 6994 | 1441 | 4.62 | 20160425 |
| 6994 | 1441 | 4.62 | 20161017 |
| 6994 | 1441 | 4.05 | 20170330 |
| 6994 | 1441 | 4.10 | 20170624 |
+-----------+-------------+-------------+----------+
I am creating a view and I have joined 2 tables PO Header and PO Details, so in this example above I just need 1 line result because Item Code and Vendor Code is repeating, I have achieved this by using the self join on the table but the main requirement is to pick 1 line with most recent date record i.e. 20170624 and Direct Cost is = 4.10
Please see my code below.
l.AUDTDATE is the date column but not in the date format, so I have converted it into date format and in descending order but I am struggling in getting the 1 line with the most recent date.
Thanks
SELECT TOP (100) PERCENT
dbo.NavMdItem.nav_item_code,
dbo.NavMdVendor.nav_vendor_code,
l.ORDERUNIT AS uom,
'' AS variant_code,
h.POCURR AS currency_code,
'1' AS minimum_qty,
l.UNITCOST AS direct_unit_cost,
SUBSTRING(CAST(h.DAYENDDATE AS VARCHAR(50)), 7, 2) + '/' +
SUBSTRING(CAST(h.DAYENDDATE AS VARCHAR(50)), 5, 2) + '/' +
SUBSTRING(CAST(h.DAYENDDATE AS VARCHAR(50)), 0, 5) AS starting_date,
l.DAYENDSEQ,
CONVERT(DATETIME, CAST(l.AUDTDATE AS VARCHAR(50)), 106) AS date
FROM
skh.POPORAH AS h
INNER JOIN
skh.POPORAL AS l ON h.PORAHSEQ = l.PORAHSEQ AND h.DAYENDSEQ = l.DAYENDSEQ
INNER JOIN
dbo.NavMdItem ON l.ITEMNO = dbo.NavMdItem.item_long_code COLLATE Latin1_General_CI_AS
INNER JOIN
dbo.NavMdVendor ON h.VENDOR = dbo.NavMdVendor.vendor_short_code COLLATE Latin1_General_CI_AS
WHERE
(NOT (l.ITEMNO LIKE 'MISC'))
AND (l.ITEMEXISTS = 1)
AND (dbo.NavMdVendor.entity_code = 10)
AND (h.DAYENDDATE > 20150101)
AND (dbo.NavMdItem.nav_item_code = 6499)
AND ((SELECT COUNT(*) AS Expr1
FROM skh.POPORAL AS f
WHERE (DAYENDSEQ = l.DAYENDSEQ)
AND (DAYENDSEQ <= l.DAYENDSEQ)) < 2)
ORDER BY
l.AUDTDATE DESC
You want to group by item code and vendor code and select MAX date.
Is there zero chance of having two on the same date?
UPDATE:
Here is some code that will do what you're wanting. Notice I assumed valid datetime data in the [aud_date] column.
CREATE TABLE #Stuff(
item_code int
,vendor_code int
,direct_cost money
,aud_date datetime
)
INSERT #Stuff VALUES (6994,1441,3.86,'2015-05-12 11:32:12')
INSERT #Stuff VALUES (6994,1441,3.86,'2015-09-10 05:11:00')
INSERT #Stuff VALUES (6994,1441,4.62,'2016-04-25 19:43:43')
INSERT #Stuff VALUES (6994,1441,4.62,'2016-10-17 11:12:13')
INSERT #Stuff VALUES (6994,1441,4.05,'2017-03-30 01:02:03')
INSERT #Stuff VALUES (6994,1441,4.10,'2017-06-24 12:12:12')
SELECT
*
FROM
#Stuff
WHERE
aud_date = (
SELECT MAX(aud_date)
FROM
#Stuff
WHERE
item_code = item_code
AND
vendor_code = vendor_code
GROUP BY
item_code, vendor_code
)
DROP TABLE #Stuff

Adding duration value from one column to the latest date in another column grouped by customers

I have a customer contract_table in my database with customer_id, contract_duration (in months) and invoice_date. I'd like to to add the duration to the latest invoice date grouped by customer.
customer_id invoice_date duration
1 2016-01-01 12
1 2017-01-01 6
2 2016-02-01 24
3 2014-03-01 24
3 2016-03-01 3
The desired output would be
customer_id contract_end
1 2017-07-01
2 2018-02-01
3 2016-05-01
I tried the following, which is not working. Up front I thought MySQL would use the duration value corresponding to the MAX(invoice_date). Is there a quick way to get the above result?
SELECT customer_id, MAX(invoice_date) + INTERVAL duration MONTH
FROM contract_table
GROUP BY customer_id
I don't quite understand your result set so I've ignored it. If my result is wrong, please try to explain why in comments...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(customer_id INT NOT NULL
,invoice_date DATE NOT NULL
,duration INT NOT NULL
,PRIMARY KEY(customer_id,invoice_date)
);
INSERT INTO my_table VALUES
(1,'2016-01-01',12),
(1,'2017-01-01',6),
(2,'2016-02-01',24),
(3,'2014-03-01',24),
(3,'2016-03-01',3);
SELECT a.*
, a.invoice_date + INTERVAL a.duration MONTH contract_end
FROM my_table a
JOIN
( SELECT customer_id
, MAX(invoice_date + INTERVAL duration MONTH) x
FROM my_table
GROUP
BY customer_id
) b
ON b.x = a.invoice_date + INTERVAL a.duration MONTH;
+-------------+--------------+----------+--------------+
| customer_id | invoice_date | duration | contract_end |
+-------------+--------------+----------+--------------+
| 1 | 2017-01-01 | 6 | 2017-07-01 |
| 2 | 2016-02-01 | 24 | 2018-02-01 |
| 3 | 2016-03-01 | 3 | 2016-06-01 |
+-------------+--------------+----------+--------------+
Try this select:
SELECT customer_id, MAX(d) AS contract_end FROM (
SELECT
customer_id,
#num:=CAST(duration AS UNSIGNED),
DATE_ADD(invoice_date, INTERVAL #num MONTH) AS d
FROM my_table
) t
GROUP BY customer_id
SQLFiddle here.

MySQL Transform a date-range into single rows/count days per year?

I am looking for a solution to count days in a daterange per year. My table looks like this:
+----+-----------+------------+------------+
| id | source_id | start_date | end_date |
+----+-----------+------------+------------+
| 1 | 1 | 2015-11-01 | 2017-01-31 |
+----+-----------+------------+------------+
Now I want to count the days in between. Its easy with DATEDIFF() in complete, but how to do it per year?
I tried a kind of temp. transformation into single rows to perform count and group actions:
+----+-----------+------------+------------+
| id | source_id | start_date | end_date |
+----+-----------+------------+------------+
| 1 | 1 | 2015-11-01 | 2015-12-31 |
+----+-----------+------------+------------+
| 1 | 1 | 2016-01-01 | 2016-12-31 |
+----+-----------+------------+------------+
| 1 | 1 | 2017-01-01 | 2017-01-31 |
+----+-----------+------------+------------+
EDIT:
The desired output should like that:
+-----------+------+------+
| source_id | year | days |
+-----------+------+------+
| 1 | 2015 | 60 |
+-----------+------+------+
| 1 | 2016 | 365 |
+-----------+------+------+
| 1 | 2017 | 30 |
+-----------+------+------+
So it become possible to summarize all days grouped by source_id and year.
Is there an easy way to do it in MySQL?
Create another table that lists all the years:
CREATE TABLE years (
year_start DATE,
year_end DATE
);
INSERT INTO years VALUES
('2015-01-01', '2015-12-31'),
('2016-01-01', '2016-12-31'),
('2017-01-01', '2017-12-31');
Then you can join with this table
SELECT t.source_id, YEAR(y.year_start) AS year, DATEDIFF(LEAST(year_end, end_date), GREATEST(year_start, start_date)) AS day_count
FROM yourTable AS t
JOIN years AS y
ON y.year_start BETWEEN t.start_date AND t.end_date
OR y.year_end BETWEEN t.start_date AND t.end_date
DEMO
If you don't want to create a real table, you can use a subquery that creates it on the fly:
SELECT t.source_id, YEAR(y.year_start) AS year, DATEDIFF(LEAST(year_end, end_date), GREATEST(year_start, start_date)) AS day_count
FROM yourTable AS t
JOIN (SELECT CAST('2015-01-01' AS DATE) AS year_start, CAST('2015-12-31' AS DATE) AS year_end
UNION
SELECT CAST('2016-01-01' AS DATE) AS year_start, CAST('2016-12-31' AS DATE) AS year_end
UNION
SELECT CAST('2017-01-01' AS DATE) AS year_start, CAST('2017-12-31' AS DATE) AS year_end
) AS y
ON y.year_start BETWEEN t.start_date AND t.end_date
OR y.year_end BETWEEN t.start_date AND t.end_date
DEMO
I found some other snippet and I combined both. Its more a working hack than a solution, but it works good enough for my purpose.
SELECT r.source_id,
YEAR(y.year_start) AS year,
DATEDIFF(LEAST(year_end, end_date), GREATEST(year_start, start_date)) AS day_count,
r.start_date,
r.end_date
FROM ranges AS r
JOIN (
SELECT #i:= #i + 1 AS YEAR,
CAST(CONCAT(#i, '-01-01') AS DATE) AS year_start,
CAST(CONCAT(#i, '-12-31') AS DATE) AS year_end
FROM INFORMATION_SCHEMA.COLLATION_CHARACTER_SET_APPLICABILITY,
(SELECT #i:= 1899) AS i
) AS y
ON r.start_date >= y.year_start AND r.start_date <= y.year_end
OR r.end_date >= y.year_start AND r.end_date <= y.year_end;
I think, the table INFORMATION_SCHEMA.COLLATION_CHARACTER_SET_APPLICABILITY is just a workaround to do the iteration. Not nice, but maybe someone needs something like that.

Combining sql select queries

I have 2 different queries, which both return results similar to below. For example
SELECT value
, date_format( statdate, "%Y-%m-%d" ) AS testdate
FROM stats
WHERE name LIKE 'myname1'
AND date >= "2014-09-20"
and date < "2014-09-24"
GROUP BY 2;
SELECT value
, date_format( statdate, "%Y-%m-%d" ) AS testdate
FROM stats
WHERE name LIKE 'myname2'
AND date >= "2014-09-20"
and date < "2014-09-24"
GROUP BY 2;
Information comes from the same table, but part of it is a different WHERE (with the date being the field which is the same in both).
Which both return information like..
value| testdate |
+--------+-------+
| 60 | 2014-09-20|
| 57 | 2014-09-21|
| 56 | 2014-09-22|
| 55 | 2014-09-23|
| 59 | 2014-09-24|
What I would like to do, is combine both sets of results into one (and then perform some maths like a ratio on the two numbers), so I want to end up with something like...
val1 | val2| ratio (val1/val2) | testdate |
+----+-------------------------|------------+
| 60 | 140 | 0.42 | 2014-09-20 |
| 57 | 180 | 0.31 | 2014-09-21 |
| 56 | 190 | 0.29 | 2014-09-22 |
| 55 | 10 | 5.5 | 2014-09-23 |
| 59 | 100 | 0.59 | 2014-09-24 |
I'm guessing I need a JOIN somewhere, but can't figure it, and how to use that value for some basic maths in there ?
You should use the "Self Join" to join the 2 instances of same table. like below
SELECT s1.value, date_format( s1.statdate, "%Y-%m-%d" ) AS testdate
s2.value, date_format( s2.statdate, "%Y-%m-%d" ) AS testdate2,
(s1.value/s2.value) as ration
FROM stats s1, stats s2
WHERE s1.id = s2.id
and s1.name LIKE 'myname1' AND s1.date >= "2014-09-20" and s1.date < "2014-09-24"
and s2.name LIKE 'myname2' AND s2.date >= "2014-09-20" and s2.date < "2014-09-24"
GROUP BY 2;
I would recommend conditional aggregation:
SELECT date_format(statdate, '%Y-%m-%d') AS testdate,
max(case when name = 'myname1' then value end) as val1,
max(case when name = 'myname2' then value end) as val2,
(max(case when name = 'myname1' then value end) /
max(case when name = 'myname2' then value end)
) as ratio,
FROM stats
WHERE name in ('myname1', 'myname2') AND date >= '2014-09-20' and date < '2014-09-24'
GROUP BY date_format(statdate, '%Y-%m-%d');

Null Values during Query

I have the below table, pretty simple.
==========================================================================
attendanceID | agentID | incurredDate | points | comment
==========================================================================
10 | vimunson | 2013-07-22 | 2 | Some Text
11 | vimunson | 2013-07-29 | 2 | Some Text
12 | vimunson | 2013-12-06 | 1 | Some Text
The his query below:
SELECT
attendanceID,
agentID,
incurredDate,
leadDate,
points,
#1F:=IF(incurredDate <= curdate() - 90
AND leadDate = NULL,
points - 1,
IF(DATEDIFF(leadDate, incurredDate) > 90,
points - 1,
points)) AS '1stFallOff',
#2F:=IF(incurredDate <= curdate() - 180
AND leadDate = NULL,
points - 2,
IF(DATEDIFF(leadDate, incurredDate) > 180,
points - 2,
#1F)) AS '2ndFallOff',
IF(#total < 0, 0, #total:=#total + #2F) AS Total,
comment,
linked
FROM
(SELECT
attendanceID,
mo.agentID,
#r AS leadDate,
(#r:=incurredDate) AS incurredDate,
comment,
points,
linked
FROM
(SELECT
m . *
FROM
(SELECT #_date = NULL, #total:=0) varaible, attendance m
ORDER by agentID , incurredDate desc) mo
where
agentID = 'vimunson'
AND (case
WHEN #_date is NULL or #_date <> incurredDate THEN #r:=NULL
ELSE NULL
END IS NULL)
AND (#_date:=incurredDate) IS NOT NULL) T
ORDER BY agentID , incurredDate
When I run the query it returns the below:
========================================================================================================================================
attendanceID | agentID | incurredDate | leadDate | points | 1stFallOff | 2ndFallOff | Total | comment
========================================================================================================================================
10 | vimunson | 2013-07-22 | NULL | 2 | 2 | 2 | 2 | Some Text
11 | vimunson | 2013-07-29 | NULL | 2 | 2 | 2 | 4 | Some Text
12 | vimunson | 2013-12-06 | NULL | 1 | 2 | 1 | 5 | Some Text
I cannot figure out why the leadDate column is `null'. I have narrowed it down to a user session. For example if I run it again with the same user session it will come back with what I want.
The way variables #r and #_date are passed around relies on a specific order in which certain parts of the query are evaluated. That's a risky assumption to make in a query language that is declarative rather than imperative. The more sophisticated a query optimizer is, the more unpredictable the behaviour of this query will be. A 'simple' engine might follow your intentions, another engine might adapt its behaviour as you go, for example because it uses temporary indexes to improve query performance.
In situations where you need to pass values from one row to another, it would be better to use a cursor.
http://dev.mysql.com/doc/refman/5.0/en/cursors.html
EDIT: sample code below.
I focused on column 'leadDate'; implementation of the falloff and total columns should be similar.
CREATE PROCEDURE MyProc()
BEGIN
DECLARE done int DEFAULT FALSE;
DECLARE currentAttendanceID int;
DECLARE currentAgentID, previousAgentID varchar(8);
DECLARE currentIncurredDate date;
DECLARE currentLeadDate date;
DECLARE currentPoints int;
DECLARE currentComment varchar(9);
DECLARE myCursor CURSOR FOR
SELECT attendanceID, agentID, incurredDate, points, comment
FROM attendance
ORDER BY agentID, incurredDate DESC;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
CREATE TEMPORARY TABLE myTemp (
attendanceID int,
agentID varchar(8),
incurredDate date,
leadDate date,
points int,
comment varchar(9)
) ENGINE=MEMORY;
OPEN myCursor;
SET previousAgentID := NULL;
read_loop: LOOP
FETCH myCursor INTO currentAttendanceID, currentAgentID, currentIncurredDate, currentPoints, currentComment;
IF done THEN
LEAVE read_loop;
END IF;
IF previousAgentID IS NULL OR previousAgentID <> currentAgentID THEN
SET currentLeadDate := NULL;
SET previousAgentID := currentAgentID;
END IF;
INSERT INTO myTemp VALUES (currentAttendanceID, currentAgentID, currentIncurredDate, currentLeadDate, currentPoints, currentComment);
SET currentLeadDate := currentIncurredDate;
END LOOP;
CLOSE myCursor;
SELECT *
FROM myTemp
ORDER BY agentID, incurredDate;
DROP TABLE myTemp;
END
FYC: http://sqlfiddle.com/#!2/910a3/1/0
Try this:
SELECT a.attendanceID, a.agentID, a.incurredDate, a.leadDate, a.points,
#1F:= (CASE WHEN incurredDate <= CURDATE()-90 AND leadDate=NULL THEN points-1
WHEN DATEDIFF(leadDate, incurredDate) > 90 THEN points-1
ELSE points
END) AS '1stFallOff',
#2F:= (CASE WHEN incurredDate <= CURDATE()-180 AND leadDate=NULL THEN points-2
WHEN DATEDIFF(leadDate, incurredDate) > 180 THEN points-2
ELSE #1F
END) AS '2ndFallOff',
(#Total:=#Total + a.points) AS Total, a.comment
FROM (SELECT a.attendanceID, a.agentID, a.incurredDate, b.incurredDate leadDate, a.points, a.comment
FROM attendance a
LEFT JOIN attendance b ON a.agentID = b.agentID AND a.incurredDate < b.incurredDate
GROUP BY a.agentID, a.incurredDate
) AS A, (SELECT #Total:=0, #1F:=0, #2F:=0) B;
Check the SQL FIDDLE DEMO
OUTPUT
| ATTENDANCEID | AGENTID | INCURREDDATE | LEADDATE | POINTS | 1STFALLOFF | 2NDFALLOFF | TOTAL | COMMENT |
|--------------|----------|---------------------------------|---------------------------------|--------|------------|------------|-------|-----------|
| 10 | vimunson | July, 22 2013 00:00:00+0000 | July, 29 2013 00:00:00+0000 | 2 | 2 | 2 | 2 | Some Text |
| 11 | vimunson | July, 29 2013 00:00:00+0000 | December, 06 2013 00:00:00+0000 | 2 | 1 | 1 | 4 | Some Text |
| 12 | vimunson | December, 06 2013 00:00:00+0000 | (null) | 1 | 1 | 1 | 5 | Some Text |
After reviewing multiple results I was able to come up with something that is what I expect. I have entered more data and the below syntax. I liked the idea of the cursor but it was not ideal for my use, so I did not use it. I did not want to use CASE or any JOINS since they can be complex.
http://sqlfiddle.com/#!2/2fb86/1
SELECT
attendanceID,
agentID,
incurredDate,
#ld:=(select
incurredDate
from
attendance
where
incurredDate > a.incurredDate
and agentID = a.agentID
order by incurredDate
limit 1) leadDate,
points,
#1F:=IF(incurredDate <= DATE_SUB(curdate(),
INTERVAL IF(incurredDate < '2013-12-02', 90, 60) DAY)
AND #ld <=> NULL,
points - 1,
IF(DATEDIFF(COALESCE(#ld, '1900-01-01'),
incurredDate) > IF(incurredDate < '2013-12-02', 90, 60),
points - 1,
points)) AS '1stFallOff',
#2F:=IF(incurredDate <= DATE_SUB(curdate(),
INTERVAL IF(incurredDate < '2013-12-02',
180,
120) DAY)
AND getLeadDate(incurredDate, agentID) <=> NULL,
points - 1,
IF(DATEDIFF(COALESCE(#ld, '1900-01-01'),
incurredDate) > IF(incurredDate < '2013-12-02',
180,
120),
points - 2,
#1F)) AS '2ndFallOff',
IF((#total + #2F) < 0,
0,
IF(DATE_ADD(incurredDate, INTERVAL 365 DAY) <= CURDATE(),
#total:=0,
#total:=#total + #2F)) AS Total,
comment,
linked,
DATE_ADD(incurredDate, INTERVAL 365 DAY) AS 'fallOffDate'
FROM
(SELECT #total:=0) v,
attendance a
WHERE
agentID = 'vimunson'
GROUP BY agentID , incurredDate