Access SQL statement: if no entries are valid, return the last one - sql

I am trying to get a SQL statement which solves the following issue.
I have a table "calendar" which includes only one column "date". This table has 12 entries for each month in 2019 (01.31.2019, 02.28.2019 and so on). The second table "values" (which I get from an ERP system) has three columns, "from", "to" and "amount" (e.g. 01.01.2019, 06.30.2019, 50 and 08.01.2019, 08.31.2019, 100).
I have this simple statement which checks which entry is valid on the specific date:
SELECT Calendar.Date, Values.From, Values.To, Values.Amount
FROM Calendar, [Values]
WHERE Calendar.Date >= Values.From
AND Calendar.Date <= Values.To;
There is no valid entry (in the table "values") for July, September, October, November and December.
In the case there is no valid entry the last entry should be used. In July it would be 50 and for September, October ... it would be 100.
I tried subquery and left joins, but I never got the wanted result.
Has anybody an idea or better a solution for this issue. I appreciate any support

I think that you are looking for an additional join on the Values table, that will return the last entry before the current date. When the first (LEFT) JOIN does not succeed, you can use the result returned by the second one.
To locate the last entry before the current date, we can use a NOT EXISTS condition with a correlated subquery.
SELECT
c.Date,
Nz(v.From, v1.From) AS [From],
Nz(v.To, v1.To) AS [To],
Nz(v.Amount, v1.Amount) AS [Amount]
FROM Calendar AS c
LEFT JOIN [Values] AS v
ON c.Date >= v.From AND c.Date <= v.To
LEFT JOIN [Values] AS v1
ON v1.To < c.Date
AND NOT EXISTS (
SELECT 1 FROM [Values] v2 WHERE v2.To < c.Date AND v2.To > v1.To
)
PS : it's been a good practice for a long time in SQL to avoid old-school, implicit JOINs, and always use explicit JOINs.

You can do it with a LEFT JOIN and a subquery to get the last amount:
SELECT c.Date, v.From, v.To,
Nz(
v.Amount,
(SELECT MAX([Values].Amount) FROM [Values] WHERE [Values].From =
(SELECT MAX([Values].From) FROM [Values] WHERE [Values].From <= c.Date))
) AS Amount
FROM Calendar AS c LEFT JOIN [Values] AS v
ON c.Date>=v.From AND c.Date<=v.To;

Related

SQL - Grouping by Last Day of Quarter

I currently have a query running to average survey scores for agents. We use the date range of the LastDayOfTheQuarter and 180 days back to calculate these scores. I ran into an issue for this current quarter.
One of my agents hasn't received any surveys in 2020 which is causing the query to not pull the current lastdayofquarter and 180 days back of results.
The code I am using:
SELECT
Agent,
U.Position,
U.BranchDescription,
(ADDDATE(LastDayOfQuarter, -180)) AS MinDate,
(LastDayOfQuarter) AS MaxDate,
COUNT(DISTINCT Response ID) as SurveyCount,
AVG(CASE WHEN Question ID = Q1_2 THEN Answer Value END) AS EngagedScore,
AVG(CASE WHEN Question ID = Q1_3 THEN Answer Value END) AS KnowledgableScore,
AVG(CASE WHEN Question ID = Q1_6 THEN Answer Value END) AS ValuedScore
FROM qualtrics_responses
LEFT JOIN date D
ON (D.`Date`) = (DATE(`End Date`))
LEFT JOIN `users` U
ON U.`UserID` = `Agent ID`
WHERE `Agent` IS NOT NULL
AND DATE(`End Date`) <= (`LastDayOfQuarter`)
AND DATE(`End Date`) >= (ADDDATE(`LastDayOfQuarter`, -180))
GROUP BY `Agent`, (ADDDATE(`LastDayOfQuarter`, -180))
i know the issue is due to the way I am joining the dates and since he doesn't have a result in this current year, the end date to date join isn't grabbing the desired date range. I can't seem to come up with any alternatives. Any help is appreciated.
I make the assumption that table date in your query is a calendar table, that stores the starts and ends of the quarters (most likely with one row per date in the quarter).
If so, you can solve this problem by rearranging the joins: first cross join the users and the calendar table to generate all possible combinations, then bring in the surveys table with a left join:
SELECT
U.UserID,
U.Position,
U.BranchDescription,
D.LastDayOfQuarter - interval 180 day AS MinDate,
D.LastDayOfQuarter AS MaxDate,
COUNT(DISTINCT Q.ResponseID) as SurveyCount,
AVG(CASE WHEN Q.QuestionID = 'Q1_2' THEN Q.Answer Value END) AS EngagedScore,
AVG(CASE WHEN Q.QuestionID = 'Q1_3' THEN Q.Answer Value END) AS KnowledgableScore,
AVG(CASE WHEN Q.QuestionID = 'Q1_6' THEN Q.Answer Value END) AS ValuedScore
FROM date D
CROSS JOIN users U
LEFT JOIN qualtrics_responses Q
ON Q.EndDate >= D.Date
AND Q.EndDate < D.Date + interval 1 day
AND U.UserID = Q.AgentID
AND Q.Agent IS NOT NULL
GROUP BY
U.UserID,
U.Position,
U.BranchDescription,
D.LastDayOfQuarter
Notes:
I adapted the date arithmetics - this assumes that you are using MySQL, as the syntax of the query suggests
You should really qualify all the columns in the query, by prefixing them with the alias of the table they belong to; this makes the query so much easier to understand. I gave a tried at it, you might need to review that.
All non-aggregated columns should appear in the group by clause (also see the comment from Eric); this is a a requirement in most databaseses, and good practice anywhere

