Count only one record pr day - sql-server

SELECT COUNT(*)
FROM dbo.dutyrostershift
WHERE employeeid = 1931
AND DATO BETWEEN '20210901' AND '20220831'
AND shifttype IN (0,1,4,5,6,15,16,20)
In short it asks about how many jobs one employee has got of some types of work in a given period.
It's used for counting days with work.
Which means, that if there is two or more records with the same date, it should only count 1 for that day.

Instead of counting the rows, count the distinct dates in that period. ie:
SELECT COUNT(distinct cast(dato as date))
FROM dbo.dutyrostershift
WHERE employeeid = 1931 AND
DATO BETWEEN '20210901' AND '20220831' AND
shifttype IN (0,1,4,5,6,15,16,20);

Related

Combining rows with overlapping dates in T-SQL

I have some data similar to the below:
Base data
Student Start Date End Date Course
John 01-Jan-20 30-Sep-20 Business
John 01-Jan-20 30-Dec-20 Psychology
John 01-Oct-20 NULL Music
Jack 01-Feb-20 30-Sep-20 Business
Jack 01-Apr-20 30-Nov-20 Music
I want to transform the data so I have a row for each student, for each time period, with a concatenated list of courses, i.e.
Target output
Student Start Date End Date Course
John 01-Jan-20 30-Sep-20 Business, Psychology
John 01-Oct-20 30-Dec-20 Psychology, Music
John 01-Jan-21 NULL Music
Jack 01-Feb-20 31-Mar-20 Business
Jack 01-Apr-20 30-Sep-20 Business, Music
Jack 01-Oct-20 30-Nov-20 Music
I have a script that works if the dates are identical, using STUFF on the course field and grouping on student/dates (code below). But I can't work out how to handle the overlapping dates?
Select Student,
Courses =
STUFF((select ',' + course
from Table1 b
where a.student = b.student
for XML PATH('')
),1,1,''
)
from table1 a
Group by student
This is a little long winded, as you need to get the groups for the dates. As the dates don't overlap, you then need to do a bit of elimination of some of the groupings too, so it takes a couple of sweeps.
I use CTEs to get the groups I need, and then use a subquery to string aggregate (on a more recent version of SQL Server you can use STRING_AGG and not need a second scan of the table). This ends up with this:
WITH YourTable AS(
SELECT *
FROM (VALUES('John',CONVERT(date,'01-Jan-20'),CONVERT(date,'30-Sep-20'),'Business'),
('John',CONVERT(date,'01-Jan-20'),CONVERT(date,'30-Dec-20'),'Psychology'),
('John',CONVERT(date,'01-Oct-20'),CONVERT(date,NULL),'Music'),
('Jack',CONVERT(date,'01-Feb-20'),CONVERT(date,'30-Sep-20'),'Business'),
('Jack',CONVERT(date,'01-Apr-20'),CONVERT(date,'30-Nov-20'),'Music'))V(Student,StartDate,EndDate,Course)),
Dates AS(
SELECT DISTINCT V.Student, V.[Date]
FROM YourTable YT
CROSS APPLY (VALUES(YT.Student,YT.StartDate),
(YT.Student,YT.EndDate)) V(Student,[Date])),
Islands AS(
SELECT *,
LEAD(ISNULL([Date],'99991231')) OVER (PARTITION BY Student ORDER BY ISNULL([Date],'99991231')) AS NextDate
FROM Dates
WHERE [Date] IS NOT NULL),
Groups AS(
SELECT I.Student,
I.Date AS StartDate,
CASE DATEPART(DAY,I.NextDate) WHEN 1 THEN DATEADD(DAY, -1, I.NextDate) ELSE I.NextDate END AS EndDate,
STUFF((SELECT ',' + YT.Course
FROM YourTable YT
WHERE YT.Student = I.Student
AND YT.StartDate <= I.[Date]
AND (YT.EndDate >= I.NextDate OR YT.EndDate IS NULL)
ORDER BY YT.Course
FOR XML PATH(''),TYPE).value('(./text())[1]','nvarchar(MAX)'),1,1,'') AS Courses
FROM Islands I)
SELECT Student,
StartDate,
EndDate,
Courses
FROM Groups
WHERE ([StartDate] != EndDate OR EndDate IS NULL)
AND Courses IS NOT NULL
ORDER BY Student DESC,
StartDate ASC;

SQL Server : Join If Between

