postgres query (more than function) - postgresql

Let's assume that I have 2 tables: Human, Car.
Table Car has some columns and one of them is Human_id. For example, like this:
Human_id Type
1 BMW
5 Mercedes
1 Audi
2 Peugeot
3 BMW
2 JEEP
How can I select all humans who have more than one car? I mean I need to control if Human_id is used more than once in the Human_id column. But how I can do that?
SELECT human.name
FROM Human, Car
WHERE .... <- what goes here??

SELECT human.name, car.human_id, count(*) as total
FROM Human, Car
group by human.name, car.human_id
having count(*) > 1

Try grouping your Car records by Human_id, and take a look at the HAVING clause.

If you have a FK between Human and Car tables, it's best to use a Join instead of a Cartesian product:
create table Human (
idHuman integer primary key,
name varchar(15),
...
);
insert into Human (idHuman, name) values
(1,'A'), (2,'B'), (3,'C'), (4,'D'), (5,'E');
create table Car (
Human_id integer REFERENCES Human ON DELETE CASCADE,
Type varchar(15),
...
);
insert into Car (Human_id, Type) values
(1,'BMW'), (5,'Mercedes'), (1,'Audi')
,(2,'Peugeot'), (3,'BMW'), (2,'JEEP');
And the query:
select hu.name
from Human hu
join Car ca on (ca.Human_id = hu.idHuman)
group by hu.name
having count(*) > 1
I hope that I help you

select human.name
from human
where human.id in
select human.id
from car
where count(car) > 1
group by human.id

Related

PostgreSQL: recursively join a second table

I'm struggling with recursion in PostgreSQL. I need to join a first table with a second one, and then recursively join within the second table. I looked at quite a number of examples, but most are about finding the parent records within a single table, and this has left me utterly confused.
Here's a minimal example with tables thing and category. Records in thing may or may not have a category:
id
name
category
1
a5
3
2
passat
2
3
apple
NULL
Records in category may have one or more parents in the same table:
id
name
parent_category
1
vehicle
NULL
2
car
1
3
coupe
2
The result I'm looking for is the combination of all things with their categories, as well as the category level (1 for the direct parent, 2 for the level above).
thing_name
category_name
level
a5
coupe
1
a5
car
2
a5
vehicle
3
passat
car
1
passat
vehicle
2
apple
NULL
NULL
I have a DB Fiddle here: https://www.db-fiddle.com/f/b7V8ddragZZ9x2RsMkdFYn/5
CREATE TABLE category (
id INT,
name TEXT,
parent_category INT
);
INSERT INTO category VALUES (1, 'vehicle', null);
INSERT INTO category VALUES (2, 'car', 1);
INSERT INTO category VALUES (3, 'coupe', 2);
CREATE TABLE thing (
id INT,
name TEXT,
category INT
);
INSERT INTO thing VALUES (1, 'a5', 3);
INSERT INTO thing VALUES (2, 'passat', 2);
INSERT INTO thing VALUES (3, 'apple', null);
Use a CTE to join the tables, giving you a tree-like view of combined thing_categories, which you can then use with a normal recursive CTE.
with recursive join_thing_category as (
select thing.id as thing_id,
thing.name as thing_name,
thing.category as thing_category,
category.id as category_id,
category.name as category_name,
category.parent_category as parent_category
from thing left join category on thing.category=category.id
),
recursive_part(n) as (
select thing_id, thing_name, thing_category, category_id, category_name, parent_category, (0*parent_category) + 1 as level from join_thing_category
union all
select 1, thing_name, thing_category, cat.id category_id, cat.name, cat.parent_category as parent, level+1 as level from recursive_part rp cross join category cat
where cat.id=rp.parent_category
)
select thing_name, category_name, level from recursive_part order by 1, 2, 3 limit 1024;
thing_name
category_name
level
a5
car
2
a5
coupe
1
a5
vehicle
3
apple
passat
car
1
passat
vehicle
2
View on DB Fiddle
The (0*parent_category) + 1 as level bit is so that things with no category get NULL as their level instead of 1.

filter taking to much time in posgresdb on gender field

I have one table with 100M plus rows which looks like this
Create table member (
id bigint,
gender text,
//..other fields
primary key (id)
);
Now the gender field has two possible value 'M' or 'F'
Whenever I am using the gender field then it's taking to much time I have indexes on other fields like id, member details, mobile number
select
count(1) filter (where mod.is_active and m.gender = 'M') as male,
count(1) filter (where mod.is_active and m.gender = 'F') as female
from member_other_details mod
inner join member m on m.id = mod.member_id
This query is taking hrs to complete
How can I optimize this?
Personnally i would execute this query
select m.gender,count(*)
from member_other_details mod inner join member m on m.id = mod.member_id
where mod.is_active
group by m.gender

Update Variable based on Group

I need to perform an update to a field in a table with a variable, but I need the variable to change when the group changes. It is just an INTt, so for example if I The example below I want to update the record of texas with a 1 and flordia with the next number of 2:
UPDATE table
set StateNum = #Count
FROM table
where xxxxx
GROUP BY state
Group Update Variable
Texas 1
Texas 1
Florida 2
Florida 2
Florida 2
I think you should use a lookup table with the state and its number StateNum Then you should store this number instead of the name to your table.
You might use DENSE_RANK within an updateable CTE:
--mockup data
DECLARE #tbl TABLE([state] VARCHAR(100),StateNum INT);
INSERT INTO #tbl([state]) VALUES
('Texas'),('Florida'),('Texas'),('Nevada');
--your update-statement
WITH updateableCTE AS
(
SELECT StateNum
,DENSE_RANK() OVER(ORDER BY [state]) AS NewValue
FROM #tbl
)
UPDATE updateableCTE SET StateNum=NewValue;
--check the result
SELECT * FROM #tbl;
And then you should use this to get the data for your lookup table
SELECT StateNum,[state] FROM #tbl GROUP BY StateNum,[state];
Then drop the state-column from your original table and let the StateNum be a foreign key.

