Using the tables below as an example and the listed query as a base query, I want to add a way to select only rows with a max id! Without having to do a second query!
TABLE VEHICLES
id vehicleName
----- --------
1 cool car
2 cool car
3 cool bus
4 cool bus
5 cool bus
6 car
7 truck
8 motorcycle
9 scooter
10 scooter
11 bus
TABLE VEHICLE NAMES
nameId vehicleName
------ -------
1 cool car
2 cool bus
3 car
4 truck
5 motorcycle
6 scooter
7 bus
TABLE VEHICLE ATTRIBUTES
nameId attribute
------ ---------
1 FAST
1 SMALL
1 SHINY
2 BIG
2 SLOW
3 EXPENSIVE
4 SHINY
5 FAST
5 SMALL
6 SHINY
6 SMALL
7 SMALL
And the base query:
select a.*
from vehicle a
join vehicle_names b using(vehicleName)
join vehicle_attribs c using(nameId)
where c.attribute in('SMALL', 'SHINY')
and a.vehicleName like '%coo%'
group
by a.id
having count(distinct c.attribute) = 2;
So what I want to achieve is to select rows with certain attributes, that match a name but only one entry for each name that matches where the id is the highest!
So a working solution in this example would return the below rows:
id vehicleName
----- --------
2 cool car
10 scooter
if it was using some sort of max on the id
at the moment I get all the entries for cool car and scooter.
My real world database follows a similar structure and has 10's of thousands of entries in it so a query like above could easily return 3000+ results. I limit the results to 100 rows to keep execution time low as the results are used in a search on my site. The reason I have repeats of "vehicles" with the same name but only a different ID is that new models are constantly added but I keep the older one around for those that want to dig them up! But on a search by car name I don't want to return the older cards just the newest one which is the one with the highest ID!
The correct answer would adapt the query I provided above that I'm currently using and have it only return rows where the name matches but has the highest id!
If this isn't possible, suggestions on how I can achieve what I want without massively increasing the execution time of a search would be appreciated!
If you want to keep your logic, here what I would do:
select a.*
from vehicle a
left join vehicle a2 on (a.vehicleName = a2.vehicleName and a.id < a2.id)
join vehicle_names b on (a.vehicleName = b.vehicleName)
join vehicle_attribs c using(nameId)
where c.attribute in('SMALL', 'SHINY')
and a.vehicleName like '%coo%'
and a2.id is null
group by a.id
having count(distinct c.attribute) = 2;
Which yield:
+----+-------------+
| id | vehicleName |
+----+-------------+
| 2 | cool car |
| 10 | scooter |
+----+-------------+
2 rows in set (0.00 sec)
As other said, normalization could be done on few levels:
Keeping your current vehicle_names table as the primary lookup table, I would change:
update vehicle a
inner join vehicle_names b using (vehicleName)
set a.vehicleName = b.nameId;
alter table vehicle change column vehicleName nameId int;
create table attribs (
attribId int auto_increment primary key,
attribute varchar(20),
unique key attribute (attribute)
);
insert into attribs (attribute)
select distinct attribute from vehicle_attribs;
update vehicle_attribs a
inner join attribs b using (attribute)
set a.attribute=b.attribId;
alter table vehicle_attribs change column attribute attribId int;
Which led to the following query:
select a.id, b.vehicleName
from vehicle a
left join vehicle a2 on (a.nameId = a2.nameId and a.id < a2.id)
join vehicle_names b on (a.nameId = b.nameId)
join vehicle_attribs c on (a.nameId=c.nameId)
inner join attribs d using (attribId)
where d.attribute in ('SMALL', 'SHINY')
and b.vehicleName like '%coo%'
and a2.id is null
group by a.id
having count(distinct d.attribute) = 2;
The table does not seems normalized, however this facilitate you to do this :
select max(id), vehicleName
from VEHICLES
group by vehicleName
having count(*)>=2;
I'm not sure I completely understand your model, but the following query satisfies your requirements as they stand. The first sub query finds the latest version of the vehicle. The second query satisfies your "and" condition. Then I just join the queries on vehiclename (which is the key?).
select a.id
,a.vehiclename
from (select a.vehicleName, max(id) as id
from vehicle a
where vehicleName like '%coo%'
group by vehicleName
) as a
join (select b.vehiclename
from vehicle_names b
join vehicle_attribs c using(nameId)
where c.attribute in('SMALL', 'SHINY')
group by b.vehiclename
having count(distinct c.attribute) = 2
) as b on (a.vehicleName = b.vehicleName);
If this "latest vehicle" logic is something you will need to do a lot, a small suggestion would be to create a view (see below) which returns the latest version of each vehicle. Then you could use the view instead of the find-max-query. Note that this is purely for ease-of-use, it offers no performance benefits.
select *
from vehicle a
where id = (select max(b.id)
from vehicle b
where a.vehiclename = b.vehiclename);
Without going into proper redesign of you model you could
1) Add a column IsLatest that your application could manage.
This is not perfect but will satisfy you question (until next problem, see not at the end)
All you need is when you add a new entry to issue queries such as
UPDATE a
SET IsLatest = 0
WHERE IsLatest = 1
INSERT new a
UPDATE a
SET IsLatest = 1
WHERE nameId = #last_inserted_id
in a transaction or a trigger
2) Alternatively you can find out the max_id before you issue your query
SELECT MAX(nameId)
FROM a
WHERE vehicleName = #name
3) You can do it in single SQL, and providing indexes on (vehicleName, nameId) it should actually have decent speed with
select a.*
from vehicle a
join vehicle_names b ON a.vehicleName = b.vehicleName
join vehicle_attribs c ON b.nameId = c.nameId AND c.attribute = 'SMALL'
join vehicle_attribs d ON b.nameId = c.nameId AND d.attribute = 'SHINY'
join vehicle notmax ON a.vehicleName = b.vehicleName AND a.nameid < notmax.nameid
where a.vehicleName like '%coo%'
AND notmax.id IS NULL
I have removed your GROUP BY and HAVING and replaced it with another join (assuming that only single attribute per nameId is possible).
I have also used one of the ways to find max per group and that is to join a table on itself and filter out a row for which there are no records that have a bigger id for a same name.
There are other ways, search so for 'max per group sql'. Also see here, though not complete.
Related
I have TableA with 5 rows in column siteID ( so 5 different sites).
I have TableB that stores userIDs and which sites they have access to. I need to take all 5 siteIDs from TableA and check to see if any of the siteIDs are NOT in TableB for a specific user.
I'm trying something similar to this pseudocode but not sure how the syntax should go:
SET #invalidSites = (
SELECT COUNT(*)
FROM userSiteAccess AS usa
RIGHT JOIN customer.sitesVsUsers AS cust
ON usa.listOfSites = cust.siteID
WHERE cust.siteID IS NULL);
EXAMPLE:
TABLE A
listOfSites
1
2
3
TableB
userID siteAccess
50 3
The count should return 2 as the list of sites has 2 additional rows (site 1 and site 2) but the user only has access to site 3.
You need to use LEFT JOIN rather than RIGHT JOIN, and you need to specify the user ID in the ON condition.
SELECT COUNT(*)
FROM userSiteAccess AS usa
LEFT JOIN sitesVsUsers AS cust
ON usa.listOfSites = cust.siteID AND cust.userId = 2
WHERE cust.siteID IS NULL
DEMO
With NOT EXISTS:
select count(*) counter
from tablea a
where not exists (
select 1 from tableb
where userid = 50 and siteaccess = a.listofsites
)
For transaction listing I need to provide the following columns:
log_out.timestamp
items.description
log_out.qty
category.name
storage.name
log_out.dnr ( Representing the users id )
Table structure from log_out looks like this:
| id | timestamp | storageid | itemid | qty | categoryid | dnr |
| | | | | | | |
| 1 | ........ | 2 | 23 | 3 | 999 | 123 |
As one could guess, I only store the corresponding ID's from other tables in this table. Note: log_out.id is the primary key in this table.
To get the the corresponding strings, int's or whatever back, I tried two queries.
Approach 1
SELECT i.description, c.name, s.name as sname, l.*
FROM items i, categories c, storages s, log_out l
WHERE l.itemid = i.id AND l.storageid = s.id AND l.categoryid = c.id
ORDER BY l.id DESC
Approach 2
SELECT log_out.id, items.description, storages.name, categories.name AS cat, timestamp, dnr, qty
FROM log_out
INNER JOIN items ON log_out.itemid = items.id
INNER JOIN storages ON log_out.storageid = storages.id
INNER JOIN categories ON log_out.categoryid = categories.id
ORDER BY log_out.id DESC
They both work fine on my developing machine, which has approx 99 dummy transactions stored in log_out. The DB on the main server got something like 1100+ tx stored in the table. And that's where trouble begins. No matter which of these two approaches I run on the main machine, it always returns 0 rows w/o any error *sigh*.
First I thought, it's because the main machine uses MariaDB instead of MySQL. But after I imported the remote's log_out table to my dev-machine, it does the same as the main machine -> return 0 rows w/o error.
You guys got any idea what's going on ?
If the table has the data then it probably has something to do with JOIN and related records in corresponding tables. I would start with log_out table and incrementally add the other tables in the JOIN, e.g.:
SELECT *
FROM log_out;
SELECT *
FROM log_out
INNER JOIN items ON log_out.itemid = items.id;
SELECT *
FROM log_out
INNER JOIN items ON log_out.itemid = items.id
INNER JOIN storages ON log_out.storageid = storages.id;
SELECT *
FROM log_out
INNER JOIN items ON log_out.itemid = items.id
INNER JOIN storages ON log_out.storageid = storages.id
INNER JOIN categories ON log_out.categoryid = categories.id;
I would execute all the queries one by one and see which one results in 0 records. Additional join in that query would be the one with data discrepancy.
You're queries look fine to me, which makes me think that it is probably something unexpected with the data. Most likely the ids in your joins are not maintained right (do all of them have a foreign key constraint?). I would dig around the data, like SELECT COUNT(*) FROM items WHERE id IN (SELECT itemid FROM log_out), etc, and seeing if the returns make sense. Sorry I can't offer more advise, but I would be interested in hearing if the problem is in the data itself.
Suppose I have the following table structure:
TABLE 1
main_id | type | information
first segway excellent
second car mercedes
third bike sliceofwind
TABLE segway
id | grade
1 excellent
2 bad
3 (...)
TABLE car
id | brand
1 mercedes
2 honda
3 (...)
TABLE bike
id | tires
1 sliceofwind
2 flatasfaque
3 (...)
What I'd like to do is dinamically obtain information from different tables based on type from table1.
Here's the generic example of a query that I've tried
SELECT (CASE
WHEN table1.type = 'segway' AND segway.grade = table1.information
THEN segway.id,
WHEN table1.type = 'car' AND car.brand = table1.information
THEN car.id,
WHEN table1.type = 'bike' AND bike.tires = table1.information
THEN bike.id
END) AS information
FROM table1,segway,bike,car WHERE table1.main_id IN ("ids")
The result of this query is a cartesian product because all the data from all tables will be retrieved despite the restrictions inside the case because not all tables have restrictions.
I'd like to know if there is a way to work around this without changing the table structure, and if not plea for some hints! (I'm up to some kinky sql stuff, what I'm asking here if it is indeed possible to do this, despite it being advised or not and why!).
This might be one way to do it.
SELECT t1.*
FROM table1 t1
LEFT JOIN segway s
on T1.main_id = s.id and T1.type= 'segway'
LEFT JOIN car c
on T1.main_id = c.id and T1.type= 'car'
LEFT JOIN bike b
on T1.main_id = b.id and T1.type= 'bike'
WHERE t1.main_ID in (SomeList)
segway, car, and bike table columns will be null when the Table1's type doesn't match.
However this seems like it would give you back more data/columns than you need. I think you'd be better off writing separate queries outside the database and call them depending on the value they select. OR using a procedure within the database and conditional logic to return the desired result set. (again 3 separate queries and conditional logic in the database) but without understanding use case, I can't really say which would be better.
We could further coalese the brand, tires and grade into a "Value" field as in
Select t1.*, coalese(s.grade,c.brand,b.tires) as value but I'm not sure this offers any help either.
if we needed to only return table 1 values and set values from the other tables... you said kinky, not me.
I can't see how the Cartesian would occur this way.
This will be your expected result, try this query..
SELECT (CASE WHEN s.`id` IS NOT NULL THEN s.`id`
WHEN c.`id` IS NOT NULL THEN c.`id`
WHEN b.`id` IS NOT NULL THEN b.`id`
END) AS information FROM table1 AS t1
LEFT JOIN segway s ON t1.type= 'segway'
LEFT JOIN car c ON t1.type= 'car'
LEFT JOIN bike b ON t1.type= 'bike'
WHERE t1.main_ID IN (1, 2, 3) AND (t1.information = s.`grade` OR
t1.`information`=c.brand OR
t1.`information`=b.tires);
SELECT art.*,arg. FROM rd_articles AS art
LEFT JOIN rd_argument AS arg ON art.cat=arg.id WHERE art.enabled=1 ORDER BY art.id DESC
LIMIT 10
This is simple join query
Article table structure is
ID cat Description Date
1 1 Abc 08-01-2014
2 1 Aaa 10-01-2014
3 2 Abcv 11-01-2014
4 3 Aaa 12-01-2014
5 3 Aaa 14-01-2014
Arguments table is
ID Name
1 A
2 B
3 C
I want pick last updated(Date) one item from each cat.
How ?
This assumes that the enabled column is in rd_articles:
SELECT art.*, arg.*
FROM (
SELECT * FROM rd_articles
INNER JOIN (
SELECT cat, MAX(date) AS maxdate
FROM rd_articles
WHERE enabled = 1
GROUP BY cat
) md ON rd_articles.cat = md.cat AND rd_articles.date = md.maxdate
) art
LEFT JOIN rd_argument AS arg ON art.cat = arg.id
The innermost query gets the maximum date for each category, then joins it to the rd_articles table to get only those rd_articles rows that have the latest date for each article. That becomes the cat alias, which is then left-joined to the arguments table just like in your original query. You can add the LIMIT 10 at the end if needed; I wasn't sure what to do with that.
Note that if there's a tie for a category's latest date, you'll get more than one row for each category. If a tie could happen you'll need to break the tie somehow, for example by using the description or the ID. Let me know if that's the case and I'll update my answer.
SELECT ART.*, ARG.*
FROM ARTICLE AS ART
INNER JOIN RD_AGRUEMENT AS ARG
ON ARG.ID = ART.ID
WHERE (ID, DATE) IN
(SELECT ID, MAX(DATE) FROM ARTICLE GROUP BY ID)
I have one supertype table where I have to pick 1 subtype table from 2 subtypes a,b. A subtype cannot go with the other one so for me to query I have to check whether if the supertype id is contained on one of the subtypes. I have been doing experiment queries but cannot get it right.
This is what somehow I thought of:
SELECT * from supertypetable INNER JOIN
IF (a.id = given.id) then a ON a.id = supertypetable.id
ELSE b ON b.id = supertetable.id
job Table
________________________________
|job_id| blach2x....
________________________________
| 1 |
| 2 |
| 3 |
________________________________
partime Table
________________________________
|job_id| blach2x....
________________________________
| 2 |
| 3 |
________________________________
fulltime Table
________________________________
|job_id| blach2x....
________________________________
| 1 |
| |
________________________________
I want to join tables that satisfy my given id
This looks a lot like a polymorphic join in rails/activerecord. The way it's implemented there, the 'supertype' table has two fields: subtype_id and subtype_type. The subtype_type table has a string that can be easily turned into the name of the right subtype table; subtype_id has the id of the row in that table. Structuring your tables like this might help.
The next question you have to ask is what exactly are you expecting to see in the results? If you want to see the supertype table plus ALL of the subtype tables, you're probably going to have to join them one at a time, then union them all together. In other words, first join against just one of the subtype tables, then against the next one, etc. If this isn't what you're going for, maybe you could clarify your question further.
If a.id can never equal b.id you could do joing on both tables and then do a UNION and only the table where the id matched would return results:
SELECT * from supertypetable
INNER JOIN
a ON a.id = supertypetable.id
UNION
SELECT * from supertypetable
INNER JOIN
b ON b.id = supertypetable.id
If a.id can equal b.id, then this would not work. But it's an idea
EDITTING PER COMMENTS:
This approach only works if the structures of a and b are identical.
So one simple suggestion might be just:
SELECT * FROM job
left join parttime on parttime.job_id = job.job_id
left join fulltime on fulltime.job_id = job.job_id
where job.job_id = #job_id
And then let your application figure out which of the two joined tables doesn't have NULL data and display that.
If you don't mind inconsistent datasets and just always want the correct returned set regardless (although you're still going to need some kind of application logic since as you said, the structures of parttime and fulltime are different, so how are you going to display/utilize their data conditionally without some kind of inspection? And if you're going to do that inspection, you might as well do it up front, figure out for your given job_id what the subtype is, and then just pick the appropriate query to run there.)
Sorry! Digression!
A stored procedure can do this logic for you (removed all the joins, just an example):
CREATE PROCEDURE getSubTypeDATA (#job_id int)
BEGIN
IF (SELECT EXISTS(SELECT 1 FROM parttime WHERE job_id = #job_id)) = 1
BEGIN
SELECT * from parttime
END
ELSE
BEGIN
SELECT * from fulltime
END
END
Alternatively, if you want a consistent dataset (ideal for application logic), why not put the common columns between fulltime and parttime into a UNION statement, and create hard-coded NULLs for the columns they don't share. For example, if fullTime looked like
EmployeeID, DepartmentID, Salary
and partTime looked like
EmployeeID, DepartmentID, HourlyRate
you could do
SELECT job.*, employeeid, departmentid, salary, null as hourlyrate FROM job inner join fulltime on fulltime.job_id = job.job_id
where job.job_id = ?
union
SELECT job.*, employeeid, departmentid, null as salary, hourlyrate FROM job inner join parttime on parttime.job_id = job.job_id
where job.job_id = ?
If there are hundred different columns, this might be unwieldy. Also, in case this didn't make it obvious, having subtypes with completely different structures but using the same foreign key is a very good clue that you're breaking third normal form.