Performing joins - mysql

So this my first run into mysql databases,
I got a lot of help from my first question :
MYSQL - First Database Structure help Please
and built my database as pitchinnate recommended
I have :
Table structure for table club
Column Type Null Default
id int(11) No
clubname varchar(100) No
address longtext No
phone varchar(12) No
website varchar(255) No
email varchar(100) No
Table structure for table club_county
Column Type Null Default
club_id int(11) No
county_id int(11) No
Table structure for table county
Column Type Null Default
id int(11) No
state_id tinyint(4) No
name varchar(50) No
Table structure for table states
Column Type Null Default
id tinyint(4) No
longstate varchar(20) No
shortstate char(2) No
I set up foreign key relationships for everything above that looks that way.... states.id -> county.state_id for example
What I tried to run :
SELECT *
FROM club
JOIN states
JOIN county
ON county.state_id=states.id
JOIN club_county
ON club_county.club_id=club.id
club_county.county_id=county.id
This didn't work... I'm sure the reason is obvious to those of you who know what SHOULD be done.
What I'm trying to do is
get a listing of all clubs, with their associated state and county(ies)

You need to specify a JOIN condition for each of your joins. It should look something like the following:
SELECT *
FROM club
JOIN club_county ON club.id = club_county.club_id
JOIN county ON club_county.county_id = county.id
JOIN states ON county.state_id = state.id
Your version omitted an ON clause on the line that reads JOIN states.
One thing regarding your table names: It's advisable to stick to either singular or plural table names and not to mix them (notice you have club (singular) and states (plural) tables). This makes things easier to remember when you're developing and you're less likely to make mistakes.
EDIT:
If you want to limit which columns appear in your result, you just need to modify the SELECT clause. Instead if "SELECT *", you comma separate just the fields you want.
E.g.
SELECT club.id, club.name, county.name, states.name
FROM club
JOIN club_county ON club.id = club_county.club_id
JOIN county ON club_county.county_id = county.id
JOIN states ON county.state_id = state.id

The query you have written will not even execute as it has syntax error.
Please see this link for more details on JOINS:
13.2.8.2. JOIN Syntax
Also, `
SELECT *
FROM club a, county b, states c, club_county d
WHERE a.id = d.county_id
AND b.id = d.county_id
AND b.state_id = c.id
`
I hope this will help... If you still need help, please let us know...
Thanks...
Mr.777

So after you have modified the question, now the answer would be more like:
SELECT club.id,club.clubname,county.name,states.longstate,states.shortstate
FROM club,club_county,county,states
WHERE club.id=club_county.club_id
AND county.id=club_county.county_id
AND states.id = county.state_id
Please let me know if you need more help...
Thanks...
Mr.777

Related

MYSQL ERROR CODE: 1288 - can't update with join statement

