Is it possible to return the row number as an ID?
In my view where i merge my tables I can't make a unique ID because I need to create left
joins to store every value:
CREATE VIEW
sphinx
AS
SELECT
company.company_id,
company.company_name,
company.company_keywords,
company_address.address_street,
company_address.address_number,
company_address.address_telephone,
company_address.address_fax,
company_address.address_email,
company_address.address_website,
company_address.address_authorized,
company_contact.contact_name,
company_contact.contact_surname,
company_contact.contact_telephone,
company_contact.contact_mobilephone,
company_contact.contact_fax,
company_contact.contact_email,
company_page.page_content,
company_page.page_description,
company_page.page_keywords
FROM company
LEFT JOIN company_address ON company.company_id = company_address.company_id
LEFT JOIN company_page ON company.company_id = company_page.company_id
LEFT JOIN company_contact ON company.company_id = company_contact.company_id
When I receive the row number i can just do SELECT * FROM sphinx LIMIT 1 OFFSET rowNumber
to get the information of that row.
Thanks for your time.
You can do it the following way
SELECT * FROM
(
SELECT
(#rownum:=#rownum + 1) as rownumber, q.*
FROM (
SELECT
* /*shortened for readability*/
FROM company
LEFT JOIN company_address ON company.company_id = company_address.company_id
LEFT JOIN company_page ON company.company_id = company_page.company_id
LEFT JOIN company_contact ON company.company_id = company_contact.company_id
, (select #rownum:=0) r
) q
) asdf
WHERE rownumber BETWEEN 2 AND 3 /*or whatever*/
EDIT: To explain a bit more:
This one
, (select #rownum:=0) r
declares and initializes the variable which will serve as our rownumber.
If you would include #rownum in the SELECT list of your inner most query, you would get a column which has the value 0 in each row.
So you make this inner query a subquery and just add 1 to the #rownum variable.
With only this
SELECT
(#rownum:=#rownum + 1) as rownumber, q.*
FROM (
SELECT
* /*shortened for readability*/
FROM company
LEFT JOIN company_address ON company.company_id = company_address.company_id
LEFT JOIN company_page ON company.company_id = company_page.company_id
LEFT JOIN company_contact ON company.company_id = company_contact.company_id
, (select #rownum:=0) r
) q
you would already have a rownumber. Since you can't refer to the column alias rownumber in the WHERE clause, you would have to write something like
WHERE (#rownum:=#rownum + 1) BETWEEN 2 AND 3 /*or whatever*/
but this would lead to false results, because the #rownum variable would get incremented again and your rownumber would be like
2
4
6
and so on. Therefore you have to put it in another subquery and then you can "limit" your query via the WHERE clause.
Any more questions?
Related
I have 3 column here in tblItem
purchase
stock
calculate
and this 3 column have to join with tblMeasurement
name
I have tried this, but when I display 3 columns show the same all.
SQL
$sql = "SELECT A.itemID, A.categoryID, A.purchaseMeasurementID, A.stockMeasurementID, A.calculationMeasurementID, A.itemName, B.itemCategoryName, C.measurementName
FROM tblPurItem A
LEFT JOIN tblPurItemCategory B
ON A.categoryID = B.itemcategoryID
LEFT JOIN tblPurMeasurement C
ON A.purchaseMeasurementID= C.measurementID";
As you see my sql above, it has join only for purchaseMeasurementID. How do I join stockMeasurementID and calculationMeasurementID based on also tblPurMeasurement ? Is there any missing here?
Try using this subquery instead.
SELECT A.itemID, A.categoryID, A.purchaseMeasurementID, A.stockMeasurementID, A.calculationMeasurementID, A.itemName
, (select B.itemCategoryName from tblPurItemCategory B where A.categoryID = B.itemcategoryID limit 1) as itemCategoryName
, (select C.measurementName from tblPurMeasurement C where A.purchaseMeasurementID= C.measurementID limit 1) as purchasemeasurementName
, (select C.measurementName from tblPurMeasurement C where A.stockMeasurementID= C.measurementID limit 1) as stockmeasurementName
, (select C.measurementName from tblPurMeasurement C where A.calculationMeasurementID= C.measurementID limit 1) as calculationmeasurementName
FROM tblPurItem A
Trying to select one row at a time from this query (for example where the rank = 1). However, it doesn't work because "unknown column 'rank'. if i change it to "WHERE id = 1" then it works, but for some reason it doesn't know what rank is even tho it is set
SET #rownum=0;
SELECT #rownum := #rownum + 1 AS rank, id, client, date, time, pickupCity, pickupState
FROM (
SELECT r.id, CONCAT(c.fname, ' ', c.lname) as client, r.date,
LOWER(TIME_FORMAT(r.time, '%l:%i%p')) as time, r.pickupCity, r.pickupState
FROM request r
INNER JOIN client c ON r.client = c.id
INNER JOIN pickup p ON r.id = p.request
INNER JOIN driver d ON d.id = p.driver
WHERE date = '2018-04-18' AND d.id = 1
GROUP BY time
) AS tab
HAVING rank = 1;
In MySQL, you can do this using HAVING:
SELECT #rownum := #rownum + 1 AS rank, id, client, date, time, pickupCity, pickupState
FROM (SELECT r.id, CONCAT(c.fname, ' ', c.lname) as client, r.date,
LOWER(TIME_FORMAT(r.time, '%l:%i%p')) as time, r.pickupCity,
r.pickupState
FROM request r JOIN
client c
ON r.client = c.id JOIN
driver d
ON ?
pickup p
ON d.id = p.driver
WHERE date = '2018-04-18' AND d.id = 1
GROUP BY time
) t CROSS JOIN
(SELECT #rank := 0) params
HAVING rank = 1;
Notes:
The ?? is for the missing JOIN conditions.
I went through the effort of fixing your JOIN syntax. You should go through the effort of learning proper, explicit, standard JOIN syntax.
You can set #rank in the query; you don't need a separate statement.
The GROUP BY makes no sense, because you have many unaggregated columns in the SELECT.
If I had to speculate, the root cause of your problems is the missing JOIN condition, and you've put a lot of effort into working around that.
I have 3 tables (stars mach the ids from the table before):
product:
prod_id* prod_name prod_a_id prod_b_id prod_user
keywords:
key_id** key_word key_prod* kay_country
data:
id dat_id** dat_date dat_rank_a dat_traffic_a dat_rank_b dat_traffic_b
I want to run a query (in a function that gets a $key_id) that outputs all these columns but only for the last 2 dates(dat_date) from the 'data' table for the key_id inserted - so that for every key_word - I have the two last dat_dates + all the other variables included in my SQL query:
So... This is what I have so far. and I don't know how to get only the MAX vars. I tried using "max(dat_date)" in different ways that didn't work.
SELECT prod_id, prod_name, prod_a_id, prod_b_id, key_id, key_word, kay_country, dat_date, dat_rank_a, dat_rank_b, dat_traffic_a, dat_traffic_b
FROM keywords
INNER JOIN data
ON keywords.key_id = data.dat_id
INNER JOIN prods
ON keywords.key_prod = prods.prod_id
Is there a possability to do this with only one query?
EDIT (FOR IgorM):
public function newnew() {
$query = $this->db->query('WITH CTE AS
(
SELECT *,
ROW_NUMBER() OVER (PARTITION BY dat_id ORDER BY dat_date ASC) AS
RowNo FROM data
)
SELECT *
FROM CTE
INNER JOIN keywords
ON keywords.key_id = CTE.dat_id
INNER JOIN prods
ON keywords.key_prod = prods.prod_id
WHERE RowNo < 3
');
$result = $query->result();
return $result;
}
This is the error on the output:
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'CTE AS ( SELECT *, ROW_NUMBER() OVER (' at line 1
WITH CTE AS ( SELECT *, ROW_NUMBER() OVER (PARTITION BY dat_id ORDER BY dat_date ASC) AS RowNo FROM data ) SELECT * FROM CTE INNER JOIN keywords ON keywords.key_id = CTE.dat_id INNER JOIN prods ON keywords.key_prod = prods.prod_id WHERE RowNo < 3
For SQL
WITH CTE AS
(
SELECT *,
ROW_NUMBER() OVER (PARTITION BY dat_id ORDER BY dat_date ASC) AS
RowNo FROM data
)
SELECT *
FROM CTE
INNER JOIN keywords
ON keywords.key_id = CTE.dat_id
INNER JOIN prods
ON keywords.key_prod = prods.prod_id
WHERE RowNo < 3
For MySQL (not tested)
SET #row_number:=0;
SET #dat_id = '';
SELECT *,
#row_number:=CASE WHEN #dat_id=dat_id THEN #row_number+1 ELSE 1 END AS row_number,
#dat_id:=dat_id AS dat_id_row_count
FROM data d
INNER JOIN keywords
ON keywords.key_id = d.dat_id
INNER JOIN prods
ON keywords.key_prod = prods.prod_id
WHERE d.row_number < 3
The other approach is self joining. I don't want to take credit for somebody else's job, so please look on the following example:
ROW_NUMBER() in MySQL
Look for the following there:
SELECT a.i, a.j, (
SELECT count(*) from test b where a.j >= b.j AND a.i = b.i
) AS row_number FROM test a
If you only want to do this for one key_id at a time (as alluded to in your responses to other answers) and only want two rows, you can just do:
SELECT p.prod_id,
p.prod_name,
p.prod_a_id,
p.prod_b_id,
k.key_id,
k.key_word,
k.key_country,
d.dat_date,
d.dat_rank_a,
d.dat_rank_b,
d.dat_traffic_a,
d.dat_traffic_b
FROM keywords k
JOIN data d
ON k.key_id = d.dat_id
JOIN prods p
ON k.key_prod = p.prod_id
WHERE k.key_id = :key_id /* Bind in key id */
ORDER BY d.dat_date DESC
LIMIT 2;
Whether you want this depends on your data structure and whether there is more than one key/prod combination per date.
Another option limiting just the data rows would be:
SELECT p.prod_id,
p.prod_name,
p.prod_a_id,
p.prod_b_id,
k.key_id,
k.key_word,
k.key_country,
d.dat_date,
d.dat_rank_a,
d.dat_rank_b,
d.dat_traffic_a,
d.dat_traffic_b
FROM keywords k
JOIN (
SELECT dat_id,
dat_date,
dat_rank_a,
dat_rank_b,
dat_traffic_a,
dat_traffic_b
FROM data
WHERE dat_id = :key_id /* Bind in key id */
ORDER BY dat_date DESC
LIMIT 2
) d
ON k.key_id = d.dat_id
JOIN prods p
ON k.key_prod = p.prod_id;
If you want some kind of grouped results for all the keywords, you'll need to look at the other answers.
I think a window function is the best way to go. without knowing a lot about the structure of the data you can try a subquery of what you are trying to restrict and then joining that to the rest of the data. Then within the where clause restrict the rows you pull back.
select p.prod_id, p.prod_name, p.prod_a_id, p.prod_b_id,
t.key_id, t.key_word, t.kay_country, t.dat_date,
t.dat_rank_a, t.dat_rank_b, t.dat_traffic_a, t.dat_traffic_b
from
(
select
k.key_id, k.key_word, k.kay_country, d.dat_date, d.dat_rank_a,
d.dat_rank_b, d.dat_traffic_a, d.dat_traffic_b,
row_number() over (partition by dat_id order by dat_date desc) as 'RowNum'
from keywords as k
inner join
data as d on k.key_id = d.dat_id
) as t
inner join
prods as p on t.key_prod = p.prod_id
where tmp.RowNum <=2
This is a "groupwise max" problem. Reference. CTE does not exist in MySQL.
I'm not totally clear on how your tables are linked, but here is a stab:
SELECT
*
FROM
( SELECT #prev := '', #n := 0 ) init
JOIN
( SELECT #n := if(k.key_id != #prev, 1, #n + 1) AS n,
#prev := k.key_id,
d.*, k.*, p.*
FROM data d
JOIN keywords k ON k.key_id = d.dat_id
JOIN prods p ON k.key_prod = p.prod_id
ORDER BY
k.key_id ASC,
d.dat_date ASC
) x
WHERE n <= 2
ORDER BY k.key_id, n;
you can use this query:
select prod_id, prod_name, prod_a_id, prod_b_id, key_id, key_word,
kay_country, dat_date, dat_rank_a, dat_rank_b, dat_traffic_a, dat_traffic_b
from keywords where dat_date in (
SELECT MAX(dat_date) FROM keywords temp_1
where temp_1.prod_id = keywords.prod_id
union all
SELECT MAX(dat_date) FROM keywords
WHERE dat_date NOT IN (SELECT MAX(dat_date ) FROM keywords temp_2 where
temp_2.prod_id = keywords.prod_id)
)
I've got the following sqlfiddle http://sqlfiddle.com/#!2/324628/1
I need to create a query that returns the id and the position (ranking) of each student within his class; the position is sorted in descending order according to the value of their academic average stored inside the academic_averages table.
(e.g. the first from class 1, second from the class 1, and so on... the first from class 2, the second from class 2...)
Here's the query:
SELECT students.id,
(SELECT x.position
FROM (
SELECT t.student_id, t.value, #rownum := #rownum + 1 AS position
FROM (
SELECT aa.student_id, aa.value
FROM academic_averages AS aa
INNER JOIN students AS s ON s.id = aa.student_id
INNER JOIN classes_students AS cs ON cs.student_id = s.id
INNER JOIN classes_academic_years AS cas ON cas.id = cs.class_academic_year_id
INNER JOIN classes_academic_years as cas2 on cas2.class_id = cas.class_id
INNER JOIN classes_students as cs2 on cs2.class_academic_year_id = cas2.id
INNER JOIN students as s2 on s2.id = cs2.student_id
WHERE s2.id = 243
AND cas.academic_year_id = 4
AND aa.academic_year_id = 4
GROUP BY aa.student_id
ORDER BY abs(aa.value) DESC
) t
JOIN (SELECT #rownum := 0) r
) AS x WHERE x.student_id = students.id ) AS ranking_by_class
FROM students
However, since it contains a subquery, I cannot change the WHERE from the inner most query to s2.id = students.id because it throws an error (unknown column).
I've tried using an INNER JOIN instead of subqueries, but no luck so far.
Does anyone have a solution?
Thanks
LE: Performance wise the query must be optimized
LE: Here's the structure of the tables:
academic_averages:
id
student_id
value
academic_year_id
classes_academic_years:
id
class_id
name
grade
academic_year_id
classes_students:
id
class_academic_year_id
student_id
classes:
id
school_id
students:
id
The desired output should be student_id, position.
There seems to be some issues with sql fiddle, meanwhile here's the schema: http://snippi.com/s/db8za8k
SELECT x.id
, x.position
, x.academic_average
FROM (SELECT
s.id
, #rownum := #rownum + 1 position
, av.value academic_average
FROM students s
JOIN classes_students cs ON s.id = cs.student_id
JOIN classes_academic_years cay ON cay.id = cs.class_academic_year_id
JOIN academic_averages av ON av.student_id = s.id
WHERE cay.academic_year_id = 4 -- change these two parameters in
AND av.academic_year_id = 4 -- the subquery for different years
ORDER BY av.value DESC) x,
(SELECT #rownum := 0) y
ORDER BY academic_average DESC
I think the above query should work for you.
I've made the assumption that the academic ranking position is determined in a descending order by academic average.
I don't have access to your dataset, so I've added three extra lines, two to select the students' academic average and one to order the result in descending order according to the academic average. This should help you verify that it works as intended. If you run the query, and it works, it should display records with position starting at 1 and incrementing by 1.
In production I would omit these fragments in order to get the result set you specify:
1. , x.academic_average
2. , av.value academic_average
3. ORDER BY academic_average DESC
Edit following elaboration in comments by OP (students' ranking required by class)
This query should give you students' positions by class. If you want to get rid of some fields, you can wrap the SELECT in another SELECT, or ignore the columns once the dataset is extracted to another language.
SELECT
x.student_id
, x.cay_class_id
, x.academic_average
, if(#classid = x.cay_class_id, #rownum := #rownum + 1, #rownum := 1) position
, #classid := x.cay_class_id
FROM (SELECT
s.id student_id
, cay.class_id cay_class_id
, av.value academic_average
FROM students s
JOIN classes_students cs ON s.id = cs.student_id
JOIN classes_academic_years cay ON cay.id = cs.class_academic_year_id
JOIN academic_averages av ON av.student_id = s.id
WHERE cay.academic_year_id = 4 -- change these two parameters in
AND av.academic_year_id = 4 -- the subquery for different years
ORDER BY cay.class_id, av.value DESC) x,
(SELECT #classid := 0, #rownum := 0) y
I Have a table of random names, simply 3 columns(id, firstname, lastname).
I am trying to have SQL update an entire table of names with a random selected name from this table.
Here is the SQL i am using. It seems to work for some of the records, but it doesn't always do all of them, sometime leaving NULLS, or not always updating all rows. Sometime it runs affecting 9 rows, other times it says affected 11 rows... thoughts?
update TestNames,rndnames
set TestNames.fname = rndnames.FirstName,TestNames.lname=rndnames.LastName
where rndnames.ID=floor(1+(rand()*600))
answer:
update
TestNames left join
(select ID,
floor(1+(rand()*600)) as rndid
from TestNames) as TN on TN.ID=TestNames.id
left join rndnames on TN.rndid=rndnames.id
set TestNames.fname=rndnames.FirstName,TestNames.lname=rndnames.LastName
Here is the query:
update TestNames t cross join
rndnames r
set t.fname = r.FirstName,
t.lname = r.LastName
where r.ID = floor(1+(rand()*600));
It only updates a row in testnames when the random id chosen by the expression matches an id in the table. Are the id values in rndnames all populated?
If your table is not very big and it has an id, here is another approach:
update TestName t join
(select t.*,
(select id from rndnames order by rand() limit 1) as rndid
from testname t
) tr
on t.id = tr.id join
rndnames r
on t.rndid = r.id
set t.fname = r.FirstName,
t.lname = r.LastName;
EDIT:
I think this will also work:
update TestNames t cross join
rndnames r
set t.fname = r.FirstName,
t.lname = r.LastName
where r.ID = (select id
from rndnames
order by rand()
limit 1
);
update
TestNames left join
(select ID,
floor(1+(rand()*600)) as rndid
from TestNames) as TN on TN.ID=TestNames.id
left join rndnames on TN.rndid=rndnames.id
set TestNames.fname=rndnames.FirstName,TestNames.lname=rndnames.LastName