Aggregate in UPDATE command - sql-server-2008

I am trying to update all customers payment terms based on the oldest invoice. So far I have the following code:
UPDATE [CUSTOMERS - Main]
SET Payterms = 0
FROM (SELECT Max(INVOICES.InvoiceDate) AS LastInvoice,
Datediff(day, Max(INVOICES.InvoiceDate), Getdate()) AS age,
INVOICES.CompanyName,
[CUSTOMERS - Main].PayTerms,
[CUSTOMERS - Main].CreditLimit,
[CUSTOMERS - Main].CompanyRegNo
FROM INVOICES
INNER JOIN [CUSTOMERS - Main]
ON INVOICES.CompanyName = [CUSTOMERS - Main].CompanyName
GROUP BY INVOICES.CompanyName,[CUSTOMERS - Main].PayTerms,[CUSTOMERS - Main].CreditLimit,[CUSTOMERS - Main].CompanyRegNo
HAVING (Datediff(day, Max(INVOICES.InvoiceDate), Getdate()) > 365)
AND ([CUSTOMERS - Main].PayTerms > 0
OR [CUSTOMERS - Main].CreditLimit > 0))
But it's not working. How do I update a query that has an aggregate function?

This seems to be a much tidier approach, though I may be off by a day from your actual intention, e.g. today is December 10, do you want to exclude customers with any invoice newer than December 10 of last year, or December 11? This excludes customers with an invoice newer than December 10.
DECLARE #cutoff DATE = DATEADD(DAY, -365, GETDATE());
UPDATE c SET Payterms = 0
FROM dbo.[CUSTOMERS - Main] AS c
WHERE (c.PayTerms > 0 OR c.CreditLimit > 0)
AND NOT EXISTS
(
SELECT 1 FROM dbo.INVOICES AS i
WHERE i.CompanyName = c.CompanyName
AND InvoiceDate >= #cutoff
);

Related

Extracting year and total days between range of date - SQL

