How to make a query of the max month/year of a group of columns - sql

I don't really know how to make a MySQL query where I get all the data(Columns) of the most present month + year.
Being a little bit more specific I need to group the columns (description, data1, data2, data3) and only get the one that has the highest month + year.
The table contains data like this one:
description data1 data2 data3 year month adddate
desc1 0 7 1 2019 5 2019-05-23
desc2 0 7 1 2019 5 2019-05-23
desc3 1 7 1 2019 5 2019-05-23
desc4 0 2 1 2018 12 2019-05-23
I've tried using max on the month, year and adddate.
select description, data1, data2, data3, max(year) as year, max(month) as month, max(adddate) as adddate
from tabledata
group by description, data1, data2, data3
But with this I'm getting that the max register is desc2 with the month 12 which is not correct since the month is desc2 is 6.

You need to get the max value of an expression like:
100 * year + month
or
12 * year + month
So do this:
select *
from tabledata
where 100 * year + month = (
select max(100 * year + month)
from tabledata
)
See the demo.
Results:
> description | data1 | data2 | data3 | year | month | adddate
> :---------- | ----: | ----: | ----: | ---: | ----: | :---------
> desc1 | 0 | 7 | 1 | 2019 | 5 | 23/05/2019
> desc2 | 0 | 7 | 1 | 2019 | 5 | 23/05/2019
> desc3 | 1 | 7 | 1 | 2019 | 5 | 23/05/2019

You need to find the max combination of year and month (MaxYearMonth) and then join to your table again to find all the rows that match that MaxYearMonth. Here is the solution for SQL Server...
IF OBJECT_ID('tempdb.dbo.#tabledata', 'U') IS NOT NULL DROP TABLE #tabledata;
CREATE TABLE #tabledata
(
description VARCHAR(10)
, data1 INT
, data2 INT
, data3 INT
, year INT
, month INT
, adddate DATE
);
INSERT INTO #tabledata VALUES ('desc1', 0, 7, 1, 2019, 5, '2019-05-23')
INSERT INTO #tabledata VALUES ('desc2', 0, 7, 1, 2019, 5, '2019-05-23')
INSERT INTO #tabledata VALUES ('desc3', 1, 7, 1, 2019, 5, '2019-05-23')
INSERT INTO #tabledata VALUES ('desc4', 0, 2, 1, 2018, 12, '2019-05-23')
select a.description, a.data1, a.data2, a.data3
from #tabledata a
inner join
(
select max(convert(char(4), year) + right('0' + convert(varchar(2), month), 2)) as [MaxYearMonth]
from #tabledata
) b on convert(char(4), a.year) + right('0' + convert(varchar(2), a.month), 2) = b.MaxYearMonth
Yields this...
description data1 data2 data3
----------- ----------- ----------- -----------
desc1 0 7 1
desc2 0 7 1
desc3 1 7 1
Click here to see it in action.
You mention "mysql" so if you are really using mySQL then the syntax will likely need to change a bit.

Related

SQL Count Rows GROUP BY Month Name

