I have a table table1 with these columns:
region (varchar)
surface (float8)
decision (bool)
I want to select AVG(surface) for the cases where decision is True and where it is False and group the result by region.
I finally want 3 columns :
region
the average surface of surfaces where decision is true
the average surface of surfaces where decision is false
I tried :
SELECT
region,
(SELECT AVG(surface_m2) FROM table1 WHERE avis_final_bri),
(SELECT AVG(surface_m2) FROM table1 WHERE avis_final_bri)
FROM
table1
GROUP BY
region
but the query does not work.
I also tried to define another table the WITH statement but it did not work. I tried with the JOIN but it failed as well.
You can use the FILTER clause:
demo:db<>fiddle
SELECT
region,
AVG(surface) FILTER (WHERE decision = true),
AVG(surface) FILTER (WHERE decision = false)
FROM
table1
GROUP BY region
Alternatively to use more common SQL, you can use the CASE clause:
SELECT
region,
AVG(CASE WHEN decision = true THEN surface END),
AVG(CASE WHEN decision = false THEN surface END)
FROM
table1
GROUP BY region
Related
I have a table with two columns: parties and their respective spendings (party and per_capita). I have to return a table with the column of the parties and the average of all the spedings done by them if the party in the column is not used in the calculation: so, supose I have x, y and z. I want something like:
X | Average of spendings of (Y,Z)
Y | Average of spendings of (X,Z)
Z | Average of spendings of (X,Y)
I tried the following, resulting in NULL for the spending columns:
SELECT pcp.party, avg(pcp.per_capita) OVER (PARTITION BY pcp.party ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE CURRENT ROW) AS average
FROM per_capita_party pcp
ORDER BY average DESC;
You can use lateral for doing this. For example:
select distinct symbol,v
from myTable t1, lateral (
select avg(amount)
from myTable t2 where t1.symbol != t2.symbol) t(v);
Here is DBFiddle demo.
A scalar subquery will do the job close to natural language.
SELECT pcp.party,
(select avg(per_capita) from per_capita_party where party <> pcp.party) average
FROM per_capita_party pcp
ORDER BY average DESC;
You can also use window function exclude group frame clause.
Based on Cetin's DBfilddle.
new dbfiddle
SELECT
symbol,
avg(amount) OVER (ORDER BY symbol GROUPS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE GROUP)
FROM
mytable;
or
SELECT
symbol,
avg(amount) OVER (ORDER BY symbol DESC GROUPS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE GROUP)
FROM
mytable;
I have the following query:
WITH MY_CTE as
(
select
....
.....
)
SELECT
MY_CTE.*
,tt.currency as most_used_currency
from MY_CTE
cross join
(select t.currency
from My_CTE t
group by t.currency
order by count(*) desc
limit 1
) tt
where MY_CTE.currency = 'EUR'
but the cross join is ignoring my where clause.
How can I enforce that it processes the where clause before working on the cross join please?
Sample data returned:
This is obviously wrong because I said do not include currency SEK, and yet it is saying its the most popular currency.
I cannot put the where clause inside of the cross join because I will be using this in tableau and need the users to be able to filter on certain criteria, e.g. currency.
The most popular currency should be EUR if the MY_CTE is filtered to show only EUR currency
WHERE condition in this case has nothing to do with cross join, it just filters rows after join is already performed. If you need to report only single currency there are simplest two options where to add currency filter (added as comments in SQL):
1) Option 1 - add filter already in CTE statement
2) Option 2 - add filter at the end (as already done) and within tt part.
WITH MY_CTE as
(
select
....
.....
/* OPTION 1*/
)
SELECT
MY_CTE.*
,tt.currency as most_used_currency
from MY_CTE
cross join
(select t.currency
from My_CTE t
/* OPTION 2 first place*/
group by t.currency
order by count(*) desc
limit 1
) tt
where MY_CTE.currency = 'EUR' /* OPTION 2a second place*/
The alias tt will return the most popular currency overall, which is SEK. If you want to filter for separate currencies, you'll need to put them in the inner query as well as the outer one. However, if that isn't an option, you'll want to return all currencies with their popularity, and filter on the most popular one you allow.
....
....
SELECT
LAST_VALUE(MY_CTE.customer_id)
OVER (partition by customer_id
ORDER BY tt.popularity
rows between unbounded preceding and unbounded following)
.... /* rest of your columns */
, LAST_VALUE(tt.currency)
OVER (partition by customer_id
ORDER BY tt.popularity
rows between unbounded preceding and unbounded following)
from MY_CTE
cross join
(select t.currency,
count(*) popularity
from My_CTE t
group by t.currency
order by count(*) desc
/* removed limit 1 */
) tt
where MY_CTE.currency = 'EUR'
AND tt.currency IN ('EUR') /* Added tt.currency filter */
I have hundreds of polygon (circles) where some of the polygon intersected with each others. This polygon is come from single feature layer. What I am trying to do is to delete the intersected circles.
It is similar to this question: link, but those were using two different layer. In my case the intersection is from single feature layers.
If I understood your question right, you just need to either create a CTE or simple subquery.
This might give you a good idea of how to solve your issue:
CREATE TABLE t (id INTEGER, geom GEOMETRY);
INSERT INTO t VALUES
(1,'POLYGON((-4.54 54.30,-4.46 54.30,-4.46 54.29,-4.54 54.29,-4.54 54.30))'),
(2,'POLYGON((-4.66 54.16,-4.56 54.16,-4.56 54.14,-4.66 54.14,-4.66 54.16))'),
(3,'POLYGON((-4.60 54.19,-4.57 54.19,-4.57 54.15,-4.60 54.15,-4.60 54.19))'),
(4,'POLYGON((-4.40 54.40,-4.36 54.40,-4.36 54.38,-4.40 54.38,-4.40 54.40))');
This data set contains 4 polygons in total and two of them overlap, as seen in the following picture:
Applying a CTE with a subquery might give you what you want, which is the non-overlapping polygons from the same table:
SELECT id, ST_AsText(geom) FROM t
WHERE id NOT IN (
WITH j AS (SELECT * FROM t)
SELECT j.id
FROM j
JOIN t ON t.id <> j.id
WHERE ST_Intersects(j.geom,t.geom)
);
id | st_astext
----+---------------------------------------------------------------------
1 | POLYGON((-4.54 54.3,-4.46 54.3,-4.46 54.29,-4.54 54.29,-4.54 54.3))
4 | POLYGON((-4.4 54.4,-4.36 54.4,-4.36 54.38,-4.4 54.38,-4.4 54.4))
(2 rows)
You can write quite clear delete statement using EXISTS clause. You literally want to delete the rows, for which there exists other rows which geometry intersects:
DELETE
FROM myTable t1
WHERE EXISTS (SELECT 1 FROM myTable t2 WHERE t2.id <> t1.id AND ST_Intersects(t1.geom, t2.geom))
I have a table called Aircraft and there are many records. The problem is that some are duplicates. I know how to select the duplicates and their counts:
SELECT flight_id, latitude, longitude, altitude, call_sign, measurement_time, COUNT(*)
FROM Aircraft
GROUP BY flight_id, latitude, longitude, altitude, call_sign, measurement_time
HAVING COUNT(*) > 1;
This returns something like:
Now, what I need to do is remove the duplicates, leaving just one each so that when I run the query again, all counts become 1.
I know that I can use the DELETE keyword, but I'm not sure how to delete it from the SELECT.
I'm sure I am missing an easy step, but I do not want to ruin my DB being a newbie.
How do I do this?
SELECT
flight_id, latitude, longitude, altitude, call_sign, measurement_time
FROM Aircraft a
WHERE EXISTS (
SELECT * FROM Aircraft x
WHERE x.flight_id = a.flight_id
AND x.latitude = a.latitude
AND x.longitude = a.longitude
AND x.altitude = a.altitude
AND x.call_sign = a.call_sign
AND x.measurement_time = a.measurement_time
AND x.id < a.id
)
;
If the query above returns thecorrect rows (to be deleted)
you can change it into a delete statement:
DELETE
FROM Aircraft a
WHERE EXISTS (
SELECT * FROM Aircraft x
WHERE x.flight_id = a.flight_id
AND x.latitude = a.latitude
AND x.longitude = a.longitude
AND x.altitude = a.altitude
AND x.call_sign = a.call_sign
AND x.measurement_time = a.measurement_time
AND x.id < a.id
)
;
I have always used the CTE method in SQL SERVER. This allows you to define columns that you want to compare, once you have established what columns make up a duplicate, then you can assign a CTE value to it and then go back and cleanup the CTE values that are greater than 1. This is an example of duplicate checking that I do.
WITH CTE AS
(select d.UID
,d.LotKey
,d.SerialNo
,d.HotWeight
,d.MarketValue
,RN = ROW_NUMBER()OVER(PARTITION BY d.HotWeight, d.serialNo, d.MarketValue order by d.SerialNo)
from LotDetail d
where d.LotKey = ('1~20161019~305')
)
DELETE FROM CTE WHERE RN <> 1
In my example I am looking at the LotDetail table where the d.hotweight and d.serial no are matching. if there is a match then the original gets CTE 1 and any duplicates get CTE 2 or greater depending on the amount of duplicates. Then you use the last DELETE statement to clear the entries that come up as duplicate. THis is really flexible so you should be able to adapt it to your issue.
Here is an example tailored to your situation.
WITH CTE AS
(select d.Flight_ID
,d.Latitude
,d.Longitude
,d.Altitude
,d.Call_sign
,d.Measurement*
,RN = ROW_NUMBER()OVER(PARTITION BY d.Flight_ID, d.Latitude, d.Longitude, d.Altitude, d.Call_Sign, d.Measurement* order by d.SerialNo)
from Aircraft d
where d.flight_id = ('**INSERT VALUE HERE')
)
DELETE FROM CTE WHERE RN <> 1
If it's a one-time operation you can create a temp table with the same schema and then copy unique rows over like so:
insert into Aircraft_temp
select distinct on (flight_id, measurement_time) Aircraft.* from Aircraft
Then swap them out by renaming, or truncate Aircraft and copy the temp contents back (truncate Aircraft; insert into Aircraft select * from Aircraft_temp;).
Safer to rename Aircraft to Aircraft_old and Aircraft_temp to Aircraft so you keep your original data until you are sure things are correct. Or at least check that the number of rows in your count query above match the count of rows in the temp table before doing the truncate.
Update2: With a separate valid primary key (assuming it is called id) you can do a DELETE based on a self join like this:
delete from Aircraft using (
select a1.id
from Aircraft a1
left join (select flight_id, measurement_time, min(id) as id from Aircraft group by 1,2) a2
on a1.id = a2.id
where a2.id is null
) as d
where Aircraft.id=d.id
This finds the minimum id (could do max too for the "latest") for each flight and identifies all the records from the full set having an id that is not the minimum (no match in the join). The unmatched ids are deleted.
I have two maps loaded into the database, one map has the geometry of the states, and the other one has the geometry of the urban areas.
I want to make and intersection to make a relation between the urban areas and the states to know which urban area belong to each state.
The problem is that some urban areas occupy two states, the urban area belongs to the state in which it has more area within the state geometry.
i could use the command ST_Intersects, but it would add it both states in which the urban are instersect with the states.
What sql command i have to use? i have read the documentation of
ST_CoveredBy and ST_Within but i'm not really sure if they work for what i need to do.
First create the instersection object between state and urban region and calculate area size, using the ST_intersect on the JOIN will use index to avoid overhead.
Then assign a row_number to each urban_id order by area size.
With rn = 1 mean only return the largest area for each urban_id.
.
WITH cte as (
SELECT S.state_id,
U.urban_id,
ST_Area(ST_Intersection( S.geom, U.geom )) a_geom
-- This create the intersect geom and calculate area
FROM states S
JOIN urban U
ON ST_Intersects( S.geom, U.geom ) -- This is a boolean function
),
area as (
SELECT state_id,
urban_id,
row_number() over (partition by urban_id order by a_geom desc) as rn
FROM cte
)
SELECT state_id,
urban_id
FROM area
WHERE rn = 1
You can use ST_Area on ST_Intersection to sort and LATERAL JOIN.
WITH states(id, geom) AS(
VALUES (1, ST_MakePolygon(ST_GeomFromText('LINESTRING(0 0, 1 0, 1 1, 0 1, 0 0)')))
,(2, ST_MakePolygon(ST_GeomFromText('LINESTRING(1 0, 2 0, 2 1, 1 1, 1 0)')))
),cities(id, geom) AS(
VALUES (1,ST_Buffer(ST_GeomFromText('POINT(0.5 0.5)'), 0.3))
,(2,ST_Buffer(ST_GeomFromText('POINT(1.5 0.5)'), 0.3))
,(3,ST_Buffer(ST_GeomFromText('POINT(1.1 0.5)'), 0.3))
)
SELECT c.id AS city, s.id AS state
FROM cities AS c
CROSS JOIN LATERAL (SELECT s.id, s.geom
FROM states AS s
WHERE ST_Intersects(s.geom, c.geom)
ORDER BY ST_AREA(ST_Intersection(s.geom,c.geom)) DESC
LIMIT 1) AS s