SQL Query construct - sql-server

I have three tables. I want to get data from all those tables and put it in a virtual table. i am using SQL Server 2012.
Sorry if my format or tags are wrong because I m getting error Stack overflow requires external javascrip from another source domain, which is blocked of failed to load.
Booking Table
BookingId | date
======================
2 | 7/1/2017 (MM/dd/yyyy)
3 | 7/1/2017
BookingCost Table
Id | bookinId | Cost
==========================
1 | 2 | 2000
2 | 3 | 4000
Expense Table
Id | ExpenseCost | Date
======================
1 | 1400 | 7/2/2017 (MM/dd/yyyy)
2 | 1422 | 7/1/2017
3 | 4000 | 6/3/2017
I want to get Monthly result like following Table.
Date | Expense | Bookings
===================================
jan/2017 | 0 | 0
feb/2017 | 0 | 0
. | . | .
. | . | .
. | . | .
jun/2017 | 4000 | 0
jul/2017 | 2822 | 6000
. | . | .
. | . | .
. | . | .

How is something like this (assuming your dates are DATE types and not VARCHAR - otherwise you could convert them).
SELECT COALESCE(EXPENSE.MONTH, BOOKINGS.MONTH) [Date], EXPENSE.Cost Expense, BOOKINGS.Cost Bookings
FROM (
SELECT DATEADD(DD,1-DAY([date]),[date]) MONTH, SUM(Cost) Cost
FROM Booking
INNER JOIN BookingCost
ON Booking.BookingID = BookingCost.BookingID
GROUP BY DATEADD(DD,1-DAY([date]),[date])
) BOOKINGS
FULL JOIN (
SELECT DATEADD(DD,1-DAY([date]),[date]) MONTH, SUM(ExpenseCost) Cost
FROM Expense
GROUP BY DATEADD(DD,1-DAY([date]),[date])
) EXPENSE
ON EXPENSE.MONTH = BOOKINGS.MONTH
ORDER BY 1

To also get the 0 counts, you could left join the totals to a tally table which has all the months for the year.
The sql is using FORMAT to transform the Date
For example:
;WITH MONTHS AS
(
select
[Year], [Month],
format(datefromparts([Year],[Month],1),'MMM/yyyy') as [MonthYear]
from (values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)) m([Month])
cross join (values (2017)) y([Year])
)
select
m.[MonthYear] as [Date],
coalesce(e.TotalExpense,0) as Expense,
coalesce(bc.TotalCost,0) as Bookings
from MONTHS m
left join (
select
datepart(year,[Date]) as [Year],
datepart(month,[Date]) as [Month],
sum(ExpenseCost) as TotalExpense
from Expense
where datepart(year,[Date]) in (select distinct [Year] from MONTHS)
group by datepart(year,[Date]), datepart(month,[Date])
)e on (e.[Year] = m.[Year] and e.[Month] = m.[Month])
left join (
select
datepart(year,b.[date]) as [Year],
datepart(month,b.[date]) as [Month],
sum(c.Cost) as TotalCost
from Booking b
join BookingCost c on c.BookingId = b.BookingId
where datepart(year,b.[date]) in (select distinct [Year] from MONTHS)
group by datepart(year,b.[date]), datepart(month,b.[date])
) bc
on (bc.[Year] = m.[Year] and bc.[Month] = m.[Month])
order by m.[Year], m.[Month];
Test data I used
declare #Booking table (BookingId int, [date] date);
insert into #Booking (BookingId,[date]) values (2,'2017-07-01'),(3,'2017-07-01');
declare #BookingCost table (Id int, BookingId int, Cost int);
insert into #BookingCost (Id, BookingId, Cost) values (1,2,2000),(2,3,4000);
declare #Expense table (Id int, ExpenseCost int, [Date] date);
insert into #Expense (Id, ExpenseCost, [Date]) values
(1,1400,'2017-07-02'),(2,1422,'2017-07-01'),(3,4000,'2017-06-03');

Related

How can I take the sum of only the max values?