I have a table and it has the following structure:
DEVICE_ID | DATE | STATUS
------------------------------------------
1 | 2021/01/05 | accepted
2 | 2021/01/23 | success
3 | 2021/02/07 | success
4 | 2021/03/11 | accepted
5 | 2021/03/20 | unsuccess
6 | 2021/03/26 | success
I want to calculate no of records in 2021 by status and GROUP BY month name like this :
MONTH | ACCEPTED | SUCCESS | UNSUCCESS
------------------------------------------------
January | 1 | 1 | 0
February | 0 | 1 | 0
March | 1 | 1 | 1
April | 0 | 0 | 0
May | 0 | 0 | 0
June | 0 | 0 | 0
July | 0 | 0 | 0
August | 0 | 0 | 0
September | 0 | 0 | 0
October | 0 | 0 | 0
November | 0 | 0 | 0
December | 0 | 0 | 0
Please help me to solve this issue
Explanation - because you want the full month list you need to be able to have all 12 months somewhere in the data. Then you want the custom status columns pivoted to display as you asked.
You should next time at least or tell us what you tried. It helps us figure out how youre thinking about it and how we can help you get past whatever roadblocks youve encountered.
IF OBJECT_ID('TempDb..#tmp') IS NOT NULL DROP TABLE #tmp;
IF OBJECT_ID('TempDb..#tmp2') IS NOT NULL DROP TABLE #tmp2;
CREATE TABLE #tmp
(
Device_ID INT
, Date VARCHAR(12)
, Status VARCHAR(15)
)
;
CREATE TABLE #tmp2
(
MOnthName VARCHAR(25)
)
;
INSERT INTO #tmp2
(MonthName)
VALUES
('January'),
('February'),
('March'),
('April'),
('May'),
('June'),
('July'),
('August'),
('September'),
('October'),
('November'),
('December')
;
INSERT INTO #tmp
(
Device_ID
, Date
, Status
)
VALUES
(1,'2021/01/05','accepted'),
(2,'2021/01/23','success'),
(3,'2021/02/07','success'),
(4,'2021/03/11','accepted'),
(5,'2021/03/20','unsuccess'),
(6,'2021/03/26','success')
;
SELECT
MOnthName
, success
, accepted
, unsuccess
FROM
(
SELECT
tt.MonthName
, Status
FROM
#tmp2 tt
LEFT JOIN #tmp t ON tt.MOnthName = DATENAME(month, CAST(Date AS DATE))
GROUP BY
tt.MonthName
, Status
) AS SourceTable
PIVOT
(
COUNT(Status) FOR Status IN ([accepted], [success], [unsuccess])
) AS PivotTable
ORDER BY
CASE
WHEN MonthName ='January' THEN 1
WHEN MonthName ='February' THEN 2
WHEN MonthName ='March' THEN 3
WHEN MonthName ='April' THEN 4
WHEN MonthName ='May' THEN 5
WHEN MonthName ='June' THEN 6
WHEN MonthName ='July' THEN 7
WHEN MonthName ='August' THEN 8
WHEN MonthName ='September' THEN 9
WHEN MonthName ='October' THEN 10
WHEN MonthName ='November' THEN 11
WHEN MonthName ='December' THEN 12
END
create table yourtable(DEVICE_ID int, DATE date, STATUS varchar(50));
insert into yourtable values(1, '2021/01/05' , 'accepted');
insert into yourtable values(2, '2021/01/23' , 'success');
insert into yourtable values(3, '2021/02/07' , 'success');
insert into yourtable values(4, '2021/03/11' , 'accepted');
insert into yourtable values(5, '2021/03/20' , 'unsuccess');
insert into yourtable values(6, '2021/03/26' , 'success');
Query:
;WITH months(MonthNumber) AS
(
SELECT 0
UNION ALL
SELECT MonthNumber+1
FROM months
WHERE MonthNumber < 11
)
SELECT DATENAME(MONTH,DATEADD(MONTH,MonthNumber,'01-01-2021')) AS [month],
coalesce(sum(case when status='ACCEPTED' then 1 end),0) ACCEPTED,
coalesce(sum(case when status='SUCCESS' then 1 end),0) SUCCESS,
coalesce(sum(case when status='UNSUCCESS' then 1 end),0) UNSUCCESS
FROM months m left join yourtable y
on m.monthnumber=month(y.[date])-1
group by monthnumber
Output:
month
ACCEPTED
SUCCESS
UNSUCCESS
January
1
1
0
February
0
1
0
March
1
1
1
April
0
0
0
May
0
0
0
June
0
0
0
July
0
0
0
August
0
0
0
September
0
0
0
October
0
0
0
November
0
0
0
December
0
0
0
db<>fiddle here

Check if a month is skipped then add values dynamically?

