Expand header row into multiple child rows - sql

Within my SQL database I have a table which represents books of tickets [Books] where the number of tickets within a book can vary.
This is represented by two columns [Books].[StartNo] and [Books].[BookSize]
What I need to achieve is a select statement that repeats each row in the table [Books] for each ticket in that book with an additional calculated column that displays the ticket number for that row.
So from
--------+---------+----------
Book | StartNo | BookSize
--------+---------+----------
Book 1 | 1 | 3
Book 2 | 4 | 4
Book 3 | 19 | 4
to something like this
--------+---------+----------+----------
Book | StartNo | BookSize | TicketNo
--------+---------+----------+----------
Book 1 | 1 | 3 | 1
Book 1 | 1 | 3 | 2
Book 1 | 1 | 3 | 3
Book 2 | 4 | 4 | 4
Book 2 | 4 | 4 | 5
Book 2 | 4 | 4 | 6
Book 2 | 4 | 4 | 7
Book 3 | 19 | 4 | 19
Book 3 | 19 | 4 | 20
Book 3 | 19 | 4 | 21
Book 3 | 19 | 4 | 22
I'm just not quite sure where to start.

Try this:
;WITH Counts AS (
SELECT Max(StartNo + BookSize) AS TotalBookSize
FROM t
), CTE(Tickets) AS (
SELECT 1
UNION ALL
SELECT Tickets + 1
FROM CTE
WHERE Tickets < (SELECT TotalBookSize FROM Counts)
)
SELECT *
FROM t JOIN CTE ON CTE.Tickets BETWEEN t.StartNo AND t.StartNo + t.BookSize - 1

Use tally table
WITH lv0 AS (SELECT 0 g UNION ALL SELECT 0 UNION ALL SELECT 0 UNION ALL SELECT 0 UNION ALL SELECT 0 UNION ALL SELECT 0 UNION ALL SELECT 0 UNION ALL SELECT 0 UNION ALL SELECT 0 UNION ALL SELECT 0)
,lv1 AS (SELECT 0 g FROM lv0 a CROSS JOIN lv0 b) --10 * 10 = 100
,lv2 AS (SELECT 0 g FROM lv1 a CROSS JOIN lv0 b) --100 * 10 = 1000
,Tally (num) AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM lv2)
SELECT (num+StartNo-1) as TicketNo, *
FROM Tally
CROSS JOIN Yourtable
WHERE num <= booksize
ORDER BY book

you need a list of numbers and join it with the books table
select b.*, number
from Books b
join master.dbo.spt_values v on v.number between b.StartNo AND b.StartNo+b.BookSize-1

Related

Bigquery: Joining 2 tables one having repeated records and one with count ()

I want to join tables after unnest arrays in Table:1 but the records duplicated after the join because of the unnest.
Table:1
| a | d.b | d.c |
-----------------
| 1 | 5 | 2 |
- -------------
| | 3 | 1 |
-----------------
| 2 | 2 | 1 |
Table:2
| a | c | f |
-----------------
| 1 | 12 | 13 |
-----------------
| 2 | 14 | 15 |
I want to join table 1 and 2 on a but I need also to have the output of:
| a | d.b | d.c | f | h | Sum(count(a))
---------------------------------------------
| 1 | 5 | 2 | 13 | 12 |
- ------------- - - 1
| | 3 | 1 | | |
---------------------------------------------
| 2 | 2 | 1 | 15 | 14 | 1
a can be repeated in table 2 for that I need to count(a) then select the sum after join.
My problem is when I'm joining I need the nested and repeated record to be the same as in the first table but when use aggregation to get the sum I can't group by struct or arrays so I UNNEST the records first then use ARRAY_AGG function but also there was an issue in the sum.
SELECT
t1.a,
t2.f,
t2.h,
ARRAY_AGG(DISTINCT(t1.db)) as db,
ARRAY_AGG(DISTINCT(t1.dc)) as dc,
SUM(t2.total) AS total
FROM (
SELECT
a,
d.b as db,
d.c as dc
FROM
`table1`,
UNNEST(d) AS d,
) AS t1
LEFT JOIN (
SELECT
a,
f,
h,
COUNT(*) AS total,
FROM
`table2`
GROUP BY
a,f,h) AS t2
ON
t1.a = t2.a
GROUP BY
1,
2,
3
Note: the error is in the total number after the sum it is much higher than expected all other data are correct.
I guess your table 2 contains is not unique for column a.
Lets assume that the table 2 looks like this:
a
c
f
1
12
13
2
14
15
1
100
101
There are two rows where a is 1. Since b and f are different, the grouping does not solve this ( GROUP BY a,f,h) AS t2) and counts(*) as total is one for each row.
a
c
f
total
1
12
13
1
2
14
15
1
1
100
101
1
In the next step you join this table to your table 1. The rows of table1 with value 1 in column a are duplicated, because table2 has two entries. This lead to the fact that the sum is too high.
Instead of unnesting the tables, I recommend following approach:
-- Creating of sample data as given:
with tbl_A as (select 1 a, [struct(5 as b,2 as c),struct(3,1)] d union all select 2,[struct(2,1)] union all select null,[struct(50,51)]),
tbl_B as (select 1 as a,12 b, 13 f union all select 2,14,15 union all select 1,100,101 union all select null,500,501)
-- Query:
select *
from tbl_A A
left join
(Select a,array_agg(struct(b,f)) as B, count(1) as counts from tbl_B group by 1) B
on ifnull(A.a,-9)=ifnull(B.a,-9)

