mysql query - multilevel locations - mysql

I've a table with following structure and values:
location_id, location_name, parent_id
1, USA, 0
2, Illinois, 1
3, Chicago, 2
4, New York, 1
In this fragment, then row with 'USA' would be considered a country, because value of parent_id is 0. But, if parent_id has some other value, then it would signify, that the particular location is under some location. We have multiple levels of location and it is not set. It can be 2 level, 3 level, 4 level, etc.
For ex. USA > Alabama > Alabama Gulf Coast > Gulf Shores
I need to get all location_id entries, which has no further location. So that in above example, I should get answer as "Gulf Shores".
I think the solution should be something like:
SELECT location_id FROM location WHERE "( parent_id does not contain any value from location_id )"
But i cannot figure out the exact syntax. How should I implement this?

Maybe you would be better off, if you used Closure Tables to implement this ( as recommended in "SQL Antipatterns" book).

select location_id from location l
where not exists
(select null
from location
where parent_id = l.location_id);

Related

Saving a COUNT column appearance while printing each row in the table

So there is a question I have not been able to find an answer to. Say you want to print each row in a table like the following:
ID | Name | Location
----+------+----------
1 | Adam | New York
2 | Eva | London
3 | Jon | New York
which would give the result
1 Adam New York
2 Eva London
3 Jon New York
Say that I at the same time would like to count the number of occurrences someone lives in a specific city, and save that value for printing after I've iterated through the table; is that possible? For example, printing the following:
1 Adam New York
2 Eva London
3 Jon New York
Inhabitants in New York: 2
Inhabitants in London: 1
Is this possible or would you have to iterate through the entire table twice by grouping by Location the second time, and counting those?
EDIT:
To clarify, I know I can solve it by calling:
SELECT * FROM table;
SELECT CONCAT('Inhabitants in ', Location, ': ', COUNT(ID))
FROM table
GROUP BY Location;
But now I am iterating through it twice. Is it possible to do it in only one iteration?
Generally speaking, yes, displaying every row from the table and displaying aggregated data is two separate tasks which should be handled by application, not by the database.
You have the option to run two queries - a plain select * from T, and select location, count(*) from T group by location, and displaying results sequentially. You also have the option to run only a select * from T one, and count the rows within your application, since you're displaying all rows anyway: use any dictionary-like structure your app language provides, with location string for key and running total integer for value.
If you're keen on keeping it a single query, check out WITH ROLLUP clause - https://dev.mysql.com/doc/refman/8.0/en/group-by-modifiers.html. This would certainly be an unusual way of using it, but if you group by location, id and then tamper with results a little, you can get what you want.
select if(id is null, CONCAT('Inhabitants in ', location, ': ', cnt), concat(id, ' ', name, ' ', location))
from
(
select id, location, name, count(*) cnt
from t
where location is not null
group by location, id with rollup
) q
where location is not null
order by id is null, id asc;
Though the performance could be questionable, compared to two plain queries; you should experiment or check with EXPLAIN.
Try below query, use subquery
select concat(concat(concat('Inhabitants in ',location),':'),total)
from
(select location, count(id) total
from tablename group by location)a

Putting one line per name using SQL

