How can I get what I'm looking for here? - sql-server

I think this question has been answered but I am not skilled enough (yet!) to have recognized how someone elses' answer will help me fix my problem so I apologize if this feels like a repost.
I am using MS Server2012
I need the following results from a query:
LoanNumber | OpenDate | CreditLimit | CaptureDate | CaptureBalance | TodayDate | TodayBalance
LoanNumber is a unique identifier | OpenDate is the date the credit line was opened | CaptureDate is OpenDate + 6 days | CaptureBalance is what we consider to be the initial balance on the credit line and is defined as the balance 6 days after it was opened | TodayDate is today | TodayBalance is the balance today
I want to be able to look at a credit line and compare the initial balance (aka CaptureBalance) to the credit limit as well as compare that to the balance today.
Here's my code and see below for more definitions
select top 100
L1.LOANNUMBER as 'LoanNumber'
,L1.OPENDATE as 'OpenDate' --this is stored as Date
,L2.OPENDATE+6 as 'CaptureDate'
,L1.CREDITLIMIT as 'CreditLimit'
,( Select L2.BALANCE
From LOAN as L2
INNER JOIN LOAN as L1 on L2.LOANNUMBER = L1.LOANNUMBER
Where CONVERT(datetime,convert(char(8),L2.RUNDATE )) = L2.OPENDATE+6
) as 'CaptureBalance'
From LOAN as L1
INNER JOIN LOAN as L2 on L1.LOANNUMBER = L2.LOANNUMBER
Where L1.RUNDATE = 20151130 -- this is stored as INT
and L1.[TYPE] = 'Line of Credit'
RUNDATE is important because every day our system logs a snapsot of that loan. Where L1.RUNDATE = 20151130 is telling the system to give me the balance on Nov 30 2015. I also need to get what the balance was 6 days after the date the loan was opened causing me to reference 2 different run dates.
I have to compare the run date (INT) to OpenDate (Date) so I used CONVERT(datetime,convert(char(8),L2.RUNDATE )) to convert the run date INT --> Date so I can effectively compare the two dates.
When I run this I get:
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
Initially I was running all of this off of the same table. Then I decided to try giving the loan table 2 different aliases and that's where I stopped.
Is the way I'm using that subquery resulting in "more than 1 value" because each result of that query is trying to get listed as a column header? If yes, I still don't know how to get what I'm looking for.
HELP!?

I am pretty sure this is what you want, or at least one approach to it:
select top 100
L1.LOANNUMBER as 'LoanNumber'
,L1.OPENDATE as 'OpenDate' --this is stored as Date
,L2.RUNDATE as 'CaptureDate'
,L1.CREDITLIMIT as 'CreditLimit'
,L2.BALANCE as 'CaptureBalance'
,L1.RUNDATE as 'TodayDate'
,L1.BALANCE as 'TodayBalance'
From LOAN as L1
INNER JOIN LOAN as L2
on L1.LOANNUMBER = L2.LOANNUMBER
AND L2.RUNDATE=DATEADD(dd, 6, L1.OPENDATE)
Where L1.RUNDATE = 20151130 -- this is stored as INT
and L1.[TYPE] = 'Line of Credit'

Related

TSQL - Matching a date between two dates in another table

I currently have two tables, tbl_Invoices
InvoiceNumber NextBillingDate
------------------------------
100 3/15/21
200 3/31/21
300 4/15/21
400 5/15/21
and tbl_GLPeriods:
GLPeriod PeriodStartDate PeriodEndDate
----------------------------------------------
250 3/3/21 4/3/21
251 4/4/21 5/2/21
252 5/3/21 6/3/21
I need a view that returns a column where the GL period for the next billing date is provided, ie:
InvoiceNumber NextBillingPeriod
---------------------------------
100 250
200 250
300 251
400 252
How do I query to find if one column is between the two columns in another table? I'm blanking on how to do this, thinking something with a CASE.
Edit: where I'm currently at, structurally won't work, but it shows what I'm currently trying to get going:
SELECT
*,
CASE
WHEN tbl_Invoices.NextBillingDate BETWEEN (SELECT PeriodStartDate FROM tbl_GLPeriods) AS stdt
AND (SELECT PeriodEndDate FROM tbl_GLPeriods) AS endt
THEN endt.GLPeriod
END AS NextBillingPeriod
FROM
tbl_Invoices
Solved with this thanks to #Charlieface:
select tbl_Invoices.InvoiceNumber, tbl_GLPeriods.GLPeriod
from tbl_Invoices
left join tbl_GLPeriods on tbl_Invoices.NextBillingDate between tbl_GLPeriods.PeriodStartDate AND tbl_GLPeriods.PeriodEndDate
You can use AND to connect multiple predicates to check for a range with <= and > (or equivalent). Like that you can use a correlated subquery similar to what you've tried, provided the periods cannot overlap.
SELECT i.invoicenumber,
(SELECT p.glperiod
FROM tbl_glperiods p
WHERE p.periodstartdate <= i.nextbillingdate
AND dateadd(DAY, 1, p.periodenddate) > i.nextbillingdate) nextbillingperiod
FROM tbl_invoices i;
You can also use a left join. Then the periods can overlap, you'll get multiple rows, if a date falls in two or more periods. A join might also perform better.
SELECT i.invoicenumber,
p.glperiod nextbillingperiod
FROM tbl_invoices i
LEFT JOIN tbl_glperiods p
ON p.periodstartdate <= i.nextbillingdate
AND dateadd(DAY, 1, p.periodenddate) > i.nextbillingdate;
Note that you can shorten dateadd(DAY, 1, p.periodenddate) to just p.periodenddate if tbl_glperiods.periodenddate is meant to be and exclusive upper bound or if it's inclusive but tbl_invoices.nextbillingdate is guaranteed not to be more precise than a day, i.e. it cannot have an hour, minute, second and so on portion. Otherwise you might miss timestamps on the last day past midnight.
select InvoiceNumber, (select GLPeriod from tbl_GLPeriods where NextBillingDate between PeriodStartDate and PeriodEndDate) 'NextBillingPeriod' from tbl_Invoices