I need to take the max cost of each tracking number (TN) and then sum those values grouped by the OrderNo.
Here's a table:
+----+-----+-------+
|TNo |cost| OrderNo|
+----+-----+-------+
| 1 | 5 | 12 |
| 1 | 4 | 12 |
| 2 | 6 | 12 |
| 2 | 3 | 12 |
| 3 | 3 | 15 |
| 4 | 2 | 15 |
| 4 | 3 | 15 |
+----+-----+-------+
Here's what I want my results to be:
+--------+-----+
| OrderNo| Sum |
+--------+-----+
| 12 | 11 | (6+5)
| 15 | 6 | (3+3)
+--------+-----+
This is what I have so far, but this sums the max but for all instances of the Tracking No. For example, in the above table, for Order# 12, it would sum 5+5+6+6. I only want to sum the max values (5+6).
SELECT ol.OrderNo, SUM(t.maxCost)
FROM (
SELECT
ol.TrackingNumber, MAX(ol.Cost) maxCost
FROM OzLink ol GROUP BY ol.TrackingNumber) t
JOIN OzLink ol ON ol.TrackingNumber=t.TrackingNumber
GROUP BY ol.OrderNo
**Also, I'm new to this work and asking questions on stackoverflow so feedback on how I asked this question would be appreciated!
you could do it like this:
SELECT ol.OrderNo, SUM(ol.maxCost)
FROM (
SELECT
ol.TrackingNumber, MAX(ol.Cost) maxCost, ol.OrderNo
FROM OzLink ol GROUP BY ol.TrackingNumber,ol.OrderNo) ol
GROUP BY ol.OrderNo
You can benefit from cte like below:
CREATE TABLE mytab
(
TNo INT,
Cost INT,
OrderNo INT
)
insert into mytab values (1,5,12)
insert into mytab values (1,4,12)
insert into mytab values (2,6,12)
insert into mytab values (2,3,12)
insert into mytab values (3,3,13)
insert into mytab values (4,2,13)
insert into mytab values (4,3,13)
;with cte (TNo,OrderNo,maxcost) as (
select TNo,OrderNo,Max(Cost) as maxcost
from mytab
group by TNo, OrderNo
)
select OrderNo,SUM(maxcost)
from cte
group by OrderNo
There is a few ways, like the answers below. But you can also use the below query, and create a Row number based on OrderNo and TN and Order by the Cost DESC in the Subquery and then only return the highest cost.
SELECT OrderNo,
SUM(Cost) As Cost
FROM
(
SELECT ROW_NUMBER() OVER(PARTITION BY OrderNo, TN ORDER BY Cost DESC) AS HighestCost,
Cost,
OrderNo,
TN
FROM TableName
) AS Data
WHERE HighestCost = 1
GROUP BY OrderNo
Same as another answer
declare #T TABLE (TNo INT, Cost INT, OrderNo INT);
insert into #T values (1,5,12), (1,4,12), (2,6,12), (2,3,12), (3,3,15), (4,2,15), (4,3,15);
select t.OrderNo, sum(t.cost)
from ( select OrderNo, cost
, ROW_NUMBER() over (partition by TNo, OrderNo order by cost desc) as rn
from #T
) t
where t.rn = 1
group by t.OrderNo;
OrderNo
----------- -----------
12 11
15 6

