Transposing rows into columns - sql

I have following data
with sample_data as (select to_date('05/01/2015', 'dd/mm/yyyy') dt, '1' Period, 'A' code from dual union all
select to_date('05/01/2015', 'dd/mm/yyyy') dt, '2' Period, 'A' code from dual union all
select to_date('05/01/2015', 'dd/mm/yyyy') dt, '3' Period, 'P' code from dual union all
select to_date('05/01/2015', 'dd/mm/yyyy') dt, '4' Period, 'A' code from dual union all
select to_date('05/01/2015', 'dd/mm/yyyy') dt, '5' Period, 'P' code from dual
)
select * from sample_data
That gives me simple query results as below
DT PERIOD CODE
2015-01-05 00:00:00 1 A
2015-01-05 00:00:00 2 A
2015-01-05 00:00:00 3 P
2015-01-05 00:00:00 4 A
2015-01-09 00:00:00 5 P
I would like to transpose the results like this -
DATE ATTENDANCE
2015-01-05 12345
AAPAP
How can I do that?
Thanks a bunch!

This is an Oracle SQL question and not PL/SQL!
That could be something like:
select to_char(dt,'YYYY-MM-DD') date,listagg(period) within group (order by period)||chr(13)||chr(10)||listagg(code) within group (order by period) attendance
from (
with sample_data as (select to_date('05/01/2015', 'dd/mm/yyyy') dt, '1' Period, 'A' code from dual union all
select to_date('05/01/2015', 'dd/mm/yyyy') dt, '2' Period, 'A' code from dual union all
select to_date('05/01/2015', 'dd/mm/yyyy') dt, '3' Period, 'P' code from dual union all
select to_date('05/01/2015', 'dd/mm/yyyy') dt, '4' Period, 'A' code from dual union all
select to_date('05/01/2015', 'dd/mm/yyyy') dt, '5' Period, 'P' code from dual
) select * from sample_data
) group by to_char(dt,'YYYY-MM-DD')
Note that the function listagg only can be used from Oracle 11.2. If you are on an earlier version you can use xmlagg, but thats a bit more clotted.

Related

SQL: How to split data from quaterly to monthly with date

