Similar SQLs with session variable produce different result - mysql

MariaDB 10.1.18
Table P (id int AUTO_INCREMENT, rownum int, code int, s int, PRIMARY KEY(id));
select id, rownum, code, s from P order by id;
+----+--------+------+------+
| id | rownum | code | s |
+----+--------+------+------+
| 1 | 1 | 5 | 1 |
| 2 | 2 | 5 | 1 |
| 3 | 3 | 5 | 1 |
| 4 | 4 | 5 | 1 |
| 5 | 5 | 5 | 1 |
| 6 | 6 | 7 | 1 |
| 7 | 7 | 7 | 1 |
| 8 | 8 | 7 | 1 |
| 9 | 9 | 7 | 1 |
| 10 | 10 | 7 | 1 |
+----+--------+------+------+
Issue: the following 2 queries are very similar: 1st query join on id, 2nd join on rownum; the id and rownum columns are having the same values (see table above), but the query result is different in calculated column N:
Query 1: join on id column
SELECT P.id, P.rownum, P2.s,
IF(P2.s IS NULL, #val:=#val+1, #val) as N
FROM P CROSS JOIN (SELECT #val:=0) init
LEFT JOIN P P2
ON (P.id+1=P2.id AND P.s=1 AND P2.s=1 AND P.code = P2.code)
ORDER BY P.id;
+----+--------+------+------+
| id | rownum | s | N |
+----+--------+------+------+
| 1 | 1 | 1 | 0 |
| 2 | 2 | 1 | 0 |
| 3 | 3 | 1 | 0 |
| 4 | 4 | 1 | 0 |
| 5 | 5 | NULL | 1 |
| 6 | 6 | 1 | 1 |
| 7 | 7 | 1 | 1 |
| 8 | 8 | 1 | 1 |
| 9 | 9 | 1 | 1 |
| 10 | 10 | NULL | 2 |
+----+--------+------+------+
Query 2: join on rownum column
SELECT P.id, P.rownum, P2.s,
IF(P2.s IS NULL, #val:=#val+1, #val) as N
FROM P CROSS JOIN (SELECT #val:=0) init
LEFT JOIN P P2
ON (P.rownum+1=P2.rownum AND P.s=1 AND P2.s=1 AND P.code = P2.code)
ORDER BY P.id;
+----+--------+------+------+
| id | rownum | s | N |
+----+--------+------+------+
| 1 | 1 | 1 | 0 |
| 2 | 2 | 1 | 0 |
| 3 | 3 | 1 | 0 |
| 4 | 4 | 1 | 0 |
| 5 | 5 | NULL | 1 |
| 6 | 6 | 1 | 0 |
| 7 | 7 | 1 | 0 |
| 8 | 8 | 1 | 0 |
| 9 | 9 | 1 | 0 |
| 10 | 10 | NULL | 2 |
+----+--------+------+------+

As explicitly documented both in MariaDB knowledge base and MySQL manual, you should not read a user-defined variable and set its value in the same statement, unless this statement is SET. For other statements it is unsafe and the result is not guaranteed, as your example clearly demonstrates.
Additional note: variables you are talking about in your question are more commonly referred to as 'user-defined variables' or 'user variables', as opposed to 'system variables', 'global variables' and 'session variables', each of which imply system-defined variables, either all of them or limited to the given scope.

Related

Select ans specific value or, if not exists, the minimum one

I'm stuck trying to solve a problem using SQL (MySQL 5.6). Need to get the address which has ID 2 or, if not exists ID 2, the lower ID.
For example
|-----------------|---------------|
| CostumerID | AddressID |
|-----------------|---------------|
| 1 | 4 |
| 1 | 1 |
| 1 | 2 |
| 2 | 3 |
| 2 | 4 |
| 3 | 4 |
| 4 | 3 |
| 4 | 4 |
| 5 | 2 |
| 6 | 4 |
| 7 | 2 |
| 7 | 4 |
| 8 | 3 |
| 9 | 1 |
| 9 | 3 |
| 9 | 4 |
| 9 | 2 |
|-----------------|---------------|
If a costumerID have an AddressID 2, must get that. If not, must get the minimum AddressID.
The output must be like:
|-----------------|---------------|
| CostumerID | AddressID |
|-----------------|---------------|
| 1 | 2 |
| 2 | 3 |
| 3 | 4 |
| 4 | 3 |
| 5 | 2 |
| 6 | 4 |
| 7 | 2 |
| 8 | 3 |
| 9 | 2 |
|-----------------|---------------|
So far I've tried this:
SELECT distinct CostumerID,
if (AddressID= 2, AddressID,
(select min(b.AddressID) from Addresses b where b.AddressID= a.AddressID)) as tipus
FROM from Addresses a
but get duplicates at CostumerID.
Use aggregation with CASE logic:
SELECT
CostumerID,
CASE WHEN COUNT(CASE WHEN AddressID = 2 THEN 1 END) > 0
THEN 2 ELSE MIN(AddressID) END AS AddressID
FROM yourTable
GROUP BY
CostumerID;
SELECT CASE
WHEN EXISTS(SELECT *
FROM tbl_name
WHERE AddressID = 2)
THEN (SELECT *
FROM tbl_name
WHERE AddressID > 2 )
ELSE 'Default Value'
END

Mysql Best way to query notifications like on stackoverflow

I have 4 tables:
comments
+----+-----------+--------------+-------+
| id | content | user_id | article_id |
+----+-----------+--------------+-------+
| 1 | Comment 1 | 2 | 5 |
| 2 | Comment 2 | 5 | 3 |
| 3 | Comment 3 | 1 | 6 |
| 4 | Comment 4 | 6 | 8 |
| 5 | Comment 5 | 1 | 6 |
| ...| ... | ... | ... |
+----------------+---------+------------+
votes
+----+----------+--------------+---+
| id | type | user_id | article_id |
+----+----------+--------------+---+
| 1 | 1 | 2 | 5 |
| 2 | 1 | 3 | 3 |
| 3 | 0 | 1 | 6 |
| 4 | 1 | 7 | 4 |
| 5 | 0 | 9 | 4 |
| 6 | 0 | 1 | 6 |
| ...| ... | ... | ... |
+------------+----------+----------+
notifications (object_id is the id of the vote|comment)
+----+----------+--------------+-------------+-------------+--------------+
| id | object_url| object_id |activitytype_id| sender_id | recipient_id |
+----+----------+------------+---------------+-------------+--------------+
| 1 | /../../.. | 1 | 2 | 2 | 6 |
| 2 | /../../.. | 2 | 2 | 3 | 2 |
| 3 | /../../.. | 1 | 1 | 2 | 7 |
| 3 | /../../.. | 2 | 1 | 5 | 2 |
| 3 | /../../.. | 3 | 1 | 1 | 3 |
| 3 | /../../.. | 3 | 3 | 1 | 2 |
| 3 | /../../.. | 4 | 2 | 7 | 8 |
| 3 | /../../.. | 5 | 3 | 9 | 1 |
| 3 | /../../.. | 6 | 3 | 1 | 5 |
| ...| ... | ... | ... | | |
+----+-----------+-----------+---------------+-------------+--------------+
activitytypes
+----+------------+
| id | label |
+----+------------+
| 1 | comment |
| 2 | vote up |
| 3 | vote down |
| ...| ... |
+-----------------+
I would like to get notifications like on stackoverflow.
I want to query the last notification (with comment content if the activity type is a comment or null if not) for every activitytype and object_url combinaison for a specific user.
For example I have 3 artiles A,B and C which all have 3 comments, 4 voteup and 2 votedown. How to get the last comment, voteup and votedown for every article ?
I have tried this query:
SELECT n.id, n.object_url , n.object_id, n.activitytype_id, IF(n.activitytypeId = 1,
(SELECT content FROM comments WHERE id=n.object_id), null) AS activitycontent
FROM notifications n WHERE n.recipient_id =1
GROUP BY n.activitytype_id,n.object_url
ORDER BY n.id DESC
But it doesn't work. Can anyone help ?
EDIT:
This following query in farhadamjady's answer gives me the first comment:
SELECT
n.id,
n.object_url,
n.object_id,
n.activitytype_id,
cm.content AS activitycontent
FROM
notifications n
LEFT OUTER JOIN `COMMENT` AS cm ON cm.id = n.object_id and n.activitytypeId = 1
WHERE
n.recipient_id = 1
GROUP BY
n.activitytype_id,
n.object_url
HAVING MAX(cm.id)
ORDER BY
n.id DESC
How can I change it to get the last ?
you should use left outer join like this :
SELECT
n.id,
n.object_url,
n.object_id,
n.activitytype_id,
cm.content AS activitycontent
FROM
notifications n
LEFT OUTER JOIN `COMMENT` AS cm ON cm.id = n.object_id and n.activitytypeId = 1
WHERE
n.recipient_id = 1
GROUP BY
n.activitytype_id,
n.object_url
HAVING MAX(cm.id)
ORDER BY
n.id DESC

Concatenate string with the number of ocurrence

I'm migrating a database from one application to another. In the first one I've two tables: proyectos and presupuestos. A row in 'proyectos' can have one or more rows in 'presupuestos'.
The new application has a field in presupuestos that is made concatenating the code of the proyect with the number of 'presupuesto' of this proyect. That's what I don't know how to do it.
My tables are like:
Proyectos:
+--------------+------------------+
| proyectos_id | proyectos_codigo |
+--------------+------------------+
| 1 | E+-00001 |
| 2 | E+-00002 |
| 3 | E+-00003 |
| 4 | E+-00004 |
| 5 | E+-00005 |
+--------------+------------------+
Presupuestos:
+-----------------+--------------+
| presupuestos_id | proyectos_id |
+-----------------+--------------+
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 2 |
| 5 | 3 |
| 6 | 3 |
| 7 | 3 |
| 8 | 4 |
| 9 | 4 |
| 10 | 5 |
+-----------------+--------------+
I've tried with this query:
select presupuestos_id, p.proyectos_id, concat(pr.proyectos_codigo,'_1') from presupuestos p join proyectos pr on p.proyectos_id = pr.proyectos_id
Which result is:
+-----------------+--------------+----------------------------------+
| presupuestos_id | proyectos_id | concat(pr.proyectos_codigo,'_1') |
+-----------------+--------------+----------------------------------+
| 1 | 1 | E+-00001_1 |
| 2 | 1 | E+-00001_1 |
| 3 | 1 | E+-00001_1 |
| 4 | 2 | E+-00002_1 |
| 5 | 3 | E+-00003_1 |
| 6 | 3 | E+-00003_1 |
| 7 | 3 | E+-00003_1 |
| 8 | 4 | E+-00004_1 |
| 9 | 4 | E+-00004_1 |
| 10 | 5 | E+-00005_1 |
+-----------------+--------------+----------------------------------+
But obviusly, It doesn't what I want. My desired result is:
+-----------------+--------------+----------------------------------+
| presupuestos_id | proyectos_id | some code |
+-----------------+--------------+----------------------------------+
| 1 | 1 | E+-00001_1 |
| 2 | 1 | E+-00001_2 |
| 3 | 1 | E+-00001_3 |
| 4 | 2 | E+-00002_1 |
| 5 | 3 | E+-00003_1 |
| 6 | 3 | E+-00003_2 |
| 7 | 3 | E+-00003_3 |
| 8 | 4 | E+-00004_1 |
| 9 | 4 | E+-00004_2 |
| 10 | 5 | E+-00005_1 |
+-----------------+--------------+----------------------------------+
Try this :
SELECT presupuestos_id, p.proyectos_id,
CONCAT(pr.proyectos_codigo,'_',
(CASE p.proyectos_id
WHEN #p_id
THEN #rownumber := #rownumber + 1
ELSE #rownumber := 1 AND #p_id := p.proyectos_id END)
)AS result
FROM presupuestos p
JOIN proyectos pr ON p.proyectos_id = pr.proyectos_id
JOIN (SELECT #rownumber:=0, #p_id:='') AS t
This should do what you want, although the answer by RubahMalam looks better... :
SELECT a.presupuestos_id, a.proyectos_id, concat(p.proyectos_codigo,'_', count(*)) as "Some code"
FROM (
SELECT pr.presupuestos_id, pr.proyectos_id
FROM Presupuestos pr JOIN Proyectos p ON pr.proyectos_id = p.proyectos_id
) a
JOIN (
SELECT pr.presupuestos_id, pr.proyectos_id
FROM Presupuestos pr JOIN Proyectos p ON pr.proyectos_id = p.proyectos_id
) b
ON a.proyectos_id = b.proyectos_id AND a.presupuestos_id >= b.presupuestos_id
JOIN Proyectos p ON a.proyectos_id = p.proyectos_id
GROUP BY a.proyectos_id, a.presupuestos_id, p.proyectos_codigo
Sample SQL Fiddle

Create a view with top N per group rows in mysql

I need to create a view that stores the top 200 rows for each userId from another table that has userId as one of its columns.
Found a way to do this using user-defined variables in SELECT, but then MySQL does not allow views with variables in the SELECT.
This is a part of the SELECT statement to be used in the view:
select *,#num:= if(#userId = userId, #num + 1, 1) as row_number,
#userId := userId as dummy from (SELECT #userId:=0, #num:=0) as init
Is it possible to replace #userId and #num with functions instead? Similar examples would of great help!!
Consider the following...
SELECT * FROM results;
+----+------------+--------+-------+-------+
| id | discipline | member | event | value |
+----+------------+--------+-------+-------+
| 1 | 1 | 2 | 4 | 10 |
| 2 | 1 | 1 | 4 | 8 |
| 3 | 1 | 2 | 5 | 9 |
| 4 | 2 | 3 | 5 | 9 |
| 5 | 1 | 2 | 6 | 11 |
| 6 | 1 | 2 | 7 | 11 |
| 7 | 1 | 2 | 1 | 11 |
| 8 | 1 | 2 | 3 | 7 |
| 9 | 1 | 1 | 8 | 8 |
+----+------------+--------+-------+-------+
Say I want to get the top 3 'value' results for each member. In the event of a tie, I'll take the lower 'event' first...
SELECT x.*
, COUNT(*) rank
FROM results x
JOIN results y
ON y.member = x.member
AND (y.value > x.value OR (y.value = x.value AND y.event <= x.event))
GROUP
BY x.member
, x.value
, x.event
HAVING COUNT(*) <=3;
+----+------------+--------+-------+-------+---------+
| id | discipline | member | event | value | rank |
+----+------------+--------+-------+-------+---------+
| 2 | 1 | 1 | 4 | 8 | 1 |
| 9 | 1 | 1 | 8 | 8 | 2 |
| 7 | 1 | 2 | 1 | 11 | 1 |
| 5 | 1 | 2 | 6 | 11 | 2 |
| 6 | 1 | 2 | 7 | 11 | 3 |
| 4 | 2 | 3 | 5 | 9 | 1 |
+----+------------+--------+-------+-------+---------+

Updating multiple rows with first instance of record in same table

Assume following table:
+----+-----------+
| id | session |
+----+-----------+
| 1 | abcd1234 |
| 2 | abcd1234 |
| 3 | abcd1234 |
| 4 | qwert5678 |
| 5 | qwert5678 |
| 6 | abcd1234 |
| 7 | abcd1234 |
| 8 | qwert5678 |
| 9 | abcd1234 |
| 10 | qwert5678 |
| 11 | qwert5678 |
| 12 | qwert5678 |
+----+-----------+
Suppose we want to get the first id of a given session, then set every instance of that session to the id for all sessions, such that the table becomes:
+----+-----------+
| id | session |
+----+-----------+
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 4 |
| 5 | 4 |
| 6 | 1 |
| 7 | 1 |
| 8 | 4 |
| 9 | 1 |
| 10 | 4 |
| 11 | 4 |
| 12 | 4 |
+----+-----------+
We have a table with approximately 45M records, and are essentially changing every instance of column b to the value of min(column a) when grouped by column b.
Is there a way to do this in a single query? We have attempted several.
update example e
set session =
(select id from
(select id,min(session)
from example as first_id
group by session
) as this_id
);
...which errors out: "Subquery returns more than 1 row".
update example e
join
(select id
from
(select id,min(session)
from example as first_id
group by session
) as this_id
) as etable
set session = first_id;
...which errors out: "Unknown column 'first_id' in 'field list'". Also used 'this_id' to the same effect.
And other queries. Is this possible in a single query? Are we thinking about this incorrectly?
Query:
SQLFIDDLEExample
UPDATE example
SET session =(SELECT MIN(e2.ID)
FROM (SELECT *
FROM example) e2
WHERE e2.session = example.session)
Result:
| ID | SESSION |
----------------
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 4 |
| 5 | 4 |
| 6 | 1 |
| 7 | 1 |
| 8 | 4 |
| 9 | 1 |
| 10 | 4 |
| 11 | 4 |
| 12 | 4 |