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

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.

Related

Case Statement using Dates

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

SQL - Joining a list of dates with observations within a date range

I have a sample query below that uses GETDATE to pull the most recent estimates. The table has TWO date columns, effectiveDate and toDate. The problem? I want to pull a weekly value so I can have a time series of estimates. If I run the query now, I will end up with all the estimates as of today, but I also want to know what they were last week, the week before, etc.
Should I create a new table containing the dates that I want and then join them against the results of the query. This is where I am stuck. Thank you.
select GETDATE() as observeDate
, (select C.companyName from ciqCompany C where C.companyId = EP.companyId) as companyName
, (select EPT.periodTypeName from ciqEstimatePeriodType EPT where EPT.periodTypeId = EP.periodTypeId) as periodTypeName
, EP.fiscalYear
, EB.brokerName as brokerName
, EA.firstName+' '+EA.lastName as AnalystName
, EDND.tradingitemid
, (select DI.dataItemName from ciqdataitem DI where DI.dataitemid = EDND.dataitemid) as dataItemName
, (select EAS.accountingStandardDescription from dbo.ciqEstimateAccountingStd EAS where EAS.accountingStandardId = EDND.accountingStandardId) as AccountingStandard
, (select Cu.ISOCode from ciqCurrency Cu where Cu.currencyid = EDND.currencyid) as ISOCode
, (select EST.estimateScaleName from ciqEstimateScaleType EST where EST.estimateScaleId = EDND.estimateScaleId) as estimateScaleName
,EDND.dataItemValue,EDND.effectiveDate,EDND.isExcluded
from ciqEstimatePeriod EP
--- link estimate period table to detailed numeric data table
----------------------------------------------------------
join ciqEstimateDetailNumericData EDND
on EDND.estimatePeriodId = EP.estimatePeriodId
and GETDATE() between EDND.effectiveDate and EDND.toDate
----------------------------------------------------------
left outer join ciqEstimateBroker EB
on EB.estimateBrokerId = EDND.estimateBrokerId --- left outer join must be used if you receive any of the anonymous estimates packages
left outer join ciqEstimateAnalyst EA
on EA.estimateAnalystId = EDND.estimateAnalystId --- left outer join must be used if you receive any of the anonymous estimates packages
where EP.companyId = 112350 -- IBM
and EP.periodTypeId = 1 -- annual
and EDND.dataItemId = 21634 --- EPS Normalized (Detailed)
and EP.fiscalYear = 2010
order by 4,5,6,10
This query is complicated enough as it is - I would hate to add to it. I would probably turn it into a view or a stored proc and query from it as needed. Then you could also have, instead of just GETDATE(), a date range as input.

Want to filter SQL Server query