I have the data in the sql table in quarterly format. I need to be able to split it into monthly with value split evenly ([value/3) in to each month. Can you please assist on how to achieve this using SQL? Thank you.
start
end
value
2022-01-01
2022-04-01
25629
2022-04-01
2022-07-01
993621
CREATE TABLE #your_tbl
("start_dt" timestamp, "end_dt" timestamp, "values" int)
;
INSERT INTO #your_tbl
("start_dt", "end_dt", "values")
VALUES
('2020-01-01 00:00:00', '2020-04-01 00:00:00', 114625),
('2020-04-01 00:00:00', '2020-07-01 00:00:00', 45216),
('2020-07-01 00:00:00', '2020-10-01 00:00:00', 513574)
DECLARE #datefrom datetime
DECLARE #dateto datetime
SET #datefrom='2022-04-01'
SET #dateto = '2022-07-01'
;WITH cte AS
(
SELECT #datefrom as MyDate
UNION ALL
SELECT DATEADD(month,1,MyDate)
FROM cte
WHERE DATEADD(month,1,MyDate)<#dateto
),
combined AS (
SELECT *
FROM #your_tbl q
JOIN cte m
ON YEAR(m.MyDate) >= q.start_dt
AND MONTH(m.MyDate) < q.end_dt
)
SELECT *, [values]/COUNT(1) OVER(PARTITION BY [start_dt], [end_dt]) as monthly_values
FROM combined
DROP TABLE #your_tbl
In Oracle can you use this script:
with mytable as (
select to_date('2022-01-01', 'YYYY-MM-DD') as startX, to_date('2022-04-01', 'YYYY-MM-DD') as endX, 25629 as valueX from dual union
select to_date('2022-04-01', 'YYYY-MM-DD') as startX, to_date('2022-07-01', 'YYYY-MM-DD') as endX, 993621 as valueX from dual union
select to_date('2022-07-01', 'YYYY-MM-DD') as startX, to_date('2022-10-01', 'YYYY-MM-DD') as endX, 21 as valueX from dual union
select to_date('2022-10-01', 'YYYY-MM-DD') as startX, to_date('2023-01-01', 'YYYY-MM-DD') as endX, 7777 as valueX from dual
),
mymonths as (
select '01' as month_n from dual union
select '02' as month_n from dual union
select '03' as month_n from dual union
select '04' as month_n from dual union
select '05' as month_n from dual union
select '06' as month_n from dual union
select '07' as month_n from dual union
select '08' as month_n from dual union
select '09' as month_n from dual union
select '10' as month_n from dual union
select '11' as month_n from dual union
select '12' as month_n from dual
)
select month_n, startX, valueX/3
from mytable, mymonths
where month_n between to_char(startX, 'MM') and to_char(endX-1, 'MM');
MONTHS_N STARTX VALUEX/3
-------- ---------- ----------
01 01/01/2022 8543
02 01/01/2022 8543
03 01/01/2022 8543
04 01/04/2022 331207
05 01/04/2022 331207
06 01/04/2022 331207
07 01/07/2022 7
08 01/07/2022 7
09 01/07/2022 7
10 01/10/2022 2592,33333
11 01/10/2022 2592,33333
12 01/10/2022 2592,33333
Thank you.
Assuming you can figure out how to generate monthly dates, which is RDBMS dependent, here's a solution that might work depending on if you can use window functions.
Note this doesn't hard-code divide by 3 in case you're in a partial quarter.
WITH combined AS (
SELECT *,
FROM your_tbl q
JOIN monthly_dates m
ON m.monthly_dt >= q.start_dt
AND m.monthly_dt < q.end_dt
)
SELECT *
, values / COUNT(1) OVER(PARTITION BY start_dt, end_dt) as monthly_values
FROM combined
sqlfiddle

How to select dates from dual, but with joined data?

I got an SQL problem I'm not capable to solve.
First of all, an SQL fiddle with it: http://sqlfiddle.com/#!4/fe7b07/2
As you see, I fill the table with some dates, which are bound to some ID. Those dates are day by day. So for this example, we'd have something like this, if we only look at January:
The timelines spanning from 2020-01-01 to 2020-01-31, the blocks are the dates in the database. So this would be the simple SELECT * FROM days output.
What I now want is to fill in some days to this output. These would span from timeline_begin to MIN(date_from); and from MAX(date_from) to timeline_end.
I'll mark these red in the following picture:
The orange span is not necessary to be added, too, but if your solution would do that too, that would be also ok.
Ok, so far so good.
For this I created the SELECT * FROM minmax, which will select the MIN(date_from) and MAX(date_from) for every id_othertable. Still no magic involved.
What I struggle is now creating those days for every id_othertable, while also joining the data they have on them (in this fiddle, it's just the some_info field).
I tried to write this in the SELECT * FROM days_before query, but I just can't get it to work. I read about the magical function CONNECT BY, which will on its own create dates line by line, but I can't get to join my data from the former table. Every time I join the info, I only get one line per id_othertable, not all those dates I need.
So the ideal solution I'm looking for would be to have three select queries:
SELECT * FROM days which select dates out of the database
SELECT * FROM days_before which will show the dates before MIN(date_from) of query 1
SELECT * FROM days_after for dates after MAX(date_from) of query 1
And in the end I'd UNION those three queries to have them all combined.
I hope I could explain my problem good enough. If you need any information or further explaining, please don't hesitate to ask.
EDIT 1: I created a pastebin with some example data: https://pastebin.com/jskrStpZ
Bear in mind that only the first query has actual information from the database, the other two have created data. Also, this example output only has data for id_othertable = 1, so the actual query should also have the information for id_othertable = 2, 3.
EDIT 2: just for clarification, the field date_to is just a simple date_from + 1 day.
If you have denormalised date it's quite simple:
with bas as (
select 1 id_other_table, to_date('2020-01-05', 'YYYY-MM-DD') date_from, to_date('2020-01-06', 'YYYY-MM-DD') date_to, 'hello' some_info from dual
union all select 1 id_other_table, to_date('2020-01-06', 'YYYY-MM-DD') date_from, to_date('2020-01-07', 'YYYY-MM-DD') date_to, 'hello' some_info from dual
union all select 1 id_other_table, to_date('2020-01-07', 'YYYY-MM-DD') date_from, to_date('2020-01-08', 'YYYY-MM-DD') date_to, 'hello' some_info from dual
union all select 1 id_other_table, to_date('2020-01-10', 'YYYY-MM-DD') date_from, to_date('2020-01-11', 'YYYY-MM-DD') date_to, 'hello' some_info from dual
union all select 1 id_other_table, to_date('2020-01-11', 'YYYY-MM-DD') date_from, to_date('2020-01-12', 'YYYY-MM-DD') date_to, 'hello' some_info from dual
union all select 1 id_other_table, to_date('2020-01-12', 'YYYY-MM-DD') date_from, to_date('2020-01-13', 'YYYY-MM-DD') date_to, 'hello' some_info from dual
union all select 2 id_other_table, to_date('2020-01-10', 'YYYY-MM-DD') date_from, to_date('2020-01-11', 'YYYY-MM-DD') date_to, 'my' some_info from dual
union all select 2 id_other_table, to_date('2020-01-11', 'YYYY-MM-DD') date_from, to_date('2020-01-12', 'YYYY-MM-DD') date_to, 'my' some_info from dual
union all select 2 id_other_table, to_date('2020-01-12', 'YYYY-MM-DD') date_from, to_date('2020-01-13', 'YYYY-MM-DD') date_to, 'my' some_info from dual
union all select 3 id_other_table, to_date('2020-01-20', 'YYYY-MM-DD') date_from, to_date('2020-01-21', 'YYYY-MM-DD') date_to, 'friend' some_info from dual
union all select 3 id_other_table, to_date('2020-01-21', 'YYYY-MM-DD') date_from, to_date('2020-01-22', 'YYYY-MM-DD') date_to, 'friend' some_info from dual
union all select 3 id_other_table, to_date('2020-01-22', 'YYYY-MM-DD') date_from, to_date('2020-01-23', 'YYYY-MM-DD') date_to, 'friend' some_info from dual)
, ad as (select trunc(sysdate,'YYYY') -1 + level all_dates from dual connect by level <= 31)
select distinct some_info,all_dates from bas,ad where (some_info,all_dates) not in (select some_info,date_from from bas)
If you have longer date ranges or mind of the time the query needs another solution is helpful. But that is harder to debug. Because it's quite hard to get the orange time slot
If you want the dates per id that are not in the database then you can use the LEAD analytic function:
WITH dates ( id, date_from, date_to ) AS (
SELECT id_othertable,
DATE '2020-01-01',
MIN( date_from )
FROM some_dates
WHERE date_to > DATE '2020-01-01'
AND date_from < ADD_MONTHS( DATE '2020-01-01', 1 )
GROUP BY id_othertable
UNION ALL
SELECT id_othertable,
date_to,
LEAD( date_from, 1, ADD_MONTHS( DATE '2020-01-01', 1 ) )
OVER ( PARTITION BY id_othertable ORDER BY date_from )
FROM some_dates
WHERE date_to > DATE '2020-01-01'
AND date_from < ADD_MONTHS( DATE '2020-01-01', 1 )
)
SELECT id,
date_from,
date_to
FROM dates
WHERE date_from < date_to
ORDER BY id, date_from;
so for the test data:
CREATE TABLE some_dates ( id_othertable, date_from, date_to, some_info ) AS
SELECT 1, DATE '2020-01-05', DATE '2020-01-06', 'hello1' FROM DUAL UNION ALL
SELECT 1, DATE '2020-01-06', DATE '2020-01-07', 'hello2' FROM DUAL UNION ALL
SELECT 1, DATE '2020-01-07', DATE '2020-01-08', 'hello3' FROM DUAL UNION ALL
SELECT 1, DATE '2020-01-10', DATE '2020-01-13', 'hello4' FROM DUAL UNION ALL
SELECT 2, DATE '2020-01-10', DATE '2020-01-13', 'my' FROM DUAL UNION ALL
SELECT 3, DATE '2020-01-20', DATE '2020-01-23', 'friend' FROM DUAL UNION ALL
SELECT 4, DATE '2019-12-31', DATE '2020-01-05', 'before' FROM DUAL UNION ALL
SELECT 4, DATE '2020-01-30', DATE '2020-02-02', 'after' FROM DUAL UNION ALL
SELECT 5, DATE '2019-12-31', DATE '2020-01-10', 'only_before' FROM DUAL UNION ALL
SELECT 6, DATE '2020-01-15', DATE '2020-02-01', 'only_after' FROM DUAL UNION ALL
SELECT 7, DATE '2019-12-31', DATE '2020-02-01', 'exlude_all' FROM DUAL;
this outputs:
ID | DATE_FROM | DATE_TO
-: | :--------- | :---------
1 | 2020-01-01 | 2020-01-05
1 | 2020-01-08 | 2020-01-10
1 | 2020-01-13 | 2020-02-01
2 | 2020-01-01 | 2020-01-10
2 | 2020-01-13 | 2020-02-01
3 | 2020-01-01 | 2020-01-20
3 | 2020-01-23 | 2020-02-01
4 | 2020-01-05 | 2020-01-30
5 | 2020-01-10 | 2020-02-01
6 | 2020-01-01 | 2020-01-15
db<>fiddle here
If you want the days before then filter on:
WHERE day_from = DATE '2020-01-01'
and, similarly, if you want the days after then filter on:
WHERE day_to = ADD_MONTHS( DATE '2020-01-01', 1 )
If you want to specify the start date and number of months duration then use named bind parameters:
WITH dates ( id, date_from, date_to ) AS (
SELECT id_othertable,
:start_date,
MIN( date_from )
FROM some_dates
WHERE date_to > :start_date
AND date_from < ADD_MONTHS( :start_date, :number_months )
GROUP BY id_othertable
UNION ALL
SELECT id_othertable,
date_to,
LEAD( date_from, 1, ADD_MONTHS( :start_date, :number_months ) )
OVER ( PARTITION BY id_othertable ORDER BY date_from )
FROM some_dates
WHERE date_to > :start_date
AND date_from < ADD_MONTHS( :start_date, :number_months )
)
SELECT id,
date_from,
date_to
FROM dates
WHERE date_from < date_to
ORDER BY id, date_from;
Select whole range using connect by generator. Join your table partitioned by id.
select date_from, nvl(date_to, date_from +1) date_to, id_othertable, some_info
from (
select date '2020-01-01' + level - 1 as date_from
from dual
connect by level <= date '2020-01-31' - date '2020-01-01' ) gen
natural left join some_dates partition by (id_othertable)
sqlfiddle

BigQuery: How to calculate the running count of distinct visitors for each last 2 days

I want to calculate unique user count in last 2 days for each Date.
First Query:- I tried with CASE statement give me user count for that day which is not expected result, even I tried with window function.
I know one alternate solution through self join (already mention as second query) which give me correct answer what I' expecting, but I want to do it in a single query.
Reason to do in a single query want to reduce processed data size, if I make self join it will read complete table twice, and the original table size is multi TB.
SELECT
(CASE WHEN dt BETWEEN DATE_SUB(dt, INTERVAL 1 DAY) AND dt THEN
CONCAT(CAST(DATE_SUB(dt, INTERVAL 1 DAY) AS STRING), '::', CAST(dt AS STRING)) END) AS Date_range,
COUNT(DISTINCT (CASE WHEN dt BETWEEN DATE_SUB(dt, INTERVAL 1 DAY) AND dt THEN Visitor_Name END)) AS Visitor_Count
FROM
(SELECT DATE('2018-01-01') AS dt, 'A' AS Visitor_Name
UNION ALL
SELECT '2018-01-01' AS dt, 'B' AS Visitor_Name
UNION ALL
SELECT '2018-01-01' AS dt, 'C' AS Visitor_Name
UNION ALL
SELECT '2018-01-01' AS dt, 'D' AS Visitor_Name
UNION ALL
SELECT '2018-01-02' AS dt, 'B' AS Visitor_Name
UNION ALL
SELECT '2018-01-02' AS dt, 'C' AS Visitor_Name
UNION ALL
SELECT '2018-01-02' AS dt, 'E' AS Visitor_Name
UNION ALL
SELECT '2018-01-03' AS dt, 'A' AS Visitor_Name
UNION ALL
SELECT '2018-01-03' AS dt, 'P' AS Visitor_Name
UNION ALL
SELECT '2018-01-04' AS dt, 'A' AS Visitor_Name
UNION ALL
SELECT '2018-01-04' AS dt, 'C' AS Visitor_Name
UNION ALL
SELECT '2018-01-05' AS dt, 'D' AS Visitor_Name
UNION ALL
SELECT '2018-01-05' AS dt, 'B' AS Visitor_Name
UNION ALL
SELECT '2018-01-05' AS dt, 'B' AS Visitor_Name
SELECT '2018-01-06' AS dt, 'P' AS Visitor_Name)
GROUP BY Date_range
ORDER BY Date_range;
Solution:
SELECT
(CASE WHEN a.dt BETWEEN DATE_SUB(b.dt, INTERVAL 1 DAY) AND b.dt THEN
CONCAT(CAST(DATE_SUB(b.dt, INTERVAL 1 DAY) AS STRING), '::', CAST(b.dt AS STRING)) END) AS Date_range,
COUNT(DISTINCT (CASE WHEN a.dt BETWEEN DATE_SUB(b.dt, INTERVAL 1 DAY) AND b.dt THEN a.Visitor_Name END)) AS Visitor_Count
FROM
(SELECT DATE('2018-01-01') AS dt, 'A' AS Visitor_Name
UNION ALL
SELECT '2018-01-01' AS dt, 'B' AS Visitor_Name
UNION ALL
SELECT '2018-01-01' AS dt, 'C' AS Visitor_Name
UNION ALL
SELECT '2018-01-01' AS dt, 'D' AS Visitor_Name
UNION ALL
SELECT '2018-01-02' AS dt, 'B' AS Visitor_Name
UNION ALL
SELECT '2018-01-02' AS dt, 'C' AS Visitor_Name
UNION ALL
SELECT '2018-01-02' AS dt, 'E' AS Visitor_Name
UNION ALL
SELECT '2018-01-03' AS dt, 'A' AS Visitor_Name
UNION ALL
SELECT '2018-01-03' AS dt, 'P' AS Visitor_Name
UNION ALL
SELECT '2018-01-04' AS dt, 'A' AS Visitor_Name
UNION ALL
SELECT '2018-01-04' AS dt, 'C' AS Visitor_Name
UNION ALL
SELECT '2018-01-05' AS dt, 'D' AS Visitor_Name
UNION ALL
SELECT '2018-01-05' AS dt, 'B' AS Visitor_Name
UNION ALL
SELECT '2018-01-05' AS dt, 'B' AS Visitor_Name
UNION ALL
SELECT '2018-01-06' AS dt, 'P' AS Visitor_Name) AS a
INNER JOIN
(SELECT DATE('2018-01-01') AS dt, 'A' AS Visitor_Name
UNION ALL
SELECT '2018-01-01' AS dt, 'B' AS Visitor_Name
UNION ALL
SELECT '2018-01-01' AS dt, 'C' AS Visitor_Name
UNION ALL
SELECT '2018-01-01' AS dt, 'D' AS Visitor_Name
UNION ALL
SELECT '2018-01-02' AS dt, 'B' AS Visitor_Name
UNION ALL
SELECT '2018-01-02' AS dt, 'C' AS Visitor_Name
UNION ALL
SELECT '2018-01-02' AS dt, 'E' AS Visitor_Name
UNION ALL
SELECT '2018-01-03' AS dt, 'A' AS Visitor_Name
UNION ALL
SELECT '2018-01-03' AS dt, 'P' AS Visitor_Name
UNION ALL
SELECT '2018-01-04' AS dt, 'A' AS Visitor_Name
UNION ALL
SELECT '2018-01-04' AS dt, 'C' AS Visitor_Name
UNION ALL
SELECT '2018-01-05' AS dt, 'D' AS Visitor_Name
UNION ALL
SELECT '2018-01-05' AS dt, 'B' AS Visitor_Name
UNION ALL
SELECT '2018-01-05' AS dt, 'B' AS Visitor_Name
UNION ALL
SELECT '2018-01-06' AS dt, 'P' AS Visitor_Name) AS b
ON (a.dt <= b.dt)
GROUP BY Date_range
ORDER BY Date_range;
You can accomplish this by "multiplying" the records before aggregating. That is, give each user a record for each date that the user should count.
Here is an example:
with t as (
SELECT DATE('2018-01-01') AS dt, 'A' AS Visitor_Name
UNION ALL
SELECT '2018-01-01' AS dt, 'B' AS Visitor_Name
UNION ALL
SELECT '2018-01-01' AS dt, 'C' AS Visitor_Name
UNION ALL
SELECT '2018-01-01' AS dt, 'D' AS Visitor_Name
UNION ALL
SELECT '2018-01-02' AS dt, 'B' AS Visitor_Name
UNION ALL
SELECT '2018-01-02' AS dt, 'C' AS Visitor_Name
UNION ALL
SELECT '2018-01-02' AS dt, 'E' AS Visitor_Name
UNION ALL
SELECT '2018-01-03' AS dt, 'A' AS Visitor_Name
UNION ALL
SELECT '2018-01-03' AS dt, 'P' AS Visitor_Name
UNION ALL
SELECT '2018-01-04' AS dt, 'A' AS Visitor_Name
UNION ALL
SELECT '2018-01-04' AS dt, 'C' AS Visitor_Name
UNION ALL
SELECT '2018-01-05' AS dt, 'D' AS Visitor_Name
UNION ALL
SELECT '2018-01-05' AS dt, 'B' AS Visitor_Name
UNION ALL
SELECT '2018-01-05' AS dt, 'B' AS Visitor_Name
UNION ALL
SELECT '2018-01-06' AS dt, 'P' AS Visitor_Name
)
select dt, count(distinct visitor_name) as num_visitors
from (select distinct date_add(dt, interval inc day) as dt, visitor_name
from t CROSS JOIN
(select 0 as inc UNION ALL
SELECT 1
) x
) t
group by t.dt
order by t.dt;
Below is for BigQuery Standard SQL
#standardSQL
SELECT CONCAT(CAST(DATE_SUB(dt, INTERVAL 1 DAY) AS STRING), '::', CAST(dt AS STRING)) Date_range,
ANY_VALUE((SELECT COUNT(DISTINCT visitor) FROM UNNEST(arr_visitors) visitor)) AS Visitor_Count
FROM (
SELECT dt,
ARRAY_AGG(visitor_name) OVER(ORDER BY UNIX_DATE(dt) RANGE BETWEEN 1 PRECEDING AND CURRENT ROW) AS arr_visitors
FROM `project.dataset.your_table`
)
GROUP BY Date_range
You can test / play with it using dummy data from your question as below
#standardSQL
WITH `project.dataset.your_table` AS (
SELECT DATE('2018-01-01') AS dt, 'A' AS Visitor_Name UNION ALL
SELECT '2018-01-01', 'B' UNION ALL
SELECT '2018-01-01', 'C' UNION ALL
SELECT '2018-01-01', 'D' UNION ALL
SELECT '2018-01-02', 'B' UNION ALL
SELECT '2018-01-02', 'C' UNION ALL
SELECT '2018-01-02', 'E' UNION ALL
SELECT '2018-01-03', 'A' UNION ALL
SELECT '2018-01-03', 'P' UNION ALL
SELECT '2018-01-04', 'A' UNION ALL
SELECT '2018-01-04', 'C' UNION ALL
SELECT '2018-01-05', 'D' UNION ALL
SELECT '2018-01-05', 'B' UNION ALL
SELECT '2018-01-05', 'B' UNION ALL
SELECT '2018-01-06', 'P'
)
SELECT CONCAT(CAST(DATE_SUB(dt, INTERVAL 1 DAY) AS STRING), '::', CAST(dt AS STRING)) Date_range,
ANY_VALUE((SELECT COUNT(DISTINCT visitor) FROM UNNEST(arr_visitors) visitor)) AS Visitor_Count
FROM (
SELECT dt,
ARRAY_AGG(visitor_name) OVER(ORDER BY UNIX_DATE(dt) RANGE BETWEEN 1 PRECEDING AND CURRENT ROW) AS arr_visitors
FROM `project.dataset.your_table`
)
GROUP BY Date_range
ORDER BY Date_range
with result
Row Date_range Visitor_Count
1 2017-12-31::2018-01-01 4
2 2018-01-01::2018-01-02 5
3 2018-01-02::2018-01-03 5
4 2018-01-03::2018-01-04 3
5 2018-01-04::2018-01-05 4
6 2018-01-05::2018-01-06 3

SQL query for statistic info

So let's assume I have two tables, cars and engines. Every car has an id column and sold_date column which points to a date when the car was sold. Every engine has car_id and type columns, where the first one is a foreign key to the cars table and the second is an engine's type name, which can be anything from V1 to V999.
So what I want to get is a list of dates from let's say August 1st to August 3rd with every type of engine and a number of sold cars like this:
sold_date engine_type number_of_sold_cards
08.01.2015 V8 6
08.01.2015 V6 8
08.01.2015 V4 9
08.02.2015 V8 15
08.02.2015 V6 0
08.02.2015 V4 5
08.03.2015 V8 4
08.03.2015 V6 6
08.03.2015 V4 0
The example assumes that for these 3 days were sold only cars with engines' types of V8, V6 and V4. What it means is that if there had been sold 5 types of engines for the period (V8, V6, V4, V2, V0) instead, I'd need 5 rows for every date
Partition outer join to the rescue!
with cars as (select 1 id, to_date('01/08/2015', 'dd/mm/yyyy') sold_date from dual union all
select 2 id, to_date('01/08/2015', 'dd/mm/yyyy') sold_date from dual union all
select 3 id, to_date('02/08/2015', 'dd/mm/yyyy') sold_date from dual union all
select 4 id, to_date('03/08/2015', 'dd/mm/yyyy') sold_date from dual union all
select 5 id, to_date('01/08/2015', 'dd/mm/yyyy') sold_date from dual union all
select 6 id, to_date('01/08/2015', 'dd/mm/yyyy') sold_date from dual union all
select 7 id, to_date('03/08/2015', 'dd/mm/yyyy') sold_date from dual union all
select 8 id, to_date('03/08/2015', 'dd/mm/yyyy') sold_date from dual union all
select 9 id, to_date('02/08/2015', 'dd/mm/yyyy') sold_date from dual union all
select 10 id, to_date('02/08/2015', 'dd/mm/yyyy') sold_date from dual union all
select 11 id, to_date('02/08/2015', 'dd/mm/yyyy') sold_date from dual union all
select 12 id, to_date('04/08/2015', 'dd/mm/yyyy') sold_date from dual union all
select 13 id, to_date('04/08/2015', 'dd/mm/yyyy') sold_date from dual union all
select 14 id, to_date('04/08/2015', 'dd/mm/yyyy') sold_date from dual union all
select 14 id, to_date('04/08/2015', 'dd/mm/yyyy') sold_date from dual),
engines as (select 1 car_id, 'V8' engine_type from dual union all
select 2 car_id, 'V8' engine_type from dual union all
select 3 car_id, 'V8' engine_type from dual union all
select 4 car_id, 'V8' engine_type from dual union all
select 5 car_id, 'V4' engine_type from dual union all
select 6 car_id, 'V4' engine_type from dual union all
select 7 car_id, 'V4' engine_type from dual union all
select 8 car_id, 'V4' engine_type from dual union all
select 9 car_id, 'V6' engine_type from dual union all
select 10 car_id, 'V6' engine_type from dual union all
select 11 car_id, 'V6' engine_type from dual union all
select 12 car_id, 'V6' engine_type from dual union all
select 13 car_id, 'V2' engine_type from dual union all
select 14 car_id, 'V2' engine_type from dual union all
select 15 car_id, 'V0' engine_type from dual),
engine_types as (select distinct engine_type from engines),
res as (select c.id car_id,
e.engine_type,
c.sold_date
from engines e
inner join cars c on (e.car_id = c.id)),
final_res as (select et.engine_type,
res.sold_date
from engine_types et
left outer join res on (et.engine_type = res.engine_type)),
dates as (select to_date('01/08/2015', 'dd/mm/yyyy') - 1 + level dt -- paramaterise the start date
from dual
connect by level <= to_date('03/08/2015', 'dd/mm/yyyy') - to_date('01/08/2015', 'dd/mm/yyyy') + 1 -- paramaterise the start and end dates
)
select dts.dt,
fr.engine_type,
count(fr.sold_date) cnt
from dates dts
left outer join final_res fr partition by (fr.engine_type) on (dts.dt = fr.sold_date)
group by dts.dt,
fr.engine_type
order by dts.dt,
fr.engine_type;
DT ENGINE_TYPE CNT
---------- ----------- ----------
01/08/2015 V0 0
01/08/2015 V2 0
01/08/2015 V4 2
01/08/2015 V6 0
01/08/2015 V8 2
02/08/2015 V0 0
02/08/2015 V2 0
02/08/2015 V4 0
02/08/2015 V6 3
02/08/2015 V8 1
03/08/2015 V0 0
03/08/2015 V2 0
03/08/2015 V4 2
03/08/2015 V6 0
03/08/2015 V8 1
The first and second subqueries ("cars" and "engines") are just mimicking your tables; you would not need to include them in your query.
The "engine_types" subquery is just getting the distinct list of engine_types used in the engines table. If you have some other table that lists the available engine_types, then use that instead.
The "dates" subquery is just generating a list of dates between a given date range - in an ideal world, the start and end dates would be parameterised (assuming this is being run in PL/SQL or some such).
The "res" subquery does the join on the cars and engines table, to get the type of engine sold on each date.
The "final_res" subquery outer joins the res and engine_types subqueries, so that every engine type is listed, along with the sold_date, if it was sold.
Once you have that, then it's easy to do a partition outer join on the final_res subquery to the dates subquery.
You need to join cars and engine tables. Need to count the sold cars and group by sold_date and engine_type:
select c.sold_date
,e.engine_type
,count(*) as number_of_sold_cars
from cars c
inner join engines e on c.id = e.car_id
where c.sold_date between date '2015-08-01' and date '2015-08-03'
group by c.sold_date,e.engine_type
Update:
With this query you will have all engine types sold in the selected period. You will have 0 for number_of_sold_cars if this engine type is not sold on the particular date:
with engine_type as (
select distinct e.engine_type
from cars c
inner join engines e on c.id = e.car_id
where c.sold_date between date '2015-08-01' and date '2015-08-03'
)
select c.sold_date
,t.engine_type
,count(*) as number_of_sold_cars
from cars c
inner join engines e on c.id = e.car_id
left join enginte_type t on t.engine_type = e.engine_type
where c.sold_date between date '2015-08-01' and date '2015-08-03'
group by c.sold_date,t.engine_type

SQL Oracle Query self query

I am trying to figure out how to populate the below NULL values with 1.245 for dates from 07-OCT-14 to 29-SEP-14 then from 26-SEP-14 to 28-JUL-14 it will be 1.447.
This means if the date is less than or equal to the given date then use the value of max effective date which is less than the given date
We could select the last available index_ratio value for given security_alias and effective date <=p.effective_date , so in other words we will need to modify the sql to return from the subquery the index ratio value identified for the maximum available effective date assuming that this effective date is less or equal position effective date
How to populate the value ?
select ab.security_alias,
ab.index_ratio,
ab.effective_date
from securitydbo.security_analytics_fi ab
where ab.security_alias = 123627
order by ab.effective_date desc
Below should be the output
Assuming I understand your requirements correctly, I think the analytic function LAST_VALUE() is what you're after. E.g.:
with sample_data as (select 1 id, 10 val, to_date('01/08/2015', 'dd/mm/yyyy') dt from dual union all
select 1 id, null val, to_date('02/08/2015', 'dd/mm/yyyy') dt from dual union all
select 1 id, null val, to_date('03/08/2015', 'dd/mm/yyyy') dt from dual union all
select 1 id, null val, to_date('04/08/2015', 'dd/mm/yyyy') dt from dual union all
select 1 id, 20 val, to_date('05/08/2015', 'dd/mm/yyyy') dt from dual union all
select 1 id, 21 val, to_date('06/08/2015', 'dd/mm/yyyy') dt from dual union all
select 1 id, null val, to_date('07/08/2015', 'dd/mm/yyyy') dt from dual union all
select 1 id, null val, to_date('08/08/2015', 'dd/mm/yyyy') dt from dual union all
select 1 id, 31 val, to_date('09/08/2015', 'dd/mm/yyyy') dt from dual union all
select 1 id, null val, to_date('10/08/2015', 'dd/mm/yyyy') dt from dual union all
select 1 id, 42 val, to_date('11/08/2015', 'dd/mm/yyyy') dt from dual)
select id,
last_value(val ignore nulls) over (partition by id order by dt) val,
dt
from sample_data
order by id, dt desc;
ID VAL DT
---------- ---------- ----------
1 42 11/08/2015
1 31 10/08/2015
1 31 09/08/2015
1 21 08/08/2015
1 21 07/08/2015
1 21 06/08/2015
1 20 05/08/2015
1 10 04/08/2015
1 10 03/08/2015
1 10 02/08/2015
1 10 01/08/2015