Thanks for past help.
While doing an update using a join, I am getting the 'Error Code: 1288. The target table _____ of the UPDATE is not updatable' and figure out why. I can update the table with a simple update statement (UPDATE sales.customerABC Set contractID = 'x';) but can't using a join like this:
UPDATE (
SELECT * #where '*' contains columns a.uniqueID and a.contractID
FROM sales.customerABC
WHERE contractID IS NULL
) as a
LEFT JOIN (
SELECT uniqueID, contractID
FROM sales.tblCustomers
WHERE contractID IS NOT NULL
) as b
ON a.uniqueID = b.uniqueID
SET a.contractID = b.contractID;
If changing that update statement a SELECT such as:
SELECT * FROM (
SELECT *
FROM opwSales.dealerFilesCTS
WHERE pcrsContractID IS NULL
) as a
LEFT JOIN (
SELECT uniqueID, pcrsContractID
FROM opwSales.dealerFileLoad
WHERE pcrsContractID IS NOT NULL
) as b
ON a."Unique ID" = b.uniqueID;
the result table would contain these columns:
a.uniqueID, a.contractID, b.uniqueID, b.contractID
59682204, NULL, NULL, NULL
a3e8e81d, NULL, NULL, NULL
cfd1dbf9, NULL, NULL, NULL
5ece009c, , 5ece009c, B123
5ece0d04, , 5ece0d04, B456
5ece7ab0, , 5ece7ab0, B789
cfd21d2a, NULL, NULL, NULL
cfd22701, NULL, NULL, NULL
cfd23032, NULL, NULL, NULL
I pretty much have all database privileges and can't find restrictions with the table reference data. Can't find much information online concerning the error code, either.
Thanks in advance guys.
You cannot update a sub-select because it's not a "real" table - MySQL cannot easily determine how the sub-select assignment maps back to the originating table.
Try:
UPDATE customerABC
JOIN tblCustomers USING (uniqueID)
SET customerABC.contractID = tblCustomers.contractID
WHERE customerABC.contractID IS NULL AND tblCustomers.contractID IS NOT NULL
Notes:
you can use a full JOIN instead of a LEFT JOIN, since you want uniqueID to exist and not be null in both tables. A LEFT JOIN would generate extra NULL rows from tblCustomers, only to have them shot down by the clause requirement that tblCustomers.contractID be not NULL. Since they allow more stringent restrictions on indexes, JOINs tend to be more efficient than LEFT JOINs.
since the field has the same name in both tables you can replace ON (a.field1 = b.field1) with the USING (field1) shortcut.
you obviously strongly want a covering index with (uniqueID, customerID) on both tables to maximize efficiency
this is so not going to work unless you have "real" tables for the update. The "tblCustomers" may be a view or a subselect, but customerABC may not. You might need a more complicated JOIN to pull out a complex WHERE which might be otherwise hidden inside a subselect, if the original 'SELECT * FROM customerABC' was indeed a more complex query than a straight SELECT. What this boils down to is, MySQL needs a strong unique key to know what it needs to update, and it must be in a single table. To reliably update more than one table I think you need two UPDATEs inside a properly write-locked transaction.

Can we use FIND_IN_SET() function for multiple column in same table

NOTE : I tried many SF solution, but none work for me. This is bit challenging for, any help will be appreciated.
Below is my SQL-Fiddle link : http://sqlfiddle.com/#!9/6daa20/9
I have tables below:
CREATE TABLE `tbl_pay_chat` (
nId int(11) NOT NULL AUTO_INCREMENT,
npayid int(11) NOT NULL,
nSender int(11) NOT NULL,
nTos varchar(255) binary DEFAULT NULL,
nCcs varchar(255) binary DEFAULT NULL,
sMailBody varchar(500) binary DEFAULT NULL,
PRIMARY KEY (nId)
)
ENGINE = INNODB,
CHARACTER SET utf8,
COLLATE utf8_bin;
INSERT INTO tbl_pay_chat
(nId,npayid,nSender,nTos,nCcs,sMailBody)
VALUES
(0,1,66,'3,10','98,133,10053','Hi this test maail'),
(0,1,66,'3,10','98,133,10053','test mail received');
_____________________________________________________________
CREATE TABLE `tbl_emp` (
empid int(11) NOT NULL,
fullname varchar(45) NOT NULL,
PRIMARY KEY (empid)
)
ENGINE = INNODB,
CHARACTER SET utf8,
COLLATE utf8_bin;
INSERT INTO `tbl_emp` (empid,fullname)
VALUES
(3, 'Rio'),
(10, 'Christ'),
(66, 'Jack'),
(98, 'Jude'),
(133, 'Mike'),
(10053, 'James');
What I want :
JOIN above two tables to get fullname in (nTos & nCcs) columns.
Also, I want total COUNT() of rows.
What I tried is below query but getting multiples time FULLNAME in 'nTos and nCcs column' also please suggest to find proper number of row count.
SELECT a.nId, a.npayid, e1.fullname AS nSender, sMailBody, GROUP_CONCAT(b.fullname ORDER BY b.empid)
AS nTos, GROUP_CONCAT(e.fullname ORDER BY e.empid) AS nCcs
FROM tbl_pay_chat a
INNER JOIN tbl_emp b
ON FIND_IN_SET(b.empid, a.nTos) > 0
INNER JOIN tbl_emp e
ON FIND_IN_SET(e.empid, a.nCcs) > 0
JOIN tbl_emp e1
ON e1.empid = a.nSender
GROUP BY a.nId ORDER BY a.nId DESC;
I hope I made my point clear. Please help.
You have a horrible data model. You should not be storing lists of ids in strings. Why? Here are some reasons:
Numbers should be stored as numbers not strings.
Relationships between tables should be declared using foreign key relationships.
SQL has pretty poor string manipulation capabilities.
The use of functions and type conversion in ON often prevents the use of indexes.
No doubt there are other good reasons. Your data model should be using properly declared junction tables for the n-m relationships.
That said, sometimes we are stuck with other people's really, really, really, really bad design decisions. There are some ways around this. I think the query that you want can be expressed as:
SELECT pc.nId, pc.npayid, s_e.fullname AS nSender, pc.sMailBody,
GROUP_CONCAT(DISTINCT to_e.fullname ORDER BY to_e.empid)
AS nTos,
GROUP_CONCAT(DISTINCT cc_e.fullname ORDER BY cc_e.empid) AS nCcs
FROM tbl_pay_chat pc INNER JOIN
tbl_emp to_e
ON FIND_IN_SET(to_e.empid, pc.nTos) > 0 INNER JOIN
tbl_emp cc_e
ON FIND_IN_SET(cc_e.empid, pc.nCcs) > 0 JOIN
tbl_emp s_e
ON s_e.empid = pc.nSender
GROUP BY pc.nId
ORDER BY pc.nId DESC;
Here is a db<>fiddle.

