Case Statement using Dates - sql

I have a data set of clients. Each have a Start date and an End Date. A client can have multiple lines with different Start dates and End dates. I have another data set with Claims info and i want to know if they had claims during the time frame there state and end date.
how can i write this?
SELECT
M.[ID]
,EN.StartDate
,EN.EndDate
,[Has Cliams History] (Column to identify if yes or no)
FROM [Test].[dbo].[tblClients] M
left join [test].[dbo].[tblCliams] EN on EN.ID = M.ID

To illustrate the use of a case statement, the ClaimDate field represents the date of a claim. The query would look something like this.
SELECT
M.[ID]
,EN.StartDate
,EN.EndDate
,CASE
WHEN ClaimDate between EN.StartDate AND EN.EndDate THEN 'yes'
ELSE 'no'
END [Has Cliams History]
FROM [Test].[dbo].[tblClients] M
left join [test].[dbo].[tblClaims] EN on EN.ID = M.ID

Your question needs to provide more clarity on the definition of both tables and the relationship between the two. I am guessing you aren't actually joining a column called ID from both (at least I hope not). Here is a stab at it based on the assumption you have a ClientID on the tblClaims and also that you support ongoing clients via a NULL EndDate. Also assuming there are Start and End dates on tblClaims.
SELECT M.[ID]
, CASE WHEN MAX(claim.ID) IS NOT NULL THEN 1 ELSE 0 END AS HasClaimsHistory
FROM [Test].[dbo].[tblClients] client
LEFT JOIN [Test].[dbo].[tblCliams] claim on client.ID = claim.ClientID
AND claim.StartDate >= client.StartDate
AND (
client.EndDate IS NULL /* ongoing support? */
OR
claim.EndDate <= client.EndDate
)
GROUP BY M.[ID]
, CASE WHEN MAX(claim.ID) IS NOT NULL THEN 1 ELSE 0 END

Related

How do I return the next follow on record based on dates in a SQL Server query?

As part of my vessel hire database I have a view that returns a selection of information about the vessels we have and also their fixture commitments. In our system, any vessel on fixture has that commitment flagged as "Live" and any fixture commitment for that vessel which comes AFTER the offhire date will be flagged as "FollowOn" (The live and follow on flagging is performed by the user on the front end).
The problem
A vessel can have multiple FollowOn commitments and so I need to find a way to return only the next commitment flagged as FollowOn after the offhire date of the current Live commitment.
The Query (So far)
SELECT
uve.[Id],
uve.[ViewId],
uve.[VesselId],
ve.[Name],
/***********************
#Fixture (Current)
***********************/
CAST((CASE WHEN fix1.[Id] IS NULL THEN 0 ELSE fix1.[Id] END) AS INT) AS [FixtureId],
fix1.[Date],
fix1.[FirmUntil],
fix1.[Charterer],
ch1.[ChartererName],
/***********************
#Fixture (Next)
***********************/
fix2.FollowOn,
fix2.Id AS NextFixtureId,
fix2.[Date] AS NextOnhire,
CAST((CASE WHEN fix2.FollowOn IS NULL THEN 0 ELSE fix2.FollowOn END) AS BIT) AS FollowOn,
fix2.[FirmUntil] AS NextOffhire,
ch2.ChartererName AS NextChartererName,
CAST((CASE WHEN fix1.[Live] IS NULL THEN 0 ELSE fix1.[Live] END) AS BIT) AS Live
FROM UserVessels AS uve
LEFT JOIN Vessel AS ve ON ve.Id = uve.VesselId
LEFT JOIN VesselTypes AS vt ON vt.[Id] = ve.[Type]
LEFT JOIN (SELECT [Id], [Date], [FirmUntil], [PeriodAmount], [PeriodFrequency], [Charterer], [Port], [Live],[FollowOn], [Broker], [WorkRole], [VesselId]
FROM Fixture
WHERE Live = '1' AND FollowOn = '0') AS fix1 ON fix1.[VesselId] = ve.Id
LEFT JOIN (SELECT [Id],[Date],[Live],[FirmUntil],[FollowOn], [Charterer], [Port], [WorkRole], [VesselId]
FROM Fixture AS f2
WHERE f2.[FollowOn] = '1') AS fix2 on fix2.[VesselId] = ve.Id
LEFT JOIN Charterer AS ch1 ON ch1.Id = fix1.Charterer
LEFT JOIN Charterer AS ch2 ON ch2.Id = fix2.Charterer
As you can see, the heavy lifting for what I'm trying to achieve happens in the join where I have a subquery looking at the FollowOn flag. This isn't ideal because if there is more than one fixture with followon then you get lots of rows. I need only the immediate next followon after the offhire of "fix1" so that I can keep each vessel as a single row and was wondering how that can be achieved?
So if you've got a list of all of the follow on fixtures, as alluded to in the comments you can use a row_number window function to get the next one:
LEFT JOIN (SELECT [Id],[Date],[Live],[FirmUntil],[FollowOn], [Charterer], [Port], [WorkRole], [VesselId], ROW_NUMBER() OVER (PARTITION BY VesselId ORDER BY Date) [RNum]
FROM Fixture AS f2
WHERE f2.[FollowOn] = '1') AS fix2 on fix2.[VesselId] = ve.Id AND fix2.RNum = 1
However the outer apply might be quicker. You'd need to test it.

