microsoft sql server - calculate return between every row and the last row - sql

I have a table like the following:
+-------+--------------+
| Value | Date |
+-------+--------------+
| 14 | 10/11/2010 |
| 12 | 10/12/2010 |
| 12 | 10/13/2010 |
| 10 | 10/14/2010 |
| 8 | 10/15/2010 |
| 6 | 10/16/2010 |
| 4 | 10/17/2010 |
| 2 | 10/18/2010 |
+-------+--------------+
I would like to calculate the return (the quotient) between every row and the last row (which is with the latest date). e.g for the row with date "10/16/2010", the result should be 6/2=3
Hence, the resulting table should be
+-------+--------------+
| result| Date |
+-------+--------------+
| 7 | 10/11/2010 |
| 6 | 10/12/2010 |
| 6 | 10/13/2010 |
| 5 | 10/14/2010 |
| 4 | 10/15/2010 |
| 3 | 10/16/2010 |
| 2 | 10/17/2010 |
| 1 | 10/18/2010 |
+-------+--------------+
Is it possible to complete this? thanks you!

You can get the value you want to divide by. Since that's always going to be a single row, you can just use a cross join to join to that and perform your division. SQL Fiddle
with maxdate as
(select max([Date]) as maxdate from table1),
divby as
(select
value as divby
from
table1
inner join maxdate md
on md.maxdate = table1.[date])
select
value / divby
,[date]
from
table1
cross join divby
To break it down a bit, the first CTE (cleverly named maxdate) gets the maximum date for the whole thing. The second CTE (divby) get the value (that you will be dividing by) for that max date. As long as you only get one row back from that, you can safely use a cross join, resulting in each row in your table being divided by that one value.

Another possible solution JOIN the the table to itself.
SQL Fiddle Example
select (t1b.value / t1a.value) as result,
t1b.date from table1 t1a
join table1 t1b on t1a.date = (select max(date) from table1)