Teradata spool space issue on running a sub query with Count

I am using below query to calculate business days between two dates for all the order numbers. Business days are already available in the teradata table Common_WorkingCalendar. But, i'm also facing spool space issue while i execute the query. I have ample space available in my data lab. Need to optimize the query. Appreciate any inputs.
SELECT
tx."OrderNumber",
(SELECT COUNT(1) FROM Common_WorkingCalendar
WHERE CalDate between Cast(tx."TimeStamp" as date) and Cast(mf.ShipDate as date)) as BusDays
from StoreFulfillment ff
inner join StoreTransmission tx
on tx.OrderNumber = ff.OrderNumber
inner join StoreMerchandiseFulfillment mf
on mf.OrderNumber = ff.OrderNumber
This is a very inefficient way to get this count which results in a product join.
The recommended approach is adding a sequential number to your calendar which increases only on business days (calculated using SUM(CASE WHEN businessDay THEN 1 ELSE 0 END) OVER (ORDER BY CalDate ROWS UNBOUNDED PRECEDING)), then it's two joins, for the start date and the end date.
If this calculation is needed a lot you better add a new column, otherwise you can do it on the fly:
WITH cte AS
(
SELECT CalDate,
-- as this table only contains business days you can use this instead
row_number(*) Over (ORDER BY CalDate) AS DayNo
FROM Common_WorkingCalendar
)
SELECT
tx."OrderNumber",
to_dt.DayNo - from_dt.DayNo AS BusDays
FROM StoreFulfillment ff
INNER JOIN StoreTransmission tx
ON tx.OrderNumber = ff.OrderNumber
INNER JOIN StoreMerchandiseFulfillment mf
ON mf.OrderNumber = ff.OrderNumber
JOIN cte AS from_dt
ON from_dt.CalDate = Cast(tx."TimeStamp" AS DATE)
JOIN cte AS to_dt
ON to_dt.CalDate = Cast(mf.ShipDate AS DATE)

SQL Command Not Properly Ended - Oracle Subquery

