I have a table with few records, I want to get month wise data along with count on one of the column. The output should contain Month and count of Isregistered flag.
Table structure
| Inserted On | IsRegistered |
+-------------+--------------+
| 10-01-2020 | 1 |
| 15-01-2020 | 1 |
| 17-01-2020 | null |
| 17-02-2020 | 1 |
| 21-02-2020 | null |
| 04-04-2020 | null |
| 18-04-2020 | null |
| 19-04-2020 | 1 |
Excepted output
| Inserted On | Registered | Not Registered
+-------------+------------+---------------
| Jan | 2 | 1
| Feb | 1 | 1
| Apr | 1 | 2
I tried by performing normal group by but didn't got desired output
SELECT
DATENAME(MONTH, dateinserted) AS [MonthName], COUNT(ISRegistered)
FROM
tablename
GROUP BY
(DATENAME(MONTH, dateinserted))
Note: here null is treated as not registered
You can use aggregation. I would include the year and use the month number rather than name, so:
select year(inserted_on), month(inserted_on),
coalesce(sum(is_registered), 0) as num_registered,
sum(case when is_registered is null then 1 else 0 end) as num_not_registered
from tablename
group by year(inserted_on), month(inserted_on)
order by year(inserted_on), month(inserted_on);
Note: If you really want the monthname and want to combine data from different years (which seems unlikely, but . . . ), then you can use:
select datename(month, inserted_on),
coalesce(sum(is_registered), 0) as num_registered,
sum(case when is_registered is null then 1 else 0 end) as num_not_registered
from tablename
group by datename(month, inserted_on)
order by month(min(inserted_on));
The GROUP BY should include both the year and month (so there's no overlapping) as well as the DATENAME (for display). Something like this
drop table if exists #tablename;
go
create table #tablename(dateinserted date, ISRegistered int);
insert #tablename values
('2020-12-01', 0),
('2020-11-02', 1),
('2020-11-03', 1),
('2020-12-01', 1),
('2020-12-03', 1),
('2020-11-02', 0);
select year(dateinserted) yr,
datename(month, dateinserted) AS [MonthName],
sum(ISRegistered) Registered ,
sum(1-ISRegistered) [Not Registered]
from #tablename
group by year(dateinserted), month(dateinserted), datename(month, dateinserted)
order by year(dateinserted), month(dateinserted);
yr MonthName Registered Not Registered
2020 November 2 1
2020 December 2 1
Below is the explanation to include the condition along with the code I want to modify. I need to put this condition in my code below, but I am unable to do so and need some help.
So you have property A and property B.
A Member should not earn multiple credits if a reservation is booked at property A for 8/1 to 8/2 and a second reservation at property A for 8/2 to 8/3. This is within the same property and there is not 24 hours in between the checkout and next check in.
A Member should earn multiple credits if a reservation is booked at property A for 8/1 to 8/2 and a second reservation at property B for 8/2 to 8/3. This is within a different property so timing doesn’t matter.
Visual Example:
Property A – check in 8/1 to 8/2 – qualified stay
Property A – check in 8/2 to 8/3 – non qualified
Property A – check in 8/4 to 8/5 - qualified
Property B – check in 8/5 to 8/6 – qualified
Property C – check in 8/6 to 8/29 - qualified
Property C – check in 8/30 to 9/15 – non qualified
This is my code:
SELECT
LP.LoyaltyMemberID, LP.MemberEmail, H.pcode, cs.TotalRevenue,
cdc.MarketSubSegment, CS.RoomNights, cs.RateType,
CS.ReservationNumber, CS.StayStatus, H.HotelStatus, cs.departuredate,
CAST(CS.ArrivalDate AS DATE) AS ArrivalDate
FROM
ODS.C_DCustomerStay AS CS
LEFT OUTER JOIN
[ODS].[MemberTransactions] AS CDC ON CDC.SourceReferenceNumber = CS.ReservationNumber
LEFT JOIN
[ODS].[Memberships] AS LP ON LP.profileID = CDC.profileID
LEFT OUTER JOIN
dbo.[Hotels] AS H ON CS.CPropertyID = H.cidcode
WHERE
CAST(ArrivalDate AS DATE) = DATEADD(day, -1, CONVERT(DATE, GETDATE())) --extracting records for yesterday
AND LoyaltyMemberID <> '' AND LoyaltyMemberID IS NOT NULL
AND RoomNights >= 1 -- Min room nights > 1
AND CAST(totalrevenue AS FLOAT) >= 1 -- Min Revenue >= 1
AND DATEDIFF(DAY, arrivaldate, departuredate) >= 1
Data is shown below and only 2 unique reservation numbers per LoyaltyMemberid satisfying the above rule will be given credits.Not all reservations that qualify will get credits.
LoyaltyMemberID MemberEmail propertycode TotalRevenue MarketSubSegment RoomNights RateType ReservationNumber StayStatus rlhc_hotelstatusname arrivaldate departuredate
102282482 ljbirr#aol.com WAFEDW 118.8 PR 1 EXT1 88676 R Active 7/30/2018 7/31/2018
102282482 ljbirr#aol.com ORPEND 285.6 BR 3 WEB 119223 R Active 7/30/2018 8/2/2018
102296283 tj711#aol.com WAPOUL 246 PR 3 FDR 975372 R Active 7/30/2018 8/2/2018
102898784 JW#gmail.com WAANGE 900.9 BR 4 RNR 33401155 R Active 7/30/2018 8/3/2018
102898784 JW#gmail.com WAANGE 900.9 BR 4 RNR 33401156 R Active 7/30/2018 8/3/2018
102898784 JW#gmail.com WAANGE 937.4 BR 4 RNR 33401170 R Active 7/30/2018 8/3/2018
103723804 hmayfield#co.net IDCANY 85.48 PR 1 EX1HR 168702 R Active 7/30/2018 7/31/2018
103723804 hmayfield#co.net WAKENT 499.75 IN 4 EX1 100803 R Active 7/30/2018 8/3/2018
104157546 dfa#pn.com CAPERR 89.38 BR 1 EX1 71220 R Active 7/30/2018 7/31/2018
104337973 ralphog#eoni.com ORPEND 160 BR 2 WEB 119221 R Active 7/30/2018 8/1/2018
104408813 dennisvaughn#msn.comPAHARR 218 IN 2 GRPNP 164701 R Active 7/30/2018 8/1/2018
104420433 mahlerkelsey#me.com WAFEDW 245.1 C0 2 RNR 87476 R Active 7/30/2018 8/1/2018
104420433 mahlerkelsey#me.com WAFEDW 118.8 C0 1 EXT1 88676 R Active 7/30/2018 7/31/2018
If I am understanding you correctly, what you are stating is that if a 24-hour window has gone by since the previous checkout then a member is qualified for some sort of credit.
First thing I am going to point out is the inconsistency in the sample data you provided.
Property A – check in 8/4 to 8/5 - qualified
This is qualified for a credit because a full day passed from the previous "check out" of 08/03.
But then you say:
Property C – check in 8/30 to 9/15 – non qualified
How can this be when Property A qualifies under the exact same circumstance?
Regardless, I moved ahead believing this perhaps on oversight. Here is an example that I think will get you moving in the right direction to accomplish what you need.
You can run the following example in SSMS:
DECLARE #data TABLE ( [member] VARCHAR(10), [hotel] VARCHAR(10), [check_in] DATETIME, [check_out] DATETIME );
INSERT INTO #data (
[member], [hotel], [check_in], [check_out]
)
VALUES
( '60135522', 'PropA', '08/01/2018', '08/02/2018' )
, ( '60135522', 'PropA', '08/02/2018', '08/03/2018' )
, ( '60135522', 'PropA', '08/04/2018', '08/05/2018' )
, ( '60135522', 'PropB', '08/05/2018', '08/06/2018' )
, ( '60135522', 'PropC', '08/06/2018', '08/29/2018' )
, ( '60135522', 'PropC', '08/30/2018', '09/15/2018' );
SELECT
MemberStays.member
, MemberStays.hotel
, MemberStays.PrevCheckOut
, MemberStays.CheckIn
, MemberStays.CheckOut
, DATEDIFF( DD, [PrevCheckOut], [CheckIn] ) PrevCheckoutDays
, CASE DATEDIFF( DD, [PrevCheckOut], [CheckIn] )
WHEN 0 THEN 'non qualified'
ELSE 'qualified'
END AS [CreditStatus]
FROM (
SELECT
data1.member
, data1.hotel
, CONVERT(
VARCHAR(10)
, LAG( [check_out], 1, NULL ) OVER ( PARTITION BY [member], [hotel] ORDER BY [member], [hotel], [check_in] )
, 101
) AS PrevCheckOut
, CONVERT( VARCHAR(10), data1.check_in, 101 ) AS CheckIn
, CONVERT( VARCHAR(10), data1.check_out, 101 ) AS CheckOut
FROM #data AS data1
) AS MemberStays
ORDER BY
[hotel], [CheckIn];
Returns
+----------+-------+--------------+------------+------------+------------------+---------------+
| member | hotel | PrevCheckOut | CheckIn | CheckOut | PrevCheckoutDays | CreditStatus |
+----------+-------+--------------+------------+------------+------------------+---------------+
| 60135522 | PropA | NULL | 08/01/2018 | 08/02/2018 | NULL | qualified |
| 60135522 | PropA | 08/02/2018 | 08/02/2018 | 08/03/2018 | 0 | non qualified |
| 60135522 | PropA | 08/03/2018 | 08/04/2018 | 08/05/2018 | 1 | qualified |
| 60135522 | PropB | NULL | 08/05/2018 | 08/06/2018 | NULL | qualified |
| 60135522 | PropC | NULL | 08/06/2018 | 08/29/2018 | NULL | qualified |
| 60135522 | PropC | 08/29/2018 | 08/30/2018 | 09/15/2018 | 1 | qualified |
+----------+-------+--------------+------------+------------+------------------+---------------+
I broke the main logic into a table subquery to keep it simple for viewing purposes. The key here is using SQL Server's LAG function (coupled with its partitioning/ordering) to look at the previous checkout for a member's stay on a given hotel. Once you have that, you can then compare it against the current row's CheckIn to determine how much time has passed between the two. From there it gets simple. If the days passed is 0 (zero) then it does not qualify for consecutive credits, otherwise, it does.
This is where the second record for Property C threw me off. If I am to apply the logic you state, both Property C records qualify.
UPDATED TO REFLECT NEW RESULTSET SPECIFIED
I have updated my example to use the updated data from your resultset. You can run this code from within SSMS for review.
-- replicate resultset definition --
DECLARE #resultset TABLE (
LoyaltyMemberID VARCHAR(10)
, MemberEmail VARCHAR(100)
, PropertyCode VARCHAR(10)
, TotalRevenue DECIMAL(18, 2)
, MarketSubSegment VARCHAR(2)
, RoomNights INT
, RateType VARCHAR(10)
, ReservationNumber VARCHAR(10)
, StayStatus VARCHAR(10)
, rlhc_HotelStatusName VARCHAR(10)
, ArrivalDate SMALLDATETIME
, DepartureDate SMALLDATETIME
);
-- insert sample data into #resultset --
INSERT INTO #resultset (
LoyaltyMemberID, MemberEmail, PropertyCode, TotalRevenue, MarketSubSegment, RoomNights, RateType, ReservationNumber, StayStatus, rlhc_HotelStatusName, ArrivalDate, DepartureDate
) VALUES
( '102282482', 'ljbirr#aol.com', 'WAFEDW', 118.8, 'PR', 1, 'EXT1', '88676', 'R', 'Active', '7/30/2018', '7/31/2018' )
, ( '102282482', 'ljbirr#aol.com', 'ORPEND', 285.6, 'BR', 3, 'WEB', '119223', 'R', 'Active', '7/30/2018', '8/2/2018' )
, ( '102296283', 'tj711#aol.com', 'WAPOUL', 246, 'PR', 3, 'FDR', '975372', 'R', 'Active', '7/30/2018', '8/2/2018' )
, ( '102898784', 'JW#gmail.com', 'WAANGE', 900.9, 'BR', 4, 'RNR', '33401155', 'R', 'Active', '7/30/2018', '8/3/2018' )
, ( '102898784', 'JW#gmail.com', 'WAANGE', 937.4, 'BR', 4, 'RNR', '33401170', 'R', 'Active', '7/30/2018', '8/3/2018' )
, ( '103723804', 'hmayfield#co.net', 'IDCANY', 85.48, 'PR', 1, 'EX1HR', '168702', 'R', 'Active', '7/30/2018', '7/31/2018' )
, ( '103723804', 'hmayfield#co.net', 'WAKENT', 499.75, 'IN', 4, 'EX1', '100803', 'R', 'Active', '7/30/2018', '8/3/2018' )
, ( '104157546', 'dfa#pn.com', 'CAPERR', 89.38, 'BR', 1, 'EX1', '71220', 'R', 'Active', '7/30/2018', '7/31/2018' )
, ( '104337973', 'ralphog#eoni.com', 'ORPEND', 160, 'BR', 2, 'WEB', '119221', 'R', 'Active', '7/30/2018', '8/1/2018' )
, ( '104408813', 'dennisvaughn#msn.com', 'PAHARR', 218, 'IN', 2, 'GRPNP', '164701', 'R', 'Active', '7/30/2018', '8/1/2018' )
, ( '104420433', 'mahlerkelsey#me.com', 'WAFEDW', 245.1, 'C0', 2, 'RNR', '87476', 'R', 'Active', '7/30/2018', '8/1/2018' )
, ( '104420433', 'mahlerkelsey#me.com', 'WAFEDW', 118.8, 'C0', 1, 'EXT1', '88676', 'R', 'Active', '7/30/2018', '7/31/2018' );
Then...
/*
SELECT data from #resultset with the following rules:
- Any stay less than 24 hours does *not* qualify for loyalty credits.
- Only 2 unique reservation numbers per LoyaltyMemberid satisfying the above rule will be given credits.
- Note: Not all reservations that qualify will get credits.
*/
SELECT
LoyaltyMemberID
, MemberEmail
, PropertyCode
, TotalRevenue
, MarketSubSegment
, RoomNights
, RateType
, ReservationNumber
, StayStatus
, rlhc_HotelStatusName
, ArrivalDate
, DepartureDate
, PrevDeparture
, DepartureSeq
-- apply business rules --
, CASE
WHEN ( DATEDIFF( DD, ArrivalDate, PrevDeparture ) = 0 ) THEN 'Not Eligible'
WHEN ( DepartureSeq > 1 ) THEN 'Not Eligible'
ELSE 'Eligible'
END AS CreditEligible
FROM (
-- perform some intital work on the base resultsel --
SELECT
MemberStays.LoyaltyMemberID
, MemberStays.MemberEmail
, MemberStays.PropertyCode
, MemberStays.TotalRevenue
, MemberStays.MarketSubSegment
, MemberStays.RoomNights
, MemberStays.RateType
, MemberStays. ReservationNumber
, MemberStays.StayStatus
, MemberStays.rlhc_HotelStatusName
, CONVERT( VARCHAR(10), MemberStays.ArrivalDate, 101) AS ArrivalDate
, CONVERT( VARCHAR(10), MemberStays.DepartureDate, 101) AS DepartureDate
, CONVERT(
VARCHAR(10),
LAG( MemberStays.DepartureDate, 1, NULL ) OVER (
PARTITION BY MemberStays.LoyaltyMemberID, MemberStays.PropertyCode, MemberStays.ReservationNumber
ORDER BY MemberStays.LoyaltyMemberID, MemberStays.PropertyCode, MemberStays.ReservationNumber, MemberStays.ArrivalDate
)
, 101
) AS PrevDeparture
, ROW_NUMBER() OVER (
PARTITION BY MemberStays.LoyaltyMemberID, MemberStays.PropertyCode, MemberStays.ReservationNumber
ORDER BY MemberStays.LoyaltyMemberID, MemberStays.PropertyCode, MemberStays.ReservationNumber, MemberStays.ArrivalDate
) AS DepartureSeq
FROM #resultset AS MemberStays
) AS LoyaltyData
ORDER BY
LoyaltyData.LoyaltyMemberID, LoyaltyData.PropertyCode, LoyaltyData.ReservationNumber, LoyaltyData.ArrivalDate;
Returns
+-----------------+----------------------+--------------+--------------+------------------+------------+----------+-------------------+------------+----------------------+-------------+---------------+---------------+--------------+----------------+
| LoyaltyMemberID | MemberEmail | PropertyCode | TotalRevenue | MarketSubSegment | RoomNights | RateType | ReservationNumber | StayStatus | rlhc_HotelStatusName | ArrivalDate | DepartureDate | PrevDeparture | DepartureSeq | CreditEligible |
+-----------------+----------------------+--------------+--------------+------------------+------------+----------+-------------------+------------+----------------------+-------------+---------------+---------------+--------------+----------------+
| 102282482 | ljbirr#aol.com | ORPEND | 285.60 | BR | 3 | WEB | 119223 | R | Active | 07/30/2018 | 08/02/2018 | NULL | 1 | Eligible |
| 102282482 | ljbirr#aol.com | WAFEDW | 118.80 | PR | 1 | EXT1 | 88676 | R | Active | 07/30/2018 | 07/31/2018 | NULL | 1 | Eligible |
| 102296283 | tj711#aol.com | WAPOUL | 246.00 | PR | 3 | FDR | 975372 | R | Active | 07/30/2018 | 08/02/2018 | NULL | 1 | Eligible |
| 102898784 | JW#gmail.com | WAANGE | 900.90 | BR | 4 | RNR | 33401155 | R | Active | 07/30/2018 | 08/03/2018 | NULL | 1 | Eligible |
| 102898784 | JW#gmail.com | WAANGE | 937.40 | BR | 4 | RNR | 33401170 | R | Active | 07/30/2018 | 08/03/2018 | NULL | 1 | Eligible |
| 103723804 | hmayfield#co.net | IDCANY | 85.48 | PR | 1 | EX1HR | 168702 | R | Active | 07/30/2018 | 07/31/2018 | NULL | 1 | Eligible |
| 103723804 | hmayfield#co.net | WAKENT | 499.75 | IN | 4 | EX1 | 100803 | R | Active | 07/30/2018 | 08/03/2018 | NULL | 1 | Eligible |
| 104157546 | dfa#pn.com | CAPERR | 89.38 | BR | 1 | EX1 | 71220 | R | Active | 07/30/2018 | 07/31/2018 | NULL | 1 | Eligible |
| 104337973 | ralphog#eoni.com | ORPEND | 160.00 | BR | 2 | WEB | 119221 | R | Active | 07/30/2018 | 08/01/2018 | NULL | 1 | Eligible |
| 104408813 | dennisvaughn#msn.com | PAHARR | 218.00 | IN | 2 | GRPNP | 164701 | R | Active | 07/30/2018 | 08/01/2018 | NULL | 1 | Eligible |
| 104420433 | mahlerkelsey#me.com | WAFEDW | 245.10 | C0 | 2 | RNR | 87476 | R | Active | 07/30/2018 | 08/01/2018 | NULL | 1 | Eligible |
| 104420433 | mahlerkelsey#me.com | WAFEDW | 118.80 | C0 | 1 | EXT1 | 88676 | R | Active | 07/30/2018 | 07/31/2018 | NULL | 1 | Eligible |
+-----------------+----------------------+--------------+--------------+------------------+------------+----------+-------------------+------------+----------------------+-------------+---------------+---------------+--------------+----------------+
Mind you, I cannot test this against your database, but my thoughts are if you modify your above SQL to:
SELECT
* -- I didn't feel like typing out all the column names again, however you should as it is a best practice
, CASE
WHEN ( DATEDIFF( DD, ArrivalDate, PrevDeparture ) = 0 ) THEN 'Not Eligible'
WHEN ( DepartureSeq > 1 ) THEN 'Not Eligible'
ELSE 'Eligible'
END AS CreditEligible
FROM (
SELECT
LP.LoyaltyMemberID
, LP.MemberEmail
, H.pcode AS PropertyCode
, CS.TotalRevenue
, CDC.MarketSubSegment
, CS.RoomNights
, CS.RateType
, CS.ReservationNumber
, CS.StayStatus
, H.HotelStatus
, CAST( CS.departuredate AS SMALLDATETIME ) AS DepartureDate
, CAST( CS.ArrivalDate AS SMALLDATETIME ) AS ArrivalDate
, CONVERT(
VARCHAR(10),
LAG( MemberStays.DepartureDate, 1, NULL ) OVER (
PARTITION BY MemberStays.LoyaltyMemberID, MemberStays.PropertyCode, MemberStays.ReservationNumber
ORDER BY MemberStays.LoyaltyMemberID, MemberStays.PropertyCode, MemberStays.ReservationNumber, MemberStays.ArrivalDate
)
, 101
) AS PrevDeparture
, ROW_NUMBER() OVER (
PARTITION BY MemberStays.LoyaltyMemberID, MemberStays.PropertyCode, MemberStays.ReservationNumber
ORDER BY MemberStays.LoyaltyMemberID, MemberStays.PropertyCode, MemberStays.ReservationNumber, MemberStays.ArrivalDate
) AS DepartureSeq
FROM ODS.C_DCustomerStay AS CS
LEFT OUTER JOIN [ODS].[MemberTransactions] AS CDC
ON CDC.SourceReferenceNumber = CS.ReservationNumber
LEFT JOIN [ODS].[Memberships] AS LP
ON LP.profileID = CDC.profileID
LEFT OUTER JOIN dbo.[Hotels] AS H
ON CS.CPropertyID = H.cidcode
WHERE
CAST( ArrivalDate AS DATE ) = DATEADD( DD, -1, GETDATE() ) --extracting records for yesterday
AND NULLIF( LoyaltyMemberID, '' ) IS NOT NULL
AND RoomNights >= 1 -- Min room nights > 1
AND CAST(totalrevenue AS FLOAT) >= 1 -- Min Revenue >= 1
AND DATEDIFF( DD, arrivaldate, departuredate ) >= 1
) AS LoyaltyInfo
ORDER BY
LoyaltyMemberID, PropertyCode, ReservationNumber, ArrivalDate;
It should give you what you need based on what I've gathered from your initial question.
Couple of notes:
I shortened
AND LoyaltyMemberID <> '' AND LoyaltyMemberID IS NOT NULL
to
AND NULLIF( LoyaltyMemberID, '' ) IS NOT NULL
It does the same thing with less code.
I changed this
CAST(ArrivalDate AS DATE) = DATEADD(day, -1, CONVERT(DATE, GETDATE()))
to
CAST( ArrivalDate AS DATE ) = DATEADD( DD, -1, GETDATE() )
You don't need to convert GETDATE(). It is already a date.
The CASE...
WHEN ( DepartureSeq > 1 ) THEN 'Not Eligible'
excludes any subsequent stays from the same reservation from being eligible for credit.
You can modify the CASE statement used for determining eligibility to return numeric values that you can perform math on if needed.
Couple of thoughts:
» You appear to have a duplicate reservation in your data with #33401156. I have removed it for this demonstrations purposes.
» Why are you having to cast your arrival and departure dates? Are they not date columns in your database?
» You should pick up a book or look into some online training in regard to SQL Best Practices. I'm going to be blunt: Your SQL is a mess of alias inconsistencies, (possible) datatype issues and inconsistent case sensitivity. Improving this will go a long ways toward making your life easier when it comes to refactoring code later on down the road.
P.S.Adding
, ( '102898784', 'JW#gmail.com', 'WAANGE', 225.23, 'BR', 4, 'RNR', '33401155', 'R', 'Active', '08/03/2018', '8/4/2018' )
to the values inserted into #resultset will demonstrate you how a "Not Eligible" works within your ruleset.
Data:
| id | Year | CPT | RVU | MOD |
+----+-------------+--------+-------+-----+
| 1 | 2015 | 99212 | 12 | 26 |
| 2 | 2015 | 99212 | 23 | TC |
| 3 | 2015 | 99212 | 56 | |
| 4 | 2015 | 99213 | 59 | 53 |
| 5 | 2015 | 99214 | 60 | |
| 6 | 2015 | 99215 | 99 | 53 |
| 7 | 2015 | 99216 | 78 | |
Output :
Return RVU = 12 for CPT 99212
Return RVU = 59 for CPT 99213
Return RVU = 60 for CPT 99214
Return RVU = 99 for CPT 99215
A CPT can have a MOD of 26, TC, 53, or NULL. My SQL needs to check for 26 first and if there is a 26 then return the RVU for that row, if not 26 then whatever else is left.
Worded differently: If a CPT does not have a MOD 26, then that CPT will only have one possible other MOD to choose from. If a CPT has a MOD 26 it will always have an accompanying TC and NULL MOD. If the CPT does not have a 26 MOD then I need to grab whatever RVU value is available regardless of the MOD. So the order of operations is to check for 26 first and if there, return RVU for that row, if no 26 then return the only possible RVU choice for that CPT.
You can use row_number() function with conditional ordering :
select top (1) with ties *
from table t
order by row_number() over (partition by cpt
order by (case when mod = '26' then 0 else 1 end)
);
Use row_number(). I think the prioritization is as follows:
select t.*
from (select t.*,
row_number() over (partition by cpt
order by (case mod when '26' then 1 when 2 end)
) as seqnum
from t
) t
where seqnum = 1;
You could do a self-join to get CPTs with a 26 mod in a separate virtual table.
Sample data creation:
declare #Data table
( id int identity
,[year] int default 2015
,CPT nchar(5)
,RVU nchar(2)
,[MOD] nchar(2)
)
insert into #Data
(CPT, RVU, [MOD])
values
('99212', '12', '26'),
('99212', '23', 'TC'),
('99212', '56', null),
('99213', '59', '53'),
('99214', '60', null),
('99215', '99', '53'),
('99216', '78', null)
Query:
select
Data.CPT
,case
when Is26.CPT is not null then Is26.RVU
else Data.RVU
end RVU
from #Data Data
left join #Data Is26 on
Is26.CPT = Data.CPT
and Is26.[MOD] = '26'
group by
Data.CPT
,case
when Is26.CPT is not null then Is26.RVU
else Data.RVU
end
I have a table like this one:
Yr | Mnth | W_ID | X_ID | Y_ID | Z_ID | Purchases | Sales | Returns |
2015 | 10 | 1 | 5210 | 1402 | 2 | 1000.00 | etc | etc |
2015 | 12 | 1 | 5210 | 1402 | 2 | 12000.00 | etc | etc |
2016 | 1 | 1 | 5210 | 1402 | 2 | 1000.00 | etc | etc |
2016 | 3 | 1 | 5210 | 1402 | 2 | etc | etc | etc |
2014 | 3 | 9 | 880 | 2 | 7 | etc | etc | etc |
2014 | 12 | 9 | 880 | 2 | 7 | etc | etc | etc |
2015 | 5 | 9 | 880 | 2 | 7 | etc | etc | etc |
2015 | 7 | 9 | 880 | 2 | 7 | etc | etc | etc |
For each combination of (W, X, Y, Z) I would like to insert the months that don't appear in the table and are between the first and last month.
In this example, for combination (W=1, X=5210, Y=1402, Z=2), I would like to have additional rows for 2015/11 and 2016/02, where Purchases, Sales and Returns are NULL. For combination (W=9, X=880, Y=2, Z=7) I would like to have additional rows for months between 2014/4 and 2014/11, 2015/01 and 2015/04, 2016/06.
I hope I have explained myself correctly.
Thank you in advance for any help you can provide.
The process is rather cumbersome in this case, but quite possible. One method uses a recursive CTE. Another uses a numbers table. I'm going to use the latter.
The idea is:
Find the minimum and maximum values for the year/month combination for each set of ids. For this, the values will be turned into months since time 0 using the formula year*12 + month.
Generate a bunch of numbers.
Generate all rows between the two values for each combination of ids.
For each generated row, use arithmetic to re-extract the year and month.
Use left join to bring in the original data.
The query looks like:
with n as (
select row_number() over (order by (select null)) - 1 as n -- start at 0
from master.spt_values
),
minmax as (
select w_id, x_id, y_id, z_id, min(yr*12 + mnth) as minyyyymm,
max(yr*12 + mnth) as maxyyyymm
from t
group by w_id, x_id, y_id, z_id
),
wxyz as (
select minmax.*, minmax.minyyyymm + n.n,
(minmax.minyyyymm + n.n) / 12 as yyyy,
((minmax.minyyyymm + n.n) % 12) + 1 as mm
from minmax join
n
on minmax.minyyyymm + n.n <= minmax.maxyyyymm
)
select wxyz.yyyy, wxyz.mm, wxyz.w_id, wxyz.x_id, wxyz.y_id, wxyz.z_id,
<columns from t here>
from wxyz left join
t
on wxyz.w_id = t.w_id and wxyz.x_id = t.x_id and wxyz.y_id = t.y_id and
wxyz.z_id = t.z_id and wxyz.yyyy = t.yr and wxyz.mm = t.mnth;
Thank you for your help.
Your solution works, but I noticed it is not very good in terms of performance, but meanwhile I have managed to get a solution for my problem.
DECLARE #start_date DATE, #end_date DATE;
SET #start_date = (SELECT MIN(EOMONTH(DATEFROMPARTS(Yr , Mnth, 1))) FROM Table_Input);
SET #end_date = (SELECT MAX(EOMONTH(DATEFROMPARTS(Yr , Mnth, 1))) FROM Table_Input);
DECLARE #tdates TABLE (Period DATE, Yr INT, Mnth INT);
WHILE #start_date <= #end_date
BEGIN
INSERT INTO #tdates(PEriod, Yr, Mnth) VALUES(#start_date, YEAR(#start_date), MONTH(#start_date));
SET #start_date = EOMONTH(DATEADD(mm,1,DATEFROMPARTS(YEAR(#start_date), MONTH(#start_date), 1)));
END
DECLARE #pks TABLE (W_ID NVARCHAR(50), X_ID NVARCHAR(50)
, Y_ID NVARCHAR(50), Z_ID NVARCHAR(50)
, PerMin DATE, PerMax DATE);
INSERT INTO #pks (W_ID, X_ID, Y_ID, Z_ID, PerMin, PerMax)
SELECT W_ID, X_ID, Y_ID, Z_ID
, MIN(EOMONTH(DATEFROMPARTS(Ano, Mes, 1))) AS PerMin
, MAX(EOMONTH(DATEFROMPARTS(Ano, Mes, 1))) AS PerMax
FROM Table1
GROUP BY W_ID, X_ID, Y_ID, Z_ID;
INSERT INTO Table_Output(W_ID, X_ID, Y_ID, Z_ID
, ComprasLiquidas, RTV, DevManuais, ComprasBrutas, Vendas, Stock, ReceitasComerciais)
SELECT TP.DB, TP.Ano, TP.Mes, TP.Supplier_Code, TP.Depart_Code, TP.BizUnit_Code
, TA.ComprasLiquidas, TA.RTV, TA.DevManuais, TA.ComprasBrutas, TA.Vendas, TA.Stock, TA.ReceitasComerciais
FROM
(
SELECT W_ID, X_ID, Y_ID, Z_ID
FROM #tdatas CROSS JOIN #pks
WHERE Period BETWEEN PerMin And PerMax
) AS TP
LEFT JOIN Table_Input AS TA
ON TP.W_ID = TA.W_ID AND TP.X_ID = TA.X_ID AND TP.Y_ID = TA.Y_ID
AND TP.Z_ID = TA.Z_ID
AND TP.Yr = TA.Yr
AND TP.Mnth = TA.Mnth
ORDER BY TP.W_ID, TP.X_ID, TP.Y_ID, TP.Z_ID, TP.Yr, TP.Mnth;
I do the following:
Get the Min and Max date of the entire table - #start_date and #end_date variables;
Create an auxiliary table with all dates between Min and Max - #tdates table;
Get all the combinations of (W_ID, X_ID, Y_ID, Z_ID) along with the min and max dates of that combination - #pks table;
Create the cartesian product between #tdates and #pks, and in the WHERE clause I filter the results between the Min and Max of the combination;
Compute a LEFT JOIN of the cartesian product table with the input data table.