Thanks for the fiddle, Andrew! Can be accomplished like this as well if 2008 and above (fiddle: http://sqlfiddle.com/#!3/ecda1/11):
SELECT [Value] / MIN([Value]) OVER () AS result,
[Date]
FROM Table1

Related

Can I generate a map that shows a particular row was in a particular group in SQLite?

Say I have the following data:
+--------+-------+
| Group | Data |
+--------+-------+
| 1 | row 1 |
| 1 | row 2 |
| 1 | row 3 |
| 20 | row 1 |
| 20 | row 3 |
| 10 | row 1 |
| 10 | row A |
| 10 | row 2 |
| 10 | row 3 |
+--------+-------+
Is it possible to draw a map that shows which groups have which rows? Groups may not be contagious, so they can be placed into a separate table and use the row index for the string index instead. Something like this:
+-------+
| Group |
+-------+
| 1 |
| 20 |
| 10 |
+-------+
+-------+----------------+
| Data | Found in group |
+-------+----------------+
| row 1 | 111 |
| row A | 1 |
| row 2 | 1 1 |
| row 3 | 111 |
+-------+----------------+
Where the first character represents Group 1, the 2nd is Group 20 and the 3rd is Group 10.
Ordering of the Group rows isn't critical so long as I can reference which row goes with which character.
I only ask this because I saw this crazy example in the documentation generating a fractal, but I can't quite get my head around it.
Is this doable?
To find the missing values, first thing is to prepare a dataset which have all possible combination. You can achieve that using CROSS JOIN.
Once you have that DataSet, compare it with the actual DataSet.
Considering the Order by is done in the Grp column, you can achieve it using below.
SELECT
a.Data,group_concat(case when base.Grp is null then "." else "1" end,'') as Found_In_Group
,group_concat(b.Grp) as Group_Order
FROM
(SELECT Data FROM yourtable Group By Data)a
CROSS JOIN
(SELECT Grp FROM yourtable Group By Grp Order by Grp)b
LEFT JOIN yourtable base
ON b.Grp=base.Grp
AND a.Data=base.Data
GROUP BY a.Data
Note: Considered . instead of blank for better visibility to represent missing Group.
Data
Found_In_Group
Group_Order
row 1
111
1,10,20
row 2
11.
1,10,20
row 3
111
1,10,20
row A
.1.
1,10,20
Demo: Try here
SELECT Data, group_concat("Group") AS "Found in group"
FROM yourtable
GROUP BY Data
will give you a CSV list of groups.

Looking for a solution to a SQL GROUP BY .... WHERE MIN date

EDIT: The following question is angled at both MS-SQL and MySQL.
I've been pondering over this for a good 7 hours now. I've seen many stack overflow answers that are similar, but none that i've properly understood or worked out how to implement.
I am looking to SELECT id, title, e.t.c e.t.c FROM a table, WHERE the date is the next available date AFTER NOW(). The catch is, it needs to be GROUPED BY one particular column.
Here is the table:
==================================
id | name | date_start | sequence_id
--------------------------------------------------------
1 | Foo1 | 20150520 | 70
2 | Foo2 | 20150521 | 70
3 | Foo3 | 20150522 | 70
4 | Foo4 | 20150523 | 70
5 | FooX | 20150524 | 70
6 | FooY | 20150525 | 70
7 | Bar | 20150821 | 61
8 | BarN | 20151110 | 43
9 | BarZ | 20151104 | 43
And here is what I would like to see:
==================================
id | name | date_start | sequence_id
--------------------------------------------------------
1 | Foo1 | 20150520 | 70
7 | Bar | 20150821 | 61
9 | BarZ | 20151104 | 43
The results are filtered by MIN(date_start) > NOW() AND GROUPED BY sequence_id.
I'm not entirely sure this can be achieved with a GROUP BY as the rest of the columns would need to be contained within an aggregate function which I don't think will work.
Does anyone have an answer for this dilemma?
Many Thanks!
Simon
Just use a join and aggregation in a subquery:
select t.*
from table t join
(select sequence_id, min(date_start) as minds
from table t
group by sequence_id
) tt
on tt.sequence_id = t.sequence_id and t.date_start = tt.minds;
This is standard SQL, so it should run in any database.
http://sqlfiddle.com/#!9/d8576/4
SELECT *
FROM table1 as t1
LEFT JOIN
(SELECT *
FROM table1
WHERE date_start>NOW()
) as t2
ON t1.sequence_id = t2.sequence_id and t1.date_start>t2.date_start
WHERE t1.date_start>NOW() and t2.date_start IS NULL
GROUP BY t1.sequence_id
MSSQL fiddle
SELECT *
FROM table1 as t1
LEFT JOIN
(SELECT *
FROM table1
WHERE date_start>GetDate()
) as t2
ON t1.sequence_id = t2.sequence_id and t1.date_start>t2.date_start
WHERE t1.date_start>GetDate() and t2.date_start IS NULL

Oracle SQL - Making a one to many join one to one based on logic

Sorry for the broad title, I had a hard time coming up with a brief way of describing what I am looking to do. I have two tables (examples below) that I want to join but under a certain condition.
The main table has a field called "DateVal", the second table has a field called "Day". After joining on field "JoinField" I only want to keep rows where the day value in "DateVal" is less than the value of "Day". However, if this criteria is met for multiple values of "Day" I only want it to keep the first instance.
In the second table below, for JoinField "A" there are three rows, for the first I only want it to return times when the day of the month is between 1-10, the second only with the day of the month is between 11-20, and the last 20-31.
A left or inner join will bring back all values, the only way I can think of to get around this is to do a complete join and only return for min("Day"). Can anyone think of a more efficient way?
Thanks in advance.
Table 1
-------------------------------
| ID | JoinField | DateVal |
-------------------------------
| 1 | A | 01/01/2014 |
| 2 | A | 01/16/2014 |
| 3 | B | 05/20/2013 |
-------------------------------
Table 2
--------------------------------
| JoinField | Day | FieldToAdd |
--------------------------------
| A | 10 | A |
| A | 20 | AA |
| A | 31 | AAA |
| B | 15 | B |
| B | 31 | BB |
--------------------------------
Desired Results
--------------------------------------------
| ID | JoinField | DateVal | FieldToAdd |
--------------------------------------------
| 1 | A | 01/01/2014 | A |
| 2 | A | 01/16/2014 | AA |
| 3 | B | 05/20/2014 | BB |
--------------------------------------------
You can do this in a variety of ways. I think a correlated subquery is the easiest way to express it, but unfortunately, the following doesn't work in Oracle:
select t1.*,
(select *
from (select t2.*
from table2 t2
where t2.day < extract(day from t1.dateval)
order by t2.day desc
) t
where rownum = 1
)
from table1 t1;
You can instead do this with join fancy window functions:
select *
from (select t1.*,
row_number() over (partition by t1.id order by t2.day desc) as seqnum
from table1 t1 left outer join
table2 t2
on t2.day < extract(day from t1.dateval)
) t
where seqnum = 1;

Add and order row on SQL result

I'm looking for a way to always get a define number of row in my SQL result (Oracle).
Let me show you what i meen.
Here's an extract of my table
|----------|----------|----------|
| DATE | NUMBER | TYPE |
|----------|----------|----------|
| 12/01/13 | 2 | A |
| 12/01/13 | 4 | B |
| 12/02/13 | 3 | D |
| 12/02/13 | 1 | A |
| 12/02/13 | 5 | X |
|----------|----------|----------|
I need to always get 5 rows in my result for a chosen date, so complete it with new rows and "null" value.
Here's what i hope the result will look like for 12/01/13
|----------|----------|----------|
| DATE | NUMBER | TYPE |
|----------|----------|----------|
| 12/01/13 | 1 | null |
| 12/01/13 | 2 | A |
| 12/01/13 | 3 | null |
| 12/01/13 | 4 | B |
| 12/01/13 | 5 | null |
|----------|----------|----------|
The idea is the same for the other date. i kind a did something with a lot of UNION but it wasn't working very well.
So how would you write this SELECT query ?
Thanks
A partition outer join will fill in the gaps.
with cte_number_list as (
select rownum my_number
from dual
connect by level <= 5)
select t.my_date,
l.my_number,
t.type
from cte_number_list l left outer join
my_table t partition by (t.my_date)
on l.my_number = t.my_number;
http://docs.oracle.com/cd/E11882_01/server.112/e25554/analysis.htm#DWHSG02013
Off the top of my head, something like this should work:
CREATE TABLE #Numbers ( Number INT );
INSERT #Numbers (Number) VALUES (1), (2), (3), (4), (5);
SELECT Dates.Date, #Numbers.Number, Type
FROM #Numbers, (SELECT DISTINCT Date FROM MyTable) AS Dates
LEFT JOIN MyTable ON MyTable.Date = Dates AND MyTable.Number = #Numbers.Number
Edit: This example probably won't work with Oracle. I just realised that the question was regarding Oracle SQL after I wrote it. Leaving the answer here anyway. If someone knows how to translate it into Oracle SQL syntax, please feel free to edit my answer.

Select field from recordset that is between dates

Here is an example as it is pretty difficult to explain:
Table one:
| Date | Place ID |
==========================
| 01-Feb-2013 | 1 |
| 21-Jun-2015 | 2 |
Table two:
| Place ID | Date Ranked | Score |
==================================
| 1 | 01-Jan-2012 | 2 |
| 1 | 01-Jan-2014 | 1 |
| 1 | 01-Jan-2010 | 3 |
| 2 | 01-Jan-2016 | 1 |
What I want to happen is with SQL (MS) is when the first record of table one is returned I want whatever the score at that time to be returned from the second table. So in this example the score should be 2 as it is after 01-Jan-2012 but before 01-Jan-2014. And when the second record from table 1 is returned it should return NULL or blank from table 2 as no score existed for that time chosen.
Hope that makes sense!!
In SQL Server, you can use outer apply:
select t1.*, t2.score
from table1 t1 outer apply
(select top 1 t2.*
from table2 t2
where t2.placeid = t1.placeid and
t2.dateranked <= t1.dateranked
order by t2.dateranked desc
) t2;
In this case, you can do the same thing with a correlated subquery as well.