Adding all values for certain dates and the following day - sql-server-2012

I am trying to do some reporting to see if an event drove sales on the day after the event as well. So for this I am trying to add all the sales from the day of an event and the day after it for each set of data. It does not matter which store the sale happened for the purpose of this report. However it is possible to have a day appear up to 22 times (1 for each store). All the data is stored in one table called UCS.
I have tried finding a way to make Lag or Lead work using case statements and temp tables but so far no luck.
Below are a couple of screenshots to help show what I am trying to do. I need to add the sales marked yellow for each screenshot.
You will notice in example 2 I am not adding the values from the days 11/4 or 11/13. While those are the next date in the data set they are not the next day on the calendar.
Example1
Example 2

Moments after posting this my brain clicked and figured it out. I can make a temp table pulling in a list of dates where there is an event doing a union to a list of dates equal to the dates of event +1 using a date table that just houses the dates of the calendar. Then use this to join back and limit the original table. Sample code below to better explain.
If OBJECT_ID('tempdb.dbo.#Event') IS NOT NULL DROP TABLE #Event
Select *
Into #Event
From (
Select
Universal_ID
,Date
From UCS
Where Month = 11
and Year = 2018
and Event = 1
Union
Select
Universal_ID
,DL.Date
From UCS
Join DateLookup as DL
on DATEADD(d,1,UCS.Date) = DL.Date
Where UCS.Month = 11
and UCS.Year = 2018
and Event = 1
) as A
Select
Sum(Sale) as Sale
From UCS
Join #Event as E
on UCS.Universal_ID = E.Universal_ID
and UCS.date = E.date
Where Month = 11
and Year = 2018

You don't really need a universal set of dates for this, if the date isn't in the UCS table it will not matter to the end result
select
sum(sale)
from UCS t
inner join (
select date from UCS where event = 1
union
select dateadd(day,1,date) from UCS where event = 1
) d on t.date = d.date
You can avoid a union in the subquery which may help avoid a pass through the UCS table by using a cross join, but this might not be worthwhile - only assessing execution plans would reveal this:
select
sum(sale)
from UCS t
inner join (
select distinct dateadd(day,cj.n,date) as date
from UCS
cross join (select 0 as n union all select 1) cj
where event = 1
) d on t.date = d.date
;

Related

How can I get the count to display zero for months that have no records

I am pulling transactions that happen on an attribute (attribute ID 4205 in table 1235) by the date that a change happened to the attribute (found in the History table) and counting up the number of changes that occurred by month. So far I have
SELECT TOP(100) PERCENT MONTH(H.transactiondate) AS Month, COUNT(*) AS Count
FROM hsi.rmObjectInstance1235 AS O LEFT OUTER JOIN
hsi.rmObjectHistory AS H ON H.objectID = O.objectID
WHERE H.attributeid = 4205) AND Year(H.transaction date) = '2020'
GROUP BY MONTH(H.transactiondate)
And I get
Month Count
---------------
1 9
2 4
3 11
4 14
5 1
I need to display a zero for months June - December instead of excluding those months.
One option uses a recursive query to generate the dates, and then brings the original query with a left join:
with all_dates as (
select cast('2020-01-01' as date) dt
union all
select dateadd(month, 1, dt) from all_dates where dt < '2020-12-01'
)
select
month(d.dt) as month,
count(h.objectid) as cnt
from all_dates d
left join hsi.rmobjecthistory as h
on h.attributeid = 4205
and h.transaction_date >= d.dt
and h.transaction_date < dateadd(month, 1, d.dt)
and exists (select 1 from hsi.rmObjectInstance1235 o where o.objectID = h.objectID)
group by month(d.dt)
I am quite unclear about the intent of the table hsi.rmObjectInstance1235 in the query, as none of its column are used in the select and group by clauses; it it is meant to filter hsi.rmobjecthistory by objectID, then you can rewrite this as an exists condition, as shown in the above solution. Possibly, you might as well be able to just remove that part of the query.
Also, note that
top without order by does not really make sense
top (100) percent is a no op
As a consequence, I removed that row-limiting clause.

Expected payments by day given start and end date