Currently I am pulling out the Facilities where the permitType is 'Hazardous waste' and the licenseStatus is 'Open', but there will be cases where the facilities will have more than one permit type suppose a facility can have two permit types
Hazardous waste (status - OPEN)
AST (OPEN)
so I should not display this facility in my output if it other permit types (Status - OPEN) apart from the 'hazardous waste', but if the other permit type has status - CLOSE then my query should output the Facility i.e if AST(Status - CLOSE) then the facility should be pulled from the database.
I wrote the following query but not sure where to include the condition.
SELECT
e.facilityID
,f.organization_core AS 'Facility Name'
,f.address_full_core AS 'Facility Address'
,a.permitNumber AS 'Permit Number'
,b.permitName AS 'Permit Name'
,a.licenseStatus AS 'Permit Status'
,c.permitType AS 'Permit Type'
FROM
tblPermits a
LEFT JOIN
dbo.tblPermit_Names b ON a.permitID = b.permitID
LEFT JOIN
dbo.tblLKP_Permit_Facilities d ON a.permitID = d.permitID
LEFT JOIN
dbo.tblPermit_Types c ON a.permitTypeID = c.permitTypeID
LEFT JOIN
dbo.tblFacility e ON d.facilityID = e.facilityID
LEFT JOIN
dbo.tblAddresses f ON e.facilityAddressID = f.addressID
WHERE
a.permitTypeID IN (SELECT permitTypeID
FROM dbo.tblPermit_Types
WHERE permitType IN ('Hazardous Waste'))
AND a.licenseStatus = 'Open'
AND isNull(a.deleted, 0) = 0
I think the following query implements your rules. The idea is to focus on the facility and not on all the extra stuff in the tables that you have put in. You need to aggregate by facilityid so you can apply logic to all the permits issued for each one:
SELECT f.facilityID
FROM dbo.tblFacility f join
dbo.tblLKP_Permit_Facilities pf
on pf.facilityID = f.facilityId join
tblPermits p
on pf.permitID = p.permitID join
dbo.tblPermit_Types pt
ON pt.permitTypeID = p.permitTypeID
GROUP BY f.facilityID
HAVING SUM(case when pt.permitType IN ('Hazardous Waste') and p.licenseStatus = 'Open'
then 1 else 0
end) > 0 and
SUM(case when pt.permitType NOT IN ('Hazardous Waste') and p.licenseStatus = 'Close'
then 1 else 0
end) > 0;
Each condition in the having clause is applying one of your rules.
I'm a little confused as to your table structure, but this is what I think you should do.
Your where statement should be id in (Select id from table where status = open and count(id) = 1 group by facility)
AND Type in ('Hazardous Waste')
The first part of the where statement will limit all ids to those that are open with only one open type per facility, the second part limits it to just hazardous waste.
If you have a facility with 10 permits, but only one is active, it will pull it into the list, but if the active permit isn't hazardous waste, it will then exclude it.
Sorry I can't give you exact code.
Also, nix as many of those outer joins as you can. Inner joins are faster, and are more likely to represent the data you want.

SQL Server:An expression of non-boolean type specified in a context where a condition is expected

I apologize I'm asking this question when this specific error type has already been asked multiple times before, but I've looked through them and am not explicitly seeing my answer.
I am trying to build a trigger that takes records inserted into a table (FDC_Trip_History), selects some information from the record, joins on other tables to pull additional data, etc. and inserts a record into another table (Staging).
I'm getting this error at the bottom of my script, the 4th line from the end after the FROM section. Any idea why?
CREATE TRIGGER Insert_Trip_History ON FDC_Trip_History
AFTER INSERT
AS BEGIN
SET NOCOUNT ON;
--If inserted record reaches a certain 'status' of archive then continue
If EXISTS (SELECT * FROM inserted WHERE inserted.description like '%archive%')
BEGIN
--If inserted record can be 'billed', and hasn't already been processed, then continue.
IF EXISTS ( SELECT * FROM inserted
INNER JOIN FDC_Trips on inserted.tdate = FDC_Trips.tdate and inserted.job = FDC_Trips.job and inserted.SourceDB = FDC_trips.sourceDB
INNER JOIN AMS.dbo.Billable_Outcome_Filter as eBill on FDC_trips.SourceDB = eBill.SourceDB and FDC_Trips.outcome = eBill.Outcome_Code)
AND NOT EXISTS ( SELECT * FROM inserted
INNER JOIN Staging as Stg on inserted.tdate = Stg.tdate and inserted.job = Stg.job and inserted.sourcedb = Stg.sourceDB)
BEGIN
INSERT INTO Staging
(EVENT_OPERATION,
EVENT_SOURCE_TABLE,
EVENT_PRIORITY,
EVENT_TIME_UPDATED,
EVENT_STATUS,
EVENT_COMMENT,
TDATE,
JOB,
SOURCEDB,
CUSTNO,
SHIFTNO,
TYPE,
PROFITCENTER,
BILLINGRATEPROFITCENTER)
SELECT
'CREATE' as [EVENT_OPERATION],
'FDC_Trip_History' as [EVENT_SOURCE_TABLE],
'1' as [EVENT_PRIORITY],
GETDATE() as [EVENT_TIME_ADDED],
null as [EVENT_TIME_UPDATED],
'0' as [EVENT_STATUS],
'' as [EVENT_COMMENT],
eTHistory.tdate as [TDATE],
eTHistory.job as [JOB],
eTHistory.sourcedb as [SOURCEDB],
eT.custno as [CUSTNO],
eT.shiftno as [SHIFTNO],
'Completed' as [TYPE],
--Decide Profit Center. Profit center (PC) determined from dispatch zone (Trips.dzone)
CASE
WHEN cType.descr LIKE 'ATS%'
THEN DispatchZone.ATS_ProfitCenter
ELSE DispatchZone.ProfitCenter
END,
--Decide Billing rate profit center. Billing rate profit center (BRPC) determined from pickup zone. Does ATS logic apply to BRPC too?
CASE
WHEN cType.descr LIKE 'ATS%'
THEN PickupZone.ATS_ProfitCenter
ELSE PickupZone.ProfitCenter
END
as [BILLINGRATEPROFITCENTER]
FROM inserted
INNER JOIN FDC_Trip_History as eTHistory
INNER JOIN FDC_Trips as eT on eTHistory.tdate = eT.tdate and eTHistory.job = eT.job and eTHistory.sourcedb = eT.sourcedb
LEFT JOIN Trips as T on T.tdate = eTHistory.tdate and T.sourcedb = eTHistory.sourceDB and T.Job = eTHistory.Job
LEFT JOIN Call_Types as cType on cType.code = eT.calltype and cType.sourceDB = eT.sourceDB
LEFT JOIN Zones as DispatchZone on DispatchZone.code = T.dzone
LEFT JOIN Zones as PickupZone on PickupZone.code = eT.puzone /* Error pops up right here */
END
END
END
You seem to have forgotton the specify the join criteria for the FDC_Trip_History table (the first INNER JOIN).
In addition, you have 14 columns in your INSERT list but 15 in your SELECT statement.

