sql nested selects - sql

I'm new to sql and I understand what's going on here in general, but I can't explain it in detail.
I thought ,
The lookupweekid value taken from the lookupday table is decreased by -1 and assigned to the lookupweekid of the t2 table. Where t1.lookupweekid=t2.lookupweekid and t1.weekdaynumber=1. start_of_week is imported into table t3.
What do you think about my thought, is it true?
(select dateoftransaction
from lookupday t1,
(select lookupweekid-1 from lookupday
where dateoftransaction = date) t2 (lookupweekid)
where t1.lookupweekid=t2.lookupweekid
and t1.weekdaynumber=1) t3 (start_of_week)

I'm guessing it's calculating the start of the previous week.
Maybe the t3 sub-query can also be written like this:
select dateoftransaction
from lookupday
where lookupweekid in (select lookupweekid - 1
from lookupday
where dateoftransaction = current_date)
and weekdaynumber = 1

Related

TSQL - Calculate difference between values from yesterday to today, SELF JOIN question

I have pieced together code from various answers online to get the result I want but, I don't understand why it's working and I would like to know what the JOIN is actually doing where it says RowNum + 1.
The original problem is to calculate the percentage difference between a value from yesterday to today. I'm a little fuzzy on Self Joins, but I do understand self join. When I add the RowNum column, that confuses me.
Question
What is T2.RowNum = T1.RowNum + 1 doing in the self join please?
IF OBJECT_ID('tempdb..#t1') IS NOT NULL DROP TABLE #t1
CREATE TABLE #T1 (
ProductTotal int
,CountDate date
)
INSERT INTO #t1
VALUES
(893911,'20200815')
,(888970,'20200816')
,(899999,'20200817')
WITH cte AS (
SELECT
ROW_NUMBER() OVER(ORDER BY CountDate) AS RowNum
,ProductTotal
,CountDate
FROM #t1
WHERE CountDate > CAST(GETDATE () - 2 AS DATE)
)
SELECT
t1.RowNum
,t1.ProductTotal
,CAST(((t1.ProductTotal - t2.ProductTotal) * 1.0 / t2.ProductTotal) * 100 AS DECIMAL(10,2)) AS ProductDiff
,t1.CountDate
FROM cte AS t1
LEFT JOIN cte t2 ON T2.RowNum = T1.RowNum + 1
Assuming you have values on each day, a better approach uses lag():
SELECT ProductTotal, CountDate,
(ProductTotal - prev_ProductTotal) * 1.0 / ProductTotal
FROM (SELECT t.*,
LAG(ProductTotal) OVER (ORDER BY CountDate) as prev_ProductTotal
FROM #t1 t
) t
WHERE CountDate > CAST(GETDATE () - 1 AS DATE)
Note, that, as I commented, I completely agree with Gordon here, and that LAG (or LEAD) is the right answer here. To explain what you ask in the comment "I don't understand how T2.RowNum = T1.RowNum + 1 works":
A JOIN returns rows where the expression in the ON is true. As you have an INNER JOIN then only rows from both sides of the JOIN where the expression evaluates to True are displayed. For a LEFT JOIN any prior previously returned would not be "lost". (There are other types of joins too.)
For T2.RowNum = T1.RowNum + 1 this is basic maths. 2 is matched to 1 (1+1), 3 is match to 2 (2+1)... 100 is matched to 99 (99 + 1). So the data from T1 is matched to the row "after" in terms of the ROW_NUMBER order defined within the CTE. In this case, that would be the row with the "next" value for CountDate in ascending order.

Update record for the last week