I'm using a Crystal Reports 13 Add Command for record selection from an Oracle database connected through the Oracle 11g Client. The error I am receiving is ORA-00933: SQL command not properly ended, but I can't find anything the matter with my code (incomplete):
/* Determine units with billing code effective dates in the previous month */
SELECT "UNITS"."UnitNumber", "BILL"."EFF_DT"
FROM "MFIVE"."BILL_UNIT_ACCT" "BILL"
LEFT OUTER JOIN "MFIVE"."VIEW_ALL_UNITS" "UNITS" ON "BILL"."UNIT_ID" = "UNITS"."UNITID"
WHERE "UNITS"."OwnerDepartment" LIKE '580' AND TO_CHAR("BILL"."EFF_DT", 'MMYYYY') = TO_CHAR(ADD_MONTHS(TRUNC(SYSDATE), -1), 'MMYYYY')
INNER JOIN
/* Loop through previously identified units and determine last billing code change prior to preious month */
(
SELECT "BILL2"."UNIT_ID", MAX("BILL2"."EFF_DT")
FROM "MFIVE"."BILL_UNIT_ACCT" "BILL2"
WHERE TO_CHAR("BILL2"."EFF_DT", 'MMYYYY') < TO_CHAR(ADD_MONTHS(TRUNC(SYSDATE), -1), 'MMYYYY')
GROUP BY "BILL2"."UNIT_ID"
)
ON "BILL"."UNIT_ID" = "BILL2"."UNIT_ID"
ORDER BY "UNITS"."UnitNumber", "BILL"."EFF_DT" DESC
We are a state entity that leases vehicles (units) to other agencies. Each unit has a billing code with an associated effective date. The application is to develop a report of units with billing codes changes in the previous month.
Complicating the matter is that for each unit above, the report must also show the latest billing code and associated effective date prior to the previous month. A brief example:
Given this data and assuming it is now April 2016 (ordered for clarity)...
Unit Billing Code Effective Date Excluded
---- ------------ -------------- --------
1 A 04/15/2016 Present month
1 B 03/29/2016
1 A 03/15/2016
1 C 03/02/2016
1 B 01/01/2015
2 C 03/25/2016
2 A 03/04/2016
2 B 07/24/2014
2 A 01/01/2014 A later effective date prior to previous month exists
3 D 11/28/2014 No billing code change during previous month
The report should return the following...
Unit Billing Code Effective Date
---- ------------ --------------
1 B 03/29/2016
1 A 03/15/2016
1 C 03/02/2016
1 B 01/01/2015
2 C 03/25/2016
2 A 03/04/2016
2 B 07/24/2014
Any assistance resolving the error will be appreciated.
You have a WHERE clause before the INNER JOIN clause. This is invalid syntax - if you swap them it should work:
SELECT "UNITS"."UnitNumber",
"BILL"."EFF_DT"
FROM "MFIVE"."BILL_UNIT_ACCT" "BILL"
LEFT OUTER JOIN
"MFIVE"."VIEW_ALL_UNITS" "UNITS"
ON "BILL"."UNIT_ID" = "UNITS"."UNITID"
INNER JOIN
/* Loop through previously identified units and determine last billing code change prior to preious month */
(
SELECT "UNIT_ID",
MAX("EFF_DT")
FROM "MFIVE"."BILL_UNIT_ACCT"
WHERE TO_CHAR("EFF_DT", 'MMYYYY') < TO_CHAR(ADD_MONTHS(TRUNC(SYSDATE), -1), 'MMYYYY')
GROUP BY "UNIT_ID"
) "BILL2"
ON "BILL"."UNIT_ID" = "BILL2"."UNIT_ID"
WHERE "UNITS"."OwnerDepartment" LIKE '580'
AND TO_CHAR("BILL"."EFF_DT", 'MMYYYY') = TO_CHAR(ADD_MONTHS(TRUNC(SYSDATE), -1), 'MMYYYY')
ORDER BY "UNITS"."UnitNumber", "BILL"."EFF_DT" DESC
Also, you need to move the "BILL2" alias outside the () brackets as you do not need the alias inside the brackets but you do outside.
Are you really sure you need the double-quotes ""? Double-quotes enforce case sensitivity in column names - the default behaviour is for Oracle to convert all table and column names to upper case to abstract the case-sensitivity from the user - since you are using both double-quotes and upper-case names the quotes seems redundant.
SOLUTION:
SELECT DISTINCT "BILL2".*
FROM "MFIVE"."BILL_UNIT_ACCT" "BILL"
LEFT JOIN
/* Returns all rows for each unit with effective date >= maximum prior effective date and effective date < current month */
(
SELECT "UNITS"."UnitNumber" AS "UNITNO",
UPPER("UNITS"."SpecNoDescription") AS "TS_DESCR",
"UNITS"."UsingDepartment" AS "USING_DEPT",
"UNITS"."OwnerDepartment" AS "OWNING_DEPT",
"BILL"."BILLING_CODE",
"BILL"."EFF_DT",
"BILL"."UNIT_ID"
FROM "MFIVE"."BILL_UNIT_ACCT" "BILL"
LEFT OUTER JOIN "MFIVE"."VIEW_ALL_UNITS" "UNITS" ON "BILL"."UNIT_ID" = "UNITS"."UNITID"
WHERE TO_NUMBER(TO_CHAR(TRUNC("BILL"."EFF_DT"), 'YYYYMM'), '999999') < TO_NUMBER(TO_CHAR(TRUNC(ADD_MONTHS(SYSDATE, 0)), 'YYYYMM'), '999999') AND
"UNITS"."OwnerDepartment" = '580' AND
"BILL"."EFF_DT" >=
/* Returns maximum effective date prior to previous month for each unit */
(
SELECT MAX("BILL3"."EFF_DT")
FROM "MFIVE"."BILL_UNIT_ACCT" "BILL3"
WHERE "BILL3"."UNIT_ID" = "BILL"."UNIT_ID" AND
TO_NUMBER(TO_CHAR(TRUNC("BILL3"."EFF_DT"), 'YYYYMM'), '999999') < TO_NUMBER(TO_CHAR(TRUNC(ADD_MONTHS(SYSDATE, -1)), 'YYYYMM'), '999999')
GROUP BY "BILL3"."UNIT_ID"
)
) BILL2
ON "BILL"."UNIT_ID" = "BILL2"."UNIT_ID"
WHERE TO_CHAR("BILL"."EFF_DT", 'YYYYMM') = TO_CHAR(ADD_MONTHS(TRUNC(SYSDATE), -1), 'YYYYMM')
ORDER BY "BILL2"."UNITNO", "BILL2"."EFF_DT" DESC
If the where before Join really matters to you, use a CTE. (Employing with clause for temporary table and joining on the same.)
With c as (SELECT "UNITS"."UnitNumber", "BILL"."EFF_DT","BILL"."UNIT_ID" -- Correction: Was " BILL"."UNIT_ID" (spacetanker)
FROM "MFIVE"."BILL_UNIT_ACCT" "BILL" -- Returning unit id column too, to be used in join
LEFT OUTER JOIN "MFIVE"."VIEW_ALL_UNITS" "UNITS" ON "BILL"."UNIT_ID" = "UNITS"."UNITID"
WHERE "UNITS"."OwnerDepartment" LIKE '580' AND TO_CHAR("BILL"."EFF_DT", 'MMYYYY') = TO_CHAR(ADD_MONTHS(TRUNC(SYSDATE), -1), 'MMYYYY'))
select * from c --Filter out your required columns from c and d alias results, e.g c.UNIT_ID
INNER JOIN
--Loop through previously identified units and determine last billing code change prior to preious month */
(
SELECT "BILL2"."UNIT_ID", MAX("BILL2"."EFF_DT")
FROM "MFIVE"."BILL_UNIT_ACCT" "BILL2"
WHERE TO_CHAR("BILL2"."EFF_DT", 'MMYYYY') < TO_CHAR(ADD_MONTHS(TRUNC(SYSDATE), -1), 'MMYYYY')
GROUP BY "BILL2"."UNIT_ID"
) d
ON c."UNIT_ID" = d."UNIT_ID"
order by c."UnitNumber", c."EFF_DT" desc -- COrrection: Removed semicolon that Crystal Reports didn't like (spacetanker)
It seems this query has lots of scope for tuning though. However, one who has access to the data and requirement specification is the best judge.
EDIT :
You are not able to see data PRIOR to previous month since you are using BILL.EFF_DT in your original question's select statement, which is filtered to give only dates of previous month(..AND TO_CHAR("BILL"."EFF_DT", 'MMYYYY') = TO_CHAR(ADD_MONTHS(TRUNC(SYSDATE), -1), 'MMYYYY'))
If you want the data as you want, I guess you have to use the BILL2 section (d part in my subquery), by giving an alias to Max(EFF_DT), and using that alias in your select clause.

