SQLAlchemy ORM query joined to aggregate subquery - mysql

I've got an SQL query:
SELECT d.device_name, d.device_category
FROM devices d
JOIN (
SELECT device_category, COUNT(*) AS cnt FROM devices GROUP BY device_category
) c ON c.device_category = d.device_category
ORDER BY c.cnt DESC
And now I need to write it (at least, I would like to) in the SQLAlchemy ORM format.
My attempt is this:
sub_dquery = db.session.query(Devices.device_category, func.count('*')).group_by(Devices.device_category).subquery()
long_dquery = db.session.query(Devices.device_name, Devices.device_category).join(sub_dquery).desc()
I don't know how to implement this part:
c ON c.device_category = d.device_category

This looks pretty close:
with Session(engine) as session:
sub_dquery = (
session.query(Devices.device_category, func.count().label("cnt"))
.group_by(Devices.device_category)
.subquery("c")
)
long_dquery = (
session.query(Devices.device_name, Devices.device_category)
.join(sub_dquery, sub_dquery.c.device_category == Devices.device_category)
.order_by(sub_dquery.c.cnt.desc())
)
print(long_dquery)
"""
SELECT devices.device_name AS devices_device_name, devices.device_category AS devices_device_category
FROM devices
JOIN (
SELECT devices.device_category AS device_category, count(*) AS cnt
FROM devices GROUP BY devices.device_category
) AS c ON c.device_category = devices.device_category
ORDER BY c.cnt DESC
"""

Related

Subquery and Filter Eloquent Laravel using Yajra Datatables

I want to view bonus recapitulation from transaction and i try write code in phpmyadmin like
SELECT
tbl2.name, b.nama_bonus,
SUM(
CASE
WHEN b.nama_bonus REGEXP 'saldo' THEN tbl2.jumlah_transaksi * b.nominal_bonus
WHEN b.nama_bonus REGEXP 'bintang' AND tbl2.nama_kategori REGEXP 'transfer' THEN FLOOR(tbl2.total_nominal_transaksi / b.keterangan_bonus)
ELSE FLOOR(tbl2.total_nominal_transaksi / b.keterangan_bonus)
END
) AS bonus_member
FROM
(
SELECT tbl1.*, u.name, pc.nama_kategori, COUNT(tbl1.nominal_transaksi) AS jumlah_transaksi, SUM(tbl1.nominal_transaksi) AS total_nominal_transaksi FROM
(
SELECT no_invoice, product_category_id, nominal_transaksi, user_id, status_transaksi_id FROM transactions
WHERE status_transaksi_id = 1
) AS tbl1
JOIN users u ON u.id = tbl1.user_id
JOIN product_categories pc ON pc.id = tbl1.product_category_id
GROUP BY u.id, pc.id
) AS tbl2
JOIN bonus b ON b.product_category_id = tbl2.product_category_id
GROUP BY b.nama_bonus, tbl2.name
ORDER BY tbl2.name
And the result is result query image
how to implement mysql code with eloquent using yajra datatables and also filter the data?

Optimize Query with JOINS and Subqueries

I want to speed up one of my slower queries.
The problem is that I can't access the outer colum value within a subquery.
What I have:
SELECT r.id AS room_id, r.room_name, coalesce(d.score,0) AS total_messages, d.latest
FROM cf_rooms_time_frames tf
INNER JOIN cf_rooms r on r.id = tf.room_id
INNER JOIN(
SELECT cf.room_id, count(*) as score, max(cf.id) as latest
FROM cf_rooms_messages cf
WHERE EXISTS(
SELECT NULL FROM cf_rooms_time_frames tf
WHERE tf.start <= cf.id AND ( tf.end IS NULL OR tf.end >= cf.id )
AND tf.room_id = cf.room_id AND tf.uid = 8
)
GROUP BY cf.room_id
ORDER BY latest
DESC ) d on d.room_id = r.id
WHERE tf.uid = 8
ORDER BY coalesce(latest, score) DESC LIMIT 0, 20
What I want:
SELECT r.id AS room_id, r.room_name, coalesce(d.score,0) AS total_messages, d.latest
FROM cf_rooms_time_frames tf
INNER JOIN cf_rooms r on r.id = tf.room_id
INNER JOIN(
SELECT cf.room_id, count(*) as score, max(cf.id) as latest
FROM cf_rooms_messages cf
/* line added here */
WHERE cf.room_id = tf.room_id
/* */
AND EXISTS(
SELECT NULL FROM cf_rooms_time_frames tf
WHERE tf.start <= cf.id AND ( tf.end IS NULL OR tf.end >= cf.id )
AND tf.room_id = cf.room_id AND tf.uid = 8
)
GROUP BY cf.room_id
ORDER BY latest
DESC ) d on d.room_id = r.id
WHERE tf.uid = 8
ORDER BY coalesce(latest, score) DESC LIMIT 0, 20
I think the markup explains what the query does.
It searches for "chatrooms" for a given user and orders them by the last message, gets the number of total message which ids are in a given range ( timeframes ), and the last message id.
I don't know why, but the first query returns all rows within the chatmessage table ( cf ) if I can trust EXPLAIN. It delivers the correct results but is kind of slow on a huge table.
I tested the second one with a "hardcoded" room_id and this one was very fast and doesn't "touched" the whole table.

