Update with an incremented value based on corresponding row - php

Here's a problem for the PHP-juggler in you.
I want to use plain-ol' mysql_* functions of PHP.
I have the following MySQL table:
+-----+-----------------+
| id | thread |
+-----+-----------------+
| 14 | 01/ |
| 14 | 02/ |
| 14 | 03/ |
| 15 | 01/ |
| 22 | 01/ |
| 24 | XXX |
| 24 | XXX |
| 24 | XXX |
| 24 | XXX |
| 32 | XXX |
| 32 | XXX |
+-----+-----------------+
The "XXX" values are my making. I want to change (UPDATE) that table to this one:
+-----+-----------------+
| id | thread |
+-----+-----------------+
| 14 | 01/ <- |
| 14 | 02/ |
| 14 | 03/ |
| 15 | 01/ <- |
| 22 | 01/ <- |
| 24 | 01/ <- |
| 24 | 02/ |
| 24 | 03/ |
| 24 | 04/ |
| 32 | 01/ <- |
| 32 | 02/ |
+-----+-----------------+
On every new value of the "id" field (where the "<-" is; my making, also), the "thread" field value has to reset itself to "01/" and continue incrementing until a new value of "id" is found.
I've tried querying with COUNT(id) to increment somehow. I tried storing in arrays. I thought of mysql_data_seek() also. Alas, I don't seem to pull it off.
I got the "thread" format right, though:
$thread = $i < 10 ? "0$i" : $i;
So, if it's bigger than 10, it doesn't get a leading zero. But this is just the fun part.
Any help would be appreciated.
Thanks