Adding a row number respecting the order of each row

I have a table like this
id, period, tag
1 1 A
1 2 A
1 3 B
1 4 A
1 5 A
1 6 A
2 1 A
2 2 B
2 3 B
2 4 B
2 5 B
2 6 A
I would like to add a new column with a ranking, respecting the order of the row given my column 'period' to obtain something like this
id, period, tag rank
1 1 A 1
1 2 A 1
1 3 B 2
1 4 A 3
1 5 A 3
1 6 A 3
2 1 A 1
2 2 B 2
2 3 B 2
2 4 B 2
2 5 B 2
2 6 A 3
What can I do?
I try rank and dense_rank function without any success
And another candidate for CONDITIONAL_CHANGE_EVENT()
less code, and quite effective, too ...!
WITH
input(id,period,tag) AS (
SELECT 1,1,'A'
UNION ALL SELECT 1,2,'A'
UNION ALL SELECT 1,3,'B'
UNION ALL SELECT 1,4,'A'
UNION ALL SELECT 1,5,'A'
UNION ALL SELECT 1,6,'A'
UNION ALL SELECT 2,1,'A'
UNION ALL SELECT 2,2,'B'
UNION ALL SELECT 2,3,'B'
UNION ALL SELECT 2,4,'B'
UNION ALL SELECT 2,5,'B'
UNION ALL SELECT 2,6,'A'
)
SELECT
*
, CONDITIONAL_CHANGE_EVENT(tag) OVER(PARTITION BY id ORDER BY period) + 1 AS rank
FROM input;
-- out id | period | tag | rank
-- out ----+--------+-----+------
-- out 1 | 1 | A | 1
-- out 1 | 2 | A | 1
-- out 1 | 3 | B | 2
-- out 1 | 4 | A | 3
-- out 1 | 5 | A | 3
-- out 1 | 6 | A | 3
-- out 2 | 1 | A | 1
-- out 2 | 2 | B | 2
-- out 2 | 3 | B | 2
-- out 2 | 4 | B | 2
-- out 2 | 5 | B | 2
-- out 2 | 6 | A | 3
-- out (12 rows)
-- out
-- out Time: First fetch (12 rows): 14.823 ms. All rows formatted: 14.874 ms
One method is a cumulative sum based on a lag():
select t.*,
sum(case when prev_tag = tag then 0 else 1 end) over (partition by id order by period) as rank
from (select t.*, lag(tag) over (partition by id order by period) as prev_tag
from t
) t;

Padding tables with 0s in redshift

I have a table of the form:
id | A | B | C
-----------------
1 | 1 | 0 | 1
1 | 2 | 1 | 0
2 | 1 | 4 | 0
I would like to pad this table with rows of 0s (excluding the id) such that each id has exactly 3 entries. So the result would be:
id | A | B | C
-----------------
1 | 0 | 0 | 0
1 | 1 | 0 | 1
1 | 2 | 1 | 0
2 | 0 | 0 | 0
2 | 0 | 0 | 0
2 | 1 | 4 | 0
This is because id 1 had two entries, so we added one row of 0s, and id 2 had one entry, so we added two rows of 0s.
Note: we can assume each id occurs no more than 3 times and that if an id occurs exactly 3 times, there is no need to add padding.
Is there an intelligent way of doing this with Amazon Redshift? I need this to scale to 30 days of padding and a few hundred columns.
If column A is always sequential you can do:
select i.id, n.num,
coalesce(t.b, 0) as b,
coalesce(t.c, 0) as c
from (select distinct id from t) i cross join
(select 1 as num union all select 2 union all select 3) n left join
t on i.id = t.id and n.num = t.A;
You do need to list each column in the select to get the zeros.
If the above is not true, you can make it true with a CTE:
with t as (
select t.*, row_number() over (partition by id order by id) as num
from t
)
select i.id, coalesce(t.a, 0) as a,
coalesce(t.b, 0) as b,
coalesce(t.c, 0) as c
from (select distinct id from t) i cross join
(select 1 as num union all select 2 union all select 3) n left join
t on i.id = t.id and n.num = t.num;

SQL how to count free reservations