Create weighted average in SQL using dates

I have a SQL query that lists details about a certain item. Everything works as should except for the last column. I want the weight of transaction column to report back a difference in days.
So for example the 4th row in the txdate column is 05/21/2014 and the 3rd row is 05/12/20014. The weight of transaction column in the 4th row should say 9.
I read about the Lag and Lead functions, but I'm not sure how to implement those with dates (if it's even possible). If it isn't possible is there a way to accomplish this?
Select t.txNumber,
t.item,
t.txCode,
t.txdate,
(t.onhandlocold + t.stockQty) as 'Ending Quantity',
tmax.maxtnumber 'Latest Transaction Code',
tmax.maxdate 'Latest Transaction Date',
tmin.mindate 'First Transaction Date',
(t.txdate - tmin.mindate) 'weight of transaction'
From tbliminvtxhistory t
Left outer join
(Select t.item, max(t.txnumber) as maxtnumber, max(t.txdate) as maxdate
From tbliminvtxHistory t
Where t.txCode != 'PAWAY'
Group By Item) tmax
on t.item = tmax.item
Left Outer Join
(Select t.item, min(t.txdate) as mindate
From tbliminvtxHistory t
WHere t.txCode != 'PAWAY'
and t.txdate > DateAdd(Year, -1, GetDate())
Group By Item) tmin
on t.item = tmin.item
where t.item = 'LR50M'
and t.txCode != 'PAWAY'
and t.txdate > DateAdd(Year, -1, GetDate())
Check out the DATEDIFF function, which will return the difference between two dates.
I think this is what you are looking for:
DATEDIFF(dd,tmin.mindate,t.txdate)
UPDATE:
Now that I understand your question a little better, here is an update. As mentioned in a comment on the above post, the LAG function is only supported in SQL 2012 and up. An alternative is to use ROW_NUMBER and store the results into a temp table. Then you can left join back to the same table on the next ROW_NUMBER in the results. Then you would use your DATEDIFF to compare the dates. This will do the exact same thing as the the LAG function.
Example:
SELECT ROW_NUMBER() OVER (ORDER BY txdate) AS RowNumber,*
INTO #Rows
FROM tbliminvtxhistory
SELECT DATEDIFF(dd,r2.txdate,r.txdate),*
FROM #Rows r
LEFT JOIN #Rows r2 ON r.RowNumber=r2.RowNumber+1
DROP TABLE #Rows
I think you are looking for this expression:
Select . . . ,
datediff(day, lag(txdate) over (order by xnumber), txdate)
This assumes that the rows are ordered by the first column, which seems reasonable given your explanation and the sample data.
EDIT:
Without lag() you can use outer apply. For simplicity, let me assume that your query is defined as a CTE:
with cte as (<your query here>)
select . . . ,
datediff(day, prev.txdate , cte.txdate)
from cte cross apply
(select top 1 cte2.*
from cte cte2
where cte2.xnumber < cte.xnumber
order by cte2.xnumber desc
) prev