SQL - Return first non-empty value for previous days

I'm currently working with an exchange rates table in SQL that has these fields:
| Country | ExchangeRateDt | ExchangeRateValue |
| DK | 202000601 | 0.2 |
| DK | 202000603 | 0.21 |
| HR | 202000601 | 0.10 |
| HR | 202000602 | 0.12 |
For each currency I don't have a value for any day of the year because of bank holidays or simply weekends.
I need to join it with an order table where some orders are placed on weekends and on a specific day I could not have an exchange rate to calculate taxes.
I need to take the first non missing value from the previous days (so in the examples should I have an order for day 2020-06-02 in Denmark I should exchange it using the rate 0.2)
I thought about using a calendar table but I can't manage to get the job done.
Can someone help me?
Thanks in advance,
R
To get the most recent value less than or equal to the current day:
SELECT
<whatever columns you need from order>
,exchange.ExchangeRateValue
FROM
<order table> order
LEFT JOIN
<exchange rate table> exchange
ON exchange.Country = order.Country
AND exchange.ExchangeRateDt =
(
SELECT
MAX(ExchangeRateDt)
FROM
<exchange rate table>
WHERE
Country = order.Country
AND ExchangeRateDt <= order.OrderDt
)
Ensure the clustered index on the exchange rate table is (Country, ExchangeRateDt).
I have this as a left join so you will still return order results if the currency information is somehow missing. You would have to refer to business rules on how to proceed if no exchange rate was available.
You would typically create a calendar table that stores all the days you are interested in, say dates, with each date on a separate row.
You would also probably have a table that lists the countries: I assumed countries.
Then, one option is a lateral join:
select c.country, d.date, t.ExchangeRateValue
from dates d
cross join countries c
outer apply (
select top (1) t.*
from mytable t
where t.country = c.country and t.ExchangeRateDt <= d.date
order by t.ExchangeRateDt desc limit 1
) t
If you don't have these two tables, or can't create them, then one option is a recursive query to generate the dates and a subquery to list the countries. For example, this would generate the data for the month of June:
with dates as (
select '20200601' date
union all
select dateadd(day, 1, date) from dates where date < '20200701'
)
select c.country, d.date, t.ExchangeRateValue
from dates d
cross join (select distinct country from mytable) c
outer apply (
select top (1) t.*
from mytable t
where t.country = c.country and t.ExchangeRateDt <= d.date
order by t.ExchangeRateDt desc limit 1
) t
You should be able to do the mapping between the transation date and the exchange rate date with this query:
select TAB.primary_key, TAB.TransationDate, max(EXR.ExchangeRateDt)
from yourtable TAB
inner join exchangerate EXR
on TAB.Country = EXR.Country and TAB.TransationDate >= EXR.ExchangeRateDt
group by TAB.primary_key, TAB.TransationDate

Summation in SQL over a computed column

