MySQL emulate lag to reduce redundant items on older server - mysql

Bottom Line Up Front: I am trying to find a way to format the output so that some data is left blank if it matches a preceding row.
I've edited as suggested by #philipxy because I really do want to learn how to write better code and write better questions to support that learning.
That being said, #ysth was able to solve my overly complex ask anyway. I changed the title and marked answered.
This is a sample table from my database:
(SELECT codename, dt_begin, id_alias FROM aliases GROUP BY codename;)
+--------------+------------+----------+
| codename | dt_begin | id_alias |
+--------------+------------+----------+
| Arachniblade | 1999-12-23 | 1 |
| Arachniblade | 2016-07-04 | 2 |
| Beta | 2015-06-03 | 1 |
| Beta | 2016-07-04 | 3 |
| Cyberwolf | 2016-07-04 | 1 |
+--------------+------------+----------+
I would like the second (and any subsequent) instances of 'Arachniblade' and 'Beta' to be blank when ORDER BY codename is used.
+--------------+------------+----------+
| codename | dt_begin | id_alias |
+--------------+------------+----------+
| Arachniblade | 1999-12-23 | 1 |
| | 2016-07-04 | 2 |
| Beta | 2015-06-03 | 1 |
| | 2016-07-04 | 3 |
| Cyberwolf | 2016-07-04 | 1 |
+--------------+------------+----------+
Similarly, if I ORDER BY id_alias I would like to see only id 1 printed once but still retain all three records for 'Arachniblade,' 'Beta,' and 'Cyberwolf.'
+--------------+------------+----------+
| codename | dt_begin | id_alias |
+--------------+------------+----------+
| Arachniblade | 1999-12-23 | 1 |
| Beta | 2015-06-03 | |
| Cyberwolf | 2016-07-04 | |
| Arachniblade | 2016-07-04 | 2 |
| Beta | 2016-07-04 | 3 |
+--------------+------------+----------+
As #ysth mentioned LAG() is a part of the solution. I'm not sure how COALESCE fits in yet.

