I need a help about a little confusing sql query. I have 3 tables. Names : article, category and category_article table.
In my article table, there are 4 columns which are :
aid -> article id
py -> Year of article
totalPoint -> point of Article
tc -> number of cited of article
In category table, there are 2 columns which are :
cid -> category id
category -> name of category
Lastly, in my category_article table, there are 2 columns which are :
cid -> category id
aid -> article id related with category
In the below, there are sample table inputs.
Article Table
______________________________
| aid | py | totalPoint | tc |
-------------------------------
| 1 | 2014| 30 | 3 |
-------------------------------
| 2 | 2013| 20 | 2 |
-------------------------------
| 3 | 2014| 50 | 10 |
_______________________________
Category Table
__________________
| cid | category |
-------------------
| 1 | Surgery |
-------------------
| 2 | Enginering|
____________________
Category_Article Table
__________________
| cid | aid |
-------------------
| 1 | 3 |
-------------------
| 2 | 5 |
____________________
My purpose is find this output with only one sql query.
Category name, year, totalArticleNumber, totalPoint(desc), id of article(the most cited article at that year and at that category), citedTime (Top 20)
The example output is :
_______________________________________________________________________________________
Category name | year | totalArticleNumber | totalPoint | id of article(best) | citedTime |
________________________________________________________________________________________
Surgery | 2013 | 182 | 5234 | 312 | 22 |
_________________________________________________________________________________________
Engineering | 2014 | 189 | 5000 | 10 | 32 |
I can do this table with java programing language. Like first do some query and after that do it second query.
But i need to do it in one query.
I try to use JOIN , HAVING , GROUP BY , DISTINCT and anaother some sql stuff but i cannot succeed.
Any help is appreciated.
EDIT
For example if there are two article with same time cited value, they are shown together.
Like in first two column, the article 419 and 385 same time cited value so they both in table.
There are two options for me.
First way -> Show only one article and limit 20. So in this way i can see 20 different category-year combination.
Second way -> Show the same value timecited articles but i have to see 20 different category-year combination.
This is a little tricky, because you're trying to do a lot of different things here. I would simply break them down and put them back together. First, if you want to JOIN all the tables together, you can use the following syntax:
SELECT *
FROM article a
JOIN category_article ca ON ca.aid = a.aid
JOIN category c ON c.cid = ca.cid;
Now, this will only show articles that are assigned to a category. In other words, if an article doesn't exist in the category_article table, or a category doesn't exist in the category_article table, it won't appear here.
If you want to get the total number of articles for a category per year, you can use the COUNT(*) aggregate function, and the SUM() function to get the total points for that category, and then group by category and year to get the amounts for that group:
SELECT c.category, a.py, COUNT(*) AS numArticles, SUM(totalPoint) AS totalPoints
FROM article a
JOIN category_article ca ON ca.aid = a.aid
JOIN category c ON c.cid = ca.cid
GROUP BY c.cid, a.py;
Next, getting the best article will have to be done using a subquery. I recommend focusing just on that subquery for a moment. You can write one query to get the MAX tc for each category and year, and then join that with your table to get all the matching conditions, like this:
SELECT c.category, a.*
FROM category c
JOIN category_article ca ON c.cid = ca.cid
JOIN article a ON a.aid = ca.aid
JOIN(
SELECT c.cid, a.py, MAX(a.tc) AS maxCited
FROM category c
JOIN category_article ca ON ca.cid = c.cid
JOIN article a ON a.aid = ca.aid
GROUP BY c.cid, a.py) temp ON temp.cid = c.cid AND temp.py = a.py AND temp.maxCited = a.tc;
Once you have that, you can JOIN that subquery with the query above to show the best article information alongside the other group information.
SELECT temp1.category, temp1.py, temp1.numArticles, temp1.totalPoints, temp2.aid AS bestArticle, temp2.tc AS citedTime
FROM(
SELECT c.category, a.py, COUNT(*) AS numArticles, SUM(totalPoint) AS totalPoints
FROM article a
JOIN category_article ca ON ca.aid = a.aid
JOIN category c ON c.cid = ca.cid
GROUP BY c.cid, a.py) temp1
JOIN(
SELECT c.category, a.*
FROM category c
JOIN category_article ca ON c.cid = ca.cid
JOIN article a ON a.aid = ca.aid
JOIN(
SELECT c.cid, a.py, MAX(a.tc) AS maxCited
FROM category c
JOIN category_article ca ON ca.cid = c.cid
JOIN article a ON a.aid = ca.aid
GROUP BY c.cid, a.py) temp ON temp.cid = c.cid AND temp.py = a.py AND temp.maxCited = a.tc) temp2
ON temp1.category = temp2.category AND temp1.py = temp2.py;
Here is an SQL Fiddle example. I am going to play around with how to use fewer JOINS, but for right now it should give you what you want and since your tables are indexed it shouldn't run too slowly.
Related
I have a 2 tables:
People table:
id | name | date
1 | Mika | 2013
2 | Rose | 2015
Work table:
id | user_id | work_name | registers
1 | 1 | rugby | 10
2 | 1 | golf | 3
I use this query to join tables:
SELECT * FROM work INNER JOIN people ON work.user_id = people.id WHERE work_name= :work_name
This work it, but I get duplicate column ID and in php when I try to print the camp id, this show the last column id..
I try with this but dont work it:
SELECT *, id AS 'work_id'
FROM work
INNER JOIN people ON work.user_id = people.id
WHERE work_name= :work_name
That is because you are using * in the SELECT that mean will bring all the fields from boths tables instead you can use
SELECT work.id as work_id,
work.name as work_name,
work.date as work_date,
people.id as people_id,
people.name as people_name,
people.date as people_date
Try to manually list all of columns you need to display, for instance
SELECT w.user_id, w.work_name, w.registers, p.name, p.date FROM work as w INNER JOIN people as p ON work.user_id = people.id WHERE work_name= :work_name
My case looks simple but i'm messing around with this..
I have 4 tables: User, Macros, Categories, and another one that relate users with categories. One Macro have many Categories.
What i need, is a query that based on the Macro, get the users and the Categories where user is NOT IN.
Example: I have a macro named VEICULES, with categories CAR,TRUCK and Motorcycle. User José is on category CAR and User Julio on category CAR and TRUCK, so my query should return:
José | TRUCK,Motorcycle
Julio | Motorcycle
Tables:
prd_users
id | name | Email
---------------------------
1 | José | jose#email.com
2 | Júlio | julio#email.com
3 | André | andre#email.com
cat_macros
macro_id | macro_name
-----------------------
1 | Veicules |
cat_categories
category_id | category_name | macro_id
---------------------------------------
1 | Cars | 1
2 | Trucks | 1
3 | Motorcycles | 1
prd_tr_rabbit_catg
id | category_id | tasker_user_id
---------------------------------------
1 | 1 | 1
2 | 1 | 2
3 | 2 | 2
I'm stucked on just getting the categories where the user already is ..
SELECT prd_users.id, prd_users.name,
prd_users.email,cat_macros.macro_name as macro,
GROUP_CONCAT(cat_categories.category_name SEPARATOR ', ') as in_categories
FROM prd_users
INNER JOIN prd_tr_rabbit_catg ON prd_tr_rabbit_catg.tasker_user_id = prd_users.id
INNER JOIN cat_categories ON cat_categories.category_id = prd_tr_rabbit_catg.category_id
INNER JOIN cat_macros ON cat_macros.macro_id = cat_categories.macro_id
WHERE cat_macros.macro_id = '45'
GROUP BY prd_users.id;
To solve this problem it's necessary to create a list of all users joined with all categories for the given macro category. This can be done with a CROSS JOIN:
SELECT *
FROM prd_users u
CROSS JOIN (SELECT m.macro_id, m.macro_name, c.category_name, c.category_id
FROM cat_macros m
JOIN cat_categories c ON c.macro_id = m.macro_id) c
This can then be LEFT JOINed to the prd_tr_rabbit_catg table and by selecting those rows where there is no matching entry in the prd_tr_rabbit_catg table, we can find the users who don't have an entry for the given category:
SELECT c.macro_name, u.id AS user_id, u.name, u.Email, GROUP_CONCAT(c.category_name) AS missing_cats
FROM prd_users u
CROSS JOIN (SELECT m.macro_id, m.macro_name, c.category_name, c.category_id
FROM cat_macros m
JOIN cat_categories c ON c.macro_id = m.macro_id) c
LEFT JOIN prd_tr_rabbit_catg x ON x.tasker_user_id = u.id AND x.category_id = c.category_id
WHERE x.id IS NULL
AND c.macro_id = 1
GROUP BY c.macro_name, u.id
For your sample data, this gives:
macro_name user_id name Email missing_cats
Veicules 1 José jose#email.com Motorcycles,Trucks
Veicules 2 Júlio julio#email.com Motorcycles
Veicules 3 André andre#email.com Cars,Motorcycles,Trucks
Update
To exclude users who don't have any of the categories, add a HAVING clause:
HAVING COUNT(*) < (SELECT COUNT(*) FROM cat_categories WHERE macro_id = 1)
Demo on SQLFiddle
I want to select all the planes that aren't belong to a certain company. I have three tables in this case: Planes, Companies, and CompanyPlanes.
Here is my query:
SELECT *
FROM planes p,
companyplanes cp,
companies c
WHERE c.id = ?
AND cp.idCompany != c.id
AND (cp.idPlane = p.id OR p.id NOT IN (SELECT idPlane FROM companyplanes))
ORDER BY name ASC
But this query returned nothing! what is the wrong here?
example:
| Plane |
---------
id | name
---------
1 | p1
2 | p2
3 | p3
|Company|
---------
id | name
---------
1 | c1
2 | c2
| companyPlanes |
------------------------
id | idCompany | idPlane
------------------------
1 | 1 | 1
2 | 1 | 2
3 | 2 | 2
if I want to get the planes that are not belong to the company c2 the result should be: p1, p3.
Update Answer
We can get the result in following way
Get all planes of the unexpected company
SELECT idplane from CompanyPlanes
WHERE idCompany = ?
Get all planes without those planes of the unexpected company
SELECT * FROM Planes
WHERE id NOT IN
(
SELECT idplane from CompanyPlanes
WHERE idCompany = ?
)
You don't need to join with Company table as you already get idCompany from CompanyPlanes table.
The inner join requires that the query return rows from planes which have a corresponding row in companyplanes but the subselect excludes any rows which have corresponding records in companyplanes.
Assuming that you want the records from planes which don't have a record in companyplanes, then why are you also selecting from companies?
Select p.*
From planes p
Left join
Companyplanes do
On p.id=cp.idplane
Where cp.idplane is null;
If I understand your question the right way, this is what you might be looking for..
select p.*
from planes p
join companyplanes cp on cp.idPlane=p.id
join companies c on c.id=cp.idCompany
where c.id != ?
To simplify my problem: Let's say I have 3 tables.
Rooms People Things
-------- -------- --------
id| name id | name | fk_rooms id | name | fk_rooms
----------- --------------------- ---------------------
1 | kitchen 1 | John | 1 1 | TV | 2
2 | bedroom 2 | Mary | 2 2 | bed | 2
3 | Andy | 1 3 | sink | 1
4 | Laura| 1
Now I'm doing something like:
SELECT r.name AS room_name, p.name AS name, t.name AS thing FROM Rooms r
LEFT JOIN People p ON p.fk_rooms = r.id
LEFT JOIN Things t ON t.fk_rooms = r.id
which in my case works perfectly except for a few that have many to one relationship with the "Rooms" table. So instead of new rows in the result set holding the different names for "People" and "Things" in relation to the "Rooms" table, I would like to receive only two rows:
1. kitchen, John, Andy, Laura, sink
2. bedroom, Mary, TV, bed
A GROUP BY on r.id will only select one row from each table. Any help is highly appreciated!
Here it is what you're looking for:
SELECT r.name AS room_name,
GROUP_CONCAT(p.name separator ',') AS people_name,
GROUP_CONCAT(t.name separator ',') AS things
FROM Rooms r
LEFT JOIN People p ON p.fk_rooms = r.id
LEFT JOIN Things t ON t.fk_rooms = r.id
GROUP BY r.id
Yes, you need the group by clause, and you need to use the GROUP_CONCAT function. You should group your results by People.fk_rooms and Thing.fk_rooms.
Maybe you could use two different queries: The first will result the join of Rooms and People, grouped by fk_rooms, having selected three columns, they are being RoomsID, RoomName, People, while the second will result the join of Rooms and Thing, grouped by fk_rooms, having selected three columns, they are being RoomID, RoomName, Things. In your query you name these selections as t1 and t2 and join t1 and t2 by RoomsID, select t1.RoomName, t1.People, t2.Things.
Good luck.
I habe a main table that i select from and a table with subelements that i select from in a join. Example:
person skill person_to_skill
id | name id | skill id | p_id | s_id
------------ ------------ ----------------
1 | jim 1 | sewing 1 | 1 | 2
2 | peter 2 | cooking 2 | 2 | 1
3 | susan 3 | singing 3 | 2 | 3
4 | kevin 4 | 3 | 1
5 | 3 | 2
6 | 4 | 3
So now we see, sim has only one skill, peter has two and so forth.
Now if i select from person, koin skill and then also join person_to_skill, but i only want two persons. How do i manage to do so without grouping and thereby not getting all the skills?
Shortly: I want to select two persons from "person" with all their skills.
I tried just using LIMIT but that limits the result rows, not the persons.
If i use GROUP BY i only get one skill per person.
Is this possible without a subselect?
Any ideas anyone?
My Approach so far, changed to work with the example, looks like this:
SELECT p.id,p.name,s.skill
FROM person AS p
LEFT JOIN person_to_skill psk ON (psk.p_id = p.id)
LEFT JOIN skill s ON (s.id = psk.s_id)
ORDER BY p.name
LIMIT 0,2
Limit number of persons at very beginning in subquery then join to them other tables as you've already done:
SELECT p.id,p.name,s.skill
FROM (select * from person ORDER BY name LIMIT 0,2) AS p
LEFT JOIN person_to_skill psk ON (psk.p_id = p.id)
LEFT JOIN skill s ON (s.id = psk.s_id)
Added after comment:
If you really can't use subqueries you can do it using two queries. Firstly select users ids:
select id from person ORDER BY name LIMIT 0,2
and then use those ids in next query:
SELECT p.id,p.name,s.skill
FROM person p
LEFT JOIN person_to_skill psk ON (psk.p_id = p.id)
LEFT JOIN skill s ON (s.id = psk.s_id)
WHERE p.id IN (ids from previous query)
You can do something like
SELECT p.id, p.name, group_concat(s.skill separator ',')
and then group by person and limit the number of rows.