exclude weekends from month except a particular weekend - sql-server

I have written the following query in SQL Server
(QUERY)
select ISNULL(count(WORKHOUR) ,'-') AS present,
ISNULL((count(*)-count(WORKHOUR)),'-') AS absent,
ISNULL(cast(SUM(DateDiff(MINUTE, CAST('0:00' as time), WORKHOUR))/60 as nvarchar(3)) + ':' + cast(SUM(DateDiff(MINUTE, CAST('0:00' as time), WORKHOUR))%60 as nvarchar(2)),'-') AS TOTALWRKHRS,
ISNULL(cast(AVG(DateDiff(MINUTE, CAST('0:00' as time), WORKHOUR))/60 as nvarchar(3)) + ':' + cast(AVG(DateDiff(MINUTE, CAST('0:00' as time), WORKHOUR))%60 as nvarchar(2)),'-') AS AVGWRKHRS
from "+**currMonthVal**+" m,"+**currYearVal**+" e WHERE E.EMPID=M.EMPID AND E.LOCATION=M.LOCATION AND M.EMPID = ?
and DATENAME(WEEKDAY, attdate) not IN ('Saturday', 'Sunday') and CONVERT(VARCHAR(10),ATTDATE,3) not in ("+**resultantHL**+")
where currMonthVal and currYearVal are Month and Year values passed from outside and resultantHL are the holiday_dates (like '24/12/2016','25/12/2016') passed from outside.
This query works fine.
I have a table named WORKING_DAYS as follows:'
COLUMN_ID COLUMN_NAME DATA_TYPE
---------------------------------------
1 ID NUMBER
2 WDATE DATE
3 INSTITUTE_ID NUMBER
and sample data in table is:
COLUMN_ID WDATE INSTITUTE_ID
---------------------------------------
1 05-12-15 1
2 19-11-16 1
These two dates in table are actually declared as extra working days in respective months and fall on either Saturday or Sunday.
My requirement is :
I have excluded all Saturday and Sunday from the month in the query above [using DATENAME(WEEKDAY, attdate) not IN ('Saturday', 'Sunday')] but I do not want to exclude the above two dates in WORKING_DAYS table but include them
And I want to do that in the same query if possible as that too in WHERE clause.
How can I do so?
Note: I have already retrieved the extra working days from WORKING_DAYS table and stored in a string variable and wish to pass that variable value in the condition

An OR your date is also on WORKING_DAYS should suffice :
Replace
DATENAME(WEEKDAY, attdate) not IN ('Saturday', 'Sunday')
by
(DATENAME(WEEKDAY, attdate) not IN ('Saturday', 'Sunday') or adddate in (select wdate from WORKING_DAYS))

Related

How to generate AutoIncrement number with prefix of financial year?

My Client wants to generate a code for every ticket number that goes into database based on the financial year they are working (April current year to March next year) and number needs to reset to 0 where the financial year changes
Example:
ID
17/18/0000001
17/18/0000002
...
18/19/0000001
18/19/0000002
...
If the financial year is stored in database like starting an ending month and year. How we can check that this is the next year broda! reset the numbers.
I wouldn't attempt to try and maintain such a counter internally. Rather, I would generate at the time of querying. The query below assumes that your table does have a single ordinary auto increment counter ID as well as a year int column for the fiscal year. We can use the following to generate the counter you want:
SELECT
RIGHT(CONVERT(varchar(4), year), 2) + '/' +
RIGHT(CONVERT(varchar(4), year + 1), 2) + '/' +
RIGHT('0000000' +
CAST(ROW_NUMBER() OVER (PARTITION BY year ORDER BY ID) AS VARCHAR), 7)
FROM yourTable;
Demo
Client is always right. Suppose you have a table
create table #trans(
id int identity(1,1),
transDate datetime
--other fields
)
--the table is filled
declare #dStart date='20160401', --start and end dates
#dEnd date='20170331' --of the first financial year
;with fy as ( -- fill following years
select 1 id, #dStart dStart, #dEnd dEnd
union all
select id+1,DATEADD(year,1,dStart),DATEADD(year,1,dEnd)
from fy
where id<5 --"majic" 5 is arbitrary
)
select dStart,dEnd,t.*,
right(cast(year(dstart) as varchar),2)+'/'+right(cast(year(dEnd) as varchar),2)+'/' -- F.Y. label
+ FORMAT( ROW_NUMBER() over(
partition by right(cast(year(dstart) as varchar),2)+'/'+right(cast(year(dEnd) as varchar),2)+'/' --restart numbering each F.Y.
order by t.id),'000000') ticket
from fy
inner join #trans t on cast(t.transDate as date) between fy.dStart and fy.dEnd
And have what the Client wants.
Disclaimer: If some data are deleted then ticket numbering change.