I have a set of data from a table that would only be populated if a user has data for a certain month just like this:
Month | MonthName | Value
3 | March | 136.00
4 | April | 306.00
7 | July | 476.00
12 | December | 510.48
But what I need is to check if a month is skipped then adding the value the month before so the end result would be like this:
Month | MonthName | Value
3 | March | 136.00
4 | April | 306.00
5 | May | 306.00 -- added data
6 | June | 306.00 -- added data
7 | July | 476.00
8 | August | 476.00 -- added data
9 | September | 476.00 -- added data
10 | October | 476.00 -- added data
11 | November | 476.00 -- added data
12 | December | 510.48
How can I do this dynamically on SQL Server?
One method is a recursive CTE:
with cte as (
select month, value, lead(month) over (order by month) as next_month
from t
union all
select month + 1, value, next_month
from cte
where month + 1 < next_month
)
select month, datename(month, datefromparts(2020, month, 1)) as monthname, value
from cte
order by month;
Here is a db<>fiddle.
you can use spt_values to get continuous number 1-12, and then left join your table by max(month)
select t1.month
,datename(month,datefromparts(2020, t1.month, 1)) monthname
,t2.value
from (
select top 12 number + 1 as month from master..spt_values
where type = 'p'
) t1
left join t t2 on t2.month = (select max(month) from t tmp where tmp.month < = t1.month)
where t2.month is not null
CREATE TABLE T
([Month] int, [MonthName] varchar(8), [Value] numeric)
;
INSERT INTO T
([Month], [MonthName], [Value])
VALUES
(3, 'March', 136.00),
(4, 'April', 306.00),
(7, 'July', 476.00),
(12, 'December', 510.48)
;
Demo Link SQL Server 2012 | db<>fiddle
note
if you have year column then you need to fix the script.

Display data for all date ranges including missing dates