Compare values from one table with the results from a query?

First, I will explain the what is being captured. User's have a member level associated with their accounts (Bronze, Gold, Diamond, etc). A nightly job needs to run to calculate the orders from today a year back. If the order total for a given user goes over or under a certain amount their level is upgraded or downgraded. The table where the level information is stored will not change much, but the minimum and maximum amount thresholds may over time. This is what the table looks like:
CREATE TABLE [dbo].[MemberAdvantageLevels] (
[Id] int NOT NULL IDENTITY(1,1) ,
[Name] varchar(255) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL ,
[MinAmount] int NOT NULL ,
[MaxAmount] int NOT NULL ,
CONSTRAINT [PK__MemberAd__3214EC070D9DF1C7] PRIMARY KEY ([Id])
)
ON [PRIMARY]
GO
I wrote a query that will group the orders by user for the year to date. The query includes their current member level.
SELECT
Sum(dbo.tbh_Orders.SubTotal) AS OrderTotals,
Count(dbo.UserProfile.UserId) AS UserOrders,
dbo.UserProfile.UserId,
dbo.UserProfile.UserName,
dbo.UserProfile.Email,
dbo.MemberAdvantageLevels.Name,
dbo.MemberAdvantageLevels.MinAmount,
dbo.MemberAdvantageLevels.MaxAmount,
dbo.UserMemberAdvantageLevels.LevelAchievmentDate,
dbo.UserMemberAdvantageLevels.LevelAchiementAmount,
dbo.UserMemberAdvantageLevels.IsCurrent as IsCurrentLevel,
dbo.MemberAdvantageLevels.Id as MemberLevelId,
FROM
dbo.tbh_Orders
INNER JOIN dbo.tbh_OrderStatuses ON dbo.tbh_Orders.StatusID = dbo.tbh_OrderStatuses.OrderStatusID
INNER JOIN dbo.UserProfile ON dbo.tbh_Orders.CustomerID = dbo.UserProfile.UserId
INNER JOIN dbo.UserMemberAdvantageLevels ON dbo.UserProfile.UserId = dbo.UserMemberAdvantageLevels.UserId
INNER JOIN dbo.MemberAdvantageLevels ON dbo.UserMemberAdvantageLevels.MemberAdvantageLevelId = dbo.MemberAdvantageLevels.Id
WHERE
dbo.tbh_OrderStatuses.OrderStatusID = 4 AND
(dbo.tbh_Orders.AddedDate BETWEEN dateadd(year,-1,getdate()) AND GETDATE()) and IsCurrent = 1
GROUP BY
dbo.UserProfile.UserId,
dbo.UserProfile.UserName,
dbo.UserProfile.Email,
dbo.MemberAdvantageLevels.Name,
dbo.MemberAdvantageLevels.MinAmount,
dbo.MemberAdvantageLevels.MaxAmount,
dbo.UserMemberAdvantageLevels.LevelAchievmentDate,
dbo.UserMemberAdvantageLevels.LevelAchiementAmount,
dbo.UserMemberAdvantageLevels.IsCurrent,
dbo.MemberAdvantageLevels.Id
So, I need to check the OrdersTotal and if it exceeds the current level threshold, I then need to find the Level that fits their current order total and create a new record with their new level.
So for example, lets say jon#doe.com currently is at bronze. The MinAmount for bronze is 0 and the MaxAmount is 999. Currently his Orders for the year are at $2500. I need to find the level that $2500 fits within and upgrade his account. I also need to check their LevelAchievmentDate and if it is outside of the current year we may need to demote the user if there has been no activity.
I was thinking I could create a temp table that holds the results of all levels and then somehow create a CASE statement in the query above to determine the new level. I don't know if that is possible. Or, is it better to iterate over my order results and perform additional queries? If I use the iteration pattern I know i can use the When statement to iterate over the rows.
Update
I updated my Query A bit and so far came up with this, but I may need more information than just the ID from the SubQuery
Select * into #memLevels from MemberAdvantageLevels
SELECT
Sum(dbo.tbh_Orders.SubTotal) AS OrderTotals,
Count(dbo.AZProfile.UserId) AS UserOrders,
dbo.AZProfile.UserId,
dbo.AZProfile.UserName,
dbo.AZProfile.Email,
dbo.MemberAdvantageLevels.Name,
dbo.MemberAdvantageLevels.MinAmount,
dbo.MemberAdvantageLevels.MaxAmount,
dbo.UserMemberAdvantageLevels.LevelAchievmentDate,
dbo.UserMemberAdvantageLevels.LevelAchiementAmount,
dbo.UserMemberAdvantageLevels.IsCurrent as IsCurrentLevel,
dbo.MemberAdvantageLevels.Id as MemberLevelId,
(Select Id from #memLevels where Sum(dbo.tbh_Orders.SubTotal) >= #memLevels.MinAmount and Sum(dbo.tbh_Orders.SubTotal) <= #memLevels.MaxAmount) as NewLevelId
FROM
dbo.tbh_Orders
INNER JOIN dbo.tbh_OrderStatuses ON dbo.tbh_Orders.StatusID = dbo.tbh_OrderStatuses.OrderStatusID
INNER JOIN dbo.AZProfile ON dbo.tbh_Orders.CustomerID = dbo.AZProfile.UserId
INNER JOIN dbo.UserMemberAdvantageLevels ON dbo.AZProfile.UserId = dbo.UserMemberAdvantageLevels.UserId
INNER JOIN dbo.MemberAdvantageLevels ON dbo.UserMemberAdvantageLevels.MemberAdvantageLevelId = dbo.MemberAdvantageLevels.Id
WHERE
dbo.tbh_OrderStatuses.OrderStatusID = 4 AND
(dbo.tbh_Orders.AddedDate BETWEEN dateadd(year,-1,getdate()) AND GETDATE()) and IsCurrent = 1
GROUP BY
dbo.AZProfile.UserId,
dbo.AZProfile.UserName,
dbo.AzProfile.Email,
dbo.MemberAdvantageLevels.Name,
dbo.MemberAdvantageLevels.MinAmount,
dbo.MemberAdvantageLevels.MaxAmount,
dbo.UserMemberAdvantageLevels.LevelAchievmentDate,
dbo.UserMemberAdvantageLevels.LevelAchiementAmount,
dbo.UserMemberAdvantageLevels.IsCurrent,
dbo.MemberAdvantageLevels.Id
This hasn't been syntax checked or tested but should handle the inserts and updates you describe. The insert can be done as single statement using a derived/virtual table which contains the orders group by caluclation. Note that both the insert and update statement be done within the same transaction to ensure no two records for the same user can end up with IsCurrent = 1
INSERT UserMemberAdvantageLevels (UserId, MemberAdvantageLevelId, IsCurrent,
LevelAchiementAmount, LevelAchievmentDate)
SELECT t.UserId, mal.Id, 1, t.OrderTotals, GETDATE()
FROM
(SELECT ulp.UserId, SUM(ord.SubTotal) OrderTotals, COUNT(ulp.UserId) UserOrders
FROM UserLevelProfile ulp
INNER JOIN tbh_Orders ord ON (ord.CustomerId = ulp.UserId)
WHERE ord.StatusID = 4
AND ord.AddedDate BETWEEN DATEADD(year,-1,GETDATE()) AND GETDATE()
GROUP BY ulp.UserId) AS t
INNER JOIN MemberAdvantageLevels mal
ON (t.OrderTotals BETWEEN mal.MinAmount AND mal.MaxAmount)
-- Left join needed on next line in case user doesn't currently have a level
LEFT JOIN UserMemberAdvantageLevels umal ON (umal.UserId = t.UserId)
WHERE umal.MemberAdvantageLevelId IS NULL -- First time user has been awarded a level
OR (mal.Id <> umal.MemberAdvantageLevelId -- Level has changed
AND (t.OrderTotals > umal.LevelAchiementAmount -- Acheivement has increased (promotion)
OR t.UserOrders = 0)) -- No. of orders placed is zero (de-motion)
/* Reset IsCurrent flag where new record has been added */
UPDATE UserMemberAdvantageLevels
SET umal1.IsCurrent=0
FROM UserMemberAdvantageLevels umal1
INNER JOIN UserMemberAdvantageLevels umal2 On (umal2.UserId = umal1.UserId)
WHERE umal1.IsCurrent = 1
AND umal2.IsCurrent = 2
AND umal1.LevelAchievmentDate < umal2.LevelAchievmentDate)
One approach:
with cte as
(SELECT Sum(o.SubTotal) AS OrderTotals,
Count(p.UserId) AS UserOrders,
p.UserId,
p.UserName,
p.Email,
l.Name,
l.MinAmount,
l.MaxAmount,
ul.LevelAchievmentDate,
ul.LevelAchiementAmount,
ul.IsCurrent as IsCurrentLevel,
l.Id as MemberLevelId
FROM dbo.tbh_Orders o
INNER JOIN dbo.UserProfile p ON o.CustomerID = p.UserId
INNER JOIN dbo.UserMemberAdvantageLevels ul ON p.UserId = ul.UserId
INNER JOIN dbo.MemberAdvantageLevels l ON ul.MemberAdvantageLevelId = l.Id
WHERE o.StatusID = 4 AND
o.AddedDate BETWEEN dateadd(year,-1,getdate()) AND GETDATE() and
IsCurrent = 1
GROUP BY
p.UserId, p.UserName, p.Email, l.Name, l.MinAmount, l.MaxAmount,
ul.LevelAchievmentDate, ul.LevelAchiementAmount, ul.IsCurrent, l.Id)
select cte.*, ml.*
from cte
join #memLevels ml
on cte.OrderTotals >= ml.MinAmount and cte.OrderTotals <= ml.MaxAmount