Querying normalized database, 3 tables - mysql

I have three tables in a MySQL database:
stores (PK stores_id)
states (PK states_id)
join_stores_states (PK join_id, FK stores_id, FK states_id)
The "stores" table has a single row for every business. The join_stores_states table links an individual business to each state it's in. So, some businesses have stores in 3 states, so they 3 rows in join_stores_states, and others have stores in 1 state, so they have just 1 row in join_stores_states.
I'm trying to figure out how to write a query that will list each business in one row, but still show all the states it's in.
Here's what I have so far, which is obviously giving me every row out of join_stores_states:
SELECT states.*, stores.*, join_stores_states.*
FROM join_stores_states
JOIN stores
ON join_stores_states.stores_id=stores.stores_id
JOIN states
ON join_stores_states.states_id=states.states_id
Loosely, this is what it's giving me:
store 1 | alabama
store 1 | florida
store 1 | kansas
store 2 | montana
store 3 | georgia
store 3 | vermont
This is more of what I want to see:
store 1 | alabama, florida, kansas
store 2 | montana
store 3 | georgia, vermont
Suggestions as to which query methods to try would be just as appreciated as a working query.

If you need the list of states as a string, you can use MySQL's GROUP_CONCAT function (or equivalent, if you are using another SQL dialect), as in the example below. If you want to do any kind of further processing of the states separately, I would prefer you run the query as you did, and then collect the resultset into a more complex structure (hashtable of arrays, as a simplest measure, but more complex OO designs are certainly possible) in the client by iterating over the resulting rows.
SELECT stores.name,
GROUP_CONCAT(states.name ORDER BY states.name ASC SEPARATOR ', ') AS state_names
FROM join_stores_states
JOIN stores
ON join_stores_states.stores_id=stores.stores_id
JOIN states
ON join_stores_states.states_id=states.states_id
GROUP BY stores.name
Also, even if you only need the concatenated string and not a data structure, some databases might not have an aggregate concatenation function, in which case you will have to do the client processing anyway. In pseudocode, since you did not specify a language either:
perform query
stores = empty hash
for each row from query results:
get the store object from the hash by name
if the name isn't in the hash:
put an empty store object into the hash under the name
add the state name to the store object's stores array

Related

Fetch rows either with one matching condition or all rows if no matching rows

I have a simple table
**targeted_url_redirect targeted_countries msg_type non_targeted_url**
http://new.botweet.com india,nepal,philippines NEW http://twisend.com
http://expapers.com United States,Canada OLD http://all.twisend.com
https://tweeasy.com india,england OLD http://all.twisend.com
I receive traffics on my website followerise.com and I want to redirect users to specific urls based on their countries as defined in the above table. I am able to write query to get rows for the users who coming from the countries that are target stored in my database. But I am looking for a solution to get all the rows if the targeted_countries condition not return any rows.
I written below queries.
SELECT * FROM tweeasy_website_redirection
WHERE message_type='OLD'
AND targeted_countries LIKE '%#country%'
This query gives me desired rows if visitor coming from the countries india,england,United States,Canada
But I want all rows (2nd and 3rd) should be fetched if a user coming from the countries not specified in targeted_countries column.
Also let me know if I need to restructure this table into 2 or more tables to get desired result.
One option uses not exists:
SELECT t.*
FROM tweeasy_website_redirection t
WHERE
t.message_type = 'OLD'
AND (
t.targeted_countries LIKE '%#country%'
OR NOT EXISTS (
SELECT 1
FROM tweeasy_website_redirection t1
WHERE t1.targeted_countries LIKE '%#country%'
)
)
When it comes to the structure of your table: one design flaw is to store list of values as CSV list. You should have two separate tables: one to store the urls, and the other to store the 1-N relationship between urls and countries. Something like:
table "url"
id targeted_url_redirect msg_type non_targeted_url
1 http://new.botweet.com NEW http://twisend.com
2 http://expapers.com OLD http://all.twisend.com
3 https://tweeasy.com OLD http://all.twisend.com
table "url_countries"
url_id country
1 india
1 nepal
1 philippines
2 united states
2 canada
3 india
3 england
select * from tweeasy_website_redirection
where targeted_countries not in (SELECT targeted_countries FROM stack WHERE targeted_countries LIKE '%#country%')

How to make a single MySQL query that uses the results of another query