I'm having a issue with dates. I have a table with given from and to dates for an employee. For an evaluation, I'd like to display each date of the month with corresponding values from the second sql table.
SQL Table:
EmpNr | datefrom | dateto | hours
0815 | 01.01.2019 | 03.01.2019 | 15
0815 | 05.01.2019 | 15.01.2019 | 15
0815 | 20.01.2019 | 31.12.9999 | 40
The given employee (0815) worked during 01.01.-15.01. 15 hours, and during 20.01.-31.01. 40 hours
I'd like to have the following result:
0815 | 01.01.2019 | 15
0815 | 02.01.2019 | 15
0815 | 03.01.2019 | 15
0815 | 04.01.2019 | NULL
0815 | 05.01.2019 | 15
...
0815 | 15.01.2019 | 15
0815 | 16.01.2019 | NULL
0815 | 17.01.2019 | NULL
0815 | 18.01.2019 | NULL
0815 | 19.01.2019 | NULL
0815 | 20.01.2019 | 40
0815 | 21.01.2019 | 40
...
0815 | 31.01.2019 | 40
as for the dates, we have:
declare #year int = 2019, #month int = 1;
WITH numbers
as
(
Select 1 as value
UNion ALL
Select value + 1 from numbers
where value + 1 <= Day(EOMONTH(datefromparts(#year,#month,1)))
)
SELECT b.empnr, b.hours, datefromparts(#year,#month,numbers.value) Datum FROM numbers left outer join
emptbl b on b.empnr = '0815' and (datefromparts(#year,#month,numbers.value) >= b.datefrom and datefromparts(#year,#month,numbers.value) <= case b.dateto )
which is working quite well, yet I have the odd issue, that this code is only shoes the dates between 01.01.2019 and 03.01.2019
thank you very much in advance!
Did you check, if datefrom and dateto is in correct range?
Minimum value of DateTime field is 1753-01-01 and maximum value is 9999-12-31.
Look at your source table to check initial values.
The recursive CTE needs to begin with MIN(datefrom) and MAX(dateto):
DECLARE #t TABLE (empnr INT, datefrom DATE, dateto DATE, hours INT);
INSERT INTO #t VALUES
(815, '2019-01-01', '2019-01-03', 15),
(815, '2019-01-05', '2019-01-15', 15),
(815, '2019-01-20', '9999-01-01', 40),
-- another employee
(999, '2018-01-01', '2018-01-31', 15),
(999, '2018-03-01', '2018-03-31', 15),
(999, '2018-12-01', '9999-01-01', 40);
WITH rcte AS (
SELECT empnr
, MIN(datefrom) AS refdate
, ISNULL(NULLIF(MAX(dateto), '9999-01-01'), CURRENT_TIMESTAMP) AS maxdate -- clamp year 9999 to today
FROM #t
GROUP BY empnr
UNION ALL
SELECT empnr
, DATEADD(DAY, 1, refdate)
, maxdate
FROM rcte
WHERE refdate < maxdate
)
SELECT rcte.empnr
, rcte.refdate
, t.hours
FROM rcte
LEFT JOIN #t AS t ON rcte.empnr = t.empnr AND rcte.refdate BETWEEN t.datefrom AND t.dateto
ORDER BY rcte.empnr, rcte.refdate
OPTION (MAXRECURSION 1000) -- approx 3 years
Demo on db<>fiddle
It could be in your select, try:
SELECT b.empnr, b.hours, datefromparts(#year,#month,numbers.value) Datum
FROM numbers
LEFT OUTER JOIN emptbl b ON b.empnr = '0815' AND
datefromparts(#year,#month,numbers.value) BETWEEN b.datefrom AND b.dateto
Your CTE produces only 31 number and therefore it is showing only January dates.
declare #year int = 2019, #month int = 1;
WITH numbers
as
(
Select 1 as value
UNion ALL
Select value + 1 from numbers
where value + 1 <= Day(EOMONTH(datefromparts(#year,#month,1)))
)
SELECT *
FROM numbers
https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=a24e58ef4ce522d3ec914f90907a0a9e
You can try below code,
with t0 (i) as (select 0 union all select 0 union all select 0),
t1 (i) as (select a.i from t0 a ,t0 b ),
t2 (i) as (select a.i from t1 a ,t1 b ),
t3 (srno) as (select row_number()over(order by a.i) from t2 a ,t2 b ),
tbldt(dt) as (select dateadd(day,t3.srno-1,'01/01/2019') from t3)
select tbldt.dt
from tbldt
where tbldt.dt <= b.dateto -- put your condition here
https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=b16469908b323b8d1b98d77dd09bab3d

Last Changed Date

ID DATE AMT
A 20180401 110
A 20180301 110
A 20180201 100
A 20171010 90
B 20181001 90
B 20180901 90
B 20180707 80
My Output should be
ID DATE AMT Result
A 20180401 110 20180201
A 20180301 110 20180201
A 20180201 100 20171010
A 20171010 90 null
B 20181001 90 20180707
B 20180901 90 20180707
B 20180707 80 null
So i need to get the result column date of Last value different from current value with in same ID
so if we take the first record in this case current AMT value is 110 and next record also has 110 and the next record is 100 which is different from current value so I need to get that date -
I have used
LAST_VALUE ( DATE) OVER ( PARTITION BY ID, AMT ORDER BY ID ) AS LASTVALUE -I'm getting the date for the records with same Amount
This is after the
LAST_VALUE ( DATE) OVER ( PARTITION BY ID, AMT ORDER BY ID ) AS LASTVALUE2
ID;DAT;AMT;LASTVALUE2 -After Last Value
A;Mar 1, 2018;130;Mar 1, 2018
A;Feb 1, 2018;110;Jan 1, 2018
A;Jan 1, 2018;110;Jan 1, 2018
A;Nov 1, 2017;140;Nov 1, 2017
B;Jun 1, 2018;110;Apr 1, 2018
B;May 1, 2018;110;Apr 1, 2018
B;Apr 1, 2018;110;Apr 1, 2018
B;Mar 1, 2018;130;Mar 1, 2018
ID;DAT;AMT;PREV_DIFF_VALUE -After Lag
A;Nov 1, 2017;140;?
A;Jan 1, 2018;110;Nov 1, 2017
A;Feb 1, 2018;110;Jan 1, 2018
A;Mar 1, 2018;130;Feb 1, 2018
B;Mar 1, 2018;130;?
B;Apr 1, 2018;110;Mar 1, 2018
B;May 1, 2018;110;Apr 1, 2018
B;Jun 1, 2018;110;May 1, 2018
The third record should be Nov 1 2017
Thanks in advance
This is tricky. I think this does what you want:
select t.*,
max(case when result <> next_result then date end) over (partition by id order by date rows between unbounded preceding and 1 preceding)
from (select t.*,
lead(result) over (partition by a order by b) as next_result
from t
) t;
Try:
SELECT s1.ID
, FORMAT(s1.theDate,'MM-dd-yyyy') AS theDate
, s1.Amt
--, s1.PrevAmt
, CASE
WHEN Amt <> prevAmt
THEN FORMAT(
LAG(theDate) OVER ( PARTITION BY ID ORDER BY theDate )
,'MM-dd-yyyy' )
END AS prevDate
FROM (
SELECT ID, theDate, Amt
, LAG(AMT) OVER ( PARTITION BY ID ORDER BY theDate) AS prevAmt
FROM t1
) s1
ORDER BY ID, theDate DESC
This should give:
ID | theDate | Amt | prevDate
:- | :--------- | --: | :---------
A | 10-10-2017 | 90 | null
A | 04-04-2018 | 110 | null
A | 03-03-2018 | 110 | 02-02-2018
A | 02-02-2018 | 100 | 10-10-2017
B | 10-10-2018 | 90 | null
B | 09-09-2018 | 90 | 07-07-2018
B | 07-07-2018 | 80 | null
db<>fiddle here
For rows that don't have a previous row to pull the date from, it will return a NULL in the prevDate field.

TSQL Check if Month and Year fields are expired

i have a table with Month and Year fields as integer, eg:
Month | Year
------------
10 | 17
------------
11 | 17
------------
12 | 17
------------
1 | 18
------------
(Year 17 is for 2017 and Year 18 is for 2018)
I want add into a query a calculated field for check if the date is expired
SELECT [Year], [Month],
CASE WHEN
([Year]+2000) < DATEPART(Year, GetDate()) OR
(([Year]+2000) = DATEPART(Year, GetDate()) AND [Month] < DATEPART(Month, GetDate()))
THEN 1 ELSE 0 END AS IsExpired
FROM test
the output is
Month | Year | IsExpired
------------------------
10 | 17 | 1
------------------------
11 | 17 | 1
------------------------
12 | 17 | 1
------------------------
1 | 18 | 1
------------------------
the expected output is (because current GetDate() is 2017-11-29):
Month | Year | IsExpired
------------------------
10 | 17 | 1
------------------------
11 | 17 | 0
------------------------
12 | 17 | 0
------------------------
1 | 18 | 0
------------------------
see live on http://sqlfiddle.com/#!6/8c807/2
what i'm doing wrong?
Convert your values to dates:
WITH IntDates AS (
SELECT *
FROM (VALUES (10,17),(11,17),(12,17),(1,18)) AS D ([Month], [Year])),
Dates AS(
SELECT *,
DATEADD(YEAR, [Year], DATEADD(MONTH, [Month], '20000101')) AS DateValue
FROM IntDates)
SELECT *,
CASE WHEN DateValue < GETDATE() THEN 1 ELSE 0 END AS Expired
FROM Dates;
If you were using the date datatype this becomes a lot simpler.
create table test2
(
ExpirationDate date
)
--have to do a bunch of string manipulation to turn this into viable dates.
--and this of course is after switching the columns posted in your sql fiddle.
insert test2
select convert(char(4), [Year] + 2000) + right('0' + convert(varchar(2), [Month]), 2) + '01'
from Test
select case when ExpirationDate < dateadd(month, datediff(month, 0, getdate()), 0) --get beginning of the current month
then 1 else 0 end
, ExpirationDate
from test2