Generating missing report submissions via reference table

I have a SQL query i'm currently working on which i would greatly appreciate some help with.
Here is a simplified version of the view I've been given to work on:
SELECT a.Organisation_Name
,a.Org_Id
,b.Activity_month
,SUM(b.Activity_Plan) 'Plan_Activity'
,SUM(b.Activity_Actual) 'Actual_Activity'
,SUM(b.Price_Actual) 'Actual_Price'
,SUM(b.Price_Plan) 'Plan_Price'
,COUNT(b.Instances) AS 'Record_Count'
,CASE WHEN COUNT(b.Instances) > 0 THEN 'Yes' ELSE 'No' END AS Submitted
FROM [ExampleDatabase].[dbo].[Organisation_Reference] a
LEFT JOIN [ExampleDatabase].[dbo].[Report_Submissions] b
ON a.Org_Id = b.Org_Id
AND ([Exmaple_Code] LIKE ('X') or [Example_Code] = 'X')
WHERE a.Category_Flag = 1
AND a.Example_Code in ('X','X','X','X','X')
GROUP BY
a.Organisation_Name
,a.Org_Id
,b.Activity_month
--
The Activity Month field is an Integer rather than a date, currently ranging from 1-8.
The problem i am facing is that within the [Report_Submissions] table, it only contains organisations which have actually submitted the reports, whereas the
[Organisation_Reference] table lists all the organisations which should be submitting.
Where the organisations have submitted the reports, the data is perfect and gives me a run down of all the details i need for each individual month.
Obviously if an organisation hasn't submitted then this detail wouldn't be available, but i do need to have a complete list of all organisations listed from the reference table for each individual month and whether they have submitted the reports or not.
At the moment where the 'Submitted' field = 'No' it's only bringing back one record for each organisation that has never submitted (With Activity_month coming through as null) and if an organisation has only submitted once or twice then it will include those submissions but still be missing the rest of the months from the result set.
I've tried various different joins etc. but I seem to be drawing a blank for a solution. Is there a way of generating this information within the script? Any advice would be great!
Kind Regards,
Mark
Since you just need numbers 1-8, using a subquery in your join to cross apply(values ()) to your Organisation_Reference table works well and does not make the query much more compliCated to read.
select
a.Organisation_Name
, a.Org_Id
, a.Activity_Month
, sum(b.Activity_Plan) 'Plan_Activity'
, sum(b.Activity_Actual) 'Actual_Activity'
, sum(b.Price_Actual) 'Actual_Price'
, sum(b.Price_Plan) 'Plan_Price'
, count(b.Instances) as 'record_count'
, case when count(b.Instances) > 0 then 'yes' else 'no' end as Submitted
from (
select o.*, t.Activity_Month
from [ExampleDatabase].[dbo].[Organisation_Reference] as o
cross apply (values (1),(2),(3),(4),(5),(6),(7),(8)) t(Activity_Month)
) as a
left join [ExampleDatabase].[dbo].[Report_Submissions] b
on a.Org_Id = b.Org_Id
and a.Activity_Month = b.Activity_Month
and ([exmaple_Code] like ('X') or [Example_Code] = 'X')
where a.Category_Flag = 1
and a.Example_Code in ('X','X','X','X','X')
group by
a.Organisation_Name
, a.Org_Id
, b.Activity_Month
You could also cross join with a numbers/tally table, or use a common table expression to generate the range of numbers you need. I would recommend either of those options as well, especially if your logic was more compliCated.
If Report_Submissions contains all of the months you want in your query, you could cross join the distinct Activity_Months from that table to your Organisation_Reference table.
select
a.Organisation_Name
, a.Org_Id
, a.Activity_Month
, sum(b.Activity_Plan) 'Plan_Activity'
, sum(b.Activity_Actual) 'Actual_Activity'
, sum(b.Price_Actual) 'Actual_Price'
, sum(b.Price_Plan) 'Plan_Price'
, count(b.Instances) as 'record_count'
, case when count(b.Instances) > 0 then 'yes' else 'no' end as Submitted
from (
select o.*, t.Activity_Month
from [ExampleDatabase].[dbo].[Organisation_Reference] as o
cross join (select distinct Activity_Month from Report_Submissions) t
) as a
left join [ExampleDatabase].[dbo].[Report_Submissions] b
on a.Org_Id = b.Org_Id
and a.Activity_Month = b.Activity_Month
and ([exmaple_Code] like ('X') or [Example_Code] = 'X')
where a.Category_Flag = 1
and a.Example_Code in ('X','X','X','X','X')
group by
a.Organisation_Name
, a.Org_Id
, b.Activity_Month