I have data with start date and end date (Say 20th Feb 2018 to 20th Feb 2020), I want to find out the total days in every year inside this range.
For example:
2018 - x days
, 2019 - 365 days
, 2020 - y days etc.
Is there a way I can do in SQL without hardcoding year values?
I tried hardcoding the values and it worked well. But I want a solution without hardcoding year values
I'm not familiar enough with MySql to know if this will port, however here is a tested and confirmed SQL Server solution.
The fiddle link is here for your use.
Given start dates 02/20/2018 and 02/20/2020, the result set is as follows:
Year
periodStart
periodEnd
DaysInPeriod
2018
2018-02-20
2018-12-31
314
2019
2019-01-01
2019-12-31
365
2020
2020-01-01
2020-02-20
51
Declare #StartDate date = '2018-02-20', #EndDate date = '2020-02-20';
WITH x AS (SELECT n FROM (VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) v(n)),
Years AS (
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS Year
FROM x ones, x tens, x hundreds, x thousands)
SELECT Years.Year,
CASE
WHEN Year(#StartDate) = Years.year THEN #StartDate
ELSE DATEFROMPARTS(years.year, 01, 01)
END AS periodStart,
CASE
WHEN Year(#EndDate) = Years.year THEN #EndDate
ELSE DATEFROMPARTS(years.year, 12, 31)
END AS periodEnd,
DATEDIFF(day,
CASE
WHEN Year(#StartDate) = Years.year THEN #StartDate
ELSE DATEFROMPARTS(years.year, 01, 01)
END,
CASE
WHEN Year(#EndDate) = Years.year THEN #EndDate
ELSE DATEFROMPARTS(years.year, 12, 31)
END
) + 1 AS DaysInPeriod
FROM Years
WHERE Years.Year >= Year(#StartDate)
AND Years.Year <= Year(#EndDate)
Using WITH RECURSIVE to create range of dates then we can easly count the number of days for each year using DATEDIFF
WITH RECURSIVE dates AS
(
SELECT min(start_date) as start_date, DATE_FORMAT(min(start_date),'%Y-12-31') as last_day FROM mytable
UNION ALL
SELECT DATE_FORMAT(start_date + INTERVAL 1 YEAR,'%Y-01-01'),
DATE_FORMAT(start_date + INTERVAL 1 YEAR,'%Y-12-31')
FROM dates
WHERE DATE_FORMAT(start_date + INTERVAL 1 YEAR,'%Y-01-01') <= (SELECT MAX(end_date) FROM mytable)
),
cte2 as (
SELECT d.start_date as start_day, if(YEAR(d.start_date) = YEAR(m.end_date), m.end_date, d.last_day) as last_day
FROM dates d, mytable m
)
select *, DATEDIFF(last_day, start_day)+1 as total_days
from cte2;
Demo here
You are looking for the DATEDIFF function.
https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_datediff
DATEDIFF() returns expr1 − expr2 expressed as a value in days from one date to the other. expr1 and expr2 are date or date-and-time expressions.
You are free to specify e.g. "2019-01-01" or "2020-01-01"
as input arguments to DATEDIFF.
You may find it convenient to store several January 1st
dates in a calendar reporting table, if you want SELECT to loop
over several years and report on number of days in each year.

Only count working days in a DATEDIFF (MySQL)

So, next problem :'), I have the following query that #MatBailie provided to me here (thanks again!):
SELECT
taskname,
employee,
SUM(
DATEDIFF(
LEAST( enddate, '2023-12-31'),
GREATEST(startdate, '2023-01-01')
)
+1
) AS total_days,
FROM
schedule
WHERE
startDate <= '2023-12-31'
AND
endDate >= '2023-01-01'
GROUP BY
employee,
taskname
This query will tell me how many days a certain employee has spent on a certain task in a given period of time, and it works great!
The next thing I would like to do however, is to substract non-working days from the SUM of DATEDIFFs for some of the tasks (e.g. when the task has "count_non_working_days= 0" in a reference table called 'activities').
For example, my schedule also keeps track of the amount of days off every employee has taken (days off are also scheduled as tasks). But of course, days off that fall in a weekend or on a holiday should not be counted towards the total of days off a person has taken in a year. (Note that I did consider scheduling days off only on weekdays/non-holidays, but this is not a practical option in the scheduling software I use because employees request a leave from date A to date B, and this request is approved or denied as-is (they don't make 3 holiday requests excluding the weekends if they want to go on a vacation for 3 weeks, if you get my drift).
So, if an employee goes on a vacation for 10 days, this is counted as 10 days off, but this holiday may have 1 or 2 weekends in it, so the sum of days of that the employee has taken off should be 6, 7 or 8, and not 10. Furthermore, if it has a holiday such as Easter Monday in it (I have all dates of my holidays in a PHP array), this should also be subtracted.
I have tried the solutions mentioned here, but I couldn't get them to work (a) because those are in SQL server and (b) because they don't allow putting in an array of holidays, (c) nor allow toggling the subtraction on and off depending on the event type.
Here's my attempt of explaining what I'm trying to do in my pseudo-SQL:
SELECT
taskname,
employee,
IF( activities.count_non_working_days=1,
-- Just count the days that fall in the current year:
SUM(
DATEDIFF(
LEAST( enddate, '2023-12-31'),
GREATEST( startdate, '2023-01-01')
)
+ 1
) AS total_days,
-- Subtract the amount of saturdays, sundays and holidays:
SUM(
DATEDIFF(
LEAST( enddate, '2023-12-31'),
GREATEST( startdate, '2023-01-01')
)
- [some way of getting the amount of saturdays, sundays and holidays that fall within this date range]
+ 1
) AS total_days
)
FROM
schedule
LEFT JOIN
activities
ON activity.name = schedule.name
WHERE
startDate <= '2023-12-31'
AND
endDate >= '2023-01-01'
GROUP BY
employee,
taskname
I know the query above is probably faulty on so many levels, but I hope it clarifies what I'm trying to do.
Thanks once more for all the help!
Edit: basically I need something like this, but in MySQL and preferably with a toggle that turns the subtraction on or off depending on the task type.
Edit 2: To clarify: my schedule table holds ALL activities, including holidays. For example, some records may include:
employee
taskname
startDate
endDate
Mr. Anderson
Programming
2023-01-02
2023-01-06
Mr. Anderson
Programming
2023-01-09
2023-01-14
Mr. Anderson
Vacation
2023-01-14
2023-01-31
In another table, Programming is defined as "count_non_working_days=1", because working in the weekends should count, while Vacation is defined as "count_non_working_days=0", because taking a day off on the weekend should not count towards your total amount of days taken off.
The totals for this month should therefore state that:
Mr. Anderson has done Programming for 11 days (of which 1 was on a saturday)
Mr. Anderson has taken 12 days off for (because the 2 weekends in this period don't count as days off).
Create a calendar table, with every date of interest (so, something like 2000-01-01 to 2099-01-01) and include columns such as is_working_day which can be set to TRUE/FLASE or 1/0. Then you can update that column as necessary, and join on that table in your query to get working dates that the employee has booked off.
In short, you count the relevant dates, rather than deducting the irrelevant dates.
SELECT
s.employee,
s.taskname,
COUNT(*) AS total_days,
FROM
(
schedule AS s
INNER JOIN
activities AS a
ON a.taskname = s.taskname
)
INNER JOIN
calendar AS c
ON c.calendar_date >= s.startDate
AND c.calendar_date <= s.endDate
AND c.is_working_day >= 1 - a.count_non_working_days
WHERE
c.calendar_date >= '2023-01-01'
AND c.calendar_date <= '2023-12-31'
GROUP BY
s.employee,
s.taskname
Your calendar table can then also include flags such as is_weekend, is_bank_holiday, is_fubar, is_amazing, etc, and the is_working_day can be a computed column from those inputs.
Note on is_working_day filter...
WHERE
( count_non_working_day = 1 AND is_working_day IN (0, 1) )
OR
( count_non_working_day = 0 AND is_working_day IN ( 1) )
-- change to (1 - count_non_working_day)
WHERE
( (1 - count_non_working_day) = 0 AND is_working_day IN (0, 1) )
OR
( (1 - count_non_working_day) = 1 AND is_working_day IN ( 1) )
-- simplify
WHERE
( (1 - count_non_working_day) <= is_working_day )
OR
( (1 - count_non_working_day) <= is_working_day )
-- simplify
WHERE
( (1 - count_non_working_day) <= is_working_day )
Demo: https://dbfiddle.uk/YAmpLmVE
This is to calculate all the weeekends between two giving dates It may help you :
SELECT (
((WEEK('2022-12-31') - WEEK('2022-01-01')) * 2) -
(case when weekday('2022-12-31') = 6 then 1 else 0 end) -
(case when weekday('2022-01-01') = 5 then 1 else 0 end)
)
You will have to substract also holidays that fall within this date range.

SQL select date range by month and year

How do I select date range from this query?
E.g : From Month('2017-05-22') And Year(date1) = Year('2017-05-22')
to Month('2018-05-22') And Year(date1) = Year('2018-05-22')
My current query :
SELECT
date1
FROM
data
WHERE
Month(date1) = Month('2018-05-22') And Year(date1) = Year('2018-05-22')
I'd convert those literals to dates using str_to_date:
SELECT MONTH(date1)
FROM data
WHERE date1 BETWEEN STR_TO_DATE('2017-05-22', '%Y-%m-%d') AND STR_TO_DATE('2018-05-23', '%Y-%m-%d')
Note that between is inclusive on the first argument and exclusive on the second, so I bumped the day to the 23rd.
BETWEEN condition to retrieve values within a date range.
For example:
SELECT *
FROM order_details
WHERE order_date BETWEEN CAST('2014-02-01' AS DATE) AND CAST('2014-02-28' AS DATE);
This MySQL BETWEEN condition example would return all records from the order_details table where the order_date is between Feb 1, 2014 and Feb 28, 2014 (inclusive). It would be equivalent to the following SELECT statement:
SELECT *
FROM order_details
WHERE order_date >= CAST('2014-02-01' AS DATE)
AND order_date <= CAST('2014-02-28' AS DATE);
SELECT TO_CHAR(systemts, 'yyyy-mm') as yyyymm,
max(notenum) - min(notenum) + 1 as notenum_range
FROM your_table_name
WHERE systemts >= to_date('2018-01-01', 'yyyy-mm-dd') AND
systemts < to_date('2018-07-17', 'yyyy-mm-dd')
GROUP BY TO_CHAR(systemts, 'yyyy-mm');
DECLARE #monthfrom int=null,
#yearfrom int=null,
#monthto int=null,
#yearto int=null
/**Region For Create Date on Behalf of Month & Year**/
DECLARE #FromDate DATE=NULL
DECLARE #ToDate DATE=NULL
SET #FromDate=DateAdd(day,0, DateAdd(month, #monthfrom - 1,DateAdd(Year, #yearfrom-1900, 0)))
SET #ToDate=DateAdd(day,-1, DateAdd(month, #monthto - 0,DateAdd(Year, #yearto-1900, 0)))
/**Region For Create Date on Behalf of Month & Year**/
SELECT DISTINCT month ,Year FROM tbl_Att
WHERE (DATEADD(yy, year - 1900, DATEADD(m, Month - 1, 1 - 1)) BETWEEN CONVERT(DATETIME, #FromDate, 102) AND
CONVERT(DATETIME, #ToDate, 102))

Returning the next-to-last entry using MySQL

A little info: people check-in but they don't check out. Each check-in creates an auto-incremented entry into the _checkins table with a timestamp, MemberID, etc.
Here's the data the query needs to return:
Member info (name, picture, ID, etc)
The number of check-ins they've had in the last 30 days
The time since they're last check-in must be less than 2 hours for
them to be on the list.
The date of their last check-in NOT COUNTING TODAY (in other words,
the next to last "Created" entry in the _checkins table).
I have it all working except the last part. I feel like LIMIT is going to be part of the solution but I just can't find a way to implement it correctly.
Here's what I've got so far:
SELECT m.ImageURI, m.ID, m.FirstName, m.LastName,
ROUND(time_to_sec(timediff(NOW(), MAX(ci.Created))) / 3600, 1) as
'HoursSinceCheckIn', CheckIns
FROM _checkins ci LEFT JOIN _members m ON ci.MemberID = m.ID
INNER JOIN(SELECT MemberID, COUNT(DISTINCT ID) as 'CheckIns'
FROM _checkins
WHERE(
Created BETWEEN NOW() - INTERVAL 30 DAY AND NOW()
)
GROUP BY MemberID
) lci ON ci.MemberID=lci.MemberID
WHERE(
ci.Created BETWEEN NOW() - INTERVAL 30 DAY AND NOW()
AND TIMESTAMPDIFF(HOUR, ci.Created, NOW()) < 2
AND ci.Reverted = 0
)
GROUP BY m.ID
ORDER BY CheckIns ASC
You can simplify greatly (and make your code safer, as well):
SELECT _Members.ImageURI, _Members.ID, _Members.FirstName, _Members.LastName,
ROUND(TIME_TO_SEC(TIMEDIFF(NOW(), _FilteredCheckins.lastCheckin)) / 3600, 1) AS hoursSinceCheckIn, _FilteredCheckins.checkIns,
(SELECT MAX(_Checkins.created)
FROM _Checkins
WHERE _Checkins.memberId = _Members.ID
AND _Checkins.created < _FilteredCheckins.lastCheckin) AS previousCheckin
FROM _Members
JOIN (SELECT memberId, COUNT(*) AS checkIns, MAX(created) AS lastCheckin
FROM _Checkins
WHERE created >= NOW() - INTERVAL 30 DAY
GROUP BY memberId
HAVING lastCheckin >= NOW() - INTERVAL 2 HOURS) _FilteredCheckins
ON _FilteredCheckins.memberId = _Members.ID
ORDER BY _FilteredCheckins.checkIns ASC
We're counting all checkins in the last 30 days, including the most recent, but that's trivially adjustable.
I'm assuming _Checkins.id is unique (it should be), so COUNT(DISTINCT ID) can be simplified to COUNT(*). If this isn't the case you'll need to put it back.
(Side note: please don't use BETWEEN, especially with date/time types)
(humorous side note: I keep mentally reading this as "chickens"....)

month wise headcount of the subcontractor for period Jan 14 to Dec 14

I have a table SOW ,which has columns ASSOCIATE, SOW_START_DATE, SOW_END_DATE,in which i need to get the count of associate for a month for year 2014, for eg i have associate Minal whose sow_start_date is 1-01-2014 and sow_end_date is 1-04-2014 i want query where minal is available for Jan,Feb,march and April.
i have tried this query but result is minal is available only for Jan month instead f Jan,Feb,march,April.
select year([SOW Start Date]) as [year], month([SOW Start Date]) as [months], Associate
from SOW
where month([SOW Start Date]) in (MONTH([SOW Start Date]), MONTH([SOW End Date]))
and month([SOW Start Date]) =2 and YEAR([SOW Start Date])=2014
group by year([SOW Start Date]), month([SOW Start Date]),Associate
having count(*) >= 1
order by month([SOW Start Date])
I understand you want to list count of employees per month. The problem is that you need to join your sow table to some list of months. I guess you don't have one so you need to manufacture it. The you need to join your sow table to the list of months based on start/end dates. This way you will get a month-employee pair if the employee worked in that month. Finally you group by month and count employees:
-- identify the date range
declare #min date, #max date
select #min=min(sow_start_date), #max=max(sow_end_date) from sow
-- we will identify the month by its first day
declare #start date
select #start=DATEADD(month, DATEDIFF(month, 0, #min), 0)
-- generate all months in the min-max range
; with months as
(
select #start as d
union all
select dateadd(mm, 1, d) as d from months where d < #max
)
-- move start dates to 1st day of month so the comparisson works
, sow2 as
(
select associate, sow_end_date,
DATEADD(month, DATEDIFF(month, 0, sow_start_date), 0) as sow_start_date
from sow
)
-- link employees to months
, employee_month as
(
select d, Associate from sow2 inner join months
on d >= sow_start_date and d < sow_end_date
)
-- count employees per month
select d, count(Associate) a from employee_month group by d
Check out the fiddle http://www.sqlfiddle.com/#!3/52c94b/3/0