I have 2 tables:
Query1: contains 3 columns, Due_Date, Received_Date, Diff
where Diff is the difference in the two dates in days
QueryHol with 2 columns, Date, Count
This has a list of dates and the count is set to 1 for everything. All these dates represent public holidays.
I want to be able to get the sum of QueryHol["Count"] if QueryHol["Date"] is between Query1["Due_Date"] and Query1["Received_Date"]
Result Wanted: a column joined onto Query1 to state how many public holidays fell into the date range so they can be subtracted from the Query1["Diff"] column to give a reflection of working days.
Because the 01-01-19 is a bank holiday i would want to minus that from the Diff to end up with results like below
Let me know if you require any more info.
Here's an option:
SELECT query1.due_date
, query1.received_date
, query1.diff
, queryhol.count
, COALESCE(query1.diff - queryhol.count, query1.diff) as DiffCount
FROM Query1
OUTER APPLY(
SELECT COUNT(*) AS count
FROM QueryHol
WHERE QueryHol.Date <= Query1.Received_Date
AND QueryHol.Date >= Query1.Due_Date
) AS queryhol
You may need to play around with the join condition - as it is assumes that the Received_Date is always later than the Due_Date which there is not enough data to know all of the use cases.
If I understand your problem, I think this is a possible solution:
select due_date,
receive_date,
diff,
(select sum(table2.count)
from table2
where table2.due_date between table1.due_date and table1.due_date) sum_holi,
table1.diff - (select sum(table2.count)
from table2
where table2.date between table1.due_date and table2.due_date) diff_holi
from table1
where [...] --here your conditions over table1.

how to get multiple min values from two SQL tables?

I have two tables, a Members table and a Plan table. They are structured as follows.
member start_date Mplan Pplan version start_dt end_dt
John 20120701 johnplan johnplan 1 20120601 20130531
John 20130201 johnplan johnplan 2 20130601 20140531
John 20130901 johnplan
John 20131201 johnplan
I need to update the start_date on the Members table to be the minimum value present for that member but within the same Plan version.
Example:
20130201 would be changed to 20120701 and 20131201 would change to 20130901.
Code:
UPDATE Members
SET start_date =(
SELECT MIN(start_date) FROM Members a
LEFT JOIN Plan ON Mplan = Pplan AND
start_date BETWEEN start_dt AND end_dt
WHERE member=a.member
AND start_date BETWEEN start_dt AND end_dt
)
Unfortunately this sets every single start_date to 19900101 aka the lowest value in the entire table for that column.
First you need to get the minimum start date of each member for a specific plan. The following will provide you that.
select MIN(start_date) as min_date,a.member as member_name,a.Mplan as plan_name FROM Members a inner JOIN [plan] p ON a.Mplan = p.Pplan AND
start_date BETWEEN p.start_dt AND p.end_dt
group by a.member, a.Mplan
The result will be something like this.
min_date member_name plan_name
2012-07-01 00:00:00.000 John johnplan1
2013-09-01 00:00:00.000 John johnplan2
Use this to update each member's start date for a plan with the lowest start date of the respective plan.
update members
set start_date= tbl.min_date from
(SELECT MIN(start_date) as min_date,a.member as member_name,a.Mplan as plan_name FROM Members a
inner JOIN [plan] p ON a.Mplan = p.Pplan AND
start_date BETWEEN p.start_dt AND p.end_dt
group by a.member, a.Mplan) as tbl
where member=tbl.member_name and Mplan=tbl.plan_name
I created your 2 tables, members and plan, and tested this solution with sample data and it works. I hope it helps.
You really need to convert the dates to Datetime. You will have a greater precision, the possibility to store hours, days and minutes as well as access to date specific functions, international conversion and localization.
If your column is a Varchar(8), then it uses no less space than a Datetime column.
That said, what you are looking for is row_number().
Something like:
SELECT Member, MPlan, Start_Date, Row_Number() OVER (PARTITION BY Member, MPLan ORDER BY Start_Date) as Version
FROM Members
Could you try this ? I didn't test it.
With Member_start_dt as
(
select *, (select start_dt from Pplan where M.start_date <= start_dt AND M.start_date >= end_dt) as Pplan_date
from Members M
),
Member_by_plan as
(
select *, ROW_NUMBER () over (partition by Pplan_date order by start_date) num
from Member_start_dt
)
update M
Set M.start_date = MBP1.start_date
from Members M
inner join Member_by_plan MBP1 ON MBP1.member = M.Member AND num = 1
inner join Member_by_plan MBP2 ON MBP2.member = M.Member AND MBP2.Pplan_date = MBP1.Pplan_date AND MBP2.start_date = M.start_date