mysql select a different field is a field is null

I have a staff table
id int(11)
names varchar(120)
family_names varchar(100)
nickname varchar(100)
and a table of who is at work
id int(11)
personid int(11)
titleid int(11)
typeid int(11)
at_work datetime
status int(11)
My problem is that I need to produce a report where I check who is at work and display names, family name and title. As people are called by nickname, I need to check first if nickname exist so I find this example on this site
SELECT IF(LENGTH(nickname)>0, nickname, names) FROM staff_list
I will get either the nickname or the names, but it do not solve my request as I need to search mu who_is_at_work table and then get the staff at work and create that list.
Is it possible to do a select within a select to get the three fields that I need
nickname/names, family name, title
After a while I figured out how to do it.
I found the solution, thanks for posting. Here is what I ended up doing.
SELECT p.id AS ID,
IF(LENGTH(p.nickname)>0, p.nickname, p.names) AS 'Names/Nickname',
p.family_names AS 'Family name',
r.rank AS 'Rank'
FROM atworklist AS who
LEFT JOIN stafflist as p on p.id = who.personid
LEFT JOIN title AS r ON r.id = p.titleid
where who.shipid= 2 and who.status in (2,3)
ORDER BY r.sortorder
Works like a charm for me
If you want to get rid of the null values you can use coalesce.
select coalesce(nickname, names) from staff_list
http://dev.mysql.com/doc/refman/5.0/en/comparison-operators.html#function_coalesce
If you want to check for empty strings you can do it this way:
select case when nickname = '' then names else nickname end from staff_list

Database Query by Keywords

I have following tables:
products_match:
atcode varchar(6)
valcode varchar(100)
id_prod varchar(15)
products:
asin varchar(15)
title varchar(155)
Example content of products_match table:
atcode='type'
valcode='wifi'
id_prod='1SC52DD'
atcode='type'
valcode='ram'
id_prod='11DD5ER'
There are multiple kwywords in this table.
I'm budilding a simple search engine - I need to display products matching multiple criteria, example:
select products where atcode='type' AND valcode='wifi' AND valcode='brand' AND 'valcode' = 'Sony'
Do I need to apply self joins for every group of arguments here?
Right now I have following query:
SELECT * FROM products_match a
JOIN products b ON a.id_prod=b.asin
JOIN assortment_match c ON a.id_prod=c.id_prod
WHERE c.atcode='brand' AND c.valcode='sony'
ORDER BY sales_rank ASC LIMIT 0,60
however it returns no products.
Can anybody help me solve this issue?
Edit
I've been told that I should use one self join for every group of keywords. What do you think?
One method is that for each match, you could use an EXISTS subquery.
AND EXISTS (select 1 from products_match
where id_prod = a.id_prod
and atcode = 'type' and valcode = 'wifi')