SQL Statement Needed - results from a range but not within another range

I have two tables.
A MEMBERS table that lists all of the details about a member and
a second table that stores a historical record of all of the changes to each column
in the MEMBERS table. For example, if a member's status changed from Active to Frozen on June 1, the MEMBERCHANGES tables would have an entry stating that change.
I need to create a SQL Statement that lists all of the MEMBER records that are in a status of Freeze, who changed to the status in a date range but that there aren't any changes in a second date range.
For example, I need all member who changed to a status of FREEZE between Jan 1 and May 1 but who haven't had any changes to the status field from May 2 through Aug 31.
SELECT MEMBERS.scancode
,MEMBERS.fname
,MEMBERS.lname
,MEMBERTYPES.description
,MEMBERS.STATUS
,MEMBERS.email
,MEMBERS.datejoin
,MEMBERS.dateexpire
,MEMBERS.daterenewal
,MEMBERCHANGES.datechange
,MEMBERCHANGES.newvalue
FROM MEMBERS
INNER JOIN MEMBERCHANGES ON MEMBERS.memid = MEMBERCHANGES.memid
INNER JOIN MEMBERTYPES ON MEMBERS.mtypeid = MEMBERTYPES.mtypeid
AND NOT EXISTS (
SELECT MEMBERS.memid
FROM MEMBERS AS MEM2
INNER JOIN MEMBERCHANGES ON MEM2.memid = MEMBERCHANGES.memid
INNER JOIN MEMBERTYPES ON MEM2.mtypeid = MEMBERTYPES.mtypeid
WHERE (MEMBERCHANGES.columnname = 'status')
AND (MEMBERCHANGES.newvalue = 'F')
AND (
MEMBERCHANGES.datechange BETWEEN '2015-05-02'
AND '2015-08-31'
)
AND (MEM2.STATUS = 'F')
)
WHERE (MEMBERCHANGES.columnname = 'status')
AND (
MEMBERCHANGES.datechange BETWEEN '2015-01-01'
AND '2015-05-01'
)
AND (MEMBERS.STATUS = 'F')
AND (MEMBERCHANGES.newvalue = 'F')
I cleaned up your query with aliases and removed some things I think were redundant. Forgive me for changing to lowercase as well.
select ...
from
MEMBERS m
inner join MEMBERCHANGES on mc.memid = m.memid
inner join MEMBERTYPES on mt.mtypeid = m.mtypeid
where
/* current status is Freeze */
m.STATUS = 'F'
/* changed to Freeze between certain dates */
and mc.columnname = 'status'
and mc.datechange between '2015-01-01' and '2015-05-01'
and mc.newvalue = 'F'
/* and no changes between later dates */
and not exist (
select 1
from MEMBERCHANGES mc2
where
mc2.memid = mc.memid
and mc2.columnname = 'status'
and mc2.datechange between '2015-05-02' and '2015-08-31'
and mc2.newvalue = 'F'
)
Since current date is well past August 31 I'm wondering if you'll have a problem checking against the current status which could have changed in the last month and a half. I trust that you know the data you're dealing with.
It's also certainly possible that the status could have changed to Freeze during the first window but later changed to something else during the same window. And then its current status could be Freeze because of a change between September 1 and present date. I don't know if you need to check for that kind of thing but this query may not guarantee anything about the member status at the end of May 1st.

