SQL: "Reverse" transpose a table - sql

I saw a lot of questions on transposing from the below table...
scanid | region | volume
-------------------------
1 A 34.4
1 B 32.1
1 C 29.1
2 A 32.4
2 B 33.2
2 C 35.6
to this table.
scanid | A_volume | B_volume | C_volume
----------------------------------------
1 34.4 32.1 29.1
2 32.4 33.2 35.6
However, I need to do the inverse, and have trouble trying to wrap my head around this problem. Can anyone help?
Thank you.

it is not clear how you restore "A", "B", "C" values, so I just add them
prepare:
t=# create table s188 (scanid int,a float, b float,c float);
CREATE TABLE
t=# insert into s188 select 1,2,3,4;
INSERT 0 1
t=# insert into s188 select 2,12,13,14;
INSERT 0 1
t=# select * from s188;
scanid | a | b | c
--------+----+----+----
1 | 2 | 3 | 4
2 | 12 | 13 | 14
(2 rows)
select:
t=# with a as (
select scanid,unnest(array[a,b,c]) from s188
)
select scanid,chr((row_number() over (partition by scanid))::int + 64),unnest
from a;
scanid | chr | unnest
--------+-----+--------
1 | A | 2
1 | B | 3
1 | C | 4
2 | A | 12
2 | B | 13
2 | C | 14
(6 rows)
and more neat solution from a_horse_with_no_name
t=# with a as (
select scanid, x.*
from s188, unnest(array[a,b,c]) with ordinality as x(volume,idx)
)
select scanid,
chr(idx::int + 64) as region,
volume
from a;
scanid | region | volume
--------+--------+--------
1 | A | 2
1 | B | 3
1 | C | 4
2 | A | 12
2 | B | 13
2 | C | 14
(6 rows)

You could do this very simply with a UNION clause:
Select Scan_ID, 'A' as Region, A_Volume as volume
union all
Select Scan_ID, 'B' as Region, B_Volume as volume
union all
Select Scan_ID, 'C' as Region, C_Volume as volume

Related

Recursive join with SUM