JOIN on DATEPART month and year is causing extra rows

I have two tables that contain a date field. This date field is one of the JOIN causes that I would like to implement, but I only want to JOIN on the month and year, not the day. The # of records about triple when I attempt to do so. I'm guessing there is something wrong with my query? Or is this even possible? I'm using Postgres
SELECT a.load_date , a.mandt, a.vbeln,a.posnr, a.matnr, b.tfed
FROM tableA a
JOIN tableB b
ON date_part('month'::text, a.erdat) = date_part('month'::text, b.gdatu)
AND date_part('year'::text, a.erdat) = date_part('year'::text, b.gdatu)
EDIT Here is my full code
SELECT a.mandt, a.vbeln,
a.erdat, a.erzet, a.ernam, a.angdt, a.audat, a.vbtyp, a.trvog,
a.auart, a.submi, a.lifsk, a.faksk, a.netwr, a.waerk, a.vkorg, a.vtweg, a.spart,
a.vkgrp, a.vkbur, a.knumv, a.vdatu, a.vprgr, a.kalsm, a.vsbed, a.fkara, a.awahr,
a.bstnk, a.bstdk, a.telf1, a.kunnr, a.stafo, a.stwae, a.aedat, a.kvgr1,a.kvgr2,
a.kvgr3, a.kokrs, a.kkber, a.knkli, a.sbgrp, a.ctlpc, a.cmwae, a.cmfre, a.cmngv,
a.amtbl, a.hityp_pr, a.abrvw, a.vgbel, a.objnr, a.bukrs_vf, a.taxk1,a.xblnr,
a.vgtyp, a.abhod, a.abhov, a.stceg_l, a.landtx, a.fmbdat, a.vsnmr_v, a.handle,
a.yybcawv1, a.yybcawv2, a.yybcawv3, a.yyawv1dat, a.yyawv2dat, a.yybcawvc,
a.kvgr5, a.augru, a.autlf, a.bname, a.bnddt, a.bsark, a.cmnup, a.fiscalper,
a.fiscalyr, a.gwldt, a.ihrez, a.intind, a.intsum, a.rplnr, a.taxk2, a.yybabt,
a.yybemail, a.yybfax, a.yybname, a.yybphone, a.yyexporter, a.yypaypal_id,
a.yysd_projid, a.zone, a.zuonr, a.zz_campaign_id, a.zzedate, a.zzrev_cat_01,
a.zzrev_cat_02, a.zzrev_cat_03, a.zzrev_cat_04, a.zzrev_cat_05, a.zzrev_cat_06,
a.zzrev_cat_07, a.zzrev_cat_08, a.zzsdate, a.mahdt,
CASE
WHEN b.fcurr::text = 'USD'::text THEN a.netwr
WHEN b.fcurr::text = 'JPY'::text AND b.kurst::text = 'M'::text THEN a.netwr * b.ukurs / 10::numeric
WHEN b.fcurr::text = 'KRW'::text AND b.kurst::text = 'M'::text THEN a.netwr * b.ukurs / 10::numeric
WHEN b.kurst::text = 'M'::text THEN a.netwr * b.ukurs
ELSE a.netwr
END AS net_value_trans_currency_netwr
FROM src.sap_vbak a
JOIN src.sap_tcurr b
ON a.waerk::text = b.fcurr::text
AND date_part('MONTH'::text, a.erdat::timestamp with time zone) = date_part('MONTH'::text, b.gdatu::timestamp with time zone)
AND date_part('YEAR'::text, a.erdat::timestamp with time zone) = date_part('YEAR'::text, b.gdatu::timestamp with time zone);
I'm attempting to get currency conversions based off of the dates (month and year only) in each of the tables. Some of the currency conversion are different ( the CASE statement for net_value_trans_currency_netwr field). I am wanting the net_value_trans_currency_netwr field to be a new row that displays the currency conversion in USD. The original table has over 5 million rows. After the joins I end up with way more rows. From what I gather I'm getting a full join. How would I be able to execute what I'm trying to do without the full join creating more than needed rows?
You get duplicate rows as you are INNER JOINING on the month and year which are not unique. This is causing a cross join e.g.
Example Rows with dates
Date Month Year
1 01/01/2014 01 14
2 02/01/2014 01 14
Result of above join has 4 rows not 2!
1) Month from (1) Year from (1)
2) Month from (1) Year from (2)
3) Month from (2) Year from (1)
4) Month from (2) Year from (2)
If you want to avoid this you need something else to include in the join that makes each join unique! Adding the day may help but again if you have more than one date recorded on the same day you will get a duplicate. Have a think what else you could include on the join.
Use date_trunc() to simplify the query:
SELECT a.load_date, a.mandt, a.vbeln,a.posnr, a.matnr, b.tfed
FROM tableA a
JOIN tableB b ON date_trunc('month', a.erdat)
= date_trunc('month', b.gdatu);
Plus, you probably want to restrict the join further. This is a limited cross join resulting in a Cartesian product. If you have 3 rows for March 2014 in a tableA and 4 rows for March 2014 in a tableB, you already produce 12 rows in the result.