SQL Server : SELECT and CASE - sql-server

I am trying to select data based on some parameters passed to my stored procedure. I have problems with the age, I am trying to do something like this:
If my stored procedure parameter #Age = 1 then I select age between 15 to 18, #Age = 2 then 19 - 25..., apparently this is incorrect, anyone can help. Thanks.:
SELECT
User
FROM
[Member] m
WHERE
((m.Gender = #Gender) or #Gender IS NULL)
and ((DATEDIFF(hour,m.DOB,GETDATE())/8766) Between
CASE
WHEN #Age = 1 THEN (SELECT DATEDIFF(hour, m.DOB, GETDATE())/8766 WHERE (SELECT DATEDIFF(hour, m.DOB, GETDATE())/8766) between 15 and 18)
WHEN #Age = 2 THEN (SELECT DATEDIFF(hour,m.DOB,GETDATE())/8766 WHERE (SELECT DATEDIFF(hour,m.DOB,GETDATE())/8766) between 19 and 25)
END)

I think this is what you are after (probably with some superfluous parenthesis):
Select
[User]
From
[Member] m
Where (
(m.Gender = #Gender) or
#Gender Is Null
) And (
(#Age = 1 And DateDiff(hour, m.Dob, GetDate())/8766 Between 15 and 18) Or
(#Age = 2 And DateDiff(hour, m.Dob, GetDate())/8766 Between 19 and 25)
)
If you've got a lot of clauses, it might be easier to read as (assuming a MemberID Primary Key)
Select
[User]
From
[Member] m
Inner Join (
Select
MemberID,
DateDiff(hour, m.Dob, GetDate())/8766 As Years
From
[Member]
) As y
On m.MemberID = y.MemberID
Where (
(m.Gender = #Gender) or
#Gender Is Null
) And (
(#Age = 1 And y.Year Between 15 and 18) Or
(#Age = 2 And y.Year Between 19 and 25)
)
Even better, you could add the ranges to a separate table called AgeRanges
+-------+------------+----------+
| AgeID | StartYears | EndYears |
+-------+------------+----------+
| 1 | 15 | 18 |
| 2 | 19 | 25 |
| ... | ... | ... |
+-------+------------+----------+
Select
[User]
From
[Member] m
Inner Join
[AgeRanges] a
On DateDiff(hour, m.Dob, GetDate())/8766 Between a.StartYears and a.EndYears And
a.AgeID = #Age
You could also make DateDiff(hour, m.Dob, GetDate())/8766 a computed column on your members table to simplify things (and make indexing possible if performance became an issue).

Related

Denomination distribution calculation

Some background story: Company A gives out vouchers to winners of a challenge. The SQL that I am currently writing needs to decide the required voucher denomination that sums to the value awarded to a person. I have a table that stores the denominations available for vouchers, depending on the country and currency.
In the example below, a particular person is awarded with €80 worth of vouchers.
The query below displays results of a lookup table for voucher denominations available for a particular country.
SELECT * FROM tblDenominationScheme WHERE CountryCode IN ('AT', 'US')
Result:
No. | CountryCode | VoucherName | VoucherValue
-------------------------------------------------
1 | AT | €50 Shop A | 50
2 | AT | €25 Shop A | 25
3 | AT | €15 Shop A | 15
4 | AT | €10 Shop A | 10
5 | US | $50 Store B | 50
6 | US | $10 Store B | 10
7 | US | $5 Store B | 5
My current SQL is as below to determine the required voucher denominations for €80 voucher:
DECLARE #CountryCode1 VARCHAR(2) = 'AT'
DECLARE #ChallengerID INT = 1172
DECLARE #RoundedAmount1 INT = 80
DECLARE #Vouchers INT
DECLARE #AmountAwarded INT = 0
SET #AmountAwarded = #RoundedAmount1
DROP TABLE IF EXISTS #tempVoucher
CREATE TABLE #tempVoucher
(
CountryCode VARCHAR(2),
ChallengerID INT,
AmountAwarded INT,
Vouchers INT,
)
WHILE (#RoundedAmount1 > 0)
BEGIN
SET #Vouchers = 0
SELECT TOP 1 #Vouchers = VoucherValue FROM tblDenominationScheme WHERE CountryCode = #CountryCode1 AND VoucherValue <= #RoundedAmount1 ORDER BY VoucherValue DESC
IF (#Vouchers > 0)
BEGIN
SET #RoundedAmount1 = #RoundedAmount1 - #Vouchers
END
ELSE
BEGIN
SELECT TOP 1 #Vouchers = VoucherValue FROM tblDenominationScheme WHERE CountryCode = #CountryCode1 ORDER BY VoucherValue
SET #RoundedAmount1 = #RoundedAmount1 - #RoundedAmount1
END
INSERT INTO #tempVoucher VALUES (#CountryCode1,#ChallengerID, #AmountAwarded, #Vouchers)
END
SELECT * FROM #tempVoucher
Result from the SQL above:
No. | CountryCode | ChallengerID | AmountAwarded | Vouchers
--------------------------------------------------------------
1 | AT | 1172 | 80 | 50
2 | AT | 1172 | 80 | 25
3 | AT | 1172 | 80 | 10
NOTE: The value in AmountAwarded column will be the same for all 3 rows. The amount in the Vouchers column for the 3 rows should sum up to 80.
The result above is obviously incorrect, because if you sum up the values in the Vouchers column, it gives you 85, which is 5 more than the AmountAwarded
Expected result (or at least closest):
No. | CountryCode | ChallengerID | AmountAwarded | Vouchers
--------------------------------------------------------------
1 | AT | 1172 | 80 | 50
2 | AT | 1172 | 80 | 10
3 | AT | 1172 | 80 | 10
4 | AT | 1172 | 80 | 10
Anyone able to help?
This might be an expensive query, but gets you different options to deliver up to 7 vouchers to get you the expected result. This, however, will generate a huge amount of reads if the rows increase or the amount of vouchers can be greater.
DECLARE #CountryCode1 VARCHAR(2) = 'AT'
DECLARE #RoundedAmount1 INT = 80;
WITH cteDenominations AS(
SELECT No, VoucherValue
FROM tblDenominationScheme
WHERE CountryCode = #CountryCode1
UNION ALL
SELECT 10000, 0
),
ctePermutations AS(
SELECT a.No AS a_No,
a.VoucherValue AS a_Value,
b.No AS b_No,
b.VoucherValue AS b_Value,
c.No AS c_No,
c.VoucherValue AS c_Value,
d.No AS d_No,
d.VoucherValue AS d_Value,
e.No AS e_No,
e.VoucherValue AS e_Value,
f.No AS f_No,
f.VoucherValue AS f_Value,
g.No AS g_No,
g.VoucherValue AS g_Value,
ROW_NUMBER() OVER(ORDER BY a.No, b.No, c.No, d.No) Permutation
FROM cteDenominations a
JOIN cteDenominations b ON a.VoucherValue >= b.VoucherValue
JOIN cteDenominations c ON b.VoucherValue >= c.VoucherValue
JOIN cteDenominations d ON c.VoucherValue >= d.VoucherValue
JOIN cteDenominations e ON d.VoucherValue >= e.VoucherValue
JOIN cteDenominations f ON e.VoucherValue >= f.VoucherValue
JOIN cteDenominations g ON f.VoucherValue >= g.VoucherValue
WHERE #RoundedAmount1 = a.VoucherValue
+ b.VoucherValue
+ c.VoucherValue
+ d.VoucherValue
+ e.VoucherValue
+ f.VoucherValue
+ g.VoucherValue
)
SELECT Permutation,
u.No,
u.VoucherValue
FROM ctePermutations
CROSS APPLY (VALUES(a_No, a_Value),
(b_No, b_Value),
(c_No, c_Value),
(d_No, d_Value),
(e_No, e_Value),
(f_No, f_Value),
(g_No, g_Value))u(No, VoucherValue)
WHERE VoucherValue > 0
AND Permutation = 1 --Remove this to get all possibilities
;
Looks like you need to solve a equation:
80 = n1*v1 + k2*n2...
where v1,v2 ... are values which you store in database
And you need to find n1, n2 ... , which are in {0, N}
There is no way how to implement it in SQL. Except - over all possible values, but it's not the smarter way.
Also, see this info:
https://math.stackexchange.com/questions/431367/solving-a-first-order-diophantine-equation-with-many-terms
Logic
Find the largest amount (that is less than or equal to starting amount) vouchers of 1 denomination can make.
Subtract this value from starting amount to get remainder,
Find the largest amount (that is less than or equal to remainder) a number of vouchers of 1 smaller denomination can make.
Subtract this value from previous remainder.
Go back to step 3
Features:
Handles multiple best combinations.
Small number of combinations are searched.
On my laptop: 100 runs take about 3 seconds
Notes
Performance may be improved by saving output of VoucherCombinations to a table variable and then using it in subsequent CTEs.
Code:
DECLARE #Vouchers TABLE( CountryCode CHAR( 2 ), VoucherValue DECIMAL( 10, 2 ))
INSERT INTO #Vouchers VALUES( 'AT', 50 ), ( 'AT', 40 ), ( 'AT', 25 ), ( 'AT', 20 ), ( 'AT', 15 ), ( 'AT', 10 ), ( 'US', 50 ), ( 'US', 10 ), ( 'US', 5 );
-- Small number table
-- Limits maximum count of Vouchers of a given denomination.
DECLARE #Numbers TABLE( Num INT )
INSERT INTO #Numbers VALUES( 1 ), ( 2 ), ( 3 ), ( 4 ), ( 5 ), ( 6 ), ( 7 ), ( 8 ), ( 9 ), ( 10 )
DECLARE #TargetAmount DECIMAL( 10, 2 ) = 60;
DECLARE #CountryCode CHAR( 2 ) = 'AT';
;WITH VoucherCombinations
AS (
-- Anchor
SELECT ROW_NUMBER() OVER( ORDER BY VoucherValue DESC ) AS ParentGroupID,
ROW_NUMBER() OVER( ORDER BY VoucherValue DESC ) AS SubGroupID,
1 AS IterationID,
VoucherValue, Num AS VoucherCumulativeCount,
CAST( VoucherValue * Num AS DECIMAL( 10, 2 )) AS TotalDenominationValue,
CAST( #TargetAmount - ( VoucherValue * Num ) AS DECIMAL( 10, 2 )) AS Remainder
FROM #Vouchers
-- Find the largest amount a given Voucher denomination can produce that is less than or equal to #TargetAmount
INNER JOIN #Numbers ON ( VoucherValue * Num ) <= #TargetAmount AND #TargetAmount - ( VoucherValue * Num ) < VoucherValue
WHERE CountryCode = #CountryCode
UNION ALL
-- Recursive query
SELECT SubGroupID,
SubGroupID * 10 + ROW_NUMBER() OVER( ORDER BY V.VoucherValue DESC ) AS SubGroupID,
IterationID + 1,
V.VoucherValue, VoucherCumulativeCount + N.Num AS VoucherCount,
CAST( V.VoucherValue * N.Num AS DECIMAL( 10, 2 )) AS TotalDenominationValue,
CAST( Remainder - ( V.VoucherValue * N.Num ) AS DECIMAL( 10, 2 )) AS Remainder
FROM VoucherCombinations AS VP
-- For each denomination look at the smaller denominations
INNER JOIN #Vouchers AS V ON VP.VoucherValue > V.VoucherValue
INNER JOIN #Numbers AS N ON V.VoucherValue * N.Num <= Remainder AND Remainder - ( V.VoucherValue * N.Num ) < V.VoucherValue
WHERE CountryCode = #CountryCode
),
-- Discard invalid combinations i.e. remainder is not 0
VoucherPoolValid AS(
SELECT *, DENSE_RANK() OVER( ORDER BY VoucherCumulativeCount ASC ) AS BestCombos
FROM VoucherCombinations
WHERE Remainder = 0
),
-- Find best combinations i.e. smallest number of Vouchers; Note: logic supports having more than 1 best combination
VoucherPoolBestCombos AS(
SELECT *, ROW_NUMBER() OVER( ORDER BY BestCombos ASC ) AS ComboID
FROM VoucherPoolValid
WHERE BestCombos = 1
),
-- Return all denominations for each combination
VoucherPoolAllDetails AS(
SELECT *
FROM VoucherPoolBestCombos
UNION ALL
SELECT Parent.*, BestCombos, ComboID
FROM VoucherPoolAllDetails AS Child
INNER JOIN VoucherCombinations AS Parent ON Child.ParentGroupID = Parent.SubGroupID
WHERE Child.SubGroupID <> Child.ParentGroupID
)
SELECT * FROM VoucherPoolAllDetails
ORDER BY ComboID

Logical lock in WHERE clause

Since FUNCCODE is shared between POSCODE and DEVCODE I can't call out both at the same time to eliminate the null values to insert the data into a separate table called JOINT. POSCODE and DEVCODE are FKs. I know there has to be a much easier way of doing this. I spent the last 2 weeks trying to craft a solution... It feels like I don't understand one thing to make this work. Any advice is appreciated.
Table setup
FUNCCODE | POSCODE | DEVCODE
11 1 NULL
12 NULL 1
13 2 NULL
14 NULL 2
The table needs to be rearranged and then inserted into a separate table called JOINT which is setup as:
POSCODE | POSFUNCCODE |DEVCODE | DEVFUNCCODE
1 11 1 12
2 13 2 14
Some of my attempts XD
Each join creates only 2 of the columns I need
SELECT
dbo.POSITION.POSCODE AS POSCODE,
dbo.FUNC.FUNCCODE AS POSFUNCCODE
FROM FUNC
INNER JOIN POSITION ON dbo.POSITION.POSCODE = dbo.FUNC.POSCODE
UNION ALL
SELECT
dbo.DEVICE.DEVCODE AS DEVCODE,
dbo.FUNC.FUNCCODE AS DEVFUNCCODE
FROM FUNC
INNER JOIN DEVICE ON dbo.DEVICE.DEVCODE = dbo.FUNC.DEVCODE
ORDER BY 1;
Only inserts the last row values
DECLARE #DATE DATETIME = GETDATE()
DECLARE #PC INT;
SELECT #PC = POSCODE
FROM func
WHERE poscode != 0
ORDER BY 1;
DECLARE #FCP INT;
SELECT #FCP = FUNCCODE
FROM FUNC
WHERE POSCODE != 0
ORDER BY 1;
DECLARE #DC INT;
SELECT #DC = devcode
FROM func
WHERE devcode != 0
ORDER BY 1;
DECLARE #FCD INT
SELECT #FCD = FUNCCODE
FROM FUNC
WHERE DEVCODE != 0
ORDER BY 1
INSERT INTO JOINT (POSCODE, POSFUNCCODE, DEVCODE, DEVFUNCCODE, JOINTTIME,
JOINTSTATUS)
VALUES (#PC, #FCP, #DC, #FCD, #DATE, 1)
If my understanding of your problem is correct, I think the below query would be a solution.
INSERT INTO JOINT (POSTCODE, DEVCODE, POSFUNCCODE, DEVFUNCCODE)
SELECT
POSTCODE,
DEVCODE,
MAX(CASE
WHEN POSCODE IS NOT NULL THEN FUNCCODE
ELSE NULL
END) POSFUNCCODE,
MAX(CASE
WHEN DEVCODE IS NOT NULL THEN FUNCCODE
ELSE NULL
END) DEVFUNCCODE
FROM FUNC
GROUP BY POSTCODE,DEVCODE
Second version after a better understanding
INSERT INTO JOINT (POSTCODE, DEVCODE, POSFUNCCODE, DEVFUNCCODE)
SELECT
t1.POSTCODE,
t2.DEVCODE,
t1.POSFUNCCODE,
t2.DEVFUNCCODE
(SELECT
POSTCODE,
MAX(CASE
WHEN POSCODE IS NOT NULL THEN FUNCCODE
ELSE NULL
END) POSFUNCCODE
FROM FUNC
GROUP BY POSTCODE) t1
INNER JOIN
(SELECT
DEVCODE,
MAX(CASE
WHEN DEVCODE IS NOT NULL THEN FUNCCODE
ELSE NULL
END) DEVFUNCCODE
FROM FUNC
GROUP BY DEVCODE) t2
ON t1.POSTCODE = t2.DEVCODE
Against my better judgment, I created a loop which inserted the proper values.... I know this is not the correct way to approach this, please excuse me since my experience in SQL is very little. But this corrects the interface issue in the client program. Shout out to #user4219031 for guidance!
DECLARE #DATE DATETIME = GETDATE()
DECLARE #PC INT = 0;
DECLARE #FCP INT = 12;
DECLARE #DC INT = 0;
DECLARE #FCD INT = 13
WHILE ( #PC < 739 )
BEGIN
INSERT INTO JOINT (POSCODE, POSFUNCCODE, DEVCODE, DEVFUNCCODE, JOINTTIME, JOINTSTATUS)
VALUES( #PC, #FCP, #DC, #FCD, #DATE, 1)
SET #PC = #PC + 1
SET #FCP = #FCP +2
SET #DC = #DC + 1
SET #FCD = #FCD + 2
END
EDIT: I changed my query to account for NULL POSCODE or DEVCODE.
SQL Fiddle
MS SQL Server 2017 Schema Setup:
CREATE TABLE t ( FUNCCODE int, POSCODE int, DEVCODE int ) ;
INSERT INTO t (FUNCCODE, POSCODE, DEVCODE)
VALUES
( 11, 1, NULL )
, ( 12, NULL, 1 )
, ( 13, 2, NULL )
, ( 14, NULL, 2 )
, ( 42, NULL, 1 )
, ( 77, NULL, 7 )
, ( 88, NULL, 8 )
, ( 99, 9, NULL )
;
Create New Table And Insert Records
CREATE TABLE ti ( POSCODE int, pos_FUNCCODE int, DEVCODE int, dev_FUNCCODE int ) ;
INSERT INTO ti ( POSCODE, pos_FUNCCODE, DEVCODE, dev_FUNCCODE)
SELECT t1.POSCODE
, t1.FUNCCODE AS pos_FUNCODE
, t2.DEVCODE
, t2.FUNCCODE AS dev_FUNCCODE
FROM t t1
FULL OUTER JOIN t t2 ON t1.POSCODE = t2.DEVCODE
WHERE t1.POSCODE IS NOT NULL OR t2.DEVCODE IS NOT NULL
;
What's In The New Table?:
SELECT * FROM ti
Results:
| POSCODE | pos_FUNCCODE | DEVCODE | dev_FUNCCODE |
|---------|--------------|---------|--------------|
| 1 | 11 | 1 | 12 |
| 1 | 11 | 1 | 42 |
| 2 | 13 | 2 | 14 |
| 9 | 99 | (null) | (null) |
| (null) | (null) | 7 | 77 |
| (null) | (null) | 8 | 88 |
==========ORIGINAL==========
SQL Fiddle
MS SQL Server 2017 Schema Setup:
CREATE TABLE t ( FUNCCODE int, POSCODE int, DEVCODE int ) ;
INSERT INTO t (FUNCCODE, POSCODE, DEVCODE)
VALUES
( 11, 1, NULL )
, ( 12, NULL, 1 )
, ( 13, 2, NULL )
, ( 14, NULL, 2 )
;
Create New Table And Insert Records
CREATE TABLE ti ( POSCODE int, pos_FUNCCODE int, DEVCODE int, dev_FUNCCODE int ) ;
INSERT INTO ti ( POSCODE, pos_FUNCCODE, DEVCODE, dev_FUNCCODE)
SELECT t1.POSCODE
, t1.FUNCCODE AS pos_FUNCODE
, t2.DEVCODE
, t2.FUNCCODE AS dev_FUNCCODE
FROM t t1
INNER JOIN t t2 ON t1.POSCODE = t2.DEVCODE
WHERE t1.POSCODE IS NOT NULL
;
What's In The New Table?:
SELECT * FROM ti
Results:
| POSCODE | pos_FUNCCODE | DEVCODE | dev_FUNCCODE |
|---------|--------------|---------|--------------|
| 1 | 11 | 1 | 12 |
| 2 | 13 | 2 | 14 |

Update multiple rows SQL Server 2008

Good day, I'm trying to update my table. Because there was an error(s) in my website. first Please check my table. (Penilaian_Header)
IdPenilaian | KodePenilaian | Nip | PositionCode | Total
1613 ----- 1603405 P028 0
1618 ----- 1602999 P028 0
1641 PE0001568 603060 P040 35
1640 PE0001567 1411862 P007 35
as you can see. There are two rows that KodePenilaian empty. So is there any chance to fill it ? so the result will be like this.
IdPenilaian | KodePenilaian | Nip | PositionCode | Total
1613 PE0001570 1603405 P028 0
1618 PE0001569 1602999 P028 0
1641 PE0001568 603060 P040 35
1640 PE0001567 1411862 P007 35
This how i generate KodePenilaian
select case
when right(max(KodePenilaian),7) is null then 'PE0000001'
else ('PE' + RIGHT('0000000' + cast(right(max(KodePenilaian),7) + 1 as nvarchar),7))
end KodePenilaian from Penilaian_Header
and here is there result when i run it
KodePenilaian
PE0001569
Thanks, Sorry for my bad english.
Not really used to sql server 2008
Try something like this:
update Penilaian_Header pen
set pen.KodePenilaian =
(select case
when right(max(newKode.KodePenilaian),7) is null then 'PE0000001'
else ('PE' + RIGHT('0000000' + cast(right(max(newKode.KodePenilaian),7) + 1 as nvarchar),7))
end KodePenilaian
from Penilaian_Header newKode)
where pen.KodePenilain = NULL
if ----- is NULL in your table
You can try this way...
;WITH cte
AS (SELECT *,
MAX(CONVERT(int, REPLACE(KodePenilaian, 'PE000', ''))) OVER () AS MaxNum,
ROW_NUMBER() OVER (ORDER BY kodePenilaian) AS rn
FROM YourTable)
UPDATE cte SET KodePenilaian = concat('PE000', maxnum + rn)
WHERE KodePenilaian IS NULL
Your table
create table YourTable (
IdPenilaian int, KodePenilaian varchar(20), Nip int, PositionCode varchar(10), Total INT)
insert into YourTable
(IdPenilaian , KodePenilaian , Nip , PositionCode , Total) values
( 1613 , NULL , 1603405 ,'P028', 0 )
,( 1618 , NULL , 1602999 ,'P028', 0 )
,( 1641 , 'PE0001568' , 603060 ,'P040', 35 )
,( 1640 , 'PE0001567' , 1411862 ,'P007', 35 )

Days difference in SQL Server

I have a question about SQL Server.
Table: holidaylist
Date | weekendStatus | Holidaystatus
2015-12-01 | 0 | 0
2015-12-02 | 0 | 0
2015-12-03 | 0 | 0
2015-12-04 | 1 | 0
2015-12-05 | 1 | 0
2015-12-06 | 0 | 1
2015-12-07 | 0 | 0
2015-12-08 | 0 | 0
2015-12-09 | 0 | 1
2015-12-10 | 0 | 0
2015-12-11 | 0 | 0
2015-12-12 | 1 | 1
2015-12-13 | 1 | 0
Table: emp
empid | doj | dos
1 | 2015-12-01 | 2015-12-06
2 |2015-12-01 | 2015-12-13
3 |2015-12-03 |2015-12-13
I want get days difference from dos-doj withoutweekenstatusandholidaysstatus
and includeweekendandholidaystatus
I want output like this:
Empid | doj | dos |includeweekendandholidays | witoutincludeweekendandholidayslist
1 | 2015-12-01 |2015-12-06 | 5 | 3
2 | 2015-12-01 |2015-12-13 | 12 | 8
3 | 2015-12-03 |2015-12-13 | 10 | 6
I tried this query:
select
a.empid, a.doj, a.dos,
case
when b.weekendstatus = 1 and c.Holidaystatus = 1
then datediff(day, c.date, b.date)
end as includeweekenandholidays
case
when b.weekendstatus != 1 or c.Holidaystatus = 1
then datediff(day, c.date, b.date)
end as witoutincludeweekendandholidayslist
from
emp a
left join
holidaylist b on a.doj = b.date
left join
holidaylist c on a.dos = c.date
Above query not given expected result please tell me how to write query to achieve this task in SQL Server
Try this :
select a.empid,
a.doj,a.dos,
IncludeRest = (select count(h.date) from holidaylist h where e.doj<=h.date AND e.dos>=h.date),
ExcludeRest = (select count(h.date) from holidaylist h where e.doj<=h.date AND e.dos>=h.date AND h.weekendstatus = 0 AND h.holdaystatus = 0)
from emp e
you can use a CASE in your COUNT to determine whether or not to count that day..
SELECT
e.empid,
e.doj,
e.dos,
COUNT(*) includeweekendandholidays,
COUNT(CASE WHEN Holidaystatus = 0
AND [weekendStatus] = 0 THEN 1
END) withoutincludeweekendandholidayslist
FROM
emp e
JOIN holidaylist hl ON hl.Date >= e.doj
AND hl.Date < e.dos
GROUP BY
e.empid,
e.doj,
e.dos
This might perform better since it only joins to holidaylist table on records you need..
SELECT
e.empid,
e.doj,
e.dos,
DATEDIFF(DAY, e.doj, e.dos) includeweekendandholidays,
COUNT(*) withoutincludeweekendandholidayslist
FROM
emp e
JOIN holidaylist hl ON hl.Date BETWEEN e.doj AND e.dos
WHERE
weekendStatus = 0
AND Holidaystatus = 0
GROUP BY
e.empid,
e.doj,
e.dos,
DATEDIFF(DAY, e.doj, e.dos)
I don't get your output though since it only appears that you're excluding weekends and not holidays..
You can use OUTER APPLY:
SELECT a.empid, a.doj, a.dos,
DATEDIFF(d, a.doj, a.dos) + 1 AS include,
DATEDIFF(d, a.doj, a.dos) + 1 - b.wd - b.hd + b.common AS without
FROM emp AS a
OUTER APPLY (
SELECT SUM(weekendStatus) AS wd,
SUM(Holidaystatus) AS hd,
COUNT(CASE WHEN weekendStatus = 1 AND Holidaystatus = 1 THEN 1 END) AS common
FROM holidaylist
WHERE [Date] BETWEEN a.doj AND a.dos) AS b
For each row of table emp, OUTER APPLY calculates weekendStatus=1 and Holidaystatus=1 rows that correspond to the interval of this row.
Calculated fields selected:
include is the total number of days of the emp interval including weekend days and holidays.
without is the total number of days of the emp interval minus weekend days and holidays. common field makes sure common weekend days and holidays are not subtracted twice.
Note: The above query includes start and end days of the interval in the calculations, so the interval considered is [doj - dos]. You can change the predicate of the WHERE clause in the OUTER APPLY operation so as to exclude start, end, or both, days of the interval.
Demo here
try another way with cross join
select t.empid,t.doj,t.dos,datediff(day,t.doj,t.dos) includeweekendandholidays,
datediff(day,t.doj,t.dos)-isnull(t1.wes,0) as witoutincludeweekendandholidayslist
from #emp t left join (
select empid, sum(hd.Holidaystatus+hd.weekendStatus) wes from
#emp emp cross join #holidaylist hd where hd.[Date] between doj
and dateadd(day,-1,dos) group by empid) t1 on t.empid=t1.empid
sample data
declare #holidaylist table ([Date] date, weekendStatus int, Holidaystatus int)
insert into #holidaylist([Date], weekendStatus, Holidaystatus) values
('2015-12-01' , 0 , 0),
('2015-12-02' , 0 , 0),
('2015-12-03' , 0 , 0),
('2015-12-04' , 1 , 0),
('2015-12-05' , 1 , 0),
('2015-12-06' , 0 , 1),
('2015-12-07' , 0 , 0),
('2015-12-08' , 0 , 0),
('2015-12-09' , 0 , 1),
('2015-12-10' , 0 , 0),
('2015-12-11' , 0 , 0),
('2015-12-12' , 1 , 1),
('2015-12-13' , 1 , 0)
declare #emp table(empid int, doj date, dos date)
insert into #emp (empid,doj,dos) values
(1 , '2015-12-01' , '2015-12-06'),
(2 ,'2015-12-01' , '2015-12-13'),
(3 ,'2015-12-03' ,'2015-12-13')

SQL Server: update multiple rows with one statement with calculation

I don't know is this possible in one SQL statement to update the refundamt?
I have these three rows initially, using insert statement:
NO | TRANAMT | REFUNDAMT
1 | 100 | 0
2 | 200 | 0
3 | 300 | 0
If refund is 350, the refundamt will be updated as follow, the refundamt cannot be more then the tranamt:
NO | TRANAMT | REFUNDAMT
1 | 100 | 100
2 | 200 | 200
3 | 300 | 50
When refund again with 50, the refundamt will be updated as follow
NO | TRANAMT | REFUNDAMT
1 | 100 | 100
2 | 200 | 200
3 | 300 | 100
I think this is impossible to update refundamt using one sql statement. How about multiple satement? Not hoping to use store procedure. Can I use select update?
I think this works. It's a single statement. Not the prettiest. And I'd imagine the real Transactions table has more columns (e.g. an account number):
declare #RefundAmt int
set #RefundAmt = 350
; with Refunds as (
select
top 1
NO,
TRANAMT,
CASE WHEN TRANAMT-REFUNDAMT < #RefundAmt THEN TRANAMT ELSE REFUNDAMT + #RefundAmt END as REFUNDAMT,
CASE WHEN TRANAMT-REFUNDAMT < #RefundAmt THEN #RefundAmt - (TRANAMT-REFUNDAMT) ELSE 0 END as Remaining
from
dbo.Transactions
where REFUNDAMT < TRANAMT ORDER BY NO
union all
select
t2.NO,
t2.TRANAMT,
CASE WHEN t2.TRANAMT-t2.REFUNDAMT < t1.Remaining THEN t2.TRANAMT ELSE t2.REFUNDAMT + t1.Remaining END as REFUNDAMT,
CASE WHEN t2.TRANAMT-t2.REFUNDAMT < t1.Remaining THEN t1.Remaining - (t2.TRANAMT-t2.REFUNDAMT) ELSE 0 END as Remaining
from
Refunds t1
inner join
dbo.Transactions t2
on
t1.NO = t2.NO - 1 and
t1.Remaining > 0
)
update t set REFUNDAMT = r.REFUNDAMT
from
dbo.Transactions t
inner join
Refunds r
on
t.NO = r.NO
Bit messy, but here's what I came up with:
Code to set up your base tables...
-- setup code
CREATE TABLE #tmp (
[NO] int identity(1,1),
[TRAN] money,
[REFUND] money
)
INSERT #tmp VALUES (100, 0)
INSERT #tmp VALUES (200, 0)
INSERT #tmp VALUES (300, 0)
Code to add your refund amount...
DECLARE #amt money SET #amt = 50
UPDATE u
SET REFUND =
CASE WHEN u.[REFUND] = u.[TRAN] THEN u.[REFUND] --'already full - do nothing'
WHEN cumulativepretran >= aiming_for_ref THEN u.[REFUND] --'after all - do nothing'
WHEN cumulativetran + cumulativeref > ((totalref + #amt) - u.[TRAN]) THEN (totalref + #amt + u.[REFUND]) - (cumulativeref) --'calc'
ELSE u.[TRAN] END -- 'fill-er-up'
FROM #tmp u
INNER JOIN (SELECT *,
ISNULL((select sum([REFUND]) FROM #tmp WHERE [NO] <= t.[NO]), 0) 'cumulativeref',
ISNULL((select sum([TRAN]) FROM #tmp WHERE [NO] <= t.[NO]), 0) 'cumulativetran',
ISNULL((select sum([REFUND]) FROM #tmp WHERE [NO] < t.[NO]), 0) 'cumulativepreref',
ISNULL((select sum([TRAN]) FROM #tmp WHERE [NO] < t.[NO]), 0) 'cumulativepretran',
ISNULL((select sum([REFUND]) FROM #tmp), 0) 'totalref',
ISNULL((select sum([REFUND]) FROM #tmp), 0) + #amt 'aiming_for_ref'
FROM #tmp t) as m ON m.[NO] = u.[NO]
Something like...: (needs work, but gives a conceptual idea)
Declare #pRefund Money
SET #pRefund = 350
WHILE #pRefund > 0
BEGIN
/** SELECT Transaction Amount of min primary key where recorded refundamt is less than tranamt
Apply matching refund
Reduce #pRefund by amount applied
If #pRefund is now less then next highest TransAMT Then apply whats left otherwise loop again
refund has now been all applied and loop will exit */
END

Resources