So you don't want to GROUP BY anything, but you want Codename to be blank where it is equal to the previous row's Codename? You would select this instead of just aliases.codename:
IF(COALESCE(LAG(aliases.codename) OVER (),'')=aliases.codename,'',aliases.codename) AS 'Codename'
assuming mysql 8.0 or mariadb 10.2+. Full query:
SELECT IF(COALESCE(LAG(aliases.codename) OVER (),'')=aliases.codename,'',aliases.codename) AS 'Codename',
aliases.dt_begin,
public_ids.fname,
public_ids.suffix
FROM public_ids
JOIN aliases ON aliases.id_alias
WHERE aliases.id_alias = public_ids.id
ORDER BY aliases.codename, aliases.dt_begin
(You omitted ORDER BY in your query; it wouldn't make much sense to want to do this without a specified order.)
On older versions, you can emulate LAG() with a variable:
SELECT IF(COALESCE(#lag,'')=aliases.codename,'',#lag:=aliases.codename) AS 'Codename',
aliases.dt_begin,
public_ids.fname,
public_ids.suffix
FROM (select #lag:=NULL) initvars
CROSS JOIN public_ids
JOIN aliases ON aliases.id_alias
WHERE aliases.id_alias = public_ids.id
ORDER BY aliases.codename, aliases.dt_begin

Related

MySQL 5.7 - Total quantity column for Bill of Materials

I would like to ask for a help.I use MySQL 5.7 and I have a Bill of Materials table called BOM.
I have many columns there which I will add later to the query, but for this questionID, Name, ParentID and Quantity is important. I would like to add a calculated column totalQty to my table that will multiply children quantities with parent quantities up to the top level. here is an example of what I want to achive:
+-------+------+------+----------+----------+--+
| ID | Name | Qty | ParentID | TotalQty | |
+-------+------+------+----------+----------+--+
| 1 | A | 1 | 0 | 1 | |
| 1.1 | AA | 2 | 1 | 2 | |
| 1.1.1 | AAA | 1 | 1.1 | 2 | |
| 1.2 | AB | 5 | 1 | 5 | |
| 1.2.1 | ABA | 2 | 1.2 | 10 | |
| 2 | B | 3 | 0 | 3 | |
| 2.1 | BA | 2 | 2 | 6 | |
+-------+------+------+----------+----------+--+
I need this to create a list of materials used per project, so I will multiply totalQTY with unit mass and unit length to get total mass and total length of part. Then I will group and aggregate them by material type.
I have searched the forums, and I have found out that I need to use Common table expressions (CTE) for this. But I have found no explanation in plain english on how to do it for my case.
Thank you very much for any help or hint how to understand the concept.

Selecting multiple columns from previous row in MySQL

Suppose I have a table like this:
| id | date | name | value |
|----|------------|------|-------|
| 0 | 2017-01-14 | foo | one |
| 1 | 2017-01-17 | bar | two |
| 2 | 2017-01-18 | john | five |
| 3 | 2017-01-19 | doe | ten |
(where date need not necessarily be ordered)
I want to be able to select some values of the previous row (based on date). Such a functionality can be achieved by the following query:
SELECT
*,
(SELECT
name
FROM
example e2
WHERE
e2.dt < e1.dt
ORDER BY dt DESC
LIMIT 1
) as prev_name
FROM example e1
with resulting table:
| id | dt | name | value | prev_name |
|----|------------|------|-------|-----------|
| 0 | 2017-01-14 | foo | one | (null) |
| 1 | 2017-01-17 | bar | two | foo |
| 2 | 2017-01-18 | john | five | bar |
| 3 | 2017-01-19 | doe | ten | john |
Now, this works just fine. However, it would be preferable if I could easily select multiple columns from the previous row, resulting in a result like:
| id | dt | name | value | prev_name | prev_value | prev_dt |
|----|------------|------|-------|-----------|------------|------------|
| 0 | 2017-01-14 | foo | one | (null) | (null) | (null) |
| 1 | 2017-01-17 | bar | two | foo | one | 2017-01-14 |
| 2 | 2017-01-18 | john | five | bar | two | 2017-01-17 |
| 3 | 2017-01-19 | doe | ten | john | five | 2017-01-18 |
This can of course be accomplished by simply copying the subquery (SELECT [..] FROM example e2 ...) into the query multiple times, but I guess this is not the preferable way to go. I have found several question on SO addressing either the "how to select records from a previous row" or the "how to select multiple columns using subqueries" problem, but not both. The latter problem is then mostly solved by using a JOIN statement, but I think this is not combinable with the "previous row" case. So my question is: what would be a better way to produce the last result, rather then copying a subquery for every column we need?
EDIT. As an extra constraint, that I did not include in the original question, "previous" could actually be something different from the previous row, but rather "the previous row that satisfies a condition". So suppose my table contains an extra boolean column b
| id | dt | name | value | b |
|----|------------|------|-------|---|
| 0 | 2017-01-14 | foo | one | 1 |
| 1 | 2017-01-17 | bar | two | 0 |
| 2 | 2017-01-18 | john | five | 1 |
| 3 | 2017-01-19 | doe | ten | 0 |
I would want the "previous row" to be the previous row with b = 1, so the desired result would be:
| id | dt | name | value | b | prev_name | prev_value | prev_dt |
|----|------------|------|-------|---|-----------|------------|------------|
| 0 | 2017-01-14 | foo | one | 1 | (null) | (null) | (null) |
| 1 | 2017-01-17 | bar | two | 0 | foo | one | 2017-01-14 |
| 2 | 2017-01-18 | john | five | 1 | foo | one | 2017-01-14 |
| 3 | 2017-01-19 | doe | ten | 0 | john | five | 2017-01-18 |
I think this can still be accomplished by James Scott's answer, by simply only updating the variables when b = 1, using an IF-statement, but maybe there is another solution possible in this case.
EDIT. SQLfiddle
Something like this will return the id of the 'previous' row.
SELECT x.*
, MAX(y.id) prev_id
FROM example x
LEFT
JOIN example y
ON y.id < x.id
AND y.b = 1
GROUP
BY x.id;
I'll leave the business of returning the rest of the data associated with this row as an exercise for the reader.
Looks like a good use case for session variables if you only want the previous row, you can use ORDER BY to get different results.
SET #VDt := NULL, #VName := NULL, #VValue := NULL;
SELECT id, #VName prev_name, #VValue prev_value, #VDt prev_dt, #VDt := dt dt, #VName := `name` `name`, #VValue := `value` `value` FROM example;
Messed this up when I first posted, note that the variables must be set after they are returned from the previous row. To reorder the columns (if desired) you can wrap this query in another that then reorders the result columns.
Let me know if you need anything else,
Regards,
James

SQL INSERT INTO query syntax

I am trying to run an MySQL query to copy over data from an old table (ps__product_review/rate) to a new table (ps_product_comment/grade) based on review ID (id_product_comment). But I am a bit lost on the SQL query, this is what I have but keep getting errors.
INSERT INTO ps_product_comment [(grade)]
SELECT rate
FROM ps__product_review
[WHERE ps__product_review.id_product_comment=ps_product_comment.id_product_comment];
Can anyone help write the correct query?
Edit:Essentially I am trying to populate the Grade column in the new table below.
Old table (ps__product_review)
+--------------------+----------+-----+
| id_product_comment | Comment | Rate|
+--------------------+----------+-----+
| 1 | Good | 2 |
| 2 | Great | 5 |
| 3 | OK | 3 |
| 4 | Brill | 4 |
| 5 | OK | 3 |
| 6 | Average | 2 |
| 7 | Bad | 1 |
+--------------------+----------+-----+
New Table (ps_product_comment)
+--------------------+----------+-------+
| id_product_comment | Comment | Grade |
+--------------------+----------+-------+
| 1 | Good | |
| 2 | Great | |
| 3 | OK | |
| 4 | Brill | |
| 5 | OK | |
| 6 | Average | |
| 7 | Bad | |
+--------------------+----------+-------+
If you want to update table with data from another table, use UPDATE with JOIN
UPDATE ps_product_comment
JOIN ps__product_review
ON ps__product_review.id_product_comment = ps_product_comment.id_product_comment
SET ps_product_comment.grade = ps__product_review.rate;
Remove the square brackets and I think you are missing the JOIN(since you are using that in your where clause):
INSERT INTO ps_product_comment (grade)
SELECT rate
FROM ps__product_review inner join ps_product_comment on
ps__product_review.id_product_comment=ps_product_comment.id_product_comment;

how to perform inner join on same table in mysql

i have table called sla in mysql, i need to get unique switch and port_no related to perticular switch. i exactly don't know which join will help me.
+-------------------------+---------+
| switch | port_no |
+-------------------------+---------+
| 00:00:00:00:00:00:00:02 | 3 |
| 00:00:00:00:00:00:00:01 | 2 |
| 00:00:00:00:00:00:00:01 | 1 |
| 00:00:00:00:00:00:00:02 | 1 |
| 00:00:00:00:00:00:00:04 | 2 |
Expected output
+-------------------------+---------+
| switch | port_no |
+-------------------------+---------+
| 00:00:00:00:00:00:00:02 | 3,1 |
| 00:00:00:00:00:00:00:01 | 2,1 |
| 00:00:00:00:00:00:00:04 | 2 |
I got expected output by using GROUP_CONCAT function.
select switch,GROUP_CONCAT(port_no) from sla group by switch;

Get maximum value of a GROUP BY query without subquery in mySQL

i have some queries which group datasets and count them, e.g.
SELECT COUNT(*)
FROM `table`
GROUP BY `column`
now i have the number of rows for which column is the same, so far so good.
problem is: how do i get the aggregate (min/max/avg/sum) values for those “grouped” counts. using a subquery sure is the easiest, but i was wondering if this is possible within this single query
For min and max you can ORDER BY and fetch the first row. For sum/avg/other aggregates you would need a subquery.
In MySQL you should be able to do this all at once. My tests seem to indicate that this works.
| date | hits |
|-------------------|
| 2009-10-10 | 3 |
| 2009-10-10 | 6 |
| 2009-10-10 | 1 |
| 2009-10-10 | 3 |
| 2009-10-11 | 12 |
| 2009-10-11 | 4 |
| 2009-10-11 | 8 |
-------------------
SELECT COUNT(*), MAX(hits), SUM(hits) FROM table GROUP BY date
| COUNT(*) | MAX(hits) |
|-----------|-----------|
| 4 | 6 |
| 3 | 12 |
-----------------------
SUM, MIN and AVG also work. Is this what you are looking for?
| date | hits |
|-------------------|
| 2009-10-10 | 3 |
| 2009-10-10 | 6 |
| 2009-10-10 | 1 |
| 2009-10-10 | 3 |
| 2009-10-11 | 12 |
| 2009-10-11 | 4 |
| 2009-10-11 | 8 |
I think knittl was trying to do something like this:
select min(hits), max(hits), avg(hits), sum(hits)<br>
from table
group by date