I know I need to use sub-queries for this, but I'm not sure how to go about it. I have multiple entries per column ID, but I want to display them as a single row. Here's the table design:
UUID | position_id | spot
-----+-------------+-----
111 | 1 | left
112 | 1 | right
113 | 3 | center
114 | 4 | right
The way I want to output this data is such:
postion_1_left | position_1_right | postion_3_center | position_4_right
---------------+------------------+------------------+-----------------
true | true | true | true
The reason for this is that I want to put this data into a BIRT report, and having absolute values for each position_id and spot as true or false would make the report much nicer. The report would look as such:
left | center | right
-----------+-------+--------+-----------
position 1 | yes | no | yes
position 2 | no | no | no
position 3 | no | yes | no
position 4 | no | no | yes
I cannot think of a better way of doing this, so if anyone has a suggestion I'm open to it. Otherwise I'll proceed with this layout but I'm having a hard time coming up with the query. I tried starting with a query like this:
SELECT (SELECT spot FROM positions_table WHERE position_id = 3 AND spot = 'left')
from positions_table
WHERE uuid = 'afb36733'
But obviously that wouldn't work.
As you simple want to check if you have a given spot out of a finite list - ('left', 'center', 'right') - for each position_id, I see a very simple solution for your case using bool_or aggregation function (see also on SQL Fiddle):
SELECT
pt.position_id,
bool_or(pt.spot = 'left') AS left,
bool_or(pt.spot = 'right') AS right,
bool_or(pt.spot = 'center') AS center
FROM
positions_table pt
GROUP BY
pt.position_id
ORDER BY
pt.position_id;
Result:
position_id | left | right | center
-------------+------+-------+--------
1 | t | t | f
3 | f | f | t
4 | f | t | f
(3 rows)
You can then expand it with CASE to format better (or do that in your presentation layer):
SELECT
pt.position_id,
CASE WHEN bool_or(pt.spot = 'left') THEN 'yes' ELSE 'no' END AS left,
CASE WHEN bool_or(pt.spot = 'right') THEN 'yes' ELSE 'no' END AS right,
CASE WHEN bool_or(pt.spot = 'center') THEN 'yes' ELSE 'no' END AS center
FROM
positions_table pt
GROUP BY
pt.position_id
ORDER BY
pt.position_id;
Result:
position_id | left | right | center
-------------+------+-------+--------
1 | yes | yes | no
3 | no | no | yes
4 | no | yes | no
(3 rows)
Another common options of pivoting would be:
using crosstab function from tablefunc
using FILTER clause or CASE inside aggregation function
But as it is only true/false, bool_or seems more than enough here.
Use generate_series() to fill gaps in position_ids and aggregate spots to array for id:
select
id,
coalesce('left' = any(arr), false) as left,
coalesce('center' = any(arr), false) as center,
coalesce('right' = any(arr), false) as right
from (
select id, array_agg(spot) arr
from generate_series(1, 4) id
left join positions_table on id = position_id
group by 1
) s
order by 1;
id | left | center | right
----+------+--------+-------
1 | t | f | t
2 | f | f | f
3 | f | t | f
4 | f | f | t
(4 rows)
Related
Suppose I have a table in the format:
+======+=========+=======+============+
| code | type | Price | parentCode |
+======+=========+=======+============+
| TR1 | initial | 100 | -1 |
+------+---------+-------+------------+
| TR2 | losing | 70 | TR1 |
+------+---------+-------+------------+
| TR3 | winning | 150 | TR1 |
+------+---------+-------+------------+
Which for example, represented a trade placed by a user at a price of 100 (TR1), and the following trades (TR2, TR3) are automatic trades that will be placed if the price hits the value specified.
Using the fact that TR2 and TR3 are linked to the initial trade using the parentCode, how would I use this relationship to generate the following table by collapsing the entries into a single row:
+=============+============+=============+==============+=============+==============+
| initialCode | losingCode | winningCode | initialPrice | losingPrice | winningPrice |
+=============+============+=============+==============+=============+==============+
| TR1 | TR2 | TR3 | 100 | 70 | 150 |
+-------------+------------+-------------+--------------+-------------+--------------+
Assuming these are always in groups of one each of initial, losing, and winning.
select i.code as initialCode,
l.code as losingCode,
w.code as winningCode,
i.price as initialPrice,
l.price as losingPrice,
w.price as winningPrice
from trades i
join trades l on l.parentCode = i.code and l.type = 'losing'
join trades w on w.parentCode = i.code and w.type = 'winning'
where i.type = 'initial';
This can also be done without joining. Ask in the comments if you want to see that.
If you have more than just the three types, then you can follow the above pattern, but the self-joins will start adding up.
To do this without all the joins, follow this pattern:
with by_parent as (
select case
when parentCode = '-1' then code
else parentCode
end as parentCode,
type, price, code
from trades
)
select parentCode as initialCode,
max(code) filter (where type='losing') as losingCode,
max(code) filter (where type='winning') as winningCode,
max(code) filter (where type='trailLosing') as trailLosingCode,
max(code) filter (where type='guaranteedLosing') as guaranteedLosingCode,
max(price) filter (where type='initial') as initialPrice,
max(price) filter (where type='losing') as losingPrice,
max(price) filter (where type='winning') as winningPrice,
max(price) filter (where type='trailLosing') as trailLosingPrice,
max(price) filter (where type='guaranteedLosing') as guaranteedLosingPrice
from by_parent
group by parentCode
;
initialcode | losingcode | winningcode | traillosingcode | guaranteedlosingcode | initialprice | losingprice | winningprice | traillosingprice | guaranteedlosingprice
-------------+------------+-------------+-----------------+----------------------+--------------+-------------+--------------+------------------+-----------------------
TR1 | TR2 | TR3 | | | 100 | 70 | 150 | |
(1 row)
If you have control over your source data, then you can get rid of the by_parent CTE if you can have the parentCode set to the code for the initial rows.
I have the following table with names on column 1 and various questions that are answered 'Y' or 'N' and I want to create a graph as given in the link below. I want the Ys to show up in the graph
I tried IF-ELSE calculation but it gives me the the first condition that passes and ignores the rest and my viz now has just one mark per line item.
http://imgur.com/a/2G52b
*I've replaced the 'N' with blanks in this table here
+--------+----+----+----+----+----+----+
| Name | Q1 | Q2 | Q4 | Q5 | Q6 | Q7 |
+--------+----+----+----+----+----+----+
| Bhansa | | Y | | | | |
| Chaga | Y | Y | | | | Y |
| Chang | | | | Y | Y | |
| Cooke | | Y | | Y | | |
+--------+----+----+----+----+----+----+
As user Ben mentioned, the trick here is to do a pivot. You can do that by selecting the Question columns from the data source tab and right clicking at any of the header.
Once you have pivoted the data, you can create the chart as shown below. Please note that we are using a filter on 'Pivot Field Values' to filter out the 'N' values
I need to set a sequence inside T-SQL when in the first column I have sequence marker (which is repeating) and use other column for ordering.
It is hard to explain so I try with example.
This is what I need:
|------------|-------------|----------------|
| Group Col | Order Col | Desired Result |
|------------|-------------|----------------|
| D | 1 | NULL |
| A | 2 | 1 |
| C | 3 | 1 |
| E | 4 | 1 |
| A | 5 | 2 |
| B | 6 | 2 |
| C | 7 | 2 |
| A | 8 | 3 |
| F | 9 | 3 |
| T | 10 | 3 |
| A | 11 | 4 |
| Y | 12 | 4 |
|------------|-------------|----------------|
So my marker is A (each time I met A I must start new group inside my result). All rows before first A must be set to NULL.
I know that I can achieve that with loop but it would be slow solution and I need to update a lot of rows (may be sometimes several thousand).
Is there a way to achive this without loop?
You can use window version of COUNT to get the desired result:
SELECT [Group Col], [Order Col],
COUNT(CASE WHEN [Group Col] = 'A' THEN 1 END)
OVER
(ORDER BY [Order Col]) AS [Desired Result]
FROM mytable
If you need all rows before first A set to NULL then use SUM instead of COUNT.
Demo here
DB
| ID| VALUE | Parent | Position | lft | rgt |
|---|:------:|:-------:|:--------------:|:--------:|:--------:|
| 1 | A | | | 1 | 12 |
| 2 | B | 1 | L | 2 | 9 |
| 3 | C | 1 | R | 10 | 11 |
| 4 | D | 2 | L | 3 | 6 |
| 5 | F | 2 | R | 7 | 8 |
| 6 | G | 4 | L | 4 | 5 |
Get All Nodes directly under current Node in left side
SELECT "categories".* FROM "categories" WHERE ("categories"."position" = 'L') AND ("categories"."lft" >= 1 AND "categories"."lft" < 12) ORDER BY "categories"."lft"
output { B,D,G } incoorect!
Question !
how have Nodes directly under current Node in left and right side?
output-lft {B,D,F,G}
output-rgt {C}
It sounds like you're after something analogous to Oracle's CONNECT_BY statement, which is used to connect hierarchical data stored in a flat table.
It just so happens there's a way to do this with Postgres, using a recursive CTE.
here is the statement I came up with.
WITH RECURSIVE sub_categories AS
(
-- non-recursive term
SELECT * FROM categories WHERE position IS NOT NULL
UNION ALL
-- recursive term
SELECT c.*
FROM
categories AS c
JOIN
sub_categories AS sc
ON (c.parent = sc.id)
)
SELECT DISTINCT categories.value
FROM categories,
sub_categories
WHERE ( categories.parent = sub_categories.id
AND sub_categories.position = 'L' )
OR ( categories.parent = 1
AND categories.position = 'L' )
here is a SQL Fiddle with a working example.
train_operators:
| train_operator_id | name |
------------------------------
| 1 | Virgin |
| 2 | First |
journeys:
| journey_id | train_operator | train_type |
--------------------------------------------
| 1 | 2 | 2 |
| 2 | 2 | 1 |
| 3 | 1 | 3 |
| 4 | 1 | 2 |
train_types:
| train_type_id | date_made |
------------------------------
| 1 | 1999-02-15 |
| 2 | 2001-03-11 |
| 3 | 2000-12-05 |
How would you write a query to find all the train operators that use the second oldest type of train?
With the given schema the query should result with just Virgin since it is the only train operator that uses the second oldest train type
Try this:
select distinct train_operator from journeys
inner join (Select * from train_types order by date_made LIMIT 1 OFFSET 1) sectrain
on sectrain.train_type_id = journeys.train_type
You're into the UK Rail Network are you? I used to work for Funkwerk IT, who in turn used to provide the timetable planning software for Network Rail...
It can be pretty easy using the power of window functions in pg
SELECT DISTINCT train_operator_id,
name
FROM (SELECT t.train_operator_id,
t.name,
Rank() OVER (ORDER BY tt.date_made) AS rank
FROM train_operators AS t
JOIN journeys AS j
ON j.train_operator = t.train_operator_id
JOIN train_types AS tt
ON tt.train_type_id = j.train_type) AS q
WHERE rank = 2;
http://sqlfiddle.com/#!12/98816/8
select to.name
from
train_operators to
inner join
journeys j on to.train_operator_id = j.train_operator
where
j.train_type = (
select train_type_id
from train_types
order by date_made
limit 1 offset 1
)