SQL Query in Ruby: Only select the changes - mysql

Suppose I have a table (PriceHistory) like this, every time I change anything in the row, I will record the whole row again in the table.
id | buy_price | sell_price | change_date
1 | 2 | 2 | 2012-06-22
2 | 3 | 2 | 2012-06-20
3 | 2 | 6 | 2012-06-15
4 | 5 | 5 | 2012-06-15
5 | 5 | 7 | 2012-06-15
6 | 4 | 8 | 2012-06-12
I only care about the change of BuyPrice, Is there a way to just select row 1, 2, 3, & 5?
Here is the Ruby code I come up with, but it does not only select the changed rows
PriceHistory.select("id, BuyPrice, change_date").
order("change_date DESC")
Both Ruby and SQL answers are fine.

Try this:
#histories = PriceHistory.select("id, BuyPrice, change_date").order("change_date DESC")
(#histories.size - 1).times do |n|
if #histories[n].buy_price != #histories[n+1].buy_price
puts "changed from : " + #histories[n].id.to_s + " to: " + #histories[n+1].id.to_s
end
end
Good luck!

Related

Postgresql Ltree query to get comment threads nested json/array and build HTML from it

I am building something which has a comment section similar to Reddit. So there can be root comments and users can reply to the root comments, then users can reply to the child comments and so on. Basically nested comment threads.
I used this tutorial and have sort of got it working:
http://www.postgresonline.com/article_pfriendly/173.html
Here's me building the table and inserting data and querying it:
CREATE TABLE comments(commentid integer PRIMARY KEY, commentparentid integer, comment varchar(1000), node_path ltree);
CREATE UNIQUE INDEX idx_comments_node_path_btree_idx ON comments USING btree(node_path);
CREATE INDEX idx_comments_node_path_gist_idx ON comments USING gist(node_path);
CREATE OR REPLACE FUNCTION get_calculated_node_path(param_commentid integer)
RETURNS ltree AS
$$
SELECT CASE WHEN s.commentparentid IS NULL THEN s.commentid::text::ltree
ELSE get_calculated_node_path(s.commentparentid) || s.commentid::text END
FROM comments As s
WHERE s.commentid = $1;
$$
LANGUAGE sql;
CREATE OR REPLACE FUNCTION trig_update_node_path() RETURNS trigger AS
$$
BEGIN
IF TG_OP = 'UPDATE' THEN
IF (COALESCE(OLD.commentparentid,0) != COALESCE(NEW.commentparentid,0) OR NEW.commentid != OLD.commentid) THEN
-- update all nodes that are children of this one including this one
UPDATE comments SET node_path = get_calculated_node_path(commentid)
WHERE OLD.node_path #> comments.node_path;
END IF;
ELSIF TG_OP = 'INSERT' THEN
UPDATE comments SET node_path = get_calculated_node_path(NEW.commentid) WHERE comments.commentid = NEW.commentid;
END IF;
RETURN NEW;
END
$$
LANGUAGE 'plpgsql' VOLATILE;
CREATE TRIGGER trig01_update_node_path AFTER INSERT OR UPDATE OF commentid, commentparentid
ON comments FOR EACH ROW
EXECUTE PROCEDURE trig_update_node_path();
INSERT INTO comments(commentid,commentparentid, comment)
VALUES (1, NULL, 'GreatGreatGrandpa'),
(2, NULL, 'GreatGreatGrandma'),
(3,1, 'GreatGrandpa'),
(4,3, 'Grandpa'),
(5,4, 'Pa'),
(6,5, 'Son'),
(7,6, 'Grandson'),
(13,6, 'Grandson2'),
(8,2, 'GreatGrandma'),
(9,8, 'Grandma'),
(10,9, 'Ma'),
(11,10, 'Daughter'),
(12,11, 'GrandDaughter');
SELECT commentid, commentparentid, comment, node_path FROM comments;
Here's the output:
commentid | commentparentid | comment | node_path
-----------+-----------------+-------------------+----------------
1 | | GreatGreatGrandpa | 1
2 | | GreatGreatGrandma | 2
3 | 1 | GreatGrandpa | 1.3
4 | 3 | Grandpa | 1.3.4
5 | 4 | Pa | 1.3.4.5
6 | 5 | Son | 1.3.4.5.6
7 | 6 | Grandson | 1.3.4.5.6.7
13 | 6 | Grandson2 | 1.3.4.5.6.13
8 | 2 | GreatGrandma | 2.8
9 | 8 | Grandma | 2.8.9
10 | 9 | Ma | 2.8.9.10
11 | 10 | Daughter | 2.8.9.10.11
12 | 11 | GrandDaughter | 2.8.9.10.11.12
Here's the query and output to get the comment thread as rows:
SELECT c.commentid, c.node_path, array_to_string(array_agg(a.comment ORDER BY a.node_path), '->') As comment_fulltree
FROM comments As c INNER JOIN comments As a
ON (a.node_path #> c.node_path)
GROUP BY c.commentid, c.node_path, c.comment
ORDER BY c.node_path;
commentid | node_path | comment_fulltree
-----------+----------------+-----------------------------------------------------------------------
1 | 1 | GreatGreatGrandpa
3 | 1.3 | GreatGreatGrandpa->GreatGrandpa
4 | 1.3.4 | GreatGreatGrandpa->GreatGrandpa->Grandpa
5 | 1.3.4.5 | GreatGreatGrandpa->GreatGrandpa->Grandpa->Pa
6 | 1.3.4.5.6 | GreatGreatGrandpa->GreatGrandpa->Grandpa->Pa->Son
13 | 1.3.4.5.6.13 | GreatGreatGrandpa->GreatGrandpa->Grandpa->Pa->Son->Grandson2
7 | 1.3.4.5.6.7 | GreatGreatGrandpa->GreatGrandpa->Grandpa->Pa->Son->Grandson
2 | 2 | GreatGreatGrandma
8 | 2.8 | GreatGreatGrandma->GreatGrandma
9 | 2.8.9 | GreatGreatGrandma->GreatGrandma->Grandma
10 | 2.8.9.10 | GreatGreatGrandma->GreatGrandma->Grandma->Ma
11 | 2.8.9.10.11 | GreatGreatGrandma->GreatGrandma->Grandma->Ma->Daughter
12 | 2.8.9.10.11.12 | GreatGreatGrandma->GreatGrandma->Grandma->Ma->Daughter->GrandDaughter
And here's a query and output to get the path to the bottom most leaf comment:
SELECT c.commentid, c.node_path, array_to_string(array_agg(a.comment ORDER BY a.node_path), '-->') As comment_fulltree
FROM (SELECT *
FROM comments AS c1
WHERE NOT EXISTS (
SELECT *
FROM comments AS c2
WHERE c1.node_path #> c2.node_path
AND c1.node_path <> c2.node_path
)) As c INNER JOIN comments As a
ON (a.node_path #> c.node_path)
GROUP BY c.commentid, c.node_path, c.comment
ORDER BY c.node_path;
commentid | node_path | comment_fulltree
-----------+----------------+----------------------------------------------------------------------------
13 | 1.3.4.5.6.13 | GreatGreatGrandpa-->GreatGrandpa-->Grandpa-->Pa-->Son-->Grandson2
7 | 1.3.4.5.6.7 | GreatGreatGrandpa-->GreatGrandpa-->Grandpa-->Pa-->Son-->Grandson
12 | 2.8.9.10.11.12 | GreatGreatGrandma-->GreatGrandma-->Grandma-->Ma-->Daughter-->GrandDaughter
Despite all this, I am having a hard time thinking and figuring out how I can convert this output to the actual HTML to display the comments as nested threads.
I already have built the HTML with a combination of nodejs and EJS views where I am able to display nested comments using nested divs.
I am also familiar with using the "pg-promise" nodejs module to query the database. I understand this part.
But I am not able to figure out how I can get the output from the database and convert it to the HTML. Like right now, the output simply tells me there is a nested thread like GreatGreatGrandma->GreatGrandma->Grandma->Ma->Daughter->GrandDaughter. But since this isn't a JSON or array, I am not sure how to iterate through it to build my HTML?
I am thinking if there is some SQL which gives me a nested JSON, then I can build the HTML. But I don't know how to get it?
I was able to solve it.
I used the following SQL:
SELECT nlevel(node_path) as depth, commentid, commentparentid, comment, node_path FROM comments ORDER BY node_path;
Which gives me:
depth | commentid | commentparentid | comment | node_path
--------+-----------+-----------------+-------------------+----------------
1 | 1 | | GreatGreatGrandpa | 1
2 | 3 | 1 | GreatGrandpa | 1.3
3 | 4 | 3 | Grandpa | 1.3.4
4 | 5 | 4 | Pa | 1.3.4.5
5 | 6 | 5 | Son | 1.3.4.5.6
6 | 13 | 6 | Grandson2 | 1.3.4.5.6.13
6 | 7 | 6 | Grandson | 1.3.4.5.6.7
1 | 2 | | GreatGreatGrandma | 2
2 | 8 | 2 | GreatGrandma | 2.8
3 | 9 | 8 | Grandma | 2.8.9
4 | 10 | 9 | Ma | 2.8.9.10
5 | 11 | 10 | Daughter | 2.8.9.10.11
6 | 12 | 11 | GrandDaughter | 2.8.9.10.11.12
using this, I loop through the rows and use the depth field to add the </div> whenever the depth is less than the previous item's depth. This way I am able to create my needed HTML.

How to do an IF statement in mysql as part of the SELECT statement

I'm comparing search engine rankings for URLs and I have 3 columns: URL, Rank, Previous rank. What I'd like to do is add a fourth column which says whether the rank has gone up or down.
For example
URL | Rank | Previous_Rank
example.com/page1 | 2 | 16
example.com/page2 | 2 | 11
example.com/page3 | 1 | 14
example.com/page4 | 1 | 4
example.com/page5 | 101| 7
example.com/page6 | 101| 14
example.com/page7 | 101| 7
example.com/page8 | 6 | 17
example.com/page9 | 10| 17
example.com/page10| 19| 1
I'd like another column to return:
URL | Rank | Previous_Rank | Movement
example.com/page1 | 2 | 16 | Up
example.com/page2 | 2 | 11 | Up
example.com/page3 | 1 | 14 | Up
example.com/page4 | 1 | 4 | Up
example.com/page5 | 101| 7 | Down
example.com/page6 | 101| 14 | Down
example.com/page7 | 101| 7 | Down
example.com/page8 | 6 | 17 | Up
example.com/page9 | 10| 17 | Up
example.com/page10| 19| 1 |Down
I'm using HeidiSQL which is mysql. The data here is part of a larger table and is pulled together with this SELECT statement:
select
URL,
Rank,
Previous_Rank
from URL_Changes
where
date = "2017-06-14"
group by URL
order by 2
;
So my question is, how do I edit that select statement to bring back that extra column?
Thanks.
You use a CASE statement for this:
select
URL,
Rank,
Previous_Rank,
CASE WHEN Rank < Previous_Rank THEN 'Up' WHEN Rank > Previous_Rank THEN 'Down' WHEN Rank = Previous_Rank THEN 'No Change' END AS Movement
from URL_Changes
where
date = "2017-06-14"
group by URL
order by 2
;
In MySQL there is also an IF() function, but CASE is used on nearly every RDBMS and IF() requires nesting to test for multiple scenarios, so Case is generally the better choice.

eloquent with Order by not returning correctly the SUM of field

I'm trying to work with eloquents and I'm having problems in generating the expected result.
To be honest, I will show you what I have, what I want and what I did. But, maybe there is another way to do what I want and I will be happy to test it as well. :)
What I have:
I do have a table, that is:
+----+------------+--------------+---------------+---------------------+---------------------+
| id | machine_id | balance_left | balance_found | created_at | updated_at |
+----+------------+--------------+---------------+---------------------+---------------------+
| 1 | 1 | 12.30 | 12.30 | 2016-01-26 16:39:18 | 2016-01-28 16:39:18 |
| 2 | 2 | 45.60 | 45.60 | 2016-01-26 16:39:18 | 2016-01-28 16:39:18 |
| 3 | 3 | 78.90 | 78.90 | 2016-01-26 16:40:22 | 2016-01-28 16:40:22 |
| 4 | 4 | 90.12 | 90.12 | 2016-01-26 16:40:22 | 2016-01-28 16:40:22 |
| 5 | 1 | 5.60 | 3.40 | 2016-01-28 17:30:33 | 2016-01-28 17:30:33 |
| 6 | 2 | 7.89 | 4.56 | 2016-01-28 17:30:33 | 2016-01-28 17:30:33 |
+----+------------+--------------+---------------+---------------------+---------------------+
What I need:
Given a date, I do need to get the newest results and SUM the 'balance_left'. BUT, having the machine_id as unique entry.
For example, if I pass as date 26/01 at 23:59, I would expect to receive the lines with ID 1, 2, 3 and 4. And, the SUM would be 12.30 + 45.60 + 78.90 + 90.12 = 226.92.
Now, if I pass as date 28/01 at 23:59, I would expect to receive the lines with ID 3, 4, 5 and 6. Once that the entries 5 and 6 are the NEWEST entries for the machine_id 1 and 2. In this case, the sum would be: 78.90 +90.12 + 5.60 + 7.89 = 182.51
What I did:
$sub = App\Transaction::orderBy('created_at','DESC');
$transcations = DB::table(DB::raw("({$sub->toSql()}) as sub"))->where('created_at','<=', '2016-01-28 23:59:59')->groupBy('machine_id')->sum('balance_left');
Without the SUM, but using a select('balance_left'), I'm getting the correct lines:
=> [
{#672
+"balance_left": 5.6,
},
{#671
+"balance_left": 7.89,
},
{#643
+"balance_left": 78.9,
},
{#677
+"balance_left": 90.12,
},
]
The problem is that when I request the sum, I'm getting:
=> 17.9
And, after a while, I discovered that actually, he is doing a sum of the machine_id entry, so the lines with ID 1 and 5, have machine_id 1, and then this sum is the sum of 12.30 +5.60 = 17.90
Problem:
How can I actually SUM the lines returned? Or, is there any other better way to return what I'm expecting?
Thanks a lot!

Grouping data in a SSRS report

In developing an SSRS 2008 R2 report, I'll like to show some data grouped by values, while merging others, I've run into a problem doing this on report builder.
| Parent Group |
|_______________________________________|
|Group A|Group B|Group C|Group D|Group E|
|_______|_______|_______|______|________|
| 5 | 2 | 1 | 1 | 5 |
| 4 | 2 | 4 | 2 | 2 |
| 1 | 3 | 1 | 3 | 2 |
Can I create a filter or grouping to combine Group C, D, E together while leaving A and B alone?
Like such,
| Parent Group |
|_____________________________|
|Group A |Group B|Other Groups|
|________|_______|____________|
| 5 | 2 | 7 |
| 4 | 2 | 8 |
| 1 | 3 | 6 |
There are two ways you can achieve this:
SQL query
Usually the best way to get the result you want is to let SQL do the heavy lifting:
SELECT GroupA, GroupB, (GroupC + GroupD + GroupE) AS OtherGroups
FROM MyTable
In the report
If you can't change your query result (for example, it is a stored procedure) then you can do the same thing in SSRS VB code.
Right-click the cell and choose Expression... and enter something like the following:
=Fields!GroupC.Value + Fields!GroupD.Value + Fields!GroupE.Value
and SUM in the same way:
=SUM(Fields!GroupC.Value) + SUM(Fields!GroupD.Value) + SUM(Fields!GroupE.Value)

Getting count of insert/update rows from ON DUPLICATE KEY UPDATE

I have a statement that tries to insert a record and if it already exists, it simply updates the record.
INSERT INTO temptable (col1,col2,col3)
VALUES (1,2,3)
ON DUPLICATE KEY UPDATE col1=VALUES(col1), col2=VALUES(col2), col3=VALUES(col3);
The full statement has multiple inserts and I'm looking to count number of INSERTs against the UPDATEs. Can I do this with MySQL variables, I've yet to find a way to do this after searching.
From Mysql Docs
In the case of "INSERT ... ON DUPLICATE KEY UPDATE" queries, the return value will be 1 if an insert was performed, or 2 for an update of an existing row.
Use mysql_affected_rows() after your query, if INSERT was performed it will give you 1 and if UPDATE was performed it will give you 2.
I've accomplished what you're describing using a while loop so that each iteration creates a MySQL statement that affects one row. Within the loop, I run the mysql_affected_rows() and then increment a counter depending upon whether the value returned was a 0 or a 1. At the end of the loop, I echo both variables for viewing.
The complete wording from MySQL Docs regarding the mysql_affected_rows function is (notice there are 3 possible values returned - 0, 1, or 2):
For INSERT ... ON DUPLICATE KEY UPDATE statements, the affected-rows
value per row is 1 if the row is inserted as a new row, 2 if an
existing row is updated, and 0 if an existing row is set to its
current values. If you specify the CLIENT_FOUND_ROWS flag, the
affected-rows value is 1 (not 0) if an existing row is set to its
current values.
(Sidenote - I set $countUpdate and $countInsert and $countUpdateNoChange to 0 prior to the while loop):
Here's the code that I developed that works great for me:
while (conditions...) {
$sql = "INSERT INTO test_table (control_number, name) VALUES ('123', 'Bob')
ON DUPLICATE KEY UPDATE name = 'Bob'";
mysql_query($sql) OR die('Error: '. mysql_error());
$recordModType = mysql_affected_rows();
if ($recordModType == 0) {
$countUpdateNoChange++;
}elseif($recordModType == 1){
$countInsert++;
}elseif($recordModType == 2){
$countUpdate++;
};
};
echo $countInsert." rows inserted<br>";
echo $countUpdateNoChange." rows updated but no data affected<br>";
echo $countUpdate." rows updated with new data<br><br>";
Hopefully, I haven't made any typos as I've recreated it to share while removing my confidential data.
Hope this helps someone. Good luck coding!
I know this is a bit old, but I was doing a bulk insert in PHP and needed to know exactly how many rows were inserted and updated (separately).
So I used this:
$dataCount = count($arrData); // number of rows in the statement
$affected = mysql_affected_rows(); // mysqli_*, PDO's rowCount() or anything
$updated = $affected - $dataCount;
$inserted = 2 * $dataCount - $affected;
Simple trace table:
-------------------------------
| data | affected | ins | upd |
-------------------------------
| 1 | 1 | 1 | 0 |
-------------------------------
| 2 | 2 | 2 | 0 |
| 2 | 3 | 1 | 1 |
| 2 | 4 | 0 | 2 |
-------------------------------
| 3 | 3 | 3 | 0 |
| 3 | 4 | 2 | 1 |
| 3 | 5 | 1 | 2 |
| 3 | 6 | 0 | 3 |
-------------------------------
| 4 | 4 | 4 | 0 |
| 4 | 5 | 3 | 1 |
| 4 | 6 | 2 | 2 |
| 4 | 7 | 1 | 3 |
| 4 | 8 | 0 | 4 |
-------------------------------
| 5 | 5 | 5 | 0 |
| 5 | 6 | 4 | 1 |
| 5 | 7 | 3 | 2 |
| 5 | 8 | 2 | 3 |
| 5 | 9 | 1 | 4 |
| 5 | 10 | 0 | 5 |
-------------------------------
if you want to get the number of records that have been inserted and updated separetly, you are to issue each statement separetly.