Joining a derived table postgres

I have 4 tables:
Competencies: a list of obviously competencies, static and a library
Competency Levels: refers to an associated group of competencies and has a number of competencies I am testing for
call_competency: a list of all 'calls' that have recorded the specified competency
competency_review_status: proving whether each call_competency was reviewed
Now I am trying to write this query to count a total and spit out the competency, id and whether a user has reached the limit. Everything works except for when I add the user. I am not sure what I am doing wrong, once I limit call competency by user in the where clause, I get a small subset that ONLY exists in call_competency returned when I want the entire list of competencies.
The competencies not reached should be false, ones recorded appropriate number true. A FULL list from the competency table.
I added the derived table, not sure if this is right, obviously it doesn't run properly, not sure what I'm doing wrong and I'm wasting time. Any help much appreciated.
SELECT comp.id, comp.shortname, comp.description,
CASE WHEN sum(CASE WHEN crs.grade = 'Pass' THEN 1 ELSE CASE WHEN crs.grade = 'Fail' THEN -1 ELSE 0 END END) >= comp_l.competency_break_level
THEN TRUE ELSE FALSE END
FROM competencies comp
INNER JOIN competency_levels comp_l ON comp_l.competency_group = comp.competency_group
LEFT OUTER JOIN (
SELECT competency_id
FROM call_competency
WHERE call_competency.user_id IN (
SELECT users.id FROM users WHERE email= _studentemail
)
) call_c ON call_c.competency_id = comp.id
LEFT OUTER JOIN competency_review_status crs ON crs.id = call_competency.review_status_id
GROUP BY comp.id, comp.shortname, comp.description, comp_l.competency_break_level
ORDER BY comp.id;
(Shooting from the hip, no installation to test)
It looks like the below should do the trick. You apparently had some of the joins mixed up, with a column from a relation that was not referenced. Also, the CASE statement in the main query could be much cleaner.
SELECT comp.id, comp.shortname, comp.description,
(sum(CASE WHEN crs.grade = 'Pass' THEN 1 WHEN crs.grade = 'Fail' THEN -1 ELSE 0 END) >= comp_l.competency_break_level) AS reached_limit
FROM competencies comp
JOIN competency_levels comp_l USING (competency_group)
LEFT JOIN (
SELECT competency_id, review_status_id
FROM call_competency
JOIN users ON id = user_id
WHERE email = _studentemail
) call_c ON call_c.competency_id = comp.id
LEFT JOIN competency_review_status crs ON crs.id = call_c.review_status_id
GROUP BY comp.id, comp.shortname, comp.description
ORDER BY comp.id;

Extracting null fields in SQL Server