I have data in the following format:
FromStateID ToStateID Seconds
1 2 10
2 3 20
3 4 15
4 5 5
I need the following output
FromStateID ToStateID Seconds
1 2 10
2 3 20
3 4 15
4 5 5
1 3 10+20
1 4 10+20+15
1 5 10+20+15+5
2 4 20+15
2 5 20+15+5
3 5 15+5
This output shows the total time taken FromStateId to ToStateId in every combination in chronological order.
Please help.
I think this is a recursive CTE that follows the links:
with cte as (
select FromStateID, ToStateID, Seconds
from t
union all
select cte.FromStateId, t.ToStateId, cte.Seconds + t.Seconds
from cte join
t
on cte.toStateId = t.FromStateId
)
select *
from cte;
Here is a db<>fiddle.
#Gordon LinOff is the better solution. Below is another option to achieve the same.
You can achieve this using CROSS JOIN and GROUP BY
DECLARE #table table(FromStateId int, ToStateId int, seconds int)
insert into #table
values
(1 ,2 ,10),
(2 ,3 ,20),
(3 ,4 ,15),
(4 ,5 ,5 );
;with cte_fromToCombination as
(select f.fromStateId, t.tostateId
from
(select distinct fromStateId from #table) as f
cross join
(select distinct toStateId from #table) as t
)
select c.FromStateId, c.ToStateId, t.sumseconds as Total_seconds
from cte_fromToCombination as c
CROSS APPLY
(SELECT sum(t.seconds)
from
#table as t
WHERE t.ToStateId <= c.ToStateId
) as t(sumseconds)
where c.tostateId > c.fromStateId
order by FromStateId,ToStateId
+-------------+-----------+---------------+
| FromStateId | ToStateId | Total_seconds |
+-------------+-----------+---------------+
| 1 | 2 | 10 |
| 1 | 3 | 30 |
| 1 | 4 | 45 |
| 1 | 5 | 50 |
| 2 | 3 | 30 |
| 2 | 4 | 45 |
| 2 | 5 | 50 |
| 3 | 4 | 45 |
| 3 | 5 | 50 |
| 4 | 5 | 50 |
+-------------+-----------+---------------+

Oracle : SQL Request with a Group By and a Percentage on two differents tables

I'm currently blocked on an complex request... (with a join) :
I have this table "DATA":
order | product
----------------
1 | A
1 | B
2 | A
2 | D
3 | A
3 | C
4 | A
4 | B
5 | Y
5 | Z
6 | W
6 | A
And this table "DICO":
order | couple | first | second
-------------------------------
1 | A-B | A | B
2 | A-D | A | D
3 | A-C | A | C
4 | A-B | A | B
5 | Y-Z | Y | Z
6 | W-A | W | A
I would like to obtain, on one line :
order | count | total1stElem | %1stElem | total2ndElem | %1ndElem
------------------------------------------------------------------
A-B | 2 | 5 | 40% | 2 | 100%
A-D | 1 | 5 | 20% | 1 | 100%
A-C | 1 | 5 | 20% | 1 | 100%
Y-Z | 1 | 1 | 100% | 1 | 100%
W-A | 1 | 1 | 100% | 5 | 20%
I'm totally blocked on the jointure part of my request. Somebody can help me ?
Without any joins - just using UNPIVOT and PIVOT:
Oracle Setup:
CREATE TABLE DICO ( "order", couple, first, second ) AS
SELECT 1, 'A-B', 'A', 'B' FROM DUAL UNION ALL
SELECT 2, 'A-D', 'A', 'D' FROM DUAL UNION ALL
SELECT 3, 'A-C', 'A', 'C' FROM DUAL UNION ALL
SELECT 4, 'A-B', 'A', 'B' FROM DUAL UNION ALL
SELECT 5, 'Y-Z', 'Y', 'Z' FROM DUAL UNION ALL
SELECT 6, 'W-A', 'W', 'A' FROM DUAL;
Query:
SELECT "order",
"count",
"1stElem_TOTAL" AS Total1stElem,
100*"count"/"1stElem_TOTAL" AS "%1stElem",
"2ndElem_TOTAL" AS Total2ndElem,
100*"count"/"2ndElem_TOTAL" AS "%2ndElem"
FROM (
SELECT couple AS "order",
key,
COUNT(*) OVER ( PARTITION BY COUPLE )/2 AS "count",
COUNT(*) OVER ( PARTITION BY VALUE ) AS num_value
FROM DICO
UNPIVOT ( Value FOR Key IN ( first AS 1, second AS 2 ) )
)
PIVOT ( MAX( NUM_VALUE ) AS Total FOR key IN ( 1 AS "1stElem", 2 AS "2ndElem" ) );
Results:
order count TOTAL1STELEM %1stElem TOTAL2NDELEM %2ndElem
----- ----- ------------ -------- ------------ --------
A-D 1 5 20 1 100
A-B 2 5 40 2 100
A-C 1 5 20 1 100
Y-Z 1 1 100 1 100
W-A 1 1 100 5 20

Find the first key by date field using sql and output also have other fields

I want to query the first occurrence of every name according to the earliest date. The output should have the complete row. Please help me to write the query in sql.
Input:
Name | ID | payment_date | Pack
------+-------+-----------------+-------
A | 11 | 31-Jan | P
C | 13 | 31-Jan | Q
B | 2 | 31-Jan | R
C | 3 | 28-Jan | P
D | 23 | 29-Jan | Q
B | 11 | 30-Jan | R
A | 17 | 25-Jan | P
C | 13 | 26-Jan | Q
D | 17 | 2-Feb | R
B | 23 | 3-Feb | P
A | 45 | 4-Feb | Q
B | 3 | 5-Feb | R
Output:
Name | ID | payment_date | Pack
-----+-------+--------------+-----
A | 17 | 25-Jan | P
B | 11 | 30-Jan | R
C | 13 | 26-Jan | Q
D | 23 | 29-Jan | Q
You can use the min function, also assuming payment_date is a date type:
select Name, ID, min(payment_date), Pack from mytable
group by payment_date,Name, ID, Pack
order by Name
The downfall about this method is putting all of the fields in the group by.
If your payment_date is a date data type, you can use not exists() like so:
select *
from t
where not exists (
select 1
from t i
where i.Name = t.Name
and i.payment_date < t.payment_date
)
rextester demo (sql server): http://rextester.com/OKB46268
returns
+------+----+-------------+------+
| Name | Id | PaymentDate | Pack |
+------+----+-------------+------+
| A | 17 | 2017-01-25 | P |
| B | 11 | 2017-01-30 | R |
| C | 13 | 2017-01-26 | Q |
| D | 23 | 2017-01-29 | Q |
+------+----+-------------+------+
You can also use Vertica's enhanced LIMIT clause:
WITH
-- input, don't use in real query
input(Name,ID,payment_date,Pack) AS (
SELECT 'A',11,DATE '31-Jan-2017','P'
UNION ALL SELECT 'C',13,DATE '31-Jan-2017','Q'
UNION ALL SELECT 'B',2, DATE '31-Jan-2017','R'
UNION ALL SELECT 'C',3, DATE '28-Jan-2017','P'
UNION ALL SELECT 'D',23,DATE '29-Jan-2017','Q'
UNION ALL SELECT 'B',11,DATE '30-Jan-2017','R'
UNION ALL SELECT 'A',17,DATE '25-Jan-2017','P'
UNION ALL SELECT 'C',13,DATE '26-Jan-2017','Q'
UNION ALL SELECT 'D',17,DATE '2-Feb-2017','R'
UNION ALL SELECT 'B',23,DATE '3-Feb-2017','P'
UNION ALL SELECT 'A',45,DATE '4-Feb-2017','Q'
UNION ALL SELECT 'B',3, DATE '5-Feb-2017','R'
)
-- end of input , start real query here:
SELECT * FROM input
LIMIT 1 OVER(PARTITION BY Name ORDER BY payment_date)
;
Happy playing ...
Marco the Sane

Show missing rows with 0 values to maintain the order

I have a table with a Name column that its values are either 'A', 'B' or 'C'. They come in order ( A, B, C, A, B, C, ...) however, sometimes a Name might be missing (A, B,[missing C] A, B, C, ...). I want a query that gives me all of Names in order without any missing name. The Value for missing names must be 0.
PS: The table is in a Netezza database and it gets truncated and reloaded with fresh data each time by an SSIS package. What we know is that there is also an ID column with a value between 1 and 27. But the number of rows after each truncation and loading could be different. The table I want does not need the ID column, but if it had, it would be from 1 to 27, meaning that the 'table I want' must always have 27 rows.
I would recommend fixing this in the source SSIS package, but I think the following will work in Netazza (for versions that support the WITH command). Note that recursion is not used which I believe isn't support by Netazza.
If the WITH command isn't supported then some other source of a numeric seqeunce could be used (e.g. by row_number() )
setup:
CREATE TABLE TableHave
(Name varchar(1), ID int, Value decimal(5,2))
;
INSERT INTO TableHave
(Name, ID)
VALUES
('A', 1),
('A', 4),
('A', 7),
('C', 21),
('B', 23),
('A', 25)
;
update TableHave set Value = id*1.12;
Query:
;WITH
Digits AS (
SELECT 0 AS digit 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
),
Tally AS (
SELECT
ones.digit
+ tens.digit * 10
+ hundreds.digit * 100
-- + thousands.digit * 1000
as num
FROM Digits ones
CROSS JOIN Digits tens
CROSS JOIN Digits hundreds
-- CROSS JOIN Digits thousands (keep adding more if needed)
)
select
d.id
, d.name
, t.value
from (
select
num + 1 as id
, case when num % 3 = 1 then 'B'
when num % 3 = 2 then 'C'
else 'A'
end Name
, coalesce(t.value,0) value
from Tally
where num <= (select ((max(id)/3)*3)+2 from TableHave)
) d
left join TableHave t on d.id = t.id
order by d.id
result:
+----+------+-------+
| id | name | value |
+----+------+-------+
| 1 | A | 1.12 |
| 2 | B | 0 |
| 3 | C | 0 |
| 4 | A | 4.48 |
| 5 | B | 0 |
| 6 | C | 0 |
| 7 | A | 7.84 |
| 8 | B | 0 |
| 9 | C | 0 |
| 10 | A | 0 |
| 11 | B | 0 |
| 12 | C | 0 |
| 13 | A | 0 |
| 14 | B | 0 |
| 15 | C | 0 |
| 16 | A | 0 |
| 17 | B | 0 |
| 18 | C | 0 |
| 19 | A | 0 |
| 20 | B | 0 |
| 21 | C | 23.52 |
| 22 | A | 0 |
| 23 | B | 25.76 |
| 24 | C | 0 |
| 25 | A | 28.00 |
| 26 | B | 0 |
| 27 | C | 0 |
+----+------+-------+
A running example (on SQL Server) is available here http://rextester.com/VXB89713

Postgres width_bucket() not assigning values to buckets correctly

In postgresql 9.5.3 I can't get width_bucket() to work as expected, it appears to be assigning values to the wrong buckets.
Dataset:
1
2
4
32
43
82
104
143
232
295
422
477
Expected output (bucket ranges and zero-count rows added to help analysis):
bucket | bucketmin | bucketmax | Expect | Actual
--------+-----------+-----------+--------|--------
1 | 1 | 48.6 | 5 | 5
2 | 48.6 | 96.2 | 1 | 2
3 | 96.2 | 143.8 | 2 | 1
4 | 143.8 | 191.4 | 0 | 0
5 | 191.4 | 239 | 1 | 1
6 | 239 | 286.6 | 0 | 1
7 | 286.6 | 334.2 | 1 | 0
8 | 334.2 | 381.8 | 0 | 1
9 | 381.8 | 429.4 | 1 | 0
10 | 429.4 | 477 | 1 | 1
Actual output:
wb | count
----+-------
1 | 5
2 | 2
3 | 1
5 | 1
6 | 1
8 | 1
10 | 1
Code to generate actual output:
create temp table metrics (val int);
insert into metrics (val) values(1),(2),(4),(32),(43),(82),(104),(143),(232),(295),(422),(477);
with metric_stats as (
select
cast(min(val) as float) as minV,
cast(max(val) as float) as maxV
from metrics m
),
hist as (
select
width_bucket(val, s.minV, s.maxV, 9) wb,
count(*)
from metrics m, metric_stats s
group by 1 order by 1
)
select * from hist;
Your calculations appear to be off. The following query:
with metric_stats as (
select cast(min(val) as float) as minV,
cast(max(val) as float) as maxV
from metrics m
)
select g.n,
s.minV + ((s.maxV - s.minV) / 9) * (g.n - 1) as bucket_start,
s.minV + ((s.maxV - s.minV) / 9) * g.n as bucket_end
from generate_series(1, 9) g(n) cross join
metric_stats s
order by g.n
Yields the following bins:
1 1 53.8888888888889
2 53.8888888888889 106.777777777778
3 106.777777777778 159.666666666667
4 159.666666666667 212.555555555556
5 212.555555555556 265.444444444444
6 265.444444444444 318.333333333333
7 318.333333333333 371.222222222222
8 371.222222222222 424.111111111111
9 424.111111111111 477
I think you intend for the "9" to be a "10", if you want 10 buckets.