I'm trying to create a SQL view that gives me the expected amount to be received by calendar day for recurring transactions. I have a table containing recurring commitments data, with the following columns:
id,
start_date,
end_date (null if still active),
payment day (1,2,3,etc.),
frequency (monthly, quarterly, semi-annually, annually),
commitment amount
For now, I do not need to worry about business days vs calendar days.
In its simplest form, the end result would contain every historical calendar day as well as future dates for the next year, and produce how much was/is expected to be received in those particular days.
I've done quite a bit of researching, but cannot seem to find an answer that addresses the specific problem. Any direction on where to start would be greatly appreciated.
The expect output would look something like this:
| Date | Expected Amount |
|1/1/18 | 100 |
|1/2/18 | 200 |
|1/3/18 | 150 |
Thank you ahead of time!
Link to data table in db-fiddle
Expected Output Spreadsheet
It's something like this, but I've never used Netezza
SELECT
cal.d, sum(r.amount) as expected_amount
FROM
(
SELECT MIN(a.start_date) + ROW_NUMBER() OVER(ORDER BY NULL) as d
FROM recurring a, recurring b, recurring c
) cal
LEFT JOIN
recurring r
ON
(
(r.frequency = 'monthly' AND r.payment_day = DATE_PART('DAY', cal.d)) OR
(r.frequency = 'annually' AND DATE_PART('MONTH', cal.d) = DATE_PART('MONTH', r.start_date) AND r.payment_day = DATE_PART('DAY', cal.d))
) AND
r.start_date >= cal.d AND
(r.end_date <= cal.d OR r.end_date IS NULL)
GROUP BY cal.d
In essence, we cartesian join our recurring table together a few times to generate a load of rows, number them and add the number onto the min date to get an incrementing date series.
The payments data table is left joined onto this incrementing date series on:
(the day of the date from the series) = (the payment day) for monthlies
(the month-day of the date from the series) = (the month and payment day of the start_date)
Finally, the whole lot is grouped and summed
I don't have a test instance of Netezza so if you encounter some minor syntax errors, do please have a stab at fixing them up yourself (to make it faster for you to get a solution). If you reach a point where you can't work out what the query is doing, let me know
Disclaimer: I'm no expert on Netezza, so I decided to write you a standard SQL that may need some tweaking to run on Netezza.
with
digit as (select 0 as x union select 1 union select 2 union select 3 union select 4
union select 5 union select 6 union select 7 union select 8 union select 9
),
number as ( -- produces numbers from 0 to 9999 (28 years)
select d1.x + d2.x * 10 + d3.x * 100 + d4.x * 1000 as n
from digit d1
cross join digit d2
cross join digit d3
cross join digit d4
),
expected_payment as ( -- expands all expected payments
select
c.start_date + nb.n as day,
c.committed_amount
from recurring_commitement c
cross join number nb
where c.start_date + nb.n <= c.end_data
and c.frequency ... -- add logic for monthly, quarterly, etc. here
)
select
day,
sum(committed_amout) as expected_amount
from expected_payment
group by day
order by day
This solution is valid for commitments that do not exceed 28 years, since the number CTE (Common Table Expression) is producing up to a maximum of 9999 days. Expand with a fifth digit if you need longer commitments.
Note: I think the way I'm adding days to a day to a date is not correct in Netezza's SQL. The expression c.start_date + nb.n may need to be rephrased.

Fetch max value from a sort-of incomplete dataset