I will try to be as detailed as possible. Our corporate controller has asked me for some information regarding our suppliers. Here are the tables:
spp = supplier table, each supplier has one record, there are 5,222 records
ast = supplier account profile, there is a (M, 1) relationship between this table and spp, there are 8,950 records in this table. Each duplicate spp_id has a different atp_id which is a transaction profile.
crt = bank account information, a supplier may or may not have bank account info
xvd = table of checking tables, xvd.xcd_id is the field that holds the checking table id. Checking table 0007 is the table that contains the discount info.
Here is my script:
select spp.spp_id supp_num,
spp.spp_matchname supp_name,
case when spp.spp_ddcalculation = 0
then 'End of Month'
else
case when spp.spp_ddcalculation = 1
then 'Net'
else
case when spp.spp_ddcalculation = 2
then 'End of 10, 20, 30'
else
case when spp.spp_ddcalculation = 3
then 'End of 15 or 30'
else null
end
end
end
end calculation1,
convert(varchar(2), spp.spp_ddduration) + case when spp.spp_ddmd = 0
then ' Days'
else case when spp.spp_ddmd = 1
then ' Months'
else null
end
end duration1,
spp.spp_ddday stop_day1,
xvd.xvd_desc discount,
crt.crt_name bank,
case when ast.ast_ddcalculation = 0
then 'End of Month'
else
case when ast.ast_ddcalculation = 1
then 'Net'
else
case when ast.ast_ddcalculation = 2
then 'End of 10, 20, 30'
else case when ast.ast_ddcalculation = 3
then 'End of 15 or 30'
else null
end
end
end
end
calculation2,
convert(varchar(2), ast.ast_ddduration) + case when ast.ast_ddmd = 0
then ' Days'
else case when ast.ast_ddmd = 1
then ' Months'
else null
end
end
duration2,
ast.ast_ddday stop_day2
from spp left join ast on spp.spp_id = ast.spp_id
left join crt on ast.crt_id = crt.crt_id
inner join xvd on ast.cfd_id = xvd.xcv_id
where xvd.xcd_id = '0007'
and xvd.lng_id = 0
order by spp.spp_id
The problem is that there are 371 records in the ast table that have a non null cfd_id which is the field that relates to the discount in checking table 0007. When I run this I get 371 records, but I need all suppliers, even those with null discounts. I know the problem is a combination of my joins and the fact that there is not a null xcv_id in checking table 0007. Can anyone see anything glaring that I have overlooked?
To recap, there are 8,950 records in ast, but only 371 of them have a non null cfd_id. I need to grab all 8,950 records, I can't seem to extract the null discounts. I think I can probably pull everything into a temp table then grab the discounts, but am wondering if there is a way to do this in one select statement.
Thanks
Tony
Edit: The last line of my from statement seems to be the primary issue
inner join xvd on ast.cfd_id = xvd.xcv_id
There are no null xcv_id but there are null cfd_id. Is there another way to join those two tables, besides checking for equality?
Forgot to mention, we are on SQL Server 2008 R2.
Does this solve the problem ?
FROM spp
LEFT JOIN ast
ON spp.spp_id = ast.spp_id
LEFT JOIN crt
ON ast.crt_id = crt.crt_id
INNER JOIN xvd
ON xvd.xcv_id = ast.cfd_id
WHERE xvd.xcd_id = '0007'
AND xvd.lng_id = 0
I think you can just change your inner join to a left join:
from spp left join ast on spp.spp_id = ast.spp_id
left join crt on ast.crt_id = crt.crt_id
inner join xvd on ast.cfd_id = xvd.xcv_id
to
from spp left join ast on spp.spp_id = ast.spp_id
left join crt on ast.crt_id = crt.crt_id
left join xvd on ast.cfd_id = xvd.xcv_id
If you are saying that you want to select records where xvd.xcd_id is 0007 or null then change your where clause to this:
(xvd.xcd_id = '0007' OR xvd.xcd_id is null)
This sounds like a perfect use for views. Instead of trying to build one complex query, you could build a series of views that build upon one another filtering the data the way you want it... then apply the final query to the last view.