optimising and scaling mysql structure + queries for large mailing groups

So I have a system that stores contacts and allows them to be put into groups. These groups can be defined by criteria (everyone with surname 'smith'), or by explicitly adding / excluding people.
The problem I am having is that when I list the mailing groups, I need to count how many contacts are in each one. This number can change as contacts are added / removed from the contacts table. On small groups / amounts of contacts it is fine, however using 50k ish contacts runs into problems
An example query I use for this is as follows:
SELECT COUNT(c_id) FROM contacts, mgroups
LEFT JOIN mgroups_explicit ON mg_id = me_mg_id
WHERE mgroups.site_id = '10'
AND mg_id = '20'
AND me_c_id = c_id
AND contacts.site_id = '10'
OR (contacts.site_id = '10' AND ( c_tags LIKE '%tag1%')) AND c_id NOT IN
( SELECT mex_c_id FROM mgroups_exclude WHERE c_id = mex_c_id ) GROUP BY c_id
The criteria table does not feature in this query, as the problem presents itself when large groups are created explicitly, rather than with a criteria. This is required as criteria based groups grow or shrink on the fly as you modify your contacts, where as explicit is generally set in stone. So in this case, if you explicitly add 20k contacts to a group, it adds 20k rows to the table marked with that mg_id as a foreign key.
This basically takes ages / times out / gets the wrong number / generally doesn't work very well. I either need to figure out a more efficient query, or figure out a better way to store everything.
Any ideas?
The 5 main tables that make up the database
contacts - where the actual contacts reside
Field Type Null Default Comments
c_id int(8) No
site_id int(6) No
c_email varchar(500) No
c_source varchar(255) No
c_subscribed tinyint(1) No 0
c_special tinyint(1) No 0
c_domain text No
c_title varchar(12) No
c_name varchar(128) No
c_surname varchar(128) No
c_company varchar(128) No
c_jtitle text No
c_ad1 text No
c_ad2 text No
c_ad3 text No
c_county varchar(64) No
c_city varchar(128) No
c_postcode varchar(32) No
c_lat varchar(100) No
c_lng varchar(100) No
c_country varchar(64) No
c_tel varchar(20) No
c_mob varchar(20) No
c_dob date No
c_registered datetime No
c_updated datetime No
c_twitter varchar(255) No
c_facebook varchar(255) No
c_tags text No
c_special_1 text No
c_special_2 text No
c_special_3 text No
c_special_4 text No
c_special_5 text No
c_special_6 text No
c_special_7 text No
c_special_8 text No
mgroups - basic mailing group info
Field Type Null Default Comments
mg_id int(8) No
site_id int(6) No
mg_name varchar(255) No
mg_created datetime No
mgroups_criteria - criteria for said mailing groups
Field Type Null Default Comments
mc_id int(8) No
site_id int(6) No
mc_mg_id int(8) No
mc_criteria text No
mgroups_exclude - anyone to exclude from criteria
Field Type Null Default Comments
mex_id int(8) No
site_id int(6) No
mex_c_id int(8) No
mex_mg_id int(8) No
mgroups_explicit - anyone to explicitly add without the use of criteria
Field Type Null Default Comments
me_id int(8) No
site_id int(6) No
me_c_id int(8) No
me_mg_id int(8) No
And the indexs / explain of query. Must admit, indexes are not my strong point, any improvements?
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY mgroups ALL PRIMARY,mg_id NULL NULL NULL 9 Using temporary; Using filesort
1 PRIMARY mgroups_explicit ref me_mg_id me_mg_id 4 engine_4.mgroups.mg_id 8750
1 PRIMARY contacts ALL PRIMARY,c_id NULL NULL NULL 86012 Using where; Using join buffer
2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const table...
I don't see any indexes in the schema above, you do have indexes don't you?
run an explain on the query
EXPLAIN
SELECT COUNT(c_id) FROM
contacts, mgroups LEFT JOIN mgroups_explicit ON mg_id = me_mg_id
WHERE
mgroups.site_id = '10'
AND mg_id = '20'
AND me_c_id = c_id
AND contacts.site_id = '10'
OR (contacts.site_id = '10'
AND ( c_tags LIKE '%tag1%'))
AND c_id NOT IN (SELECT mex_c_id FROM mgroups_exclude WHERE c_id = mex_c_id ) GROUP BY c_id
That will tell you about what indexes are being used how many records it has to sort through etc..
DC
Right so I got this answered elsewhere (Huge thanks to Hambut_Bulge), so for the sake of it being useful to anyone else heres the solution:
First things off you're mixing old and new (ANSI) style joins in the same query. This is considered a bad idea in SQL circles. By old style I mean we write a query with a join along these lines
SELECT a.column_name, b.column2
FROM table1 a, second_table b
WHERE a.id_key = b.fid_key
AND b.some_other_criteria = 'Y';
In the newer ANSI style we'd rewrite the above to this:
SELECT a.column_name, b.column2
FROM table1 a INNER JOIN second_table b ON a.id_key = b.fid_key
WHERE b.some_other_criteria = 'Y';
Its neater and easier to read which bits are join conditions and which are where clauses. Its also best to get into the habit of using ANSI style as old style support may (at some point) be discontinued.
Also try and be consistent in your use of dot notation and/or aliases. Again it makes big queries easier to read.
Back to your problem query, I began by starting to convert it into ANSI style and straight-away noticed that you don't have a join condition between contacts and mgroups. This means that optimizer will create a cross join (also called a cartesian product), which was probably something you don't want to do. The cross join (in case you didn't know) joins every row in the contacts table with every row in the mgroups table. So if you have 50,000 rows in contacts and 20,000 rows in mgroup you're going to get a joined result set containing 1,000,000,000 rows!
The other thing that is going to slow this query drastically is the subquery on mgroups_exclude. A subquery is executed once for each row in the outer query eg:
SELECT a.column1
FROM table1 a
WHERE a.id_key NOT IN ( SELECT * FROM table2 b WHERE a.id_key = b.fid_key);
Assume that table1 has 2,000,000 rows and table2 has 500,000. For each and every row in the outer query (table1) the database is going to have to do a full scan on the inner query. So to get a result the database will have read 1,000,000,000,000 rows and we may only be interested in 1,000! It will not touch any indexes no matter what.
To get around this we can use a left join (also called a left outer join) on the two tables.
SELECT a.column1
FROM table1 a LEFT JOIN table2 b ON a.id_key = b.fid_key
WHERE b.fid_key IS NULL;
An outer join does not require each record in the joined tables to have a matching record. So the example above we'd get all the records from table1 even if there is no match on table2. For non-matched records the database returns a NULL and we can test for that in the where clause. Now the optimizer can scan the indexes on the two tables id_key fields (assuming there are any), resulting in a much faster query.
So, to wrap up. I'd rewrite your orginal query thus:
SELECT COUNT( a.c_id )
FROM contacts a
INNER JOIN mgroups b ON a.c_id = b.mg_id
LEFT JOIN mgroups_explicit c ON b.mg_id = c.me_mg_id
LEFT JOIN mgroups_exclude d ON a.c_id = d.mex_c_id
WHERE b.mg_id = '20'
AND a.site_id = '10'
AND a.c_tags LIKE '%tag1%'
AND d.mex_c_id IS NULL
GROUP BY c_id;