I have a Perl program that queries a MySQL database to bring back results based upon which "report" option a user has selected from a web page.
One of the reports is all occupants of a student housing building who have applied for a parking permit, but who have not yet been given one.
When the students apply for a permit, it records the specifics about their car (make, model, year, color, etc.) in a single table row. Each apartment can have up to three students, and each student may apply for a permit. So an apartment might have 0 permits, or 1, 2, or 3 permits, depending upon how many of them have cars.
What I'd like to be able to do, is execute a MySQL query that will find out how many occupants in each apartment have applied for a parking permit, and then based on the results of that query, find out how many permits have been issued. If the number of permits issued is less than the number of applications, that apartment number should be returned in the result set. It doesn't have to name the specific occupant, just the fact that the apartment has at least one occupant who has applied for a permit, but not yet received one.
So I have two tables, one is called occupant_info and it contains all kinds of info about the occupant, but the relevant fields are:
counter (a unique row id)
parking_permit_1_number
parking_permit_2_number
parking_permit_3_number
When a parking permit has been assigned, it is recorded in the appropriate parking_permit_#_number field (if it's occupant number one's permit, it would be recorded in parking_permit_1_number, etc.).
The second table is called, parking_permits, and contains all of the car/owner specifics (make, model, year, owner, owner address, etc.). It also contains a field which references the counter from the occupant_info table.
So an example would be:
occupant_info table
counter | parking_permit_1_number | parking_permit_2_number | parking_permit_3_number
--------|-------------------------|-------------------------|------------------------
1 | 12345 | | 98765
2 | 43920 | |
3 | 30239 | | 34233
parking_permits table
counter | counter_from_occupant_info | permit_1_name | permit_2_name | permit_3_name
--------|----------------------------|---------------|-----------------|-------------------
1 |2 | David Jones | James Cameron | Michael Smerconish
2 |3 | Bill Epps | Hillary Clinton | Donald Trump
3 |1 | Joanne Miller | | Sridevi Gupta
I want a query that will first look at how many occupants in an apartment have applied for a permit. This is determined by counting the names in the parking_permits table. In that table, row 1 has three names, row 2 has three names, and row 3 has two names. The query should then look at the occupant_info table, and for each counter_from_occupant_info from the parking_permits table, see if the same number of parking permits have been issued. This can be determined by comparing the number of non-blank parking_permit_#_number fields.
Using the data above, the query would see the following :
parking_permit table row 1
Has counter_from_occupant_info equal to "2"
Has three names
The row in occupant_info with counter = "2" has only one permit number issued,
so counter_from_occupant_info 2 from parking_permits should be in the result set.
parking_permit table row 2
Has counter_from_occupant_info equal to "3"
Has three names
The row in occupant_info with counter = "3" has only two permit numbers issued,
so counter_from_occupant_info 3 from parking_permits should be in the result set.
parking_permit table row 3
Has counter_from_occupant_info equal to "1"
Has two names
The row in occupant_info with counter = "1" has two permit numbers issued,
so this row should *not* be in the result set.
I've thought about using if, then, case, when, type logic to do this in one query, but frankly can't wrap my head around how to do so.
I was thinking something like:
SELECT
CASE WHEN ( SELECT counter_from_occupant_info
FROM parking_permits
WHERE parking_permit_1_name != ""
AND parking_permit_2_name != ""
AND parking_permit_3_name != "" ) THEN
IF ( SELECT parking_permit_1_number,
parking_permit_2_number,
parking_permit_3_number
FROM occupant_info
WHERE counter = ***somehow reference counter from above case statement--I don't know how to do this***
But then my head explodes and I realize I don't know what the heck I'm doing.
Any help would be appreciated. :-)
Doug
You have a few problems:
Your occupants table schema is bad. There's worse out there, but it looks like someone that doesn't understand how a database works built this table.
Your permits table is also bad. Same reason.
You have no idea what you are doing (kidding... kidding...)
Problem 1:
Your occupants table should probably be two tables. Because an occupant could have 0-3 permits (possibly more, I can't tell from the sample data) then you need a table for your occupant's attributes (name, height, gender, age, primary smell, favorite color, first rent date, I dunno).
Occupants
OccupantID | favorite TV Show | number of limbs | first name | last name | aptBuilding
And... another table for Relationship between the occupant and the permit:
Occupant_permits
OccupantID | Permit ID | status
Now... an occupant can have as many permits as you can stuff into that table and the relationship between them has a status "Applied for", or "Granted" or "Revoked" or what have you.
Problem 2
Your permit info table is doing double duty as well. It holds the information about a permit (it's name) as well as the relationship to the occupant. Since we already have a relationship to the occupant with the "Occupant_Permits" table above, we just need a permits table to hold attributes of a permit:
Permits
Permit ID | Permit Name | Description | etc..
Problem 3
Now that you have a correct schema where objects are in their own table (Occupant, Permit, Occupant and Permit Relationship) your query to get a list of apartments that have at least one occupant that has applied, but not yet received a permit would be:
SELECT
COUNT(DISTINCT o.AptBuilding)
FROM
occupants as o
INNER JOIN occupants_permit as op
ON o.occupant_id = op.occupant_id
INNER JOIN permits as p
ON op.permit_id = p.permit_id
WHERE
op.Status = "Applied"
That's nice and simple and you aren't relying on CASE or UNION or count comparison or any fancy stuff. Just nice straight joins and a simple WHERE clause. This will be fast to query and there's no funny business.
Because your schema isn't great, in order to get something similar you'll need to make use of either UNION queries to stack your many permit_N_ fields into a single field and run something similar to the above query, or you'll have use a fair amount of CASE/IF statements:
SELECT DISTINCT p.pCounter
FROM
(
SELECT
counter as Ocounter
CASE WHEN parking_permit_1_number IS NOT NULL THEN 1 ELSE 0 END
+
CASE WHEN parking_permit_2_number IS NOT NULL THEN 1 ELSE 0 END
+
CASE WHEN parking_permit_3_number IS NOT NULL THEN 1 ELSE 0 END AS permitCount
FROM occupant_info
) as o
LEFT OUTER JOIN
(
SELECT
counter_from_occupant_info as pCounter
CASE WHEN parking_permit_1_name IS NOT NULL THEN 1 ELSE 0 END
+
CASE WHEN parking_permit_2_name IS NOT NULL THEN 1 ELSE 0 END
+
CASE WHEN parking_permit_3_Name IS NOT NULL THEN 1 ELSE 0 END AS permitPermitCount
) as p ON o.Ocounter = p.Pcounter
WHERE p.permitCounter > o.PermitCount
I'm not 100% convinced that is exactly what you are looking for since your schema is confusing where you have multiple objects in a single table and everything is pivoted, but... it should get you in the ball park.
This will be much slower too. There's intermediate result sets, CASE statements, and math, so don't expect MySQL to spit this out in milliseconds.

MySQL, how to repeat same line x times

I have a query that outputs address order data:
SELECT ordernumber
, article_description
, article_size_description
, concat(NumberPerBox,' pieces') as contents
, NumberOrdered
FROM customerorder
WHERE customerorder.id = 1;
I would like the above line to be outputted NumberOrders (e.g. 50,000) divided by NumberPerBox e.g. 2,000 = 25 times.
Is there a SQL query that can do this, I'm not against using temporary tables to join against if that's what it takes.
I checked out the previous questions, however the nearest one:
is to be posible in mysql repeat the same result
Only gave answers that give a fixed number of rows, and I need it to be dynamic depending on the value of (NumberOrdered div NumberPerBox).
The result I want is:
Boxnr Ordernr as_description contents NumberOrdered
------+--------------+----------------+-----------+---------------
1 | CORDO1245 | Carrying bags | 2,000 pcs | 50,000
2 | CORDO1245 | Carrying bags | 2,000 pcs | 50,000
....
25 | CORDO1245 | Carrying bags | 2,000 pcs | 50,000
First, let me say that I am more familiar with SQL Server so my answer has a bit of a bias.
Second, I did not test my code sample and it should probably be used as a reference point to start from.
It would appear to me that this situation is a prime candidate for a numbers table. Simply put, it is a table (usually called "Numbers") that is nothing more than a single PK column of integers from 1 to n. Once you've used a Numbers table and aware of how it's used, you'll start finding many uses for it - such as querying for time intervals, string splitting, etc.
That said, here is my untested response to your question:
SELECT
IV.number as Boxnr
,ordernumber
,article_description
,article_size_description
,concat(NumberPerBox,' pieces') as contents
,NumberOrdered
FROM
customerorder
INNER JOIN (
SELECT
Numbers.number
,customerorder.ordernumber
,customerorder.NumberPerBox
FROM
Numbers
INNER JOIN customerorder
ON Numbers.number BETWEEN 1 AND customerorder.NumberOrdered / customerorder.NumberPerBox
WHERE
customerorder.id = 1
) AS IV
ON customerorder.ordernumber = IV.ordernumber
As I said, most of my experience is in SQL Server. I reference http://www.sqlservercentral.com/articles/Advanced+Querying/2547/ (registration required). However, there appears to be quite a few resources available when I search for "SQL numbers table".

MySQL Query Search using Multiple Rows

Firstly I'd like to start by apologizing for the potentially miss-leading title... I am finding it difficult to describe what I am trying to do here.
With the current project I'm working on, we have setup a 'dynamic' database structure with MySQL that looks something like this.
item_details ( Describes the item_data )
fieldID | fieldValue | fieldCaption
1 | addr1 | Address Line 1
2 | country | Country
item_data
itemID | fieldID | fieldValue
12345 | 1 | Some Random Address
12345 | 2 | United Kingdom
So as you can see, if for example I wanted to lookup the address for the item 12345 I would simply do the statement.
SELECT fieldValue FROM item_data WHERE fieldID=1 and itemID=12345;
But here is where I am stuck... the database is relatively large with around ~80k rows and I am trying to create a set of search functions within PHP.
I would like to be able to perform a query on the result set of a query as quickly as possible...
For example, Search an address name within a certain country... ie: Search for the fieldValue of the results with the same itemID's as the results from the query:
'SELECT itemID from item_data WHERE fieldID=2 and fieldValue='United Kingdom'..
Sorry If I am unclear, I have been struggling with this for the past couple of days...
Cheers
You can do this in a couple of ways. One is to use multiple joins to the item_data table with the fieldID limited to whatever it is you want to get.
SELECT *
FROM
Item i
INNER JOIN item_data country
ON i.itemID = country.itemID
and fieldid = 2
INNER JOIN item_data address
ON i.itemID = country.itemID
and fieldid = 1
WHERE
country.fieldValue= 'United Kingdom'
and address.fieldValue= 'Whatever'
As an aside this structure is often referred to as an Entry Attribute Value or EAV database
Sorry in advance if this sounds patronizing, but (as you suggested) I'm not quite clear what you are asking for.
If you are looking for one query to do the whole thing, you could simply nest them. For your example, pretend there is a table named CACHED with the results of your UK query, and write the query you want against that, but replace CACHED with your UK query.
If the idea is that you have ALREADY done this UK query and want to (re-)use its results, you could save the results to a table in the DB (which may not be practical if there are a large number of queries executed), or save the list of IDs as text and paste that into the subsequent query (...WHERE ID in (...) ... ), which might be OK if your 'cached' query gives you a manageable fraction of the original table.

MySQL: How to pull information from multiple tables based on information in other tables?

Ok, I have 5 tables which I need to pull information from based on one variable.
gameinfo
id | name | platforminfoid
gamerinfo
id | name | contact | tag
platforminfo
id | name | abbreviation
rosterinfo
id | name | gameinfoid
rosters
id | gamerinfoid | rosterinfoid
The 1 variable would be gamerinfo.id, which would then pull all relevant data from gamerinfo, which would pull all relevant data from rosters, which would pull all relevant data from rosterinfo, which would pull all relevant data from gameinfo, which would then pull all relevant data from platforminfo.
Basically it breaks down like this:
gamerinfo contains the gamers basic
information.
rosterinfo contains basic information about the rosters
(ie name and the game the roster is
aimed towards)
rosters contains the actual link from the gamer to the
different rosters (gamers can be on
multiple rosters)
gameinfo contains basic information about the games (ie
name and platform)
platform info contains information about the
different platforms the games are
played on (it is possible for a game
to be played on multiple platforms)
I am pretty new to SQL queries involving JOINs and UNIONs and such, usually I would just break it up into multiple queries but I thought there has to be a better way, so after looking around the net, I couldn't find (or maybe I just couldn't understand what I was looking at) what I was looking for. If anyone can point me in the right direction I would be most grateful.
There is nothing wrong with querying the required data step-by-step. If you use JOINs in your SQL over 5 tables, we sure to have useful indexes on all important columns. Also, this could create a lot of duplicate data:
Imagine this: You need 1 record from gamerinfo, maybe 3 of gameinfo, 4 ouf of rosters and both 3 out of the remaining two tables. This would give you a result of 1*3*4*3*3 = 108 records, which will look like this:
ID Col2 Col3
1 1 1
1 1 2
1 1 3
1 2 1
... ... ...
You can see that you would fetch the ID 108 times, even if you only need it once. So my advice would be to stick with mostly single, simple queries to get the data you need.
There is no need for UNION just multiple JOINs should do the work
SELECT gameinfo.id AS g_id, gameinfo.name AS g_name, platforminfoid.name AS p_name, platforminfoid.abbreviation AS p_abb, rosterinfo.name AS r_name
FROM gameinfo
LEFT JOIN platforminfo ON gameinfo.platforminfoid = platforminfo.id
LEFT JOIN rosters ON rosters.gameinfoid = gameinfo.id
LEFT JOIN rosterinfo ON rosterinfo.id = rosters.rosterinfoid
WHERE gameinfo.id = XXXX
this should pull all info about game based on game id
indexing on all id(s) gameinfoid, platformid, rosterinfoid will help on performance