I have a query that is somewhat running indefinitely, I might be going in circles here but it doesn't give me an error, just continuously runs.
Query
WITH calendar AS (
SELECT dt
FROM (
SELECT TRUNC(LAST_DAY(TO_DATE(&month || '/01/' || &year, 'MM/DD/YYYY')) - ROWNUM + 1) AS dt
FROM DUAL CONNECT BY ROWNUM <= 31
)
WHERE dt >= TRUNC(TO_DATE(&month || '/01/' || &year, 'MM/DD/YYYY'), 'MM')
ORDER BY dt ASC
)
SELECT
cal.dt,
(
SELECT NVL(SUM(rd.rate_value), 0.00)
FROM cage_dates cd
LEFT JOIN cages c ON c.id = cd.cage_id
LEFT JOIN rent_item ri ON ri.id = c.rent_item_id
LEFT JOIN rate_dates rd ON rd.rent_item_id = c.rent_item_id
WHERE c.group_id = 123
AND c.room_id = 456
AND (
LOWER(c.cage_use) NOT IN ('created in error', 'prod setup')
OR c.cage_use IS NULL
)
AND ri.display_name NOT LIKE '% BR'
AND TRUNC(cd.added) <= cal.dt
AND (
CASE
WHEN cd.removed IS NOT NULL AND TRUNC(cd.removed) >= cal.dt THEN 1
WHEN cd.removed IS NULL THEN 1
ELSE 0
END = 1
)
AND TRUNC(rd.effective_on) <= cal.dt
AND (
CASE
WHEN rd.effective_until IS NOT NULL AND TRUNC(rd.effective_until) >= cal.dt THEN 1
WHEN rd.effective_until IS NULL THEN 1
ELSE 0
END = 1
)
) AS per_diem
FROM calendar cal
ORDER BY cal.dt ASC
When I change cal.dt to TO_DATE('10/25/2017', 'MM/DD/YYYY') it gives me the correct SUM of the all the rates for that day but attaches it to all the days like so:
dt per_diem
--------------------
10/01/2017 1.19
10/02/2017 1.19
...
10/31/2017 1.19
Whereas I want something like:
dt per_diem
--------------------
10/01/2017 0
10/02/2017 1.19
10/03/2017 2.52
...
10/31/2017 0.67
Anyone have any idea?
I would move the subquery into a join, like this:
WITH calendar AS (
SELECT dt
FROM (
SELECT TRUNC(LAST_DAY(TO_DATE(&month || '/01/' || &year, 'MM/DD/YYYY')) - ROWNUM + 1) AS dt
FROM DUAL CONNECT BY ROWNUM <= 31
)
WHERE dt >= TRUNC(TO_DATE(&month || '/01/' || &year, 'MM/DD/YYYY'), 'MM')
)
SELECT cal.dt, NVL(SUM(rd.rate_value), 0.00)
FROM Calendar cal
LEFT JOIN cage_dates cd ON TRUNC(cd.added) = cal.dt
LEFT JOIN cages c ON c.id = cd.cage_id
LEFT JOIN rent_item ri ON ri.id = c.rent_item_id
LEFT JOIN rate_dates rd ON rd.rent_item_id = c.rent_item_id
WHERE c.group_id = 123
AND c.room_id = 456
AND (
LOWER(c.cage_use) NOT IN ('created in error', 'prod setup')
OR c.cage_use IS NULL
)
AND ri.display_name NOT LIKE '% BR'
AND (
CASE
WHEN cd.removed IS NOT NULL AND TRUNC(cd.removed) >= cal.dt THEN 1
WHEN cd.removed IS NULL THEN 1
ELSE 0
END = 1
)
AND TRUNC(rd.effective_on) <= cal.dt
AND (
CASE
WHEN rd.effective_until IS NOT NULL AND TRUNC(rd.effective_until) >= cal.dt THEN 1
WHEN rd.effective_until IS NULL THEN 1
ELSE 0
END = 1
GROUP BY cal.dt
But I am not sure what you are actually attempting with the TRUNC(cd.added) <= cal.dt at first thought is was a running sum, but perhaps you are trying to make a value appear on every day?
Related
certainly something very simple, but for an application I would like to know how, if I know the calendar week, I can display the first to the last day of the week per row.
Currently, I am only shown the day in which content is present.
I would like to have 7 days displayed (as date, not necessarily with name) whether they are empty or not.
SELECT
MIN( TO_CHAR(LP_BELEGUNG.GEN_DATUM,'DD.MM.YYYY')) AS GRD_ROW_ID
, COUNT( DISTINCT
CASE
WHEN LP_BELEGUNG.ART = 1 THEN LP_BELEGUNG.LP_BELEGUNG_ID
ELSE NULL
END ) AS ANZAHL_ART_1
, COUNT( DISTINCT
CASE
WHEN LP_BELEGUNG.ART = 2 THEN LP_BELEGUNG.LP_BELEGUNG_ID
ELSE NULL
END ) AS ANZAHL_ART_2
, COUNT( DISTINCT
CASE
WHEN LP_BELEGUNG.ART = 3 THEN LP_BELEGUNG.LP_BELEGUNG_ID
ELSE NULL
END ) AS ANZAHL_ART_3
, COUNT( DISTINCT
CASE
WHEN LP_BELEGUNG.ART = 99 THEN LP_BELEGUNG.LP_BELEGUNG_ID
ELSE NULL
END ) AS ANZAHL_ART_4
FROM
LP_BELEGUNG
WHERE
TO_CHAR(LP_BELEGUNG.GEN_DATUM, 'WW') = 37 --the calendar week
If you want one row per day, for a week number in a given year, then you can generate all the dates in that week and use an outer join to look for matching rows in your table, if there are any.
Unfortunately Oracle doesn't supply a simple way to get a date from a week number, but based on how the WW element is defined you can start from the first day of the year and add the appropriate number of days to get the start of the week:
select trunc(sysdate, 'YYYY') + (7 * 37) - 7 from dual;
TRUNC(SYSDATE,'YYYY')+(7*37)-7
10-SEP-22
... where 37 is the week number, and I've assumed you're looking at the current year (if not, use a fixed date like date '2022-01-01' instead of trunc(sysdate)).
You can then get all the days in that week with a hierarchical query:
select trunc(sysdate, 'YYYY') + (7 * 37) + level - 8
from dual
connect by level <= 7;
TRUNC(SYSDATE,'YYYY')+(7*37)+LEVEL-8
10-SEP-22
11-SEP-22
12-SEP-22
13-SEP-22
14-SEP-22
15-SEP-22
16-SEP-22
Then use those values in an inline view or CTE, and left-join to your table using a date range (to allow for non-midnight times but still allowing an index on that column to be used), grouping by the date:
with cte (dt) as (
select trunc(sysdate, 'YYYY') + (7 * 37) + level - 8
from dual
connect by level <= 7
)
SELECT
TO_CHAR(cte.dt, 'DD.MM.YYYY') AS GRD_ROW_ID
, COUNT( DISTINCT
CASE
WHEN LP_BELEGUNG.ART = 1 THEN LP_BELEGUNG.LP_BELEGUNG_ID
ELSE NULL
END ) AS ANZAHL_ART_1
, COUNT( DISTINCT
CASE
WHEN LP_BELEGUNG.ART = 2 THEN LP_BELEGUNG.LP_BELEGUNG_ID
ELSE NULL
END ) AS ANZAHL_ART_2
, COUNT( DISTINCT
CASE
WHEN LP_BELEGUNG.ART = 3 THEN LP_BELEGUNG.LP_BELEGUNG_ID
ELSE NULL
END ) AS ANZAHL_ART_3
, COUNT( DISTINCT
CASE
WHEN LP_BELEGUNG.ART = 99 THEN LP_BELEGUNG.LP_BELEGUNG_ID
ELSE NULL
END ) AS ANZAHL_ART_4
FROM
cte
LEFT JOIN
LP_BELEGUNG
ON
LP_BELEGUNG.GEN_DATUM >= cte.dt AND LP_BELEGUNG.GEN_DATUM < cte.dt + 1
GROUP BY
cte.dt
ORDER BY
cte.dt
With some sample data to mimic your original result, that gives:
GRD_ROW_ID
ANZAHL_ART_1
ANZAHL_ART_2
ANZAHL_ART_3
ANZAHL_ART_4
10.09.2022
0
0
0
0
11.09.2022
0
0
0
0
12.09.2022
0
0
0
0
13.09.2022
0
0
0
0
14.09.2022
0
0
0
0
15.09.2022
0
0
0
0
16.09.2022
1
0
0
7
fiddle
Here is a set of dates counted and divided to days of the week using to_char and pivot.
select *
from
(
select dt
,to_char(dt, 'D') as dow
from t
) t
pivot (count(dt) for dow in('1', '2', '3', '4', '5', '6', '7')) p
'1'
'2'
'3'
'4'
'5'
'6'
'7'
1
1
0
0
1
3
1
Fiddle
Use conditional aggregation:
SELECT TO_CHAR(MIN(GEN_DATUM),'DD.MM.YYYY') AS GRD_ROW_ID,
COUNT( DISTINCT
CASE
WHEN ART = 1
AND TRUNC(gen_datum) - TRUNC(gen_datum, 'WW') = 0
THEN LP_BELEGUNG_ID
END
) AS ANZAHL_ART_1_DAY1,
COUNT( DISTINCT
CASE
WHEN ART = 1
AND TRUNC(gen_datum) - TRUNC(gen_datum, 'WW') = 1
THEN LP_BELEGUNG_ID
END
) AS ANZAHL_ART_1_DAY2,
-- ...
COUNT( DISTINCT
CASE
WHEN ART = 1
AND TRUNC(gen_datum) - TRUNC(gen_datum, 'WW') = 6
THEN LP_BELEGUNG_ID
END
) AS ANZAHL_ART_1_DAY7,
COUNT( DISTINCT
CASE
WHEN ART = 2
AND TRUNC(gen_datum) - TRUNC(gen_datum, 'WW') = 0
THEN LP_BELEGUNG_ID
END
) AS ANZAHL_ART_2_DAY1,
COUNT( DISTINCT
CASE
WHEN ART = 2
AND TRUNC(gen_datum) - TRUNC(gen_datum, 'WW') = 1
THEN LP_BELEGUNG_ID
END
) AS ANZAHL_ART_2_DAY2,
-- ...
COUNT( DISTINCT
CASE
WHEN ART = 2
AND TRUNC(gen_datum) - TRUNC(gen_datum, 'WW') = 6
THEN LP_BELEGUNG_ID
END
) AS ANZAHL_ART_2_DAY7,
COUNT( DISTINCT
CASE
WHEN ART = 3
AND TRUNC(gen_datum) - TRUNC(gen_datum, 'WW') = 0
THEN LP_BELEGUNG_ID
END
) AS ANZAHL_ART_3_DAY1,
COUNT( DISTINCT
CASE
WHEN ART = 3
AND TRUNC(gen_datum) - TRUNC(gen_datum, 'WW') = 1
THEN LP_BELEGUNG_ID
END
) AS ANZAHL_ART_3_DAY2,
-- ...
COUNT( DISTINCT
CASE
WHEN ART = 3
AND TRUNC(gen_datum) - TRUNC(gen_datum, 'WW') = 6
THEN LP_BELEGUNG_ID
END
) AS ANZAHL_ART_3_DAY7,
COUNT( DISTINCT
CASE
WHEN ART = 99
AND TRUNC(gen_datum) - TRUNC(gen_datum, 'WW') = 0
THEN LP_BELEGUNG_ID
END
) AS ANZAHL_ART_4_DAY1,
COUNT( DISTINCT
CASE
WHEN ART = 99
AND TRUNC(gen_datum) - TRUNC(gen_datum, 'WW') = 1
THEN LP_BELEGUNG_ID
END
) AS ANZAHL_ART_4_DAY2,
-- ...
COUNT( DISTINCT
CASE
WHEN ART = 99
AND TRUNC(gen_datum) - TRUNC(gen_datum, 'WW') = 6
THEN LP_BELEGUNG_ID
END
) AS ANZAHL_ART_4_DAY7
FROM LP_BELEGUNG
WHERE TO_CHAR(GEN_DATUM, 'WW') = 37
ReportDateTime EuId1 EuId2
2020-02-01 1:00 1576 Null
2020-02-01 1:00 Null 1579
2020-02-01 2:00 Null 1573
2020-02-01 2:00 1566 Null
This is what I have and this is what I want...
ReportDateTime EuId1 EuId2
2020-02-01 1:00 1576 1579
2020-02-01 2:00 1566 1573
Here is my code...
;WITH cteEq AS (
SELECT e.EntHID, e.EntCode, e.EntName
FROM cfEntity fac (NOLOCK)
JOIN cfEntityRelation er (NOLOCK) ON fac.EntHID = er.EntParentHID AND er.EntRelEffEnd = '12/31/2078'
JOIN cfEntity e (NOLOCK) ON er.EntChildHID = e.EntHID AND e.EntTypeTID = -200020
WHERE e.EntHID IN (SELECT ea.EaHID FROM cfEntityAttribute ea (NOLOCK) WHERE ea.EaKey = 'AirPermitXref_Diesel')
)
SELECT
ReportDateTime = a.EqReportDate
, EuId1 = CASE
WHEN EntName LIKE '%EU1%' THEN a.EqPwr
END
, EuId2 = CASE
WHEN EntName LIKE '%EU2%' THEN a.EqPwr
END
FROM eqHourlyAir a (NOLOCK)
JOIN cteEq e (NOLOCK) ON a.EqHID = e.EntHID
OUTER APPLY (
SELECT ad.EqHID, ad.EqReportDate, ad.EqRunTime
FROM eqHourlyAir ad (NOLOCK)
LEFT JOIN cfEntityAttribute ea (NOLOCK) ON ad.EqHID = CAST(ea.EaValue AS INT) AND ea.EaKey = 'AirPermitXref_DWI'
WHERE ea.EaHID = a.EqHID
AND ad.EqReportDate = a.EqReportDate
) dwi
OUTER APPLY (
SELECT ad.EqHID, ad.EqReportDate, ad.EqRunTime
FROM eqHourlyAir ad (NOLOCK)
LEFT JOIN cfEntityAttribute ea (NOLOCK) ON ad.EqHID = CAST(ea.EaValue AS INT) AND ea.EaKey = 'AirPermitXref_Diesel'
WHERE ea.EaHID = a.EqHID
AND ad.EqReportDate = a.EqReportDate
) dsl
WHERE CAST(a.EqReportDate AS DATE) >= #StartDate AND CAST(a.EqReportDate AS DATE) <= #EndDate
ORDER BY a.EqReportDate
using nolock hint is not the best idea !
if you have one null value and only one non value for each reportingdate you can grooup by ReportDateTime and get the max value :
...
SELECT
ReportDateTime = a.EqReportDate,
EuId1 = MAX(CASE WHEN EntName LIKE '%EU1%' THEN a.EqPwr END),
EuId2 = MAX(CASE WHEN EntName LIKE '%EU2%' THEN a.EqPwr END)
FROM
{...}
WHERE
CAST(a.EqReportDate AS DATE) >= #StartDate
AND CAST(a.EqReportDate AS DATE) <= #EndDate
GROUP BY a.EqReportDate
ORDER BY
a.EqReportDate
From you sample data it's very easy to select your desired result as below:
select ReportDateTime ,max(EuId1) EuId1,max(EuId2) EuId2 from
(select * from table1 join table2 on .... )t
group by ReportDateTime
But your query indicates that there is more to the story. Please share some more information.
I have an events table which contains the date of status changes. What I'm trying to achieve is to produce summary counts for each date, however I'm struggling as it is not a straight count by date but instead a count based on the last time the status changed.
The data is as follows:
------------------------------------------
IT_ID NEW_STATUS OLD_STATUS TIMESTAMP
------------------------------------------
100 4 3 06/05/2019
100 3 2 04/05/2019
200 2 1 03/05/2019
100 2 1 02/05/2019
300 2 1 02/05/2019
200 1 - 01/05/2019
100 1 - 01/05/2019
300 1 - 01/05/2019
-------------------------------------------
I've tried grouping, but this hasn't worked due to the above, SQL below for the straight count.
select max(trunc(timestamp)), new_status ,count(new_status)
from status_hist
where trunc(timestamp) >= '01/01/2019'
group by trunc(timestamp), new_status
Ideally I would like the data in the following format, however the key here is to counts against each date. Note, as no status changes took place on the 05/05/19 then it shows the same of the 04/05/19:
---------------------------------------------------------
Date Status 1 Status 2 Status 3 Status 4
---------------------------------------------------------
06/05/2019 0 2 0 1
05/05/2019 0 2 1 0
04/05/2019 0 2 1 0
03/05/2019 0 3 0 0
02/05/2019 1 2 0 0
01/05/2019 3 0 0 0
--------------------------------------------------------
Any help would be gratefully received.
Thanks
I think about handling this problem by getting the status of each person on each date. That requires a cross join to get the person/dates combinations and then some aggregation:
WITH dates as (
SELECT min_dt + LEVEL - 1 AS dt
FROM (SELECT MIN(ts) AS min_dt, MAX(ts) AS max_dt
FROM test_data
)
CONNECT BY min_dt + LEVEL - 1 <= max_dt
)
SELECT d.dt, i.it_id, max(td.new_status) keep (dense_rank first order by td.ts desc) as status
FROM dates d CROSS JOIN
(SELECT DISTINCT IT_ID FROM test_data) i LEFT JOIN
test_data td
ON td.IT_ID = i.IT_ID AND td.ts <= d.dt
GROUP BY d.dt, i.it_id;
The dates CTE is just calculating all dates. The rest is bringing in the latest status.
This can then be expanded to aggregate (or pivot) the results:
WITH dates as (
SELECT min_dt + LEVEL - 1 AS dt
FROM (SELECT MIN(ts) AS min_dt, MAX(ts) AS max_dt
FROM test_data
)
CONNECT BY min_dt + LEVEL - 1 <= max_dt
),
di as (
SELECT d.dt, i.it_id, max(td.new_status) keep (dense_rank first order by td.ts desc) as status
FROM dates d CROSS JOIN
(SELECT DISTINCT IT_ID FROM test_data) i LEFT JOIN
test_data td
ON td.IT_ID = i.IT_ID AND td.ts <= d.dt
GROUP BY d.dt, i.it_id
)
select dt,
sum(case when status = 1 then 1 else 0 end) as num_1,
sum(case when status = 2 then 1 else 0 end) as num_2,
sum(case when status = 3 then 1 else 0 end) as num_3,
sum(case when status = 4 then 1 else 0 end) as num_4
from di
group by dt
order by dt desc;
Here is a db<>fiddle.
You can do it using windowed aggregation functions:
Oracle Setup:
CREATE TABLE test_data ( IT_ID, NEW_STATUS, OLD_STATUS, "TIMESTAMP" ) AS
SELECT 100, 4, 3, DATE '2019-05-06' FROM DUAL UNION ALL
SELECT 100, 3, 2, DATE '2019-05-04' FROM DUAL UNION ALL
SELECT 200, 2, 1, DATE '2019-05-03' FROM DUAL UNION ALL
SELECT 100, 2, 1, DATE '2019-05-02' FROM DUAL UNION ALL
SELECT 300, 2, 1, DATE '2019-05-02' FROM DUAL UNION ALL
SELECT 200, 1, NULL, DATE '2019-05-01' FROM DUAL UNION ALL
SELECT 100, 1, NULL, DATE '2019-05-01' FROM DUAL UNION ALL
SELECT 300, 1, NULL, DATE '2019-05-01' FROM DUAL;
Query:
SELECT DISTINCT
dt AS "TIMESTAMP",
COUNT( CASE new_status WHEN 1 THEN IT_ID END ) OVER ( ORDER BY dt RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW )
- COUNT( CASE old_status WHEN 1 THEN IT_ID END ) OVER ( ORDER BY dt RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW )
AS Status1,
COUNT( CASE new_status WHEN 2 THEN IT_ID END ) OVER ( ORDER BY dt RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW )
- COUNT( CASE old_status WHEN 2 THEN IT_ID END ) OVER ( ORDER BY dt RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW )
AS Status2,
COUNT( CASE new_status WHEN 3 THEN IT_ID END ) OVER ( ORDER BY dt RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW )
- COUNT( CASE old_status WHEN 3 THEN IT_ID END ) OVER ( ORDER BY dt RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW )
AS Status3,
COUNT( CASE new_status WHEN 4 THEN IT_ID END ) OVER ( ORDER BY dt RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW )
- COUNT( CASE old_status WHEN 4 THEN IT_ID END ) OVER ( ORDER BY dt RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW )
AS Status4
FROM test_data t
RIGHT OUTER JOIN (
SELECT min_dt + LEVEL - 1 AS dt
FROM ( SELECT MIN("TIMESTAMP") AS min_dt,
MAX("TIMESTAMP") AS max_dt
FROM test_data
)
CONNECT BY min_dt + LEVEL - 1 <= max_dt
) c
ON ( c.dt = t."TIMESTAMP" )
ORDER BY "TIMESTAMP" DESC
Output:
TIMESTAMP | STATUS1 | STATUS2 | STATUS3 | STATUS4
:-------- | ------: | ------: | ------: | ------:
06-MAY-19 | 0 | 2 | 0 | 1
05-MAY-19 | 0 | 2 | 1 | 0
04-MAY-19 | 0 | 2 | 1 | 0
03-MAY-19 | 0 | 3 | 0 | 0
02-MAY-19 | 1 | 2 | 0 | 0
01-MAY-19 | 3 | 0 | 0 | 0
db<>fiddle here
You can use the pivot function of SQL.
I don't have an oracle DB to test this:
declare #dates table(Date timestamp(3), NEW_STATUS number(10))
v_StartDate DATE := (SELECT MIN(timestamp) FROM [test].dbo)
v_EndDate DATE := (SELECT MAX(timestamp) FROM [test].dbo)
insert into #dates
SELECT nbr * INTERVAL '1' DAY(5) - 1 + v_StartDate as 'Date', null as NEW_STATUS
FROM ( SELECT ROW_NUMBER() OVER ( ORDER BY c.object_id ) AS Nbr
FROM sys.columns c
) nbrs
WHERE nbr - 1 <= v_EndDate - v_StartDate
SELECT timestamp as 'Date', 1 AS 'Status 1', 2 AS 'Status 2', 3 AS 'Status 3', 4 AS 'Status 4'
FROM
(SELECT Date as 'timestamp', NVL(NVL(d.new_status, t.NEW_STATUS),t2.NEW_STATUS) as new_status
FROM #dates d
left outer join Table_test t on d.Date = t.TIMESTAMP
left outer join Table_test t2 on INTERVAL '-1' DAY(5) +d.Date = t2.TIMESTAMP and NVL(d.new_status, t.NEW_STATUS) is null ) p
PIVOT
(
COUNT (new_status)
FOR new_status IN
( 1, 2, 3, 4 )
) AS pvt
ORDER BY pvt.TIMESTAMP desc
My Microsoft SQL Syntax is:
declare #dates table([Date] datetime, [NEW_STATUS] int)
DECLARE #StartDate DATE = (SELECT MIN(timestamp) FROM [test].[dbo].[Table_test])
DECLARE #EndDate DATE = (SELECT MAX(timestamp) FROM [test].[dbo].[Table_test])
insert into #dates
SELECT DATEADD(DAY, nbr - 1, #StartDate) as 'Date', null as NEW_STATUS
FROM ( SELECT ROW_NUMBER() OVER ( ORDER BY c.object_id ) AS Nbr
FROM sys.columns c
) nbrs
WHERE nbr - 1 <= DATEDIFF(DAY, #StartDate, #EndDate)
SELECT timestamp as 'Date', [1] AS 'Status 1', [2] AS 'Status 2', [3] AS 'Status 3', [4] AS 'Status 4'
FROM
(SELECT Date as 'timestamp', ISNULL(ISNULL(d.new_status, t.NEW_STATUS),t2.NEW_STATUS) as new_status
FROM #dates d
left outer join Table_test t on d.Date = t.TIMESTAMP
left outer join Table_test t2 on DATEADD(DAY,-1,d.Date) = t2.TIMESTAMP and ISNULL(d.new_status, t.NEW_STATUS) is null ) p
PIVOT
(
COUNT (new_status)
FOR new_status IN
( [1], [2], [3], [4] )
) AS pvt
ORDER BY pvt.TIMESTAMP desc
I’m looking to join two tables that do not have a common data point, but common value (date). I want a table that lists the date and total number of hired/terminated employees on that day. Example is below:
Table 1
Hire Date Employee Number Employee Name
--------------------------------------------
5/5/2018 10078 Joe
5/5/2018 10077 Adam
5/5/2018 10078 Steve
5/8/2018 10079 Jane
5/8/2018 10080 Mary
Table 2
Termination Date Employee Number Employee Name
----------------------------------------------------
5/5/2018 10010 Tony
5/6/2018 10025 Jonathan
5/6/2018 10035 Mark
5/8/2018 10052 Chris
5/9/2018 10037 Sam
Desired result:
Date Total Hired Total Terminated
--------------------------------------
5/5/2018 3 1
5/6/2018 0 2
5/7/2018 0 0
5/8/2018 2 1
5/9/2018 0 1
Getting the total count is easy, just unsure as the best approach from the standpoint of "adding" a date column
If you need all dates within some window then you need to join the data to a calendar. You can then left join and sum flags for data points.
DECLARE #StartDate DATETIME = (SELECT MIN(ActionDate) FROM(SELECT ActionDate = MIN(HireDate) FROM Table1 UNION SELECT ActionDate = MIN(TerminationDate) FROM Table2)AS X)
DECLARE #EndDate DATETIME = (SELECT MAX(ActionDate) FROM(SELECT ActionDate = MAX(HireDate) FROM Table1 UNION SELECT ActionDate = MAX(TerminationDate) FROM Table2)AS X)
;WITH AllDates AS
(
SELECT CalendarDate=#StartDate
UNION ALL
SELECT DATEADD(DAY, 1, CalendarDate)
FROM AllDates
WHERE DATEADD(DAY, 1, CalendarDate) <= #EndDate
)
SELECT
CalendarDate,
TotalHired = SUM(CASE WHEN H.HireDate IS NULL THEN NULL ELSE 1 END),
TotalTerminated = SUM(CASE WHEN T.TerminationDate IS NULL THEN NULL ELSE 1 END)
FROM
AllDates D
LEFT OUTER JOIN Table1 H ON H.HireDate = D.CalendarDate
LEFT OUTER JOIN Table2 T ON T.TerminationDate = D.CalendarDate
/* If you only want dates with data points then uncomment out the where clause
WHERE
NOT (H.HireDate IS NULL AND T.TerminationDate IS NULL)
*/
GROUP BY
CalendarDate
I would do this with a union all and aggregations:
select dte, sum(is_hired) as num_hired, sum(is_termed) as num_termed
from (select hiredate as dte, 1 as is_hired, 0 as is_termed from table1
union all
select terminationdate, 0 as is_hired, 1 as is_termed from table2
) ht
group by dte
order by dte;
This does not include the "missing" dates. If you want those, a calendar or recursive CTE works. For instance:
with ht as (
select dte, sum(is_hired) as num_hired, sum(is_termed) as num_termed
from (select hiredate as dte, 1 as is_hired, 0 as is_termed from table1
union all
select terminationdate, 0 as is_hired, 1 as is_termed from table2
) ht
group by dte
),
d as (
select min(dte) as dte, max(dte) as max_dte)
from ht
union all
select dateadd(day, 1, dte), max_dte
from d
where dte < max_dte
)
select d.dte, coalesce(ht.num_hired, 0) as num_hired, coalesce(ht.num_termed) as num_termed
from d left join
ht
on d.dte = ht.dte
order by dte;
Try this one
SELECT ISNULL(a.THE_DATE, b.THE_DATE) as Date,
ISNULL(a.Total_Hire,0) as Total_Hire,
ISNULL (b.Total_Terminate,0) as Total_terminate
FROM (SELECT Hire_date as the_date, COUNT(1) as Total_Hire
FROM TABLE_HIRE GROUP BY HIRE_DATE) a
FULL OUTER JOIN (SELECT Termination_Date as the_date, COUNT(1) as Total_Terminate
FROM TABLE_TERMINATE GROUP BY HIRE_DATE) a
ON a.the_date = b.the_date
Ok so i have 3 tables :
[AXprod].[dbo].[RMSPOSINVOICE],[AXPROD].[dbo].[discountcard] ,[IntegrationProd].[dbo].[POS_KvitoGalva]. And i want to find out when discount card was used more than once in one inventlocation and time when it was used. The table [IntegrationProd].[dbo].[POS_KvitoGalva] has these times. I use this code to get the time each card was used each day is:
sELECT a.discountcardid,count(a.discountcardid)
FROM [AXprod].[dbo].[RMSPOSINVOICE] a
inner join [AXPROD].[dbo].[discountcard] b
on a.discountcardid = b.discountcardid
inner join [IntegrationProd].[dbo].[POS_KvitoGalva] c
on a.possalesid = c.id
where a.dataareaid = 'ermi' and len(a.discountcardid) > '0' and b.dataareaid = 'ermi' and ('500' = a.inventlocationid )
and (a.invoicedate >= '2015-04-22 00:00:00.000' and a.invoicedate <= '2015-04-22 00:00:00.000')
group by a.discountcardid,a.inventlocationid,a.posnumber
having count(a.discountcardid) > '1'
And i get the following result:
DISCOUNTCARDID COUNT
123456 2
145962 2
and i have a query to find when each card was used (date and time)
SELECT a.discountcardid,a.inventlocationid,a.posnumber,year,month,day,hour,minute,c.id
FROM [AXprod].[dbo].[RMSPOSINVOICE] a
inner join [AXPROD].[dbo].[discountcard] b
on a.discountcardid = b.discountcardid
inner join [IntegrationProd].[dbo].[POS_KvitoGalva] c
on a.possalesid = c.id
where a.dataareaid = 'ermi' and len(a.discountcardid) > '0' and b.dataareaid = 'ermi' and ('500' = a.inventlocationid )
and (a.invoicedate >= '2015-04-22 00:00:00.000' and a.invoicedate <= '2015-04-22 00:00:00.000')
group by a.discountcardid,a.inventlocationid,a.posnumber,year,month,day,hour,minute,c.id
order by DISCOUNTCARDID
And i get the result:
discountcardid inventlocationid posnumber year month day hour minute id
123456 500 7 2015 4 22 12 44 6355302
123456 500 7 2015 4 22 14 24 6355302
145962 500 7 2015 4 22 13 56 6355302
145962 500 7 2015 4 22 13 24 6355302
145555 500 7 2015 4 22 12 11 5465465
The problem:
I dont want to get discount cards that were only used once so i try this:
SELECT a.discountcardid,a.inventlocationid,a.posnumber,year,month,day,hour,minute,c.id,
( sELECT count(s.discountcardid)
FROM [AXprod].[dbo].[RMSPOSINVOICE] s
inner join [AXPROD].[dbo].[discountcard] b
on s.discountcardid = b.discountcardid
inner join [IntegrationProd].[dbo].[POS_KvitoGalva] c
on s.possalesid = c.id
where s.dataareaid = 'ermi' and len(s.discountcardid) > '0' and b.dataareaid = 'ermi' and ('500' = s.inventlocationid )
and (s.invoicedate >= '2015-04-22 00:00:00.000' and s.invoicedate <= '2015-04-22 00:00:00.000') and s.DISCOUNTCARDID = a.DISCOUNTCARDID
group by s.discountcardid,s.inventlocationid,s.posnumber
having count(a.discountcardid) > '1')
FROM [AXprod].[dbo].[RMSPOSINVOICE] a
inner join [AXPROD].[dbo].[discountcard] b
on a.discountcardid = b.discountcardid
inner join [IntegrationProd].[dbo].[POS_KvitoGalva] c
on a.possalesid = c.id
where a.dataareaid = 'ermi' and len(a.discountcardid) > '0' and b.dataareaid = 'ermi' and ('500' = a.inventlocationid )
and (a.invoicedate >= '2015-04-22 00:00:00.000' and a.invoicedate <= '2015-04-22 00:00:00.000')
group by a.discountcardid,a.inventlocationid,a.posnumber,year,month,day,hour,minute,c.id
order by DISCOUNTCARDID
But all i get is the same number of values and NULL in the last field in all columns. I hope i made myself clear ;).
You should be able to call the query once and use an windowed function in order to get the count. I don't believe you can use an analytic function in the where statement so I added an additional SELECT statement in order to add the WHERE > 1 for the count.
SELECT *
FROM (SELECT
a.discountcardid,
a.inventlocationid,
a.posnumber,
year,
month,
day,
hour,
minute,
c.id,
COUNT(*) OVER (PARTITION BY a.discountcardid, a.inventlocationid, a.posnumber) AS CardCount
FROM AXprod.dbo.RMSPOSINVOICE a
JOIN AXprod.dbo.discountcard b
ON b.discountcardid = a.discountcardid
JOIN IntegrationProd.dbo.POS_KvitoGalva c
ON c.id = a.possalesid
WHERE a.dataareaid = 'ermi'
AND len(a.discountcardid) > '0'
AND b.dataareaid = 'ermi'
AND a.inventlocationid = 500
AND a.invoicedate >= '2015-04-22 00:00:00.000'
AND a.invoicedate <= '2015-04-22 00:00:00.000'
) d
WHERE d.CardCount > 1
ORDER BY d.discountcardid