SET #oldid = 0;
SET #counter = 0;
UPDATE tablename
SET thread = CONCAT(
LPAD(
CAST(IF(id = #oldid,
#counter := #counter + 1, -- same id, increment
#counter := (#oldid := id)/id) -- other id, set to 1
AS UNSIGNED),
2,'0'), -- pad with '0'
'/') -- append '/'
WHERE thread = 'XXX' -- or enumerate the whole thing if need be
ORDER BY id, thread;
Which can just be fed to "plain ol' mysql_query" (3 in a row: feed the SET & UPDATE queries separately, alternatively forget about SETting anything, I just hate uninitialized variables ;)

Set PRIMARY KEY the tuple (id,thread) and set thread (but not the id!) as AUTO_INCREMENT, then
run the query
INSERT INTO mytable (id) VALUES (24),(24),(32),(24),(32),(24)
and thread attribute should be set autoincrementally. If you insist on "0n/" form, I suggest to create thread_string attribute and create BEFORE UPDATE trigger according to NEW.thread attribute.
Does it work?

Related

Add New Record Value, & Previous Record Value MySQL

I am busy creating a new table in my DB, and have some issues populating the correct values.
The table consists of a number of columns
| Date | CustomerID | SKUCode | NewValueCaptured |PreviousDate| PreviousValueCaptured |
|:----------|:-----------|:--------|:-----------------|:-----------|-----------------------|
| 2022-07-01| 123456 | 1028 | 10 | NULL | NULL |
| 2022-07-09| 123456 | 1028 | 15 | 2022-07-01 | 10 |
| 2022-07-12| 123456 | 1028 | 25 | 2022-07-01 | 15 |
| 2022-07-12| 123456 | 1029 | 8 | NULL | NULL |
| 2022-07-01| 789123 | 1028 | 20 | NULL | NULL |
| 2022-07-09| 789123 | 1028 | 10 | 2022-07-01 | 20 |
| 2022-07-01| 789123 | 1029 | 25 | NULL | NULL |
| 2022-07-09| 789123 | 1029 | 13 | 2022-07-01 | 25 |
Using the UPDATE ON DUPLICATE KEY, is not an option here, as I need to keep each and every record, however, adding only the previous value to the new record.
Existing Query:
INSERT IGNORE INTO CS_data (Date, CustomerID , SKUCode, NewValueCaptured, PreviousDate, PreviousValueCaptured)
SELECT * FROM (SELECT DISTINCT
DWH.Date 'SRCDate'
, DWH.CustomerID
, CASE
WHEN DWH.SKUCode IS NOT NULL THEN DWH.SKUCode
ELSE DWH.SKUCode
END 'SKUCode'
, DWH.ValueCaptured 'SRCValueCaptured'
) SRC
ON DUPLICATE KEY UPDATE
PreviousDate = Date
, PreviousValueCaptured = NewValueCaptured
, Date = SRCDate
, NewValueCaptured= SRCValueCaptured;
How do I achieve the above table results? Rather than updating the existing record.
Thanks
It seems that you need in something like (demo only)
INSERT INTO destination_table (
Date,
CustomerID,
SKUCode,
NewValueCaptured,
PreviousDate,
PreviousValueCaptured
)
SELECT Date,
CustomerID,
SKUCode,
ValueCaptured,
-- get Date value from previous row, if not exists use the value from current row
COALESCE(LAG(Date) OVER (PARTITION BY CustomerID, SKUCode ORDER BY Date),
Date),
-- and the same for ValueCaptured
COALESCE(LAG(ValueCaptured) OVER (PARTITION BY CustomerID, SKUCode ORDER BY Date),
ValueCaptured)
FROM source_table;
Documentation: Window functions.

MariaDB ignores ORDER BY in SELECT

I installed new version of MariaDB and got problem with sorting php-script:
$this->db->query("
SET #v:=0;
UPDATE `users` AS `c1`
LEFT JOIN (SELECT `id`, (#v:=#v+2) AS `ord2`, `name`, `parent`
FROM `users`
WHERE `parent`='0' ORDER BY `ord` ASC) AS `c2`
ON `c1`.`id` = `c2`.`id`
SET `c1`.`ord` = `c2`.`ord2`
WHERE `c1`.`parent` = '0'
");
In particular script sets order of entries in users table sorting them by ord:
+----+-----+--------------------+--------+
| id | ord | name | parent |
+----+-----+--------------------+--------+
| 2 | 2 | admin | 0 |
| 10 | 5 | manager | 0 |
| 12 | 7 | user | 0 |
| 11 | 9 | dev | 0 |
+----+-----+--------------------+--------+
I'm not familiar with SQL and after long hours of searching and tests I, as it seems to me, found out the SQL-query that doesn't work right:
SELECT `id`, (#v:=#v+2) AS `ord2`, `name`, `parent`
FROM `users`
WHERE `parent`='0' ORDER BY `ord` ASC;
In previous version of MariaDB (5.5.5-10.1.25) the query gives entries sorted by initial order (ord):
+----+-----+--------------------+--------+
| id | ord2| name | parent |
+----+-----+--------------------+--------+
| 2 | 2 | admin | 0 |
| 10 | 4 | manager | 0 |
| 12 | 6 | user | 0 |
| 11 | 8 | dev | 0 |
+----+-----+--------------------+--------+
In new version (10.6.3) result is:
+----+-----+--------------------+--------+
| id | ord2| name | parent |
+----+-----+--------------------+--------+
| 2 | 2 | admin | 0 |
| 10 | 4 | manager | 0 |
| 12 | 8 | user | 0 |
| 11 | 6 | dev | 0 |
+----+-----+--------------------+--------+
I tried to set ORDER BY param ord to name and id, but result was the same. It seems like col ord2 filled with even numbers is always sorted by id in ascending order.
How should I rewrite the query or the script to fix the problem?
In latest MariaDB versions you can use window function row_number in next way:
select
id,
(row_number() over (order by ord))*2 ard2,
name,
parent
from tbl;
MariaDB query test here

How to update all rows with column = to value except 1, then update that one to another value?

I have a MySQL OFFERS table with an OID, PID and STATUS. STATUS has 3 ENUM values. O A C. (Open, Accepted, Closed). It looks like this:
+---------+-------------+--------+
| oid | pid | status |
+---------+-------------+--------+
| 1 | 1 | o |
| 2 | 1 | o |
| 3 | 1 | o |
| 4 | 2 | o |
+---------+-------------+--------+
All offers are open.
When a user wants to accept an offer, they click a button, which sends the post ID (PID) and the offer ID (OID) to the php file which does the UPDATE statement to the database. I want the statement to update the table to look like this:
+---------+-------------+--------+
| oid | pid | status |
+---------+-------------+--------+
| 1 | 1 | a |
| 2 | 1 | c |
| 3 | 1 | c |
| 4 | 2 | o |
+---------+-------------+--------+
Psudo-PHP-MySQLI statement:
UPDATE offers
SET status='c'
WHERE pid = $_POST['pid'], except WHERE oid = $_POST['oid']
but also SET status='a' WHERE oid = $_POST['oid']
I am new to PHP and MySQLi so I dont know how to structure all of the statements. Is this kind of statement even possible? I know to use prepared statements, but I am just adding the values in here just for simplicity. Thanks!
Use CASE to specify different values to assign depending on a condition.
UPDATE offers
SET status =
CASE oid
WHEN $_POST['oid'] THEN 'a'
ELSE 'c'
END
WHERE pid = $_POST['pid']

PHP/MySQL Compare against a date

I'm running into a little issue concerning a datetime picker and MySQL.
I have a PHP script that should get records before (and including) the selected day. However, the dataset returns empty.
Here's the SQL part of the script:
$sql = 'SELECT `pro_title`,
`pro_part_number`,
`name` AS flag_name,
`old_val`,
`new_val`
FROM `products`
INNER JOIN `flags` ON `id` = `pro_flag_id`
INNER JOIN `flag_history` ON `pro_part_number` = `part`
WHERE `pro_flag_id` IN(:flags)
AND STR_TO_DATE(`ts`, "%y-%m-%d") <= :dateTs;';
I then use PDO to bind the params:
array(
':flags' => implode(',', $flags), # this outputs 1,2,3
':dateTs' => $date # this outputs 2019-04-30
)
I've also tried changing <= to >= to no avail (not that it should work, but thought I'd try).
I've come across a good few SO posts but nothing has actually got me there. Here is the description of the flag_history table (where ts is stored)
MariaDB [mastern]> describe `flag_history`;
+---------+-------------+------+-----+-------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------+-------------+------+-----+-------------------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| part | varchar(25) | NO | | NULL | |
| ts | datetime | YES | | CURRENT_TIMESTAMP | |
| old_val | int(4) | YES | | NULL | |
| new_val | int(4) | YES | | NULL | |
+---------+-------------+------+-----+-------------------+----------------+
And some example data:
MariaDB [mastern]> select * from `flag_history` order by `id` desc limit 5;
+-------+----------+---------------------+---------+---------+
| id | part | ts | old_val | new_val |
+-------+----------+---------------------+---------+---------+
| 24026 | PART-001 | 2019-04-30 09:42:22 | 0 | 3 |
| 24025 | PART-002 | 2019-04-30 09:42:22 | 0 | 3 |
| 24024 | PART-003 | 2019-04-30 09:42:22 | 0 | 3 |
| 24023 | PART-004 | 2019-04-30 09:42:22 | 0 | 3 |
| 24022 | PART-005 | 2019-04-30 09:42:22 | 0 | 3 |
+-------+----------+---------------------+---------+---------+
Then, using PART-001 to make sure the flag_id is actually set:
MariaDB [mastern]> select `pro_flag_id` from `products` where `pro_part_number` = "PART-001";
+-------------+
| pro_flag_id |
+-------------+
| 3 |
+-------------+
So I'm not really sure what's going wrong, the logic and everything to me (MySQL newb) looks like it should work but it's giving me empty data. I also tried changing the INNER JOIN's to LEFT JOIN's but again, didn't work.
What am I doing wrong?
I managed to solve it. As I'm really reading the flag_history table, I changed the main SELECT focus to flag_history, revised SQL:
$sql = 'SELECT `old_val`, `new_val`, `ts`, `flags`.`name` AS flag_name, `pro_part_number`, `pro_title`
FROM `flag_history`
INNER JOIN `products` ON `pro_part_number` = `part`
INNER JOIN `flags` ON `pro_flag_id` = `flags`.`id`
WHERE `ts` <= :dateTs;';
I slowly added in the INNER JOIN's and managed to get it working when using flag_history. I think the issue actually may have been down to the id field. Being an ambiguous field (flags and flag_history have the id column). Weirdly, I wasn't getting that error though (maybe as I wasn't selecting). Either way managed to resolve it by being more specific with my select and going from the read-table rather than joining it.
On a further note, removing the flags segment from the JOIN shows the constraint error. So my guess is that it didn't show first time as I was reading from a different table.

Optimization of SQL with subquery and Having

Currently we are using a custom CI library to generate PDF files from documents which exist as database records in our database.
Each document is related to the contents (== rows) with a one-has-many relation. Each row has a number (field: row_span) to indicate how many lines it will use once it gets printed in the PDF.
Per PDF page that gets build, Rows needed for that page only are selected using a subquery:
$where = $this->docType."_id = ".$this->data['doc']->id." AND visible = 1";
$sql = "SELECT *,
(SELECT
sum(row_span) FROM app_".$this->docType."_rows X
WHERE X.position <= O.position
AND ".$where."
ORDER BY position ASC) 'span_total'
FROM app_".$this->docType."_rows O
WHERE ".$where."
HAVING span_total > ".(($i-1)*$this->maxRows)." AND span_total <= ".($i*$this->maxRows)." ORDER BY O.position ASC ";
$rows = $rows->query($sql);
In the code $i is the page number and $this->maxRows is loaded from the document template record which indicates how many available lines the PDF template has.
So when the SQL renders it might look like this for page 1 of an order with ID 834:
SELECT `app_order_rows`.*,
(SELECT SUM(`app_order_rows_subquery`.`row_span`) AS row_span
FROM `app_order_rows` `app_order_rows_subquery`
WHERE `app_order_rows_subquery`.`position` <= 'app_order_rows.position'
AND `app_order_rows_subquery`.`order_id` = 834
AND `app_order_rows_subquery`.`visible` = 1
ORDER BY `app_order_rows_subquery`.`position` asc) AS span_total
FROM (`app_order_rows`)
WHERE `app_order_rows`.`order_id` = 834
AND `app_order_rows`.`visible` = 1
HAVING span_total > 0
AND span_total <= 45
ORDER BY `app_order_rows`.`position` asc
And running this with EXPLAIN gives this as output:
+====+=============+=========================+======+===============+======+=========+======+======+=============================+===+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | |
+====+=============+=========================+======+===============+======+=========+======+======+=============================+===+
| 1 | PRIMARY | app_order_rows | ALL | NULL | NULL | NULL | NULL | 1809 | Using where; Using filesort | 1 |
+----+-------------+-------------------------+------+---------------+------+---------+------+------+-----------------------------+---+
| 2 | SUBQUERY | app_order_rows_subquery | ALL | NULL | NULL | NULL | NULL | 1809 | Using where | 2 |
+====+=============+=========================+======+===============+======+=========+======+======+=============================+===+
This is working great, but... When we have large orders or invoices it renders the documents very slow. This might be due to the subquery.
Does anyone have an idea on how to do the same select without subquery? Maybe we will have to go for a whole new approach to select rows and build the PDF. We are open for suggestions ^^
Thanks in advance
------------------------------- edit ------------------------------
The EXPLAIN after index creation:
+====+=============+=========================+=======+===============+============+=========+=======+======+=============+===+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | |
+====+=============+=========================+=======+===============+============+=========+=======+======+=============+===+
| 1 | PRIMARY | app_order_rows | ref | index_main | index_main | 5 | const | 9 | Using where | 1 |
+----+-------------+-------------------------+-------+---------------+------------+---------+-------+------+-------------+---+
| 2 | SUBQUERY | app_order_rows_subquery | range | index_main | index_main | 10 | NULL | 1 | Using where | 2 |
+====+=============+=========================+=======+===============+============+=========+=======+======+=============+===+
As you confirmed in the comments, the tables have no indexes.
The immediate solution would be:
create index index_main on app_order_rows (order_id, position);

Categories