Check if date falls within Month and Year range

If a user selects a range such as:
Start: November 2016
End: September 2017,
I want to include all results that fall within the range of 2016-11-01 to 2017-09-30.
I tried concatenating together the year, month, and day, however the issue comes that not all months have the same last day. While I know all months start on day 01, a month's end day can be 28, 29, 30, or 31.
Is there a way to do this without constructing the date? SqlServer 2008 doesn't have the EOMONTH function, and I feel like anything more complex than that is not the right solution. I would like to avoid this:
WHERE
DateCol >= '2016' + '-' + '11' + '-01' AND
DateCol <= '2017' + '-' + '09' + '-30'
It really seems to me that the easiest and best answer is to go from the first of the beginning month to the first of the month after the ending month, and make the second comparison not inclusive.
In other words, instead of this:
WHERE
DateCol >= '2016' + '-' + '11' + '-01' AND
DateCol <= '2017' + '-' + '09' + '-30'
simply this:
WHERE
DateCol >= '2016' + '-' + '11' + '-01' AND
DateCol < '2017' + '-' + '10' + '-01'
There is a faster way to do so :
DECLARE #minDate DATE
DECLARE #maxDate DATE
SET #minDate = XXXXX
SET #maxDate = YYYYY
-- Get the first day of the month minDate.
SET #minDate = CONVERT(datetime,CONVERT(varchar(6),#minDate,112)+'01',112)
-- Get the last day of the month minDate.
SET #maxDate = CONVERT(datetime,CONVERT(varchar(6),#maxDate,112)+'01',112)
SET #maxDate = DATEADD(day, -1, DATEADD(month, 1, #maxDate))
SELECT * FROM myTABLE WHERE DateCol >= #minDate AND DateCol <= #maxDate
Or :
SELECT * FROM myTABLE
WHERE DateCol >= CONVERT(datetime,CONVERT(varchar(6),XXXXX,112)+'01',112)
AND DateCol <= DATEADD(day, -1, DATEADD(month, 1, CONVERT(datetime,CONVERT(varchar(6),YYYYY,112)+'01',112)))
Use syntax like CONVERT(datetime,'20170930',112) or CONVERT(datetime,'09-30-2017',110) for XXXXX and YYYYY rather than '2017-09-30' that use SQL Server implicit convertion from char to datetime (rely on the server configuration : can be hazardous!!!)).
Using this syntax is faster because #minDate and #maxDate do not need any evaluation. So that indexes can be used directly...
Otherwise a scalar function that will simulate the eomonth() behaviour could be usefull...
You could use following select statement to get last date of any month (and any year) by passing a field or date to it:
DECLARE #dtDate DATE
SET #dtDate = '09/25/2016'
SELECT CAST(DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,#dtDate)+1,0)) AS DATE) AS LastDay_AnyMonth
Please provide some example data and desired result and I will update my answer further.
Here you go:
DECLARE #YourTable TABLE (YourData DATE);
INSERT INTO #YourTable VALUES
('2016-11-01'),
('2016-09-05'),
('2017-03-03'),
('2017-11-11'),
('2017-12-14'),
('2017-09-30');
WITH CTE AS (
SELECT YourData
FROM #YourTable
WHERE YEAR(YourData) =2016 AND MONTH (YourData) >= 11
)
SELECT YourData
FROM #YourTable
WHERE YEAR(YourData) =2017 AND MONTH (YourData) <= 9
UNION ALL
SELECT YourData
FROM CTE;
There is no need to know the end of the month (28 or 30 or 31).
For 2008, you can simply convert the string
Example
Select Date1=convert(date,'November 2016')
,Date2=dateadd(DAY,-1,dateadd(MONTH,1,convert(date,'September 2017')))
Returns
Date1 Date2
2016-11-01 2017-09-30
So the WHERE would be somthing like this
...
Where DateCol between convert(date,'November 2016')
and dateadd(DAY,-1,dateadd(MONTH,1,convert(date,'September 2017')))
A useful construct for performing date-based operations is to make use of a Date Dimension table. You are creating a lookup table that is populated with a lot of information about dates over a large span of time. You can then query the table based on the information that you do have. The table is small enough so that it does not impose significant performance concerns.
In your particular case, you have the month and year. You would plug that into the date dimension table to get the first of the month from the beginning month and the last of the month from the ending month. You now have a time range to search over without any complex logic or calculations on the fly.
Aaron Bertrand explains it in depth here: https://www.mssqltips.com/sqlservertip/4054/creating-a-date-dimension-or-calendar-table-in-sql-server/

How to sort month in a table when retrieving using the sql query?

I have a query which gets month name from a table. But this column isn't a datetime data type, It's a varchar column. How can I sort it according to the month name in ascending order?
This is the output I get at the moment.
August
November
October
September
This is my query
select distinct(payemnt_month) as month from payement_details
Use below one i have appended your month with day and year. Then i am extracting month number
select * from payment_details order by DATEPART(MM,payemnt_month+'01'+'00')
Update
If possible update your query like below.
SELECT * FROM
(
SELECT DISTINCT month,
Datepart(MM, payemnt_month+ '01' + '00') MONTHNO
FROM payment_details )A
ORDER BY MONTHNO
Or Like below if you don't have any issue to keep month no
SELECT DISTINCT month,
Datepart(MM, payemnt_month+ '01' + '00') MONTHNO
FROM payment_details
order by month,Datepart(MM, payemnt_month+ '01' + '00')
ORDER BY CASE WHEN payment_month='August' THEN 8
WHEN payment_month='November' THEN 11
WHEN payment_month='October' THEN 10
WHEN payment_month='September' THEN 9 END
Frame your ORDER BY clause as above and add remaining months into it as required.
Just put order by clause as:
select distinct(payemnt_month) as month from payement_details order by payemnt_month

SQL query to fetch all results in one query

Test data:
id date company location
----------------------------------
1 03/01/2016 ABC india
2 03/25/2016 ABC us
3 02/24/2016 ABC india
4 02/25/2016 ABC us
5 03/02/2016 ABC india
Query #1
select count(id)
from table
where company = 'ABC'
and date between 03/01/2016 and 03/31/2016
Query #2
select count(id)
from table
where company = 'ABC'
and date between 02/01/2016 and 02/29/2016
Need to calculate location wise count for current and previous months.
How to write a SQL query to return location wise like the below in one query?
Expected result:
company currentmonth Previousmonth location
ABC 2 1 india
ABC 1 1 us
Try:
select company,
sum(case when month(date)=month(getdate())
then 1 else 0 end) as currentMonth,
sum(case when month(date)=month(dateadd(MONTH,-1,getdate()))
then 1 else 0 end) as Previuosmonth,
location
from yourTable
where month(date) between month(dateadd(MONTH,-1,getdate())) and month(getdate())
AND company='ABC'
group by company, location
Since you made two queries and filter both with current month (march) and previous. I decided to use the function MONTH to extract the month from the current date (getdate()) of the system and subtract 1 that way you will always have the current month and the previous one.
EDIT
As pointed out by #DhananjayaKuppu I fixed the problem for when you have a month as January, since the month function returns an integer it would return 0 in my calculation. Now it is fixed.
You may try some thing like subtract current date yearmm format (ex: 201603) and yearmm format of date from table (ex:201603) if it result to 0 treat as current month, else previous month, hope it will help:
SELECT company,
location,
sum(iif(d = 0, 0, 1)) cm,
sum(iif(d = 0, 1, 0)) pm
FROM (SELECT company,
location,
CONVERT(INT,
CONVERT(CHAR(4), GetDate(), 120) +
CONVERT(CHAR(2), GetDate(), 101)
) -
CONVERT(INT,
CONVERT(CHAR(4), date, 120) +
CONVERT(CHAR(2), date, 101)
) as d
FROM table) vw
GROUP BY company, location

Get record based on year in oracle

I am creating a query to give number of days between two days based on year. Actually I have below type of date range
From Date: TO_DATE('01-Jun-2011','dd-MM-yyyy')
To Date: TO_DATE('31-Dec-2013','dd-MM-yyyy')
My Result should be:
Year Number of day
------------------------------
2011 XXX
2012 XXX
2013 XXX
I've tried below query
WITH all_dates AS
(SELECT start_date + LEVEL - 1 AS a_date
FROM
(SELECT TO_DATE ('21/03/2011', 'DD/MM/YYYY') AS start_date ,
TO_DATE ('25/06/2013', 'DD/MM/YYYY') AS end_date
FROM dual
)
CONNECT BY LEVEL <= end_date + 1 - start_date
)
SELECT TO_CHAR ( TRUNC (a_date, 'YEAR') , 'YYYY' ) AS YEAR,
COUNT (*) AS num_days
FROM all_dates
WHERE a_date - TRUNC (a_date, 'IW') < 7
GROUP BY TRUNC (a_date, 'YEAR')
ORDER BY TRUNC (a_date, 'YEAR') ;
I got exact output
Year Number of day
------------------------------
2011 286
2012 366
2013 176
My question is if i use connect by then query execution takes long time as i have millions of records in table and hence i don't want to use connect by clause
connect by clause is creating virtual rows against the particular record.
Any help or suggestion would be greatly appreciated.
From your vague expected results I think you want the number of records between those dates, not the number of days; but it's rather unclear. Since you refer to a table in the question I assume you want something related to the table data, not simply days between two dates which wouldn't depend on a table at all. (I have no idea what the connect by clause reference means though). This should give you that, if it is what you want:
select extract(year from date_field), count(*)
from t42
where date_field >= to_date('01-Jun-2011', 'DD-MON-YYYY')
and date_field < to_date('31-Dec-2013') + interval '1' day
group by extract(year from date_field)
order by extract(year from date_field);
The where clause is as you'd expect between two dates; I've assumed there might be times in your date field (i.e. not all at midnight) and that you want to count all records on the last date in your range. Then it's grouping and counting based on the year for each record.
SQL Fiddle.
If you want the number of days that have records within the range, then you can just vary the count slightly:
select extract(year from date_field), count(distinct trunc(date_field))
...
SQL Fiddle.
you can use the below function to reduce the number of virtual rows by considering only the years in between.You can check the SQLFIDDLE to check the performance.
First consider only the number of days between start date and the year end of that year or
End date if it is in same year
Then consider the years in between from next year of start date to the year before the end date year
Finally consider the number of days from start of end date year to end date
Hence instead of iterating for all the days between start date and end date we need to iterate only the years
WITH all_dates AS
(SELECT (TO_CHAR(START_DATE,'yyyy') + LEVEL - 1) YEARS_BETWEEN,start_date,end_date
FROM
(SELECT TO_DATE ('21/03/2011', 'DD/MM/YYYY') AS start_date ,
TO_DATE ('25/06/2013', 'DD/MM/YYYY') AS end_date
FROM dual
)
CONNECT BY LEVEL <= (TO_CHAR(end_date,'yyyy')) - (TO_CHAR(start_date,'yyyy')-1)
)
SELECT DECODE(TO_CHAR(END_DATE,'yyyy'),YEARS_BETWEEN,END_DATE
,to_date('31-12-'||years_between,'dd-mm-yyyy'))
- DECODE(TO_CHAR(START_DATE,'yyyy'),YEARS_BETWEEN,START_DATE
,to_date('01-01-'||years_between,'dd-mm-yyyy'))+1,years_between
FROM ALL_DATES;
In Oracle you can perform Addition and Substraction to dates like this...
SELECT
TO_DATE('31-Dec-2013','dd-MM-yyyy') - TO_DATE('01-Jun-2011','dd-MM-yyyy')
DAYS FROM DUAL;
it will return day difference between two dates....
select to_date(2011, 'yyyy'), to_date(2012, 'yyyy'), to_date(2013, 'yyyy')
from dual;
TO_DATE(2011,'Y TO_DATE(2012,'Y TO_DATE(2013,'Y
--------------- --------------- ---------------
01-MAY-11 01-MAY-12 01-MAY-13
select to_char(date_field,'yyyy'), count(*)
from your_table
where date_field between to_date('01-Jun-2011', 'DD-MON-YYYY')
and to_date('31-Dec-2013 23:59:59', 'DD-MON-YYYY hh24:mi:ss')
group by to_char(date_field,'yyyy')
order by to_char(date_field,'yyyy');

Resources