I'm building a report that needs to show how many users were upgraded from account status 1 to account status 2 each hour for the last week (and delete hours where the upgrades = 0). My table has an updated date, however it isn't certain that the account status is the item being updated (it could be contact information etc).
The basic table config that I'm working with is below. There are other columns but they aren't needed for my query.
account_id, account_status, updated_date.
My initial idea was to first filter and look at the data for the current week, then find if they were at account_status = 1 and later account_status = 2.
What's the best way to tackle this?
This is the kind of thing that you would use a SELF JOIN for. It's tough to say exactly how to do this without getting any kind of example data, but hopefully you can build off of this at least. There are a lot of tutorials on how to write a successful self join, so I'd refer to those if you're having difficulties.
select a.account_id
from tableName a, tableName b
where a.account_id= b.account_id
and
(a.DateModified > 'YYYY-MM-DD' and a.account_status = 1)
and
(b.DateModified < 'YYYY-MM-DD' and b.account_status= 2)
Maybe you could try to rank all the updates older than an update, with a status of 2 for an account by the timestamp descending. Check if such an entry with status 1 and rank 1 exists, to know that the respective younger update did change the status from 1 to 2.
SELECT *
FROM elbat t1
WHERE t1.account_status = 2
AND EXISTS (SELECT *
FROM (SELECT rank() OVER (ORDER BY t2.updated_date DESC) r,
t2.account_status
FROM elbat t2
WHERE t2.account_id = t1.account_id
AND t2.updated_date <= t1.updated_date) x
WHERE x.account_status = 1
AND x.r = 1);
Then, to get the hours you, could create a table variable and fill it with the hours worth a week (unless you already have a suitable calender/time table). Then INNER JOIN that table (variable) to the result from above. Since it's an INNER JOIN hours where no status update exists won't be in the result.
DECLARE #current_time datetime = getdate();
DECLARE #current_hour datetime = dateadd(hour,
datepart(hour,
#current_time),
convert(datetime,
convert(date,
#current_time)));
DECLARE #hours
TABLE (hour datetime);
DECLARE #interval_size integer = 7 * 24;
WHILE #interval_size > 0
BEGIN
INSERT INTO #hours
(hour)
VALUES (dateadd(hour,
-1 * #interval_size,
#current_hour));
SET #interval_size = #interval_size - 1;
END;
SELECT *
FROM #hours h
INNER JOIN (SELECT *
FROM elbat t1
WHERE t1.account_status = 2
AND EXISTS (SELECT *
FROM (SELECT rank() OVER (ORDER BY t2.updated_date DESC) r,
t2.account_status
FROM elbat t2
WHERE t2.account_id = t1.account_id
AND t2.updated_date <= t1.updated_date) x
WHERE x.account_status = 1
AND x.r = 1)) y
ON convert(date,
y.updated_date) = h.convert(date,
h.hour)
AND datepart(hour,
y.updated_date) = datepart(hour,
h.hour);
If you use this often and/or performance is important, you might consider to introduce persistent, computed and indexed columns for the convert(...) and datepart(...) expressions and use them in the query instead. Indexing the calender/time table and the columns used in the subqueries is also worth a consideration.
(Disclaimer: Since you didn't provide DDL of the table nor any sample data this is totally untested.)

TSQL Repeat values on outer join

I've been working on this for some time now and I would like to get some help.
My database is SQL Server 2008 R2 ( I know, very old).
I basically have a transaction table that captures values per week, by job.
I would like to repeat the last value of a job until it finds the next value.
I have included some data from my table. The last column (values needed) is what I'm trying to achieve.
Thank you very much.
Bruce
image of data
I've tried the SQL below, but it is not giving the correct values. Please see the attachment.
SQL
select t.*, t2.percentcomp as value_needed
from #1 t
outer apply
(select top 1 t2.*
from #1 t2
where t2.job_skey = t.job_skey and
t2.COST_CODE_SKEY=t2.COST_CODE_SKEY and
t2.period_end_date <= t.period_end_date and
t2.percentcomp is not null
order by t.JOB_SKEY,t.phase,t.period_end_date desc
) t2
Attachment..view of SQL. Value_needed should begin with 5
You can do what you want using OUTER APPLY:
select t.*, t2.percent_comp as value_needed
from t outer apply
(select top 1 t2.*
from t t2
where t2.job_skey = t.job_skey and
t2.period_end_date_id <= t.period_end_date_id and
t2.percentcomp is not null
order by t2.period_start_date desc
) t2;
enter image description here

sql query to with insert select

I have a table with the below format:
ID curr Date Bid Ask
1 AUD/NZD 20090501 00:00:00.833 1.2866 1.28733
2 AUD/NZD 20090501 01:01:01.582 1.28667 1.2874
3 AUD/NZD 20090501 02:01:01.582 1.28667 1.28747
Now I need to select the change of Bid and Ask column and store into a different table...The result should be like the following
Bid Change Ask Change
0.0000700 0.0000700
0.0000000 0.0000700
select
t1.id,
t1.curr,
t1.date,
t1.bid,
t1.bid - t2.bid [Bid Change],
t1.ask,
t1.ask - t2.ask [Ask Change]
from tbltest t1
left join tbltest t2 on t1.ID = t2.ID + 1
order by date
This query returns everything correct except the format of Bid Change and Ask Change...like the following
BID Change ASK CHANGE
7.00000000000145E-05 7.00000000000145E-05
Am really clueless on what to do with this situation...any little help will work.
Thanks in advance!
It doesn't seem necessary to me to store them in a different table, you can just calculate them on the fly using APPLY, this way any changes to the underlying data will not cause your change data to be stale:
SELECT T.*,
BidChange = t.Bid - prev.Bid,
AskChange = t.Ask - prev.Ask
FROM T
OUTER APPLY
( SELECT TOP 1 T2.Bid, T2.Ask
FROM T AS T2
WHERE T2.Curr = T.Curr
AND T2.Date < T.Date
ORDER BY T2.Date DESC
) AS prev;
If this is something you will need regularly then you may want to consider a view, rather than storing it in a table.

update a field based on subtotal from another table

I'm using oracle(10).
I've got two tables as follows:
Table1 (uniq rows):
ID AMOUNT DATE
Table2:
ID AMOUNT1 AMOUNT2 ...AMOUNTN DATE
Table2 is connected many to one to Table1 connected via ID.
What I need is update-ing Table1.DATE with: the last (earliest) date from Table2 where Table1.AMOUNT - SUM(Table2.AMOUNT1) <= 0, when reading table 2 backwards by the Table2.DATE field.
Is there a simple way to do it?
Thanks in advance!
UPDATE: as I see from your answers I had misspecified the question a bit. So here goes a detailed example:
Table1 has:
ID: 1 AMOUNT:100 DATE:NULL
Table2 has (for ID: 1 so ID is not listed in here):
AMOUNT1 DATE
50 20080131
30 20080121
25 20080111
20 20080101
So in this case I need 20080111 as the DATE in Table1 as 50+30+25 => 100.
Based on your revised question, this is a case for using analytic functions.
Assuming you meant >=100 rather than <= 100 as your example implies, and renaming columns DATE to THEDATE since DATE is a reserved word in Oracle:
update table1 set thedate=
( select max(thedate) from
( select id, thedate,
sum(amount1) over (partition by id order by thedate desc) cumsum
from table2
) v
where v.cumsum >= 100
and v.id = table1.id
)
If the 100 means the current value of table1 then change that line to:
where v.cumsum >= table1.amount
First off - your database layout feels severely wrong, but I guess you can't / don't want to change it. Table1 should probably be a view, and Table2 does not make the impression of proper normalization. Something like (ID, AMOUNT_TYPE, AMOUNT_VALUE, DATE) would make much more sense to me.
But to solve your problem (this is T-SQL "UPDATE FROM" syntax, but I think Oracle knows it):
UPDATE
Table1
SET
Date = Table2Aggregate.MinDate
FROM
Table1
INNER JOIN (
SELECT Id, SUM(Amount1) SumAmount1, MIN(Date) MinDate
FROM Table2
GROUP BY Id
) AS Table2Aggregate ON Table1.Id = Table2Aggregate.ID
WHERE
Table1.Amount - Table2Aggregate.SumAmount1 <= 0