I have 2 tables
Reservations
reservation no | trip ID |person ID| Status
1 | 2 | 1 | C '
2 | 3 | 2 | P '
3 | 4 | 3 | P '
4 | 1 | 4 | C '
5 | 1 | 6 | P '
6 | 1 | 7 | P '
7 | 2 | 5 | P '
Where P stands for payed and C for Cancelled
And trips
trip ID |trip Name|Date | Free Places
1 | Paris |2016-12-18| 5 '
2 | New York|2016-12-17| 6 '
3 | Warsaw |2016-12-15| 5 '
4 | London |2016-12-20| 10 '
I want to select number of left free places for each trip
trip ID |trip Name|Date | Free Places | Left free Spaces
1 | Paris |2016-12-18 | 5 | 3
2 | New York| 2016-12-17 | 6 | 5
3 | Warsaw |2016-12-15 | 5 | 4
4 | London |2016-12-20 | 10 | 9
One approach would be to use a subquery of the reservation table which computes the number of spaces already taken for each trip. Then join this back to the trips table to compute the number of spaces remaining.
SELECT t1.tripID,
t1.tripName,
t1.Date,
t1.FreePlaces,
t1.FreePlaces - COALESCE(t2.numBooked, 0) AS left_free_spaces
FROM trips t1
LEFT JOIN
(
SELECT tripID, SUM(CASE WHEN Status = 'P' THEN 1 ELSE 0 END) AS numBooked
FROM Reservations
GROUP BY tripID
) t2
ON t1.tripID = t2.tripID
Try this code.it may help you
create table #RESERVATION (revservation_no bigint,trip_ID bigint,person_id bigint,status char(5))
insert into #RESERVATION values(1,2,1,'C'),(2,3,2,'P'),(3,4,3,'P'),(4,1,4,'C'),(5,1,6,'P'),(6,1,7,'P'),(7,2,5,'P')
CREATE TABLE #TRIP (TRIP_ID BIGINT,TRIP_NAME VARCHAR(50),DATE_ DATE ,FREE_PLACES BIGINT)
INSERT INTO #TRIP VALUES (1,'PARIS','2016-12-18',5),(2,'NEW YORK','2016-12-17',6),(3,'WARSAW','2016-12-15',5),(4,'LONDON','2016-12-20',10)
;WITH CTE AS(
SELECT DISTINCT T.TRIP_ID,T.TRIP_NAME,T.DATE_,T.FREE_PLACES,COUNT(revservation_no)AS LEFT_SPACES FROM #TRIP T
INNER JOIN #RESERVATION R ON R.trip_ID=T.TRIP_ID
WHERE status='P'
GROUP BY T.TRIP_ID,T.TRIP_NAME,T.DATE_,T.FREE_PLACES
)
SELECT TRIP_ID,TRIP_NAME,DATE_,FREE_PLACES,FREE_PLACES-LEFT_SPACES AS LEFT_FREE_PLACES FROM CTE
Select D1.trip_ID ,trip_Name, [Date] , D1.Free_Places,
D1.Free_Places - Sum(Case
When D2.Status = 'P' then 1
Else 0
End) as Left_free_Spaces
From Trips D1
Inner Join Reservations D2
On D1.trip_ID = D2.trip_ID
Group by D1.trip_ID ,trip_Name, [Date] , Free_Places

Select a row X times

I have a very specific sql problem.
I have a table given with order positions (each position belongs to one order, but this isn't a problem):
| Article ID | Amount |
|--------------|----------|
| 5 | 3 |
| 12 | 4 |
For the customer, I need an export with every physical item that is ordered, e.g.
| Article ID | Position |
|--------------|------------|
| 5 | 1 |
| 5 | 2 |
| 5 | 3 |
| 12 | 1 |
| 12 | 2 |
| 12 | 3 |
| 12 | 4 |
How can I build my select statement to give me this results? I think there are two key tasks:
1) Select a row X times based on the amount
2) Set the position for each physical article
You can do it like this
SELECT ArticleID, n.n Position
FROM table1 t JOIN
(
SELECT a.N + b.N * 10 + 1 n
FROM
(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) a
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) b
) n
ON n.n <= t.amount
ORDER BY ArticleID, Position
Note: subquery n generates a sequence of numbers on the fly from 1 to 100. If you do a lot of such queries you may consider to create persisted tally(numbers) table and use it instead.
Here is SQLFiddle demo
or using a recursive CTE
WITH tally AS (
SELECT 1 n
UNION ALL
SELECT n + 1 FROM tally WHERE n < 100
)
SELECT ArticleID, n.n Position
FROM table1 t JOIN tally n
ON n.n <= t.amount
ORDER BY ArticleID, Position
Here is SQLFiddle demo
Output in both cases:
| ARTICLEID | POSITION |
|-----------|----------|
| 5 | 1 |
| 5 | 2 |
| 5 | 3 |
| 12 | 1 |
| 12 | 2 |
| 12 | 3 |
| 12 | 4 |
Query:
SQLFIDDLEExample
SELECT t1.[Article ID],
t2.number
FROM Table1 t1,
master..spt_values t2
WHERE t1.Amount >= t2.number
AND t2.type = 'P'
AND t2.number <= 255
AND t2.number <> 0
Result:
| ARTICLE ID | NUMBER |
|------------|--------|
| 5 | 1 |
| 5 | 2 |
| 5 | 3 |
| 12 | 1 |
| 12 | 2 |
| 12 | 3 |
| 12 | 4 |