T-SQL - Can I force ROW_NUMBER() OVER (PARTITION BY... to number records from a set point?

I’ve got a table containing a list of patient appointments: the clinic they attended, and the date of their attendance.
I’m trying to write a query that gives me the following:
‘Which patients attended clinic ‘123-45’ at any point during the period April 2016 – March 2017, and what were the subsequent 2 appointments (the appointment date and clinic attended) for that patient’?
I’ve tried to come at this by first querying out the list of patient ID numbers for all those patients that attended clinic ‘123-45’ during the time frame, and then putting this list of Patient IDs into a WHERE clause and using ROW_NUMBER() OVER (PARTITION BY… to give me an ordered list of all appointments for each patient during the 12 month period.
SELECT
x.Patient_Id
,x.Clinic_Code
,x.Appointment_Date
,x.Row_No FROM
(
SELECT
Patient_Id
,Clinic_Code
,Appointment_Date
,ROW_NUMBER() OVER (PARTITION BY Patient_Id ORDER BY Patient_Id, Appointment_Date asc) [Row_No]
FROM
Appointments
WHERE
Appointment_Date BETWEEN '01/10/2016' AND '30/09/2017'
AND Patient_ID = 'BLO123'
) x
WHERE x.Row_No < 4
However, this has the unintended consequence of numbering any appointments that occurred prior to the clinic ‘123-45’ attendance.
So, if the following is my source:
Patient_ID | Clinic_Code | Appointment_Date
--------------------------------------------
BLO123 | QWE-QW | 01-04-2016
BLO123 | OPD-ZZ | 05-10-2016
BLO123 | 123-45 | 13-11-2016
BLO123 | 333-44 | 15-12-2016
BLO123 | 999-45 | 02-02-2017
BLO123 | 222-44 | 15-02-2017
BLO123 | 777-45 | 19-03-2017
What I'm trying to get is:
Patient_ID | Clinic_Code | Appointment_Date | Row_No
--------------------------------------------------------------
BLO123 | 123-45 | 13-11-2016 | 1
BLO123 | 333-44 | 15-12-2016 | 2
BLO123 | 999-45 | 02-02-2017 | 3
But by including the preceding appointments within the date range, I'm instead getting:
Patient_ID | Clinic_Code | Appointment_Date | Row_No
--------------------------------------------------------------
BLO123 | QWE-QW | 01-04-2016 | 1
BLO123 | OPD-ZZ | 05-10-2016 | 2
BLO123 | 123-45 | 13-11-2016 | 3
What I would like to query to do is to ignore any clinic appointments that precede the ‘123-45 attendance.
Please can anyone advise if it's possible to do this?
This approach uses a common table expression (CTE) to find the first appointment each patient has at clinic 123-45. The main body of the query returns all subsequent appointments.
Sample data:
DECLARE #Appointment TABLE
(
Patient_ID varchar(6),
Clinic_code varchar(6),
Appointment_Date date
)
;
INSERT INTO #Appointment
(
Patient_ID,
Clinic_code,
Appointment_Date
)
VALUES
('BLO123','QWE-QW','20160401'),
('BLO123','OPD-ZZ','20161005'),
('BLO123','123-45','20161113'),
('BLO123','333-44','20161215'),
('BLO123','999-45','20170202')
;
Query:
WITH
FirstAppointment AS
(
-- Find patients first vist to clinic 123-45.
SELECT
Patient_ID,
MIN(Appointment_Date) AS FirstAppointment_Date
FROM
#Appointment
WHERE
Appointment_Date >= '20160401'
AND Appointment_Date <= '20170331'
AND Clinic_code = '123-45'
GROUP BY
Patient_ID
)
SELECT
ROW_NUMBER() OVER (PARTITION BY a.Patient_ID ORDER BY a.Appointment_Date) AS Rn,
a.*
FROM
FirstAppointment AS fa
INNER JOIN #Appointment AS a ON a.Patient_ID = fa.Patient_ID
AND a.Appointment_Date >= fa.FirstAppointment_Date
;
with foo as
(
select
*
from (values
('BLO123','QWE-QW', cast('20160401' as date))
,('BLO123','OPD-ZZ',cast('20161005' as date))
,('BLO123','123-45',cast('20161113' as date))
,('BLO123','333-44',cast('20161215' as date))
,('BLO123','999-45',cast('20170202' as date))
) a(Patient_ID , Clinic_Code , Appointment_Date)
)
,lags as
(
select
*
,lag(Clinic_code,1) over (partition by Patient_id order by Appointment_Date) l1
,lag(Clinic_code,2) over (partition by Patient_id order by Appointment_Date) l2
,ROW_NUMBER() over (partition by Patient_id order by Appointment_Date) rn
from foo
)
select Patient_ID,Clinic_Code,Appointment_Date
,case when Clinic_Code='123-45' then 1
when l1='123-45' then 2
else 3 end Row_Nr
from lags
where '123-45' in (Clinic_Code,l1,l2)
The result:
+----------------------------------------------+
|Patient_ID|Clinic_Code|Appointment_Date|Row_No|
+----------------------------------------------+
|BLO123 |123-45 |2016-11-13 |1 |
|BLO123 |333-44 |2016-12-15 |2 |
|BLO123 |999-45 |2017-02-02 |3 |
+----------------------------------------------+

How to create data between two different dates with limited data

I am trying to create the data between two different dates.
Data in table looks like as shown below:
StartDate | EndDate | StudId | Active
-----------+---------------+-------------+-----------
01-01-2009 | 02-15-2009 | 12345 | Y
02-16-2009 | 03-15-2009 | 12345 | Y
03-16-2009 | 04-10-2009 | 12345 | N
04-11-2009 | 05-31-2009 | 12345 | Y
01-01-2009 | 02-15-2009 | 23642 | Y
02-16-2009 | 03-15-2009 | 23642 | Y
03-16-2009 | 04-10-2009 | 23642 | N
04-11-2009 | 05-31-2009 | 23642 | Y
and the data in table goes on with different Startdate, EndDate and StudID.
I am trying to get the result as shown below:
Startdate | StudID | Active
------------+----------+--------
01-01-2009 | 12345 | Y
01-02-2009 | 12345 | Y
01-03-2009 | 12345 | Y
01-04-2009 | 12345 | Y
. . .
. . .
02-15-2009 | 12345 | Y
02-16-2009 | 12345 | Y
As shown above I am trying to load the active data for student based on dates between Startdate and enddate.
We don't have any daily data using startdate and enddate we need to create daily data. If there is a gap between EndDate and next Startdate then the Active field should be '0' for those dates
Can someone suggest how to do this?
This requires calendar table and join
WITH calendar
AS (SELECT Min(StartDate) AS dates,
Max(EndDate) ed_date
FROM Yourtable
UNION ALL
SELECT Dateadd(dd, 1, dates),
ed_date
FROM calendar
WHERE dates < ed_date)
SELECT a.dates as Startdate,b.StudID,b.Active
FROM calendar a
JOIN Yourtable b
ON a.dates BETWEEN b.StartDate AND b.EndDate
ORDER BY dates
OPTION (maxrecursion 0)
Note: I have used Recursive CTE to generate dates. It is better to create physical calendar table and use it in queries like this
Live Demo
This should work.
declare #tmp date;
select #tmp = max(EndDate) from tmpTable;
print #tmp
;with cte as
(
select min(StartDate) over() as dd from tmpTable
union all select dateadd(day,1,dd) from cte where dd < #tmp
)
select distinct dd as StartDate, isnull(Studid, 12345), isnull(Active,0) as Active from tmpTable as t
right join cte as c on c.dd between t.startDate and t.enddate
where t.Studid = 12345 or t.studid is null
option (maxrecursion 0)

Attendance summarized report

Due to company policies I cannot give the actual query I am working with but heres the breakdown and general idea. We have an attendance register that records for each day if an employee was at work or not and where the employee works at. I am trying to make a summary of this to say between this and that date the employee worked 5 shifts. The problem I am sitting with is that one particular employee worked in workplace A for 2 days and was then transferred to workplace B. After a few days at workplace B the employee was then transferred back to workplace A.
My results to my attempt has showed that the employee begun working at workplace A from 1-Jan and ended at 10-Jan with only 2 working shifts. I have a group by on the working place and the begin and end dates are a min and max selection.
SELECT att.Employee, att.Workplace, dte.BeginDate, dte.EndDate, shf.WorkShift FROM
(SELECT * FROM Attendance WHERE WorkDate BETWEEN '1-Jan' AND '30-Jan') att
CROSS APPLY (SELECT COUNT(Shift) WorkShift FROM Attendance WHERE WorkDate BETWEEN '1-Jan' AND '30-Jan' AND Employee = att.Employee AND WorkPlace = att.WorkPlace AND Shift = 'Worked') shf
CROSS APPLY (SELECT MAX(WorkDate) BeginDate, MIN(WorkDate) EndDate FROM Attendance WHERE WorkDate BETWEEN '1-Jan' AND '30-Jan' AND Employee = att.Employee AND WorkPlace = att.WorkPlace) dte
So this employees records should appear like this (I am sorry for the very bad grid, I don't know how to make it look pretty, you are more than welcome to edit it to look better)
| Name | Workplace | beginDate | endDate | WorkShift |
| Jane | WorkPlaceA | 1-Jan | 2-Jan | 2 |
| Jane | WorkPlaceB | 3-Jan | 8-Jan | 5 |
| Jane | WorkPlaceA | 9-Jan | 10-Jan | 2 |
The attendance table looks something like this
| Name | Workplace | Date | Shift |
| Jane | WorkplaceA | 1-Jan | Worked |
| Jane | WorkplaceA | 2-Jan | Worked |
| Jane | WorkplaceB | 3-Jan | Worked |
| Jane | WorkplaceB | 4-Jan | Worked |
| Jane | WorkplaceB | 5-Jan | Worked |
| Jane | WorkplaceA | 6-Jan | Absent |
| Jane | WorkplaceA | 7-Jan | Absent |
| Jane | WorkplaceA | 8-Jan | Worked |
| Jane | WorkplaceB | 9-Jan | Worked |
| Jane | WorkplaceB | 10-Jan | Worked |
I believe you can accomplish this using CTE's. Here is a sample working code that shows your expected values.
;WITH CTE1 AS (
SELECT Employee, WorkPlace, TransactionDate,
ROW_NUMBER() OVER(PARTITION BY WorkPlace ORDER BY TransactionDate) AS WP,
ROW_NUMBER() OVER(ORDER BY TransactionDate) AS RN FROM Attendance WHERE Shift = 'Worked'),
CTE2 AS (SELECT Employee, WorkPlace, TransactionDate, WP, RN, WP-RN AS GB FROM CTE1),
CTE3 AS (SELECT Employee, WorkPlace, MIN(TransactionDate) AS TransactionDate, COUNT(1) AS Shifts FROM CTE2 GROUP BY Employee, WorkPlace, GB)
SELECT Employee, WorkPlace, TransactionDate AS [Start Date], DATEADD(DAY,Shifts - 1,TransactionDate) AS [End Date], Shifts FROM CTE3 ORDER BY TransactionDate ASC
I think your given output is wrong.
I think the way you are populating table is wrong.
Check my query,it can be further optmize,it do not count absent days
declare #t table(Name varchar(100),Workplace varchar(100), AttnDate date ,Shifts varchar(100))
insert into #t values
('Jane','WorkplaceA',' 1-Jan-16','Worked')
,('Jane','WorkplaceA',' 2-Jan-16','Worked')
,('Jane','WorkplaceB',' 3-Jan-16','Worked')
,('Jane','WorkplaceB',' 4-Jan-16','Worked')
,('Jane','WorkplaceB',' 5-Jan-16','Worked')
,('Jane','WorkplaceA',' 6-Jan-16','Absent')
,('Jane','WorkplaceA',' 7-Jan-16','Absent')
,('Jane','WorkplaceA',' 8-Jan-16','Worked')
,('Jane','WorkplaceB',' 9-Jan-16','Worked')
,('Jane','WorkplaceB','10-Jan-16','Worked')
DECLARE #Name VARCHAR(100) = 'Jane'
DECLARE #FromDate DATE = '01-Jan-16'
DECLARE #ToDate DATE = '31-Jan-16';
WITH CTE
AS (
SELECT *
,row_number() OVER (
ORDER BY attndate
) rn
FROM #t
WHERE NAME = #Name
AND (
AttnDate BETWEEN #FromDate
AND #ToDate
)
)
,CTE1
AS (
SELECT A.NAME
,A.workplace
,A.AttnDate
,Shifts
,rn
,1 RN1
FROM cte A
WHERE rn = 1
UNION ALL
SELECT a.NAME
,a.workplace
,a.AttnDate
,a.Shifts
,CASE
WHEN a.workplace = b.workplace
THEN b.rn
ELSE b.rn + 1
END rn
,RN1 + 1
FROM CTE A
INNER JOIN CTE1 b ON a.attndate > b.attndate
WHERE a.rn = RN1 + 1
)
,CTE2
AS (
SELECT NAME
,Workplace
,AttnDate beginDate
,(
SELECT max(AttnDate)
FROM CTE1 b
WHERE b.rn = a.rn
) endDate
,(
SELECT count(*)
FROM CTE1 b
WHERE b.rn = a.rn
AND Shifts = 'Worked'
) WorkShift
,rn
,ROW_NUMBER() OVER (
PARTITION BY rn ORDER BY rn
) rn3
FROM cte1 a
)
SELECT NAME
,workplace
,beginDate
,endDate
,WorkShift
FROM cte2
WHERE rn3 = 1

SQL statement - join based on date

I need to write a statement joining two tables based on dates.
Table 1 contains time recording entries.
+----+-----------+--------+---------------+
| ID | Date | UserID | DESC |
+----+-----------+--------+---------------+
| 1 | 1.10.2010 | 5 | did some work |
| 2 | 1.10.2011 | 5 | did more work |
| 3 | 1.10.2012 | 4 | me too |
| 4 | 1.11.2012 | 4 | me too |
+----+-----------+--------+---------------+
Table 2 contains the position of each user in the company. The ValidFrom date is the date at which the user has been or will be promoted.
+----+-----------+--------+------------+
| ID | ValidFrom | UserID | Pos |
+----+-----------+--------+------------+
| 1 | 1.10.2009 | 5 | PM |
| 2 | 1.5.2010 | 5 | Senior PM |
| 3 | 1.10.2010 | 4 | Consultant |
+----+-----------+--------+------------+
I need a query which outputs table one with one added column which is the position of the user at the time the entry has been made. (the Date column)
All date fileds are of type date.
I hope someone can help. I tried a lot but don't get it working.
Try this using a subselect in the where clause:
SQL Fiddle
MS SQL Server 2008 Schema Setup:
CREATE TABLE TimeRecord
(
ID INT,
[Date] Date,
UserID INT,
Description VARCHAR(50)
)
INSERT INTO TimeRecord
VALUES (1,'2010-01-10',5,'did some work'),
(2, '2011-01-10',5,'did more work'),
(3, '2012-01-10', 4, 'me too'),
(4, '2012-11-01',4,'me too')
CREATE TABLE UserPosition
(
ID Int,
ValidFrom Date,
UserId INT,
Pos VARCHAR(50)
)
INSERT INTO UserPosition
VALUES (1, '2009-01-10', 5, 'PM'),
(2, '2010-05-01', 5, 'Senior PM'),
(3, '2010-01-10', 4, 'Consultant ')
Query 1:
SELECT TR.ID,
TR.[Date],
TR.UserId,
TR.Description,
UP.Pos
FROM TimeRecord TR
INNER JOIN UserPosition UP
ON UP.UserId = TR.UserId
WHERE UP.ValidFrom = (SELECT MAX(ValidFrom)
FROM UserPosition UP2
WHERE UP2.UserId = UP.UserID AND
UP2.ValidFrom <= TR.[Date])
Results:
| ID | Date | UserId | Description | Pos |
|----|------------|--------|---------------|-------------|
| 1 | 2010-01-10 | 5 | did some work | PM |
| 2 | 2011-01-10 | 5 | did more work | Senior PM |
| 3 | 2012-01-10 | 4 | me too | Consultant |
| 4 | 2012-11-01 | 4 | me too | Consultant |
You can do it using OUTER APPLY:
SELECT ID, [Date], UserID, [DESC], x.Pos
FROM table1 AS t1
OUTER APPLY (
SELECT TOP 1 Pos
FROM table2 AS t2
WHERE t2.UserID = t1.UserID AND t2.ValidFrom <= t1.[Date]
ORDER BY t2.ValidFrom DESC) AS x(Pos)
For every row of table1 OUTER APPLY operation fetches all table2 rows of the same user that have a ValidFrom date that is older or the same as [Date]. These rows are sorted in descending order and the most recent of these is finally returned.
Note: If no match is found by the OUTER APPLY sub-query then a NULL value is returned, meaning that no valid position exists in table2 for the corresponding record in table1.
Demo here
This works by using a rank function and subquery. I tested it with some sample data.
select sub.ID,sub.Date,sub.UserID,sub.Description,sub.Position
from(
select rank() over(partition by t1.userID order by t2.validfrom desc)
as 'rank', t1.ID as'ID',t1.Date as'Date',t1.UserID as'UserID',t1.Descr
as'Description',t2.pos as'Position', t2.validfrom as 'validfrom'
from temployee t1 inner join jobs t2 on -- replace join tables with your own table names
t1.UserID=t2.UserID
) as sub
where rank=1
This query would work
select t1.*,t2.pos from Table1 t1 left outer join Table2 t2 on
t1.Date=t2.Date and t1.UserID=t2.UserID

Resources