Oracle left join behaving like inner join [closed] - sql

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 4 years ago.
Improve this question
I have Two tables:
Dates:
|ReportHeader|
--------------
| 2015-04|
| 2015-05|
| 2015-06|
Data:
|ReportHeader|Customer|Sales|
-----------------------------
| 2015-04| Plop| 684|
| 2015-06| Plop| 486|
I have my query:
select
Dates.ReportHeader,
Data.Customer,
Data.Sales
from Dates
left outer join data on Dates.ReportHeader = Data.ReportHeader
What I would expect back is:
|ReportHeader|Customer|Sales|
-----------------------------
| 2015-04| Plop| 684|
| 2015-05| null| null|
| 2015-06| Plop| 486|
The results that I am getting are:
|ReportHeader|Customer|Sales|
-----------------------------
| 2015-04| Plop| 684|
| 2015-06| Plop| 486|
Is there any reason to be found why this left join would behave like an inner join?
Or do I just not understand how a left join is supposed to work.
The Oracle version i am using is:
Oracle Database 12c Standard Edition Release 12.1.0.2.0 - 64bit Production
Thanks in advance for any help extended
edit:
It seems i have over simplified the problem so i better put the whole query here:
WITH
L0 AS (SELECT 1 C from dual UNION ALL SELECT 1 O from dual),
L1 AS (SELECT 1 C FROM L0 A CROSS JOIN L0 B),
L2 AS (SELECT 1 C FROM L1 A CROSS JOIN L1 B),
L3 AS (SELECT 1 C FROM L2 A CROSS JOIN L2 B),
L4 AS (SELECT 1 C FROM L3 A CROSS JOIN L3 B),
Nums AS (SELECT 0 N FROM dual union SELECT ROW_NUMBER() OVER ( ORDER BY (SELECT NULL from dual) ) N FROM L4),
Dates as (
select distinct
to_char(to_date(last_day(current_timestamp + (-1 * N))) , 'yyyy-MM') as ReportHeader,
to_date(add_months(last_day(current_timestamp + (-1 * N)),-1)+1) as FirstDayOfMonth,
to_date(current_timestamp + (-1 * N)) as ReportDate,
to_date(last_day(current_timestamp + (-1 * N))) as LastDayOfMonth
from Nums
where N <= 3 * 365
), Invoices AS (
select
TP.ID,
TP.Name,
h.FORMATTEDINVOICENUMBER,
l.SCHEDULEID,
to_date(h.INVOICEDUEDATE) as date_due,
to_date(L.EFFECTIVEDATEFULLYPAID) as date_payed
from ODSTHIRDPARTY TP
LEFT JOIN ODSINVOICELINE L on L.INVCUSID = TP.ID
LEFT JOIN ODSINVOICEHEADER H on h.ID = l.INVOICEHEADERID
where TP.NAME not in ('<None>','<Unknown>')
), data as (
select distinct
Invoices.Name as Customer,
Dates.ReportHeader,
count(distinct Invoices.SCHEDULEID) over (partition by Dates.ReportHeader, Invoices.Name) as NumContracts,
floor(avg(
case
when Invoices.date_due > Dates.LastDayOfMonth THEN 0
when coalesce(Invoices.date_payed, current_date) < Dates.FirstDayOfMonth THEN 0
when Invoices.date_payed < Invoices.date_due then Invoices.date_payed - Invoices.date_due
else to_date(least(coalesce(Invoices.date_payed, current_date), Dates.LastDayOfMonth )) - to_date(Invoices.date_due)
end
) over (partition by Dates.ReportHeader, Invoices.Name)) past_due,
Dates.FirstDayOfMonth as ReportStartDate,
Dates.LastDayOfMonth as ReportEndDate,
coalesce(Invoices.date_payed, current_date) as calc_date
from Dates
left join Invoices on Dates.ReportDate between Invoices.date_due and coalesce(Invoices.date_payed, current_date)
where coalesce(Invoices.date_payed, current_date) >= Invoices.date_due
)
select distinct
Dates.ReportHeader,
data.Customer,
data.NumContracts,
data.past_due
from Dates
left join data on data.ReportHeader = Dates.ReportHeader
order by data.Customer,dates.ReportHeader
The troublesome part is the final query. Select from dates left join on data
For those interested, as #APC was saying in the comments. Create a testable use case. Doing that triggered me to rewrite the complete query that does work now. My initial premise was wrong and i took an initial approach that was wrong.
Revised query below:
WITH
L0 AS (SELECT 1 C from dual UNION ALL SELECT 1 O from dual),
L1 AS (SELECT 1 C FROM L0 A CROSS JOIN L0 B),
L2 AS (SELECT 1 C FROM L1 A CROSS JOIN L1 B),
L3 AS (SELECT 1 C FROM L2 A CROSS JOIN L2 B),
L4 AS (SELECT 1 C FROM L3 A CROSS JOIN L3 B),
Nums AS (SELECT 0 N FROM dual union SELECT ROW_NUMBER() OVER ( ORDER BY (SELECT NULL from dual) ) N FROM L4),
Dates as (
select distinct
to_char(to_date(last_day(current_timestamp + (-1 * N))), 'yyyy-MM') as ReportHeader,
to_date(add_months(last_day(current_timestamp + (-1 * N)), -1) + 1) as FirstDayOfMonth,
to_date(current_timestamp + (-1 * N)) as ReportDate,
to_date(last_day(current_timestamp + (-1 * N))) as LastDayOfMonth
from Nums
where N <= 2 * 365
), ThirdParties as (
select distinct
TP.ID,
TP.Name,
Dates.ReportHeader,
Dates.FirstDayOfMonth,
Dates.ReportDate,
Dates.LastDayOfMonth
from ODSTHIRDPARTY TP
cross join Dates
where TP.NAME not in ('<None>','<Unknown>')
), Invoices AS (
select distinct
TP.ID,
TP.Name,
TP.ReportHeader,
TP.FirstDayOfMonth,
OH.FORMATTEDINVOICENUMBER,
TP.LastDayOfMonth,
il.SCHEDULEID,
to_date(il.invoiceReferenceDate) as date_due,
to_date(iL.EFFECTIVEDATEFULLYPAID) as date_payed,
case
when il.id is null then 0
else
case
when to_date(il.invoiceReferenceDate) <= to_date(coalesce(iL.EFFECTIVEDATEFULLYPAID,least(current_date,tp.LastDayOfMonth))) then
to_date(coalesce(iL.EFFECTIVEDATEFULLYPAID,least(current_date,tp.LastDayOfMonth))) - to_date(il.invoiceReferenceDate)
else 0
end
end as daysLate
from ThirdParties TP
LEFT JOIN ODSINVOICELINE IL on IL.INVCUSID = TP.ID and TP.ReportDate
between il.invoiceReferenceDate and coalesce(il.EFFECTIVEDATEFULLYPAID, current_date)
left join ODSINVOICEHEADER OH on IL.INVOICEHEADERID = OH.ID
), data as (
select distinct
Invoices.ReportHeader,
Invoices.Name as Customer,
count(distinct Invoices.SCHEDULEID) over (partition by Invoices.ReportHeader, Invoices.Name) as NumContracts,
floor(avg(
case
when Invoices.date_due > Invoices.LastDayOfMonth THEN 0
when coalesce(Invoices.date_payed, current_date) < Invoices.FirstDayOfMonth THEN 0
when Invoices.date_payed < Invoices.date_due then Invoices.date_payed - Invoices.date_due
else to_date(least(coalesce(Invoices.date_payed, current_date), Invoices.LastDayOfMonth )) - to_date(Invoices.date_due)
end
) over (partition by Invoices.ReportHeader, Invoices.Name)) past_due,
Invoices.FirstDayOfMonth as ReportStartDate,
Invoices.LastDayOfMonth as ReportEndDate,
coalesce(Invoices.date_payed, current_date) as calc_date
from Invoices
-- and coalesce(Invoices.date_payed, current_date) >= Invoices.date_due
order by Invoices.Name, Invoices.reportheader
), upvt as (
select distinct
row_number()
over (
partition by customer
order by ReportHeader ) as ColNum,
data.ReportHeader ColName,
data.Customer,
data.NumContracts,
data.past_due
from data
), pvt as (
select * from (
select Customer, ColName, ColNum, NumContracts, past_due from upvt
) pivot (
MAX(past_due) as DueDays, MAX(NumContracts) as Contracts, Max(ColName) as ColName
for ColNum in ( '1', '2', '3', '4', '5', '6', '7', '8', '9','10','11','12','13','14','15',
'16','17','18','19','20','21','22','23','24')) pvt
)
select
row_number() over (order by Customer) as No,
pvt.*
from pvt;