I have a Trans-SQL related question, concerning summations over a computed column.
I am having a problem with double-counting of these computed values.
Usually I would extract all the raw data and post-process it in Perl, but I can't do that on this occasion due to the particular reporting system we need to use. I'm relatively inexperienced with the intricacies of SQL, so I thought I'd refer this to the experts.
My data is arranged in the following tables (highly simplified and reduced for the purposes of clarity):
Patient table:
PatientId
PatientSer
Course table
PatientSer
CourseSer
CourseId
Diagnosis table
PatientSer
DiagnosisId
Plan table
PlanSer
CourseSer
PlanId
Field table
PlanSer
FieldId
FractionNumber
FieldDateTime
What I would like to do is find the difference between the maximum fraction number and the minimum fraction number over a range of dates in the FieldDateTime in the FieldTable. I would like to then sum these values over the possible plan ids associated with a course, but I do not want to double count over the two particular diagnosis ids (A or B or both) that I may encounter for a patient.
So, for a patient with two diagnosis codes (A and B) and two plans in the same course of treatment (Plan1 and Plan2), with a difference in fraction numbers of 24 for the first plan and 5 for the second what I would like to get out is something like this:
- **PatientId CourseId PlanId DiagnosisId FractionNumberDiff Sum
- AB1234 1 Plan1 A 24 29
- AB1234 1 Plan1 B * *
- AB1234 1 Plan2 A 5 *
- AB1234 1 Plan2 B * *
I've racked my brains about how to do this, and I've tried the following:
SELECT
Patient.PatientId,
Course.CourseId,
Plan.PlanId,
MAX(fractionnumber OVER PARTITION(Plan.PlanSer)) - MIN(fractionnumber OVER PARTITION(Plan.PlanSer)) AS FractionNumberDiff,
SUM(FractionNumberDiff OVER PARTITION(Course.CourseSer)
FROM
Patient P
INNER JOIN
Course C ON (P.PatientSer = C.PatientSer)
INNER JOIN
Plan Pl ON (Pl.CourseSer = C.CourseSer)
INNER JOIN
Diagnosis D ON (D.PatientSer = P.PatientSer)
INNER JOIN
Field F ON (F.PlanSer = Pl.PlanSer)
WHERE
FieldDateTime > [Start Date]
AND FieldDateTime < [End Date]
But this just double-counts over the diagnosis codes, meaning that I end up with 58 instead of 29.
Any ideas about what I can do?
change the FractionNumberDiff to
MAX(fractionnumber) OVER (PARTITION BY Plan.PlanSer) -
MIN(fractionnumber) OVER (PARTITION BY Plan.PlanSer) AS FractionNumberDiff
and remove the "SUM(FractionNumberDiff OVER PARTITION(Course.CourseSer)"
make the exisitng query as a derived table and calcualte the SUM(FractionNumberDiff) there
SELECT *, SUM(FractionNumberDiff) OVER ( PARTITION BYCourse.CourseSer)
FROM
(
< the modified existing query here>
) AS d
as for the double counting issue, please post some sample data and the expected result

SUM() column based on other columns

I have table with sales plan data for every week, which consists of few columns:
SAL_DTDGID -- which is date of every Sunday, for example 20160110, 20160117
SAL_MQuantity --sum of sales plan value
SAL_MQuantityYTD --sum of plans since first day of the year
SAL_CoreElement --sales plan data for few core elements
SAL_Site --unique identifier of place, where sale has happened
How do I sum values in SAL_MQuantityYTD as values of SAL_MQuantity since first records in 2016 to 'now' for every site and every core element?
Every site mentioned in SAL_Site has 52 rows corresponding week count in a year along with 5 different SAL_CoreElement's
Example:
SAL_DTDGID|SAL_MQuantity|SAL_MQuantityYTD|SAL_CoreElement|SAL_Site
20160110 |20000 |20000 |1 |1234
20160117 |10000 |30000 |1 |1234
20160124 |30000 |60000 |1 |1234
If something isn't clear I'll try to explain.
Not sure I completely understand your question, but this should allow you to recreate the running sum for SAL_MQuantityYTD. Replace #test with whatever your table/view is called.
SELECT *,
(SELECT SUM(SAL_MQuantity)
FROM #test T2
WHERE T2.SAL_DTDGID <= T1.SAL_DTDGID
AND T2.SAL_Site = T1.SAL_Site
AND T2.SAL_coreElement = T1.SAL_coreElement) AS RunningTotal
FROM #test T1
If you wanted to create the yearly figure then you could also use a correlated subquery like this
SELECT *,
(SELECT SUM(SAL_MQuantity)
FROM #test T2
WHERE cast(left(T2.SAL_DTDGID,4) as integer) = cast(left(T1.SAL_DTDGID,4) as integer)
AND T2.SAL_Site = T1.SAL_Site
AND T2.SAL_coreElement = T1.SAL_coreElement) AS RunningTotal
FROM #test T1
Edit: Just seen, basically the same answer, using a window function.
Let me explain you an idea. Please try below.
Select A, B,
(Select SUM(SAL_MQuantity)
FORM [Your Table]
WHERE [your date column] between '20160101' AND '[Present date]') AS SAL_MQuantityYTD
FROM [Your Table]
My understanding from your questions is that you want to have the YTD sum of SAL_MQuantity for each year (you can simply 'where' after if you only want 2016), SAL_Site, SAL_CoreElement.
The code below should achieve that and will run on SQL 2008 r2 (im running 2005).
'##t1' is the temp table name I used to test, replace it with your table name.
Select distinct
sum (SAL_MQuantity) over (partition by
left (cast (cast (SAL_DTDGID as int) as varchar (8)),4)
, SAL_Site
, SAL_CoreElement
) as Sum_SAL_DTDGID
,left (cast (cast (SAL_DTDGID as int) as varchar (8)),4) as Time_Period
, SAL_Site
, SAL_CoreElement
from ##t1

Data historian queries

I have a table that contains data for electric motors the format is:
DATE(DateTime) | TagName(VarChar(50) | Val(Float) |
2009-11-03 17:44:13.000 | Motor_1 | 123.45
2009-11-04 17:44:13.000 | Motor_1 | 124.45
2009-11-05 17:44:13.000 | Motor_1 | 125.45
2009-11-03 17:44:13.000 | Motor_2 | 223.45
2009-11-04 17:44:13.000 | Motor_2 | 224.45
Data for each motor is inserted daily, so there would be 31 Motor_1s and 31
Motor_2s etc. We do this so we can trend it on our control system displays.
I am using views to extract last months max val and last months min val.
Same for this months data. Then I join the two and calculate the difference
to get the actual run hours for that month. The "Val" is a nonresetable
Accumulation from a PLC(Controller). This is my query for Last months Max
Value:
SELECT TagName, Val AS Hours
FROM dbo.All_Data_From_Last_Mon AS cur
WHERE (NOT EXISTS
(SELECT TagName, Val
FROM dbo.All_Data_From_Last_Mon AS high
WHERE (TagName = cur.TagName) AND (Val > cur.Val)))
This is my query for Last months Max
Value:
SELECT TagName, Val AS Hours
FROM dbo.All_Data_From_Last_Mon AS cur
WHERE (NOT EXISTS
(SELECT TagName, Val
FROM dbo.All_Data_From_Last_Mon AS high
WHERE (TagName = cur.TagName) AND (Val < cur.Val)))
This is the query that calculates the difference and runs a bit slow:
SELECT dbo.Motors_Last_Mon_Max.TagName, STR(dbo.Motors_Last_Mon_Max.Hours - dbo.Motors_Last_Mon_Min.Hours, 12, 2) AS Hours
FROM dbo.Motors_Last_Mon_Min RIGHT OUTER JOIN
dbo.Motors_Last_Mon_Max ON dbo.Motors_Last_Mon_Min.TagName = dbo.Motors_Last_Mon_Max.TagName
I know there is a better way. Ultimately I just need last months total and this months total. Any help would be appreciated.
Thanks in advance
First two queries can be handled as one. Something like:
SELECT TagName, MAX(Val) AS MaxVal, MIN(Val) AS MinVal
FROM dbo.All_Data_From_Last_Mon
GROUP BY TagName
-- ORDER BY TagName (optionally)
I now see that these queries are SQL views, used for the third query... and I can see why this would be slow ;-)
The following reproduces the logic, but without the views, and this should allow SQL to optimize quite a bit. At any rate it provides more clarity as to what is being done...
Please "give it a spin".
SELECT DISTINCT Mx.TagName, STR(Mx.Hours - Mn.Hours, 12, 2) AS Hours
FROM dbo.All_Data_From_Last_Mon Mx
RIGHT OUTER JOIN dbo.All_Data_From_Last_Mon Mn ON Mx.TagName = Mn.TagName
AND dbo.All_Data_From_Last_Mon -- Cut the cross product a bit; may not be necessary
WHERE
NOT EXISTS (SELECT * FROM dbo.All_Data_From_Last_Mon Mx1
WHERE Mx1.TagName = Mx.TagName AND Mx1.Hours > Mx.Hours)
AND NOT EXISTS (SELECT * FROM dbo.All_Data_From_Last_Mon Mn1
WHERE Mn1.TagName = Mn.TagName AND Mn1.Hours < Mx.Hours)
Notes:
- notice the DISTINCT in SELECT statement. That is to avoid dup lines in he case there would be several days that show the Maximun (or minumum) Hours value for that month.
- the extra condition on the join is aimed at avoiding a full 31 * 31 cross product, but the conditions that truly bring it to a single line (or several in case of dups) are the NON EXISTS predicates that follow.
- A TagName+Hours index, if not readily present would greatly help.
==> I'd be interested in feedback on this query performance, as run with actual data.

Resources