Loop through month in SQL Server

Every year we have 12 month. I should write a query that select in one table for every month. For example I should make report that show me every month transaction count.
I did it but in wrong way.
I wrote 12 query for every month.
Like this :
SET #MONTH12M = (SELECT SUM(Amount) AS TOT
FROM [fidilio].[dbo].[CardTransactionLog] CL
JOIN CardTransaction CT ON CT.CardTransactionLogId = CL.CardTransactionLogId
WHERE (cl.TransactionPersianTimeStamp > N'1393/12/01'
AND cl.TransactionPersianTimeStamp< N'1393/12/31')
)
INSERT INTO #TEMP(MonthValue, CountValue, TypeValue)
SELECT
12,
CASE WHEN #MONTH12M IS NULL THEN 0 ELSE #MONTH12M END,4
I have 11 more query like that.
Finally I fetch my result I put in temp table .
How can I do this dynamically?
How can I do it with loop ?
You can use group by to generate statistics per month:
select month(date_column)
, sum(amount)
from YourTable
group by
month(date_column)
The T-SQL function month extracts the numeric month from a datetime column.

Printing the current value and previous value between the date range

I have a sample data like this
ID DATE TIME STATUS
---------------------------------------------
A 01-01-2000 0900 ACTIVE
A 05-02-2000 1000 INACTIVE
A 01-07-2000 1300 ACTIVE
B 01-05-2005 1000 ACTIVE
B 01-08-2007 1050 ACTIVE
C 01-01-2010 0900 ACTIVE
C 01-07-2010 1900 INACTIVE
From the above data set, if we only focus on ID='A' we note that A was initally active, then became inactive on 05-02-2000 and then it was inactive until 01-07-2000.
Which means that A was inactive from 05-Feb-2000 to 01-July-2000.
My questions are:
if I execute a query with (ID=A, Date=01-04-2000) it should give me
A 05-02-2000 1000 INACTIVE
because since that date is not available in that data set, it should search for the previous one and print that
Also, if my condition is (ID=A, Date=01-07-2000) it should not only print the value which is present in the table, but also print a previous value
A 05-02-2000 1000 INACTIVE
A 01-07-2000 1300 ACTIVE
I would really appreciate if any one can assist me solve this query. I am trying my best to solve this.
Thank you every one.
Any take on this?
Afaq
Something like the following should work:
SELECT ID, Date, Time, Status
from (select ID, Date, Time, Status, row_number() over (order by Date) Ranking
from MyTable
where ID = #SearchId
and Date <= #SearchDate) xx
where Ranking < 3
order by Date, Time
This will return at most two rows. Its not clear if you are using Date and Time datatyped columns, or if you are actually using reserved words as column names, so you'll have to fuss with that. (I left out Time, but you could easily add that to the various orderings and filterings.)
Given the revised criteria, it gets a bit trickier, as the inclusion or exclusion of a row depends upon the value returned in a different row. Here, the “second” row, if there are two or more rows, is included only if the “first” row equals a particular value. The standard way to do this is to query the data to get the max value, then query it again while referencing the result of the first set.
However, you can do a lot of screwy things with row_number. Work on this:
SELECT ID, Date, Time, Status
from (select
ID, Date, Time, Status
,row_number() over (partition by case when Date = #SearchDate then 0 else 1 end
order by case when Date = #SearchDate then 0 else 1 end
,Date) Ranking
from MyTable
where ID = #SearchId
and Date <= #SearchDate) xx
where Ranking = 1
order by Date, Time
You'll have to resolve the date/time issue, since this only works against dates.
Basically you need to pull a row if, for the specified date, it is:
1) the last record, or
2) the last inactive record.
And the two conditions may match the same row as well as two distinct rows.
Here's how this logic could be implemented in SQL Server 2005+:
WITH ranked AS (
SELECT
ID,
Date,
Time,
Status,
RankOverall = ROW_NUMBER() OVER ( ORDER BY Date DESC),
RankByStatus = ROW_NUMBER() OVER (PARTITION BY Status ORDER BY Date DESC)
FROM Activity
WHERE ID = #ID
AND Date <= #Date
)
SELECT
ID,
Date,
Time,
Status,
FROM ranked
WHERE RankOverall = 1
OR Status = 'INACTIVE' AND RankByStatus = 1

Resources