A number of devices return a value. Only upon change, this value gets stored in a table:
Device Value Date
B 5 2017-07-01
C 2 2017-07-01
A 3 2017-07-02
C 1 2017-07-04
A 6 2017-07-04
Values may enter the table at any date (i.e. date doesn't increment continiously). Several devices may store their value on the same date.
Note that, even though there are usually only a few devices for each date in the table, all devices actually have a value at that date: it's the latest one stored until then. For example, on 2017-07-02 only device A stored a value. The values for B and C on that date are the ones stored on 2017-07-01; these are still valid on -02, they just did not change.
To retrieve the values for all devices on a given date, e.g. 2017-07-04, I'm using this:
select device, value from data inner join (select device, max(date) as date from data where date <= "2017-07-04" group by device) latestdate on data.device = latestdate.device and data.date = latestdate.date
Device Value
A 6
B 5
C 1
Question: I'd like to read the max value of all devices on all dates in a given range. The result set would be like this:
Date max(value)
2017-07-01 5
2017-07-02 5
2017-07-04 6
.. and I have no clue if that's possible using only SQL. Until now all I got was lost in an exceptional bunch of joins and groupings.
(Database is sqlite3. Generic SQL would be nice, but I'd still be happy to hear about solutions specific to other databases, especially PostgreSQL or MariaDB.)
Extra bonus: Include the missing date -03, to be exact: returning values at given dates, not necessarily the ones appearing in the table.
Date max(value)
2017-07-01 5
2017-07-02 5
2017-07-03 5
2017-07-04 6
I think the most generic way to approach this is using a separate query for each date. There are definitely simpler methods, depending on the database. But getting one that works for SQLite, MariaDB, and Postgres is not going to use any sophisticated functionality:
select '2017-07-01' as date, max(data.value)
from data inner join
(select device, max(date) as date
from data
where date <= '2017-07-01' group by device
) latestdate
on data.device = latestdate.device and data.date = latestdate.date
union all
select '2017-07-02' as date, max(data.value)
from data inner join
(select device, max(date) as date
from data
where date <= '2017-07-02' group by device
) latestdate
on data.device = latestdate.device and data.date = latestdate.date
select '2017-07-03' as date, max(data.value)
from data inner join
(select device, max(date) as date
from data
where date <= '2017-07-03' group by device
) latestdate
on data.device = latestdate.device and data.date = latestdate.date
select '2017-07-04' as date, max(data.value)
from data inner join
(select device, max(date) as date
from data
where date <= '2017-07-04' group by device
) latestdate
on data.device = latestdate.device and data.date = latestdate.date;
This should be a solution for your problem.
It should be cross-database, since OVER clause is supported by the most of the databases.
You should create a table with all the dates("ALL_DATE" in the query), otherwise every database has a specific way to do it without a table.
WITH GROUPED_BY_DATE_DEVICE AS (
SELECT DATE, DEVICE, SUM(VALUE) AS VALUE FROM DEVICE_INFO
GROUP BY DATE, DEVICE
), GROUPED_BY_DATE AS (
SELECT A.DATE, MAX(VALUE) AS VALUE
FROM ALL_DATE A
LEFT JOIN GROUPED_BY_DATE_DEVICE B
ON A.DATE = B.DATE
GROUP BY A.DATE
)
SELECT DATE, MAX(VALUE) OVER (ORDER BY DATE) AS MAX_VALUE
FROM GROUPED_BY_DATE
ORDER BY DATE;

Creating a TSQL query to make a report

I am in the process of creating a report from the data I have stored in my database; just a little stuck on the next piece of it.
Here is an SQLFiddle of my structure
The report is run every Friday. It gets all records from the table that are within the last 7 days (since it was last reported).
The piece I need to add to my query is only get me records where the SUM of awardValue exceeds $75 in the current year.
I have it pulling my records for the time frame (since last report) but need to include that other piece.
How can I accomplish this?
Assuming that you don't care about the Award status when you calculate sum -
Select Main.*
from main
INNER JOIN(
SELECt EMPLOYEE,SUM(AWARDVALUE) SUM
FROM MAIN
WHERE YEAR(AWARDDATE) = YEAR(GetDate())
GROUP BY EMPLOYEE
HAVING SUM(AWARDVALUE)>75) EMPLIMIT
ON Main.EMPLOYEE = EMPLIMIT.EMPLOYEE
Where awardStatus = '1' AND awardDate BETWEEN GetDate() - 7 AND GetDate()
Modified query to Pull in SUM with results and TaxIt Column
Select Main.*,EmployeeSum,
CASE WHEN EmployeeSum>75 THEN 'Yes' ELSE 'No' END AS TaxIt
from main
INNER JOIN(
SELECt EMPLOYEE,SUM(AWARDVALUE) EmployeeSum
FROM MAIN
WHERE YEAR(AWARDDATE) = YEAR(GetDate())
GROUP BY EMPLOYEE) EMPLIMIT
ON Main.EMPLOYEE = EMPLIMIT.EMPLOYEE
Where awardStatus = '1' AND awardDate BETWEEN GetDate() - 7 AND GetDate()
for this you need to put after the group query the next.
SELECT A,B,C, SUM(D)
FROM TABLE
GROUP BY A,B,C
HAVING sum(awardValue) > 75

How to generate list of all dates between sysdate-30 and sysdate+30?

Purpose & What I've Got So Far
I am attempting to create a view which checks for missing labor transactions. The view will be fed to a Crystal report.
In this case, the view should take all dates between sysdate+30 and sysdate -30, and then should left outer join all labor records by active employees for each of those dates. It then gives a count of the number of labor transactions for each employee for each date.
This gets passed to the Crystal Report, which will filter based on a specific date range (within the +/- 30 range by the view). From there, the count of all days will summed up per employee in Crystal, and employees will show up which have zero transactions.
The Problem
Without spitting out a list of every date, initially, I'm using labor transaction for each date, but some have no counts for any date. These folks show null transaction dates with zero hours. This indicates they have no charges for the entire period, which makes sense.
However, when Crystal does a filter on that data and selects a range, I believe it leaves out these null values, thus not allowing me to show the full range of folks who don't have time submitted.
The Question
Is there a way to do the equivalent of "select every date between (sysdate+30) and (sysdate-30)" in a view, so that I can use it to compare all the time against?
The SQL (for reference)
SELECT QUERY.LABORRECLABORCODE
, QUERY.LABORRECEMPLOYEENUM
, QUERY.PERSONRECDISPLAYNAME
, QUERY.TRANSSTARTDATE
, COUNT(TRANSROWSTAMP) AS ROWCOUNT
FROM (SELECT *
FROM (SELECT LABOR.LABORCODE AS LABORRECLABORCODE
, LABOR.LA20 AS LABORRECEMPLOYEENUM
, PERSON.DISPLAYNAME AS PERSONRECDISPLAYNAME
FROM LABOR
LEFT OUTER JOIN PERSON
ON ( LABOR.LABORCODE = PERSON.PERSONID )
WHERE LABOR.STATUS = 'ACTIVE'
AND LABOR.LA20 IS NOT NULL
AND PERSON.DISPLAYNAME IS NOT NULL
AND LOWER(PERSON.DISPLAYNAME) NOT LIKE '%kimball%'
AND LOWER(PERSON.DISPLAYNAME) NOT LIKE '%electrico%'
AND LOWER(PERSON.DISPLAYNAME) NOT LIKE '%misc labor cost adj%'
AND LOWER(PERSON.DISPLAYNAME) NOT LIKE '%brossoit%'
AND LOWER(PERSON.DISPLAYNAME) NOT LIKE '%brossiot%')PERSONINFO
LEFT OUTER JOIN (SELECT STARTDATE AS TRANSSTARTDATE
, LABORCODE AS TRANSLABORCODE
, ROWSTAMP AS TRANSROWSTAMP
FROM LABTRANS
WHERE STARTDATE BETWEEN ( SYSDATE - 30 ) AND ( SYSDATE + 30 ))LABTRANSLIMITED
ON ( PERSONINFO.LABORRECLABORCODE = LABTRANSLIMITED.TRANSLABORCODE ))QUERY
GROUP BY LABORRECLABORCODE
, TRANSSTARTDATE
, LABORRECEMPLOYEENUM
, PERSONRECDISPLAYNAME
ORDER BY LABORRECLABORCODE
, TRANSSTARTDATE
;
select trunc(sysdate)+31-level from dual connect by level <=61
This is a good method for generating any arbitrary list of values.
Or another method: pick a table with a lot of rows
select sysdate+30 - rownum from user_objects where rownum<61
In order to meet my requirements of being sysdate -30 and sysdate + 30 in a range, this seems be the most elegant way of doing things for now:
SELECT *
FROM (SELECT TRUNC(SYSDATE - ROWNUM) DT
FROM DUAL
CONNECT BY ROWNUM < 31
UNION
SELECT TRUNC(SYSDATE + ROWNUM) DT
FROM DUAL
CONNECT BY ROWNUM < 31)DATERANGE;
I used this answer from this SO Question and expanded upon that thinking, using a union to join the queries that went in separate directions.