Find all records NOT in any blocked range where blocked ranges are in a table

I have a table TaggedData with the following fields and data
ID GroupID Tag MyData
** ******* *** ******
1 Texas AA01 Peanut Butter
2 Texas AA15 Cereal
3 Ohio AA05 Potato Chips
4 Texas AA08 Bread
I have a second table of BlockedTags as follows:
ID StartTag EndTag
** ******** ******
1 AA00 AA04
2 AA15 AA15
How do I select from this to return all data matching a given GroupId but NOT in any blocked range (inclusive)? For the data given if the GroupId is Texas, I don't want to return Cereal because it matches the second range. It should only return Bread.
I did try left joins based queries but I'm not even that close.
Thanks
create table TaggedData (
ID int,
GroupID varchar(16),
Tag char(4),
MyData varchar(50))
create table BlockedTags (
ID int,
StartTag char(4),
EndTag char(4)
)
insert into TaggedData(ID, GroupID, Tag, MyData)
values (1, 'Texas', 'AA01', 'Peanut Butter')
insert into TaggedData(ID, GroupID, Tag, MyData)
values (2, 'Texas' , 'AA15', 'Cereal')
insert into TaggedData(ID, GroupID, Tag, MyData)
values (3, 'Ohio ', 'AA05', 'Potato Chips')
insert into TaggedData(ID, GroupID, Tag, MyData)
values (4, 'Texas', 'AA08', 'Bread')
insert into BlockedTags(ID, StartTag, EndTag)
values (1, 'AA00', 'AA04')
insert into BlockedTags(ID, StartTag, EndTag)
values (2, 'AA15', 'AA15')
select t.* from TaggedData t
left join BlockedTags b on t.Tag between b.StartTag and b.EndTag
where b.ID is null
Returns:
ID GroupID Tag MyData
----------- ---------------- ---- --------------------------------------------------
3 Ohio AA05 Potato Chips
4 Texas AA08 Bread
(2 row(s) affected)
So, to match on given GroupID you change the query like that:
select t.* from TaggedData t
left join BlockedTags b on t.Tag between b.StartTag and b.EndTag
where b.ID is null and t.GroupID=#GivenGroupID
I Prefer the NOT EXISTS simply because it gives you more readability, usability and better performance usually in large data (several cases get better execution plans):
would be like this:
SELECT * from TaggedData
WHERE GroupID=#GivenGroupID
AND NOT EXISTS(SELECT 1 FROM BlockedTags WHERE Tag BETWEEN StartTag ANDEndTag)

postgis advanced (?) selection query

The problem: I need to select, for each building in my table that has say at least 2 pharmacies and 2 education centers within a radius of 1km, all POIs (pharmacies, comercial centres, medical centers, education centers, police stations, fire stations) which are within 1km of the respective building. table structure->
building (id serial, name varchar )
poi_category(id serial, cname varchar) --cname being the category name of course
poi(id serial, name varchar, c_id integer)-- c_id is the FK referencing poi_category(id)
all coordinate columns are of type geometry not geography (let's call them geom)
here's the way i thought it should be done but i'm not sure it's even correct let alone the optimal solution to this problem
SELECT r.id_b, r.id_p
FROM (
SELECT b.id AS id_b, p.id AS id_p, pc.id AS id_pc,pc.cname
FROM building AS b, poi AS p, poi_category AS pc
WHERE ST_DWithin(b.geom,p.geom, 1000) AND p.c_id=pc.id
) AS r,
(
SELECT * FROM r GROUP BY id_b
) AS r1
HAVING count (
SELECT *
FROM r, r1
WHERE r1.id_b=r.id_b AND r.id_pc='pharmacy'
)>1
AND
count (
SELECT *
FROM r, r1
WHERE r1.id_b=r.id_b AND r.id_pc='ed. centre'
)>1
Is this the way to go for what i need ? What solution would be better from a performance point of view? What about the most elegant solution?
I've also posted here :http://gis.stackexchange.com/questions/11445/postgis-advanced-selection-query
This is a solution I elaborated. It's the fastest one I could find but it's still slow. Given the nature of the task I doubt it can be made faster...
WITH
building AS (
SELECT way, osm_id
FROM osm_polygon
WHERE tags #> hstore('building','yes')
--ORDER BY 1
LIMIT 1000
),
pharmacy AS (
SELECT way
FROM osm_poi
WHERE tags #> hstore('amenity','pharmacy')
),
school AS (
SELECT way
FROM osm_poi
WHERE tags #> hstore('amenity','school')
)
SELECT ST_AsText(building.way) AS geom, building.osm_id AS label
FROM building
WHERE
(SELECT count(*) > 1
FROM pharmacy
WHERE ST_DWithin(building.way,pharmacy.way,1000))
AND
(SELECT count(*) > 1
FROM school
WHERE ST_DWithin(building.way,school.way,1000))
Yours. S.