I have this sql output that gives me different row for the same person based on their location. I wanted one line per person and three columns with a Y if they lived there.
Select name, Paris, London, NYC from location
Name Paris London NYC
John y
John y
John y
I want this
Name Paris London NYc
John y y y
you can use max function :
select name, max(paris), max(london), max(nyc) from location
group by name
SELECT name,
IF(SUM(IF(Paris='y',1,0)>0,'y','') as Paris,
IF(SUM(IF(London='y',1,0)>0,'y','') as London,
IF(SUM(IF(NYC='y',1,0)>0,'y','') as NYC
FROM location
GROUP BY name
Side-note, the database is not designed optimally! 3 tables with Name, Cities and Location with proper joins would be much more efficient.
Use Subselects like:
Select name,
(SELECT Paris FROM location WHERE name = a.name),
(SELECT London FROM location WHERE name = a.name)
FROM location a
This way you make a Select in an Select, and link the subselect to the name of the overlaying select.
Some of the other users told you to use aggregations.
In the most database systems an aggregation query is less efficient than a subquery, so you should use aggregations with care.

ruby code to direct all users to a particular location instead of multiple locations and delete duplicates

I am having a User model and a Location Model. Each user belongs to a particular location in the Location model.
I am having duplicate locations in the Location table.
and User belongs to Location.
how can i remove duplicate rows in the location table and keep one row and make all users belong to that single row using ruby. Both the tables are connected through location_ID attribute.
I tried to do this through migration:
def dedupe(model, *key_attrs)
model.select(key_attrs).group(key_attrs).having('count(*) > 1').each { |duplicates|
dup_rows = model.where(duplicates.attributes.slice(key_attrs)).to_a
# the first one we want to keep right?
first_one = dup_rows.shift #stored the first one
dup_rows.each{ |double| double.destroy } # duplicates can now be destroyed
}
end
But there is foreign key constraint of User not letting the migration to run. How can I achieve this?
Current Models are :
User
user_id name location_id
1 tim 1
2 adam 2
3 Joy 3
Location
location_id name
1 NewYork
2 NewYork
3 NewYork
Expected Ouput:
User
user_id name location_id
1 tim 1
2 adam 1
3 Joy 1
Location
location_id name
1 NewYork
Kinda ugly, but you can use a subquery:
First, grab the first occurrence of all records which are duplicates;
original_duplicate_locations = Location.select("MIN(id) AS id, name, user_id").group(:name, :user_id).having("COUNT(id) > 1")
The extra duplicates are defined as locations having the same name, and user_id but not the same id:
duplicates_not_including_originals = Location.joins("JOIN (#{duplicates.to_sql}) dupes ON locations.name = dupes.name AND locations.user_id = dupes.user_id AND locations.id <> dupes.id")
Hey you can try this way:
1)First Update all entries with location first entry in location table using
User.joins(:location).update_all("location_id = select id from locations as l2 where l2.name = locations.name limit 1")
Note: you can also use order by id here if sub query not return first entry from table.
2)Destroy All entries from location table excluding first entry-
Before this make sure all your data get updated with first entry in location table properly means first id of repeated location is updated or not. because after deletion it is not possible to recover your data again. then just destroy all your repeated entries excluding first entry using
Location.where("id not in (?)", Location.select("min(id) as id").group("name").map(&:id)).destroy_all

for loop functionality in mysql query

this is MYSQL query question
First, let say we have
[ALL_MENU]
name
-----
pasta
pizza
and people ordered
ordered
customer name status
john pasta delivered
kim pasta delivered
john pizza delivered
john pasta delivered
I want to go through the ordered table and find anyone who ordered all the menu
In this example, kim only ordered pasta so it should not be included
but john ordered both pasta and pizza, so it should be included on the result.
is There any type of query to do the 'For loop' ability?
thank you
(ps, right now I only allow to use
some basic function command from mysql
group by , create view, exists , all, not exist , unique such and such )
=========================================================================
Ok, From the answer
the count number of all menu thing is work when (customer,name) are the primary key
But what if i added the data column, and primary key as (customer,name,data)
customer name status date
john pasta delivered 3/4
kim pasta delivered 3/5
john pasta delivered 3/5
this is the new column i made
john ordered the only pasta with 2 different date,
but johns number of order count as 2.
so count method will not work for this
How do we fix this problem?
The functionality of a for loop is a cursor. (FYI, most DBMS have such a construct.)
But never use a cursor when a plain old query will do just fine.
Here is one possible solution:
SELECT customer
FROM ordered
GROUP BY customer
HAVING COUNT(DISTINCT name) = (SELECT COUNT(*) FROM all_menu)
(This assumes that all names in ordered are found in all_menus, e.g. there is foreign key. If not, you'll have to add JOIN all_menu ON ordered.name = all_menu.name in the FROM clause.)
EDIT: "Simple"(!) commands only:
SELECT customer
FROM ordered o1
WHERE NOT EXISTS (
SELECT * FROM all_menu
WHERE name NOT IN (SELECT name FROM ordered o2 WHERE o1.customer = o2.customer)
)

MySQL: Get results given a condition

I have a table that looks like this:
target_id || country_code
5-----------||-------US----
5-----------||-------CA---
2----------||-------FR----
3----------||-------SP----
3----------||-------FR----
And another table that looks like this:
target_id || region_name
5-----------||---North America
2-----------||-----France------
3-----------||-----Some Europe
As you can see, table 2 contains locations and target_ids, while table 1 contains where these locations are targeted. In the case of North America, it is targeted to 5, which belongs to Canada and US. France, on the other hand has a target_id of 2, and Some Europe a target_id of 3, which contains France again and Spain.
What I would like to do via MySQL, is to get a table of target_id, country_code, country_name but only for countries. This means, only to the target_ids of table 1 that are in only one row (for example, we know that FR is a country because number 2 is only in FR, and we know that 3 represents a region because it has both Spain and France associated). Is this possible to do via MySQL or will I need two queries and PHP in the middle?
Thanks!
SELECT t1.target_id, t1.country_code, t2.region_name
FROM table1 t1
JOIN table t2
ON t1.target_id = t2.target_id
WHERE (SELECT COUNT(*) FROM table1 t3 WHERE t3.target_id = t1.target_id) = 1
table1 is the one with the country codes, table2 is the one with the the region names.