use below query
select
Dates.ReportHeader,
Data.Customer,
Data.Sales
from
Dates, Data
where
Dates.ReportHeader = Data.ReportHeader(+)

Related

Display All Quarters based on Year In SQL Server

I Need to display all the Quarters from date field.
SELECT '2021' AS YOE,DATEPART(Quarter,'2021')AS [Quarter],450 AS Qty
Actual Result:
YOE
Quarter
Qty
2021
1
450
Expected Result:
YOE
Quarter
Qty
2021
1
450
2021
2
0
2021
3
0
2021
4
0
Here is an answer that will get you results for any given year.
What you need to do is start with a calendar table as the driving row, then LEFT JOIN your table to it.
We will use Itzik Ben-Gan's tally table for this purpose. We pass through the starting year as #startingYear:
;WITH
L0 AS ( SELECT 1 AS c
FROM (VALUES(1),(1),(1),(1),(1),(1),(1),(1),
(1),(1),(1),(1),(1),(1),(1),(1)) AS D(c) ),
L1 AS ( SELECT 1 AS c FROM L0 AS A CROSS JOIN L0 AS B ),
Nums AS ( SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS rownum
FROM L1 ),
Years AS ( SELECT DATEADD(year, rownum - 1, #startingYear) AS Year FROM Nums)
SELECT
y.Year AS YOE,
q.Quarter,
SUM(t.Value) AS Qty
FROM Years AS y
CROSS JOIN (VALUES (1), (2), (3), (4) ) AS q(Quarter)
LEFT JOIN (Table AS t
-- any other inner or left joins between Table and ON
)
ON DATEPART(Quarter,t.Date) = q.Quarter AND
t.Date >= DATETIMEFROMPARTS(#startingYear, 1, 1);
I would suggest values():
SELECT '2021' AS YOE, vv.*
FROM (VALUES (1, 350), (2, 0), (3, 0), (4, 0)) v([Quarter], Qty)
Considering it seems that this isn't against a table, but you are "creating" the data, use a VALUES table construct:
SELECT YOE,
[Quarter],
Qty
FROM (VALUES(2021,1,450), --Numerical values don't go in quotes.
(2021,2,0), --Strings and Numerical values are very different,
(2021,3,0), --don't confuse the 2.
(2021,4,0))V(YOE,[Quarter],Qty)
ORDER BY YOE,
[Quarter];

Oracle SQL Hierarchy Summation

I have a table TRANS that contains the following records:
TRANS_ID TRANS_DT QTY
1 01-Aug-2020 5
1 01-Aug-2020 1
1 03-Aug-2020 2
2 02-Aug-2020 1
The expected output:
TRANS_ID TRANS_DT BEGBAL TOTAL END_BAL
1 01-Aug-2020 0 6 6
1 02-Aug-2020 6 0 6
1 03-Aug-2020 6 2 8
2 01-Aug-2020 0 0 0
2 02-Aug-2020 0 1 1
2 03-Aug-2020 1 0 1
Each trans_id starts with a beginning balance of 0 (01-Aug-2020). For succeeding days, the beginning balance is the ending balance of the previous day and so on.
I can create PL/SQL block to create the output. Is it possible to get the output in 1 SQL statement?
Thanks.
Try this following script using CTE-
Demo Here
WITH CTE
AS
(
SELECT DISTINCT A.TRANS_ID,B.TRANS_DT
FROM your_table A
CROSS JOIN (SELECT DISTINCT TRANS_DT FROM your_table) B
),
CTE2
AS
(
SELECT C.TRANS_ID,C.TRANS_DT,SUM(D.QTY) QTY
FROM CTE C
LEFT JOIN your_table D
ON C.TRANS_ID = D.TRANS_ID
AND C.TRANS_DT = D.TRANS_DT
GROUP BY C.TRANS_ID,C.TRANS_DT
ORDER BY C.TRANS_ID,C.TRANS_DT
)
SELECT F.TRANS_ID,F.TRANS_DT,
(
SELECT COALESCE (SUM(QTY), 0) FROM CTE2 E
WHERE E.TRANS_ID = F.TRANS_ID AND E.TRANS_DT < F.TRANS_DT
) BEGBAL,
(
SELECT COALESCE (SUM(QTY), 0) FROM CTE2 E
WHERE E.TRANS_ID = F.TRANS_ID AND E.TRANS_DT = F.TRANS_DT
) TOTAL ,
(
SELECT COALESCE (SUM(QTY), 0) FROM CTE2 E
WHERE E.TRANS_ID = F.TRANS_ID AND E.TRANS_DT <= F.TRANS_DT
) END_BAL
FROM CTE2 F
You can as well do like this (I would assume it's a bit faster): Demo
with
dt_between as (
select mindt + level - 1 as trans_dt
from (select min(trans_dt) as mindt, max(trans_dt) as maxdt from t)
connect by level <= maxdt - mindt + 1
),
dt_for_trans_id as (
select *
from dt_between, (select distinct trans_id from t)
),
qty_change as (
select distinct trans_id, trans_dt,
sum(qty) over (partition by trans_id, trans_dt) as total,
sum(qty) over (partition by trans_id order by trans_dt) as end_bal
from t
right outer join dt_for_trans_id using (trans_id, trans_dt)
)
select
trans_id,
to_char(trans_dt, 'DD-Mon-YYYY') as trans_dt,
nvl(lag(end_bal) over (partition by trans_id order by trans_dt), 0) as beg_bal,
nvl(total, 0) as total,
nvl(end_bal, 0) as end_bal
from qty_change q
order by trans_id, trans_dt
dt_between returns all the days between min(trans_dt) and max(trans_dt) in your data.
dt_for_trans_id returns all these days for each trans_id in your data.
qty_change finds difference for each day (which is TOTAL in your example) and cumulative sum over all the days (which is END_BAL in your example).
The main select takes END_BAL from previous day and calls it BEG_BAL, it also does some formatting of final output.
First of all, you need to generate dates, then you need to aggregate your values by TRANS_DT, and then left join your aggregated data to dates. The easiest way to get required sums is to use analitic window functions:
with dates(dt) as ( -- generating dates between min(TRANS_DT) and max(TRANS_DT) from TRANS
select min(trans_dt) from trans
union all
select dt+1 from dates
where dt+1<=(select max(trans_dt) from trans)
)
,trans_agg as ( -- aggregating QTY in TRANS
select TRANS_ID,TRANS_DT,sum(QTY) as QTY
from trans
group by TRANS_ID,TRANS_DT
)
select -- using left join partition by to get data on daily basis for each trans_id:
dt,
trans_id,
nvl(sum(qty) over(partition by trans_id order by dates.dt range between unbounded preceding and 1 preceding),0) as BEGBAL,
nvl(qty,0) as TOTAL,
nvl(sum(qty) over(partition by trans_id order by dates.dt),0) as END_BAL
from dates
left join trans_agg tr
partition by (trans_id)
on tr.trans_dt=dates.dt;
Full example with sample data:
alter session set nls_date_format='dd-mon-yyyy';
with trans(TRANS_ID,TRANS_DT,QTY) as (
select 1,to_date('01-Aug-2020'), 5 from dual union all
select 1,to_date('01-Aug-2020'), 1 from dual union all
select 1,to_date('03-Aug-2020'), 2 from dual union all
select 2,to_date('02-Aug-2020'), 1 from dual
)
,dates(dt) as ( -- generating dates between min(TRANS_DT) and max(TRANS_DT) from TRANS
select min(trans_dt) from trans
union all
select dt+1 from dates
where dt+1<=(select max(trans_dt) from trans)
)
,trans_agg as ( -- aggregating QTY in TRANS
select TRANS_ID,TRANS_DT,sum(QTY) as QTY
from trans
group by TRANS_ID,TRANS_DT
)
select
dt,
trans_id,
nvl(sum(qty) over(partition by trans_id order by dates.dt range between unbounded preceding and 1 preceding),0) as BEGBAL,
nvl(qty,0) as TOTAL,
nvl(sum(qty) over(partition by trans_id order by dates.dt),0) as END_BAL
from dates
left join trans_agg tr
partition by (trans_id)
on tr.trans_dt=dates.dt;
You can use a recursive query to generate the overall date range, cross join it with the list of distinct tran_id, then bring the table with a left join. The last step is aggregation and window functions:
with all_dates (trans_dt, max_dt) as (
select min(trans_dt), max(trans_dt) from trans group by trans_id
union all
select trans_dt + interval '1' day, max_dt from all_dates where trans_dt < max_dt
)
select
i.trans_id,
d.trans_dt,
coalesce(sum(sum(t.qty)) over(partition by i.trans_id order by d.trans_dt), 0) - coalesce(sum(t.qty), 0) begbal,
coalesce(sum(t.qty), 0) total,
coalesce(sum(sum(t.qty)) over(partition by i.trans_id order by d.trans_dt), 0) endbal
from all_dates d
cross join (select distinct trans_id from trans) i
left join trans t on t.trans_id = i.trans_id and t.trans_dt = d.trans_dt
group by i.trans_id, d.trans_dt
order by i.trans_id, d.trans_dt

SQL - Select values from a table based on dates using incrementing dates

I have a SQL table of dates (MM/DD format), targets, and levels, as such:
Date Target Level
10/2 1000 1
10/4 2000 1
10/7 2000 2
I want to use those dates as tiers, or checkpoints, for when to use the respective targets and levels. So, anything on or after those dates (until the next date) would use that target/level. Anything before the first date just uses the values from the first date.
I want to select a range of dates (a 5 week range of dates, with the start date and end date of the range being determined by the current day: 3 weeks back from today, to 2 weeks forward from today) and fill in the targets and levels accordingly, as such:
Date Target Level
10/1 1000 1
10/2 1000 1
10/3 1000 1
10/4 2000 1
10/5 2000 1
10/6 2000 1
10/7 2000 2
10/8 2000 2
...
11/5 2000 2
How do I go about:
Selecting the range of dates (as efficiently as possible)
Filling in the range of dates with the respective target/level from the appropriate date in my table?
Thank you.
You can do this using outer apply. The following creates a list of dates using a recursive CTE:
with d as (
select cast(getdate() as date) as dte
union all
select dateadd(day, -1, dte)
from d
where dte >= getdate() - 30
select d.dte, t.target, t.level
from d outer apply
(select top 1 t.*
from t
where d.dte >= t.dte
order by t.dte desc
);
you can use a CTE to generate your 'missing' dates, then use a CROSS APPLY to obtain the target and level that was last active (by querying the TOP 1 DESC where the date is on or before current date) - finally I introduced 'maximum date' as a variable
DECLARE #MAXD as DATETIME = '20161105';
WITH DATS AS (SELECT MIN([Date]) D FROM dbo.YourTab
UNION ALL
SELECT dateadd(day,1,D) FROM DATS WHERE D < #MAXD)
select DATS.D, CA.Target, CA.Level from DATS
CROSS APPLY
(SELECT TOP 1 Y.Target, Y.Level FROM YourTab Y
WHERE
Y.[Date] <= DATS.D
ORDER BY Y.Date DESC) CA
option (maxrecursion 0);
I made a bit of a change with dates to go back 3 and forward two weeks - also I switched to outer apply to handle no data in force
DECLARE #MIND as DATETIME = dateadd(week,-3,cast(getdate() as date));
DECLARE #MAXD as DATETIME = dateadd(week, 5,#MIND);
WITH DATS AS (SELECT #MIND D
UNION ALL
SELECT dateadd(day,1,D) FROM DATS WHERE D < #MAXD)
select DATS.D, CA.Target, CA.Level from DATS
OUTER APPLY
(SELECT TOP 1 Y.Target, Y.Level FROM YourTab Y WHERE Y.[Date] <= DATS.D ORDER BY Y.Date DESC) CA
ORDER BY DATS.D
option (maxrecursion 0);
Final change - if there is no earlier value for the date - take first future row
DECLARE #MIND as DATETIME = dateadd(week,-3,cast(getdate() as date));
DECLARE #MAXD as DATETIME = dateadd(week, 5,#MIND);
WITH DATS AS (SELECT #MIND D
UNION ALL
SELECT dateadd(day,1,D) FROM DATS WHERE D < #MAXD)
select DATS.D, COALESCE(CA.Target, MQ.Target) Target , COALESCE(CA.Level, MQ.Level) Level from DATS
OUTER APPLY
(SELECT TOP 1 Y.Target, Y.Level FROM YourTab Y WHERE Y.[Date] <= DATS.D ORDER BY Y.Date DESC) CA
OUTER APPLY
(
SELECT TOP 1 M.Target, M.Level FROM YourTab M ORDER BY M.[Date] ASC
) MQ
ORDER BY DATS.D
option (maxrecursion 0);
I don't know why you store dates as MM/DD but you need some conversion into right datatype. This could do a trick:
;WITH YourTable AS (
SELECT *
FROM (VALUES
('10/2', 1000, 1),
('10/4', 2000, 1),
('10/7', 2000, 2)
) as t([Date], [Target], [Level])
), dates_cte AS ( --this CTE is generating dates you need
SELECT DATEADD(week,-3,GETDATE()) as d --3 weeks back
UNION ALL
SELECT dateadd(day,1,d)
FROM dates_cte
WHERE d < DATEADD(week,2,GETDATE()) --2 weeks forward
)
SELECT REPLACE(CONVERT(nvarchar(5),d,101),'/0','/') as [Date],
COALESCE(t.[Target],t1.[Target]) [Target],
COALESCE(t.[Level],t1.[Level]) [Level]
FROM dates_cte dc
OUTER APPLY ( --Here we got PREVIOUS values
SELECT TOP 1 *
FROM YourTable
WHERE CONVERT(datetime,REPLACE([Date],'/','/0')+'/2016',101) <= dc.d
ORDER BY CONVERT(datetime,REPLACE([Date],'/','/0')+'/2016',101) DESC
) t
OUTER APPLY ( --Here we got NEXT values and use them if there is no PREV
SELECT TOP 1 *
FROM YourTable
WHERE CONVERT(datetime,REPLACE([Date],'/','/0')+'/2016',101) >= dc.d
ORDER BY CONVERT(datetime,REPLACE([Date],'/','/0')+'/2016',101) ASC
) t1
Output:
Date Target Level
10/5 2000 1
10/6 2000 1
10/7 2000 2
10/8 2000 2
10/9 2000 2
10/10 2000 2
10/11 2000 2
10/12 2000 2
...
11/9 2000 2
EDIT
With Categories:
;WITH YourTable AS (
SELECT *
FROM (VALUES
('10/2', 1000, 1, 'A'),
('10/4', 3000, 1, 'B'),
('10/7', 2000, 2, 'A')
) as t([Date], [Target], [Level], [Category])
), dates_cte AS (
SELECT DATEADD(week,-3,GETDATE()) as d
UNION ALL
SELECT dateadd(day,1,d)
FROM dates_cte
WHERE d < DATEADD(week,2,GETDATE())
)
SELECT REPLACE(CONVERT(nvarchar(5),d,101),'/0','/') as [Date],
COALESCE(t.[Target],t1.[Target]) [Target],
COALESCE(t.[Level],t1.[Level]) [Level],
c.Category
FROM dates_cte dc
CROSS JOIN (
SELECT DISTINCT Category
FROM YourTable
) c
OUTER APPLY (
SELECT TOP 1 *
FROM YourTable
WHERE CONVERT(datetime,REPLACE([Date],'/','/0')+'/2016',101) <= dc.d
AND c.Category = Category
ORDER BY CONVERT(datetime,REPLACE([Date],'/','/0')+'/2016',101) DESC
) t
OUTER APPLY (
SELECT TOP 1 *
FROM YourTable
WHERE CONVERT(datetime,REPLACE([Date],'/','/0')+'/2016',101) >= dc.d
AND c.Category = Category
ORDER BY CONVERT(datetime,REPLACE([Date],'/','/0')+'/2016',101) ASC
) t1
ORDER BY c.Category, d
Not sure if I'm over simplifying this, but:
select min(X.Date) Date_Range_Start, max(X.date) Date_Range_End
, V.<value_date>
, isnull(X.Target, 'Out of range') Target
, isnull(X.Level, 'Out of range') Level
from X --replace this with your table
left join <value_table> V --table with dates to be assessed
on V.<Date> between X.Date_Range_Start and X.Date_Range_End
group by Target, Level, V.<value_date>

Calculating the number of scheduled dates within a date range from a Weekly Schedule

I have a Schedule table with a Worker ID columns Monday to Sunday. If the contains value greater than 0, it means the Worker is scheduled to work on that day.
How can I efficiently calculate how many days a worker needs to work between a date range? Currently I'm building a date table and cross joining it with the Schedule table and then generating a 1 or 0 for each date. Then I sum up the values to get the total.
http://sqlfiddle.com/#!3/a3292a/7
This appears to work, but it's relatively slow because of the cross-join. Is there a better/faster way of doing it?
You could try UNPIVOT on the WorkSchedule and join to your DateValue query on DateName.. Then you just SUM the values
;WITH
L0 AS (SELECT 1 AS c UNION ALL SELECT 1),
L1 AS (SELECT 1 AS c FROM L0 A CROSS JOIN L0 B),
L2 AS (SELECT 1 AS c FROM L1 A CROSS JOIN L1 B),
L3 AS (SELECT 1 AS c FROM L2 A CROSS JOIN L2 B),
L4 AS (SELECT 1 AS c FROM L3 A CROSS JOIN L3 B),
Nums AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS i FROM L4),
Schedule AS (
SELECT *
FROM [WorkerSchedule]
UNPIVOT (
Working
FOR [WorkDay] IN ([Sunday], [Monday], [Tuesday], [Wednesday], [Thursday], [Friday], [Saturday])
) u
)
SELECT s.WorkerId, SUM(s.Working) ScheduledDays
FROM Schedule s
INNER JOIN (SELECT DATENAME(weekday, DATEADD(DAY, i - 1, #StartDate)) AS DateValue
FROM Nums
WHERE i <= 1 + DATEDIFF(DAY, #StartDate, #EndDate)) d ON s.[WorkDay] = d.DateValue
WHERE s.Working = 1
GROUP BY s.WorkerId

Getting error with query statement sql server

When I execute the folowing query :
SELECT DISTINCT
dat.FiscalYear,
dat.MonthName As FiscalMonth,
dat.FiscalQuarter,
dat.FiscalSemester,
a.m AS m,
o.oName AS o,
o.Calculation,
o.oDispOrder ,
o.oGrpId
FROM
[V].[dbo].[DimDate] dat
CROSS JOIN
(SELECT
cal.calculation, ltrim(p.oName) As oName,
p.DisplayingOrder as oDispOrder, p.oGrpId
FROM
[V].[dbo].[Reporto] p
CROSS JOIN
(SELECT 'Year to Date' as Calculation
UNION
SELECT 'Current Dim Date' as Calculation
UNION
SELECT 'Previous Year' as Calculation
UNION
SELECT 'Last Year Current Month' as Calculation) cal
CROSS JOIN
(SELECT
ltrim(b.m) As m, b.DisplayingOrder as mDispOrder,
b.Used as used
FROM
[V].[dbo].[ReportBusinessLine] b
WHERE b.used = 1) a
WHERE
(p.used = 1 and p.reportId = 1)) o
WHERE
dat.FiscalYear = 2013
I always got the following error :
Msg 4104, Level 16, State 1, Line 6
The multi-part identifier "a.m" could not be bound.
Many thanks .
Formatted more clearly, your from statement is:
FROM [V].[dbo].[DimDate] dat CROSS JOIN
(SELECT cal.calculation, ltrim(p.oName) As oName, p.DisplayingOrder as oDispOrder, p.oGrpId
FROM [V].[dbo].[Reporto] p CROSS JOIN
(SELECT 'Year to Date' as Calculation UNION
SELECT 'Current Dim Date' as Calculation UNION
SELECT 'Previous Year' as Calculation UNION
SELECT 'Last Year Current Month' as Calculation
) cal CROSS JOIN
(SELECT ltrim(b.m) As m, b.DisplayingOrder as mDispOrder, b.Used as used
FROM [V].[dbo].[ReportBusinessLine] b
WHERE b.used = 1
) a
WHERE (p.used = 1 and p.reportId =1 )
) o
WHERE dat.FiscalYear = 2013
You can clearly see that a is a table alias in a subquery. If you want the field m, then you need to put it at the level of o:
FROM [V].[dbo].[DimDate] dat CROSS JOIN
(SELECT cal.calculation, ltrim(p.oName) As oName, p.DisplayingOrder as oDispOrder, p.oGrpId,
a.m
FROM [V].[dbo].[Reporto] p CROSS JOIN
(SELECT 'Year to Date' as Calculation UNION
SELECT 'Current Dim Date' as Calculation UNION
SELECT 'Previous Year' as Calculation UNION
SELECT 'Last Year Current Month' as Calculation
) cal CROSS JOIN
(SELECT ltrim(b.m) As m, b.DisplayingOrder as mDispOrder, b.Used as used
FROM [V].[dbo].[ReportBusinessLine] b
WHERE b.used = 1
) a
WHERE (p.used = 1 and p.reportId =1 )
) o
WHERE dat.FiscalYear = 2013
And then refer to it as o.m in the select.
Formatting code so it is readable can do wonders for finding and preventing errors.
Try this one -
SELECT DISTINCT
dat.FiscalYear,
dat.monthname AS FiscalMonth,
dat.FiscalQuarter,
dat.FiscalSemester,
o.m AS m, --<--- invalid alias
o.oName AS o,
o.Calculation,
o.oDispOrder,
o.oGrpId
FROM [dbo].[DimDate] dat
CROSS JOIN (
SELECT
cal.calculation,
LTRIM(p.oName) AS oName,
p.DisplayingOrder AS oDispOrder,
p.oGrpId,
a.m --<--- missing in SELECT
FROM [dbo].[Reporto] p
CROSS JOIN (
SELECT 'Year to Date' AS Calculation
UNION ALL
SELECT 'Current Dim Date'
UNION ALL
SELECT 'Previous Year'
UNION ALL
SELECT 'Last Year Current Month'
) cal
CROSS JOIN (
SELECT
LTRIM(b.m) AS m,
b.DisplayingOrder AS mDispOrder,
b.Used AS used
FROM [dbo].[ReportBusinessLine] b
WHERE b.used = 1
) a
WHERE p.used = 1 AND p.reportId = 1
) o
WHERE dat.FiscalYear = 2013