SQL: How to get cells by 2 last dates from 3 different tables?

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)
)

Join between sub-queries in SQLAlchemy

In relation to the answer I accepted for this post, SQL Group By and Limit issue, I need to figure out how to create that query using SQLAlchemy. For reference, the query I need to run is:
SELECT t.id, t.creation_time, c.id, c.creation_time
FROM (SELECT id, creation_time
FROM thread
ORDER BY creation_time DESC
LIMIT 5
) t
LEFT OUTER JOIN comment c ON c.thread_id = t.id
WHERE 3 >= (SELECT COUNT(1)
FROM comment c2
WHERE c.thread_id = c2.thread_id
AND c.creation_time <= c2.creation_time
)
I have the first half of the query, but I am struggling with the syntax for the WHERE clause and how to combine it with the JOIN. Any one have any suggestions?
Thanks!
EDIT: First attempt seems to mess up around the .filter() call:
c = aliased(Comment)
c2 = aliased(Comment)
subq = db.session.query(Thread.id).filter_by(topic_id=122098).order_by(Thread.creation_time.desc()).limit(2).offset(2).subquery('t')
subq2 = db.session.query(func.count(1).label("count")).filter(c.id==c2.id).subquery('z')
q = db.session.query(subq.c.id, c.id).outerjoin(c, c.thread_id==subq.c.id).filter(3 >= subq2.c.count)
this generates the following SQL:
SELECT t.id AS t_id, comment_1.id AS comment_1_id
FROM (SELECT count(1) AS count
FROM comment AS comment_1, comment AS comment_2
WHERE comment_1.id = comment_2.id) AS z, (SELECT thread.id AS id
FROM thread
WHERE thread.topic_id = :topic_id ORDER BY thread.creation_time DESC
LIMIT 2 OFFSET 2) AS t LEFT OUTER JOIN comment AS comment_1 ON comment_1.thread_id = t.id
WHERE z.count <= 3
Notice the sub-query ordering is incorrect, and subq2 somehow is selecting from comment twice. Manually fixing that gives the right results, I am just unsure of how to get SQLAlchemy to get it right.
Try this:
c = db.aliased(Comment, name='c')
c2 = db.aliased(Comment, name='c2')
sq = (db.session
.query(Thread.id, Thread.creation_time)
.order_by(Thread.creation_time.desc())
.limit(5)
).subquery(name='t')
sq2 = (
db.session.query(db.func.count(1))
.select_from(c2)
.filter(c.thread_id == c2.thread_id)
.filter(c.creation_time <= c2.creation_time)
.correlate(c)
.as_scalar()
)
q = (db.session
.query(
sq.c.id, sq.c.creation_time,
c.id, c.creation_time,
)
.outerjoin(c, c.thread_id == sq.c.id)
.filter(3 >= sq2)
)

Lots of nested queries

SELECT
productos.prod_id,
productos.prod_codigo1,
productos.prod_descripcion,
(SELECT SUM(cotdetalle.cotd_cantidad)
FROM cotdetalle
WHERE cotdetalle.cotd_codigo = productos.prod_codigo1
AND cotdetalle.cotd_cote_id IN(
SELECT cotencabezado.cote_id
FROM cotencabezado
WHERE cotencabezado.cote_status = 'cerrada'
AND MONTH(cotencabezado.cote_cierre) = MONTH(NOW()) AND YEAR(cotencabezado.cote_cierre) = YEAR(NOW())
)
) AS cuantos,
(SELECT SUM(cotdetalle.cotd_cantidad * cotdetalle.cotd_precio)
FROM cotdetalle
WHERE cotdetalle.cotd_codigo = productos.prod_codigo1
AND cotdetalle.cotd_cote_id IN(
SELECT cotencabezado.cote_id
FROM cotencabezado
WHERE cotencabezado.cote_status = 'cerrada'
AND MONTH(cotencabezado.cote_cierre) = MONTH(NOW()) AND YEAR(cotencabezado.cote_cierre) = YEAR(NOW())
)
) AS monto
FROM productos
ORDER BY monto DESC
LIMIT 0, 50
Came out with this query last night, I guess my question is: is it too much? I'm pretty sure there are other ways to get the same results without all that nested queries.. It works but it takes some time on my development environment, I bet it will take a lot more on a production environment... Any suggestions?
Please let me know if you need the table structure...
Edit: Actually that second SELECT is what bothers me most, it is exactly the same as the first one but I need that second result, but if I try to get that second result in the first SELECT it give me the "Operand should contain 1 column(s)" error...
A little more info: I need to be able to order by monto, cuantos, productos.prod_codigo1, productos.prod_descripcion
sqlfiddle.com example: http://sqlfiddle.com/#!2/c4391/1
You should do this as a simple join with a group by:
SELECT p.prod_id, p.prod_codigo1, p.prod_descripcion,
t.quantos, t.monto
FROM productos p left outer join
(SELECT cotdetalle.cotd_codigo, SUM(cotdetalle.cotd_cantidad) as quantos,
SUM(cotdetalle.cotd_cantidad * cotdetalle.cotd_precio) as monto
FROM cotdetalle
WHERE cotdetalle.cotd_codigo = productos.prod_codigo1 and
cotdetalle.cotd_cote_id IN
(SELECT cotencabezado.cote_id
FROM cotencabezado
WHERE cotencabezado.cote_status = 'cerrada' and
MONTH(cotencabezado.cote_cierre) = MONTH(NOW()) AND
YEAR(cotencabezado.cote_cierre) = YEAR(NOW()
)
group by cotdetalle.cotd_codigo
) t
on t.cotd_codigo = p.prod_codigo1
ORDER BY monto DESC
LIMIT 0, 50
This should improve things. However, MySQL does a poor job with IN and a subquery. So, instead of "IN" in the subquery, we want to change that to a join. Note the addition of "distinct" in the subquery. This isn't necessary for IN but it is for the join:
SELECT p.prod_id, p.prod_codigo1, p.prod_descripcion,
t.quantos, t.monto
FROM productos p join
(SELECT cd.cotd_codigo, SUM(cd.cotd_cantidad) as quantos,
SUM(cd.cotd_cantidad * cd.cotd_precio) as monto
FROM cotdetalle cd join
(SELECT distinct cc.cote_id
FROM cotencabezado cc
WHERE cc.cote_status = 'cerrada' and
MONTH(cc.cote_cierre) = MONTH(NOW()) AND
YEAR(cc.cote_cierre) = YEAR(NOW()
) cc
on cd.cotd_cote_id = cc.cote_id
group by cd.cotd_codigo
) t
on t.cotd_codigo = p.prod_codigo1
ORDER BY monto DESC
LIMIT 0, 50
I didn't test this on SQL Fiddle, so there may be syntax errors.
Why not simplify the statement as
SELECT productos.prod_id,
productos.prod_codigo1,
productos.prod_descripcion,
( SELECT SUM(cotdetalle.cotd_cantidad) AS cuantos,
SUM(cotdetalle.cotd_cantidad * cotdetalle.cotd_precio) AS monto
FROM cotdetalle
WHERE cotdetalle.cotd_codigo = productos.prod_codigo1
AND cotdetalle.cotd_cote_id IN( SELECT cotencabezado.cote_id
FROM cotencabezado
WHERE cotencabezado.cote_status = 'cerrada'
AND MONTH(cotencabezado.cote_cierre) = MONTH(NOW())
AND YEAR(cotencabezado.cote_cierre) = YEAR(NOW())
)
)
FROM productos
ORDER BY monto DESC
LIMIT 0, 50
Ok, it ended like this:
SELECT p.prod_id, p.prod_codigo1, p.prod_descripcion, t.cuantos, t.monto
FROM productos AS p
LEFT JOIN(
SELECT cd.cotd_codigo,
SUM(cd.cotd_cantidad) AS cuantos,
SUM(cd.cotd_cantidad * cd.cotd_precio) AS monto
FROM cotdetalle AS cd
JOIN(
SELECT DISTINCT ce.cote_id
FROM cotencabezado AS ce
WHERE ce.cote_status = 'cerrada'
AND MONTH(ce.cote_cierre) = MONTH(NOW())
AND YEAR(ce.cote_cierre) = YEAR(NOW())
) AS ce
ON cd.cotd_cote_id = ce.cote_id
GROUP BY cd.cotd_codigo
) AS t
ON cd.cotd_codigo = p.prod_codigo1
ORDER BY monto DESC
LIMIT 0, 50
This works a lot better an it's based on Gordon Linoff's suggestions, Thanks man!