N1QL (CB6.6) ANSI merge - couchbase

I am trying to fetch customer id from ":ao.mos:pro:%" and update corresponding doc ":pd:pro:%", we have thousands of ":ao.mos:pro:%" docs and corresponding ":pd:pro:%" for each mo:pro doc.
I am using below query and it works but it updates only one doc it can find indexes.
MERGE INTO `bucket1` AS d
USING `bucket1` AS p
ON d.icc = p.icc AND META(d).id LIKE ':pd:pro:%' AND META(p).id LIKE ':ao.mos:pro:%'
WHEN MATCHED THEN
UPDATE SET d.customerId = p.customerId;
Any suggestions on how to get this working for all matching docs in the bucket.

The following works.
CREATE INDEX ix11 ON `default` (icc, customerId) WHERE META().id LIKE ":ao.mos:pro:%";
CREATE INDEX ix12 ON `default` (icc, customerId) WHERE META().id LIKE ":pd:pro:%";
INSERT INTO default VALUES(":ao.mos:pro:1", {"icc":1, "customerId":100});
INSERT INTO default VALUES(":ao.mos:pro:2", {"icc":2, "customerId":101});
INSERT INTO default VALUES(":pd:pro:1", {"icc":1});
INSERT INTO default VALUES(":pd:pro:2", {"icc":1});
INSERT INTO default VALUES(":pd:pro:3", {"icc":2});
MERGE INTO `default` AS m
USING (SELECT p1.icc, p1.customerId
FROM `default` AS p1
WHERE META(p1).id LIKE ':ao.mos:pro:%' AND p1.icc IS NOT NULL) AS p
ON m.icc = p.icc AND META(m).id LIKE ':pd:pro:%'
WHEN MATCHED THEN
UPDATE SET m.customerId = p.customerId;
MERGE INTO `default` AS m
USING default AS p
ON m.icc = p.icc AND META(m).id LIKE ':pd:pro:%' AND META(p).id LIKE ':ao.mos:pro:%'
WHEN MATCHED THEN
UPDATE SET m.customerId = p.customerId;
Are you having following issue:
First source row matches 2 target rows. Second source matches same target row (by changing icc value to 1 of document key ":ao.mos:pro:2") then returns error (5320 Multiple UPDATE/DELETE of the same document (document key 'xxx') in a MERGE statement) (as with partial updated rows, You need transactions don't want update anything) as you can't update same row again. To resolve that you need to change your ON clause.

If it is a one off operation you can use Eventing Service and create a function with a bucket alias of "src_bkt" to map to bucket1 in r+w mode and the deployment feed boundary set to "From Everything" can easily do 100's of millions of items quickly without indexes.
function OnUpdate(doc,meta) {
// Filter out all non-interesting items
if (!meta.id.startsWith(":pd:pro:")) return;
// No need to do anything already updated.
if (doc.customerId)return;
var myint = meta.id.substring(8);
var otherkey = ":ao.mos:pro:" + myint;
var otherdoc = src_bkt[otherkey];
if (otherdoc) {
if (doc.icc === otherdoc.icc ) {
// update the field as we have a match and it is missing.
doc.customerId = otherdoc.customerId;
src_bkt[meta.id] = doc;
}
}
}
So now you just insert some test documents using the QWB
INSERT INTO bucket1 VALUES(":ao.mos:pro:1", {"icc":1, "customerId":100});
INSERT INTO bucket1 VALUES(":ao.mos:pro:2", {"icc":2, "customerId":101});
INSERT INTO bucket1 VALUES(":pd:pro:1", {"icc":1});
INSERT INTO bucket1 VALUES(":pd:pro:2", {"icc":1});
INSERT INTO bucket1 VALUES(":pd:pro:3", {"icc":2});
Then inspect the documents you should have
:ao.mos:pro:1{"customerId":100,"icc":1}
:ao.mos:pro:2{"customerId":101,"icc":2}
:pd:pro:1{"icc":1}
:pd:pro:2{"icc":1}
:pd:pro:3{"icc":2}
Deploy the Eventing function and look at your documents again.
:ao.mos:pro:1{"customerId":100,"icc":1}
:ao.mos:pro:2{"customerId":101,"icc":2}
:pd:pro:1{"icc":1,"customerId":100}
:pd:pro:2{"icc":1}
:pd:pro:3{"icc":2}
As expected only one doc with matching keys and matching icc properties updated.
For more performance (doing millions) you can up the workers in the functions settings to 2x the physical cores.

Related

Yii2 mysql how to insert a record into a table where column value already exists and should be unique?

I have a table, say 'mytable' that use a "rank" column that is unique. After having created some record where rank is successively rec A(rank=0), rec B (rank=1), rec C (rank=2), rec D (rank=3), rec E (rank=4).
I need to insert a new record that will take an existing rank, say 1, and modify the rank value of the following records accordingly.
The result being : rec A(rank=0), new rec (rank=1), rec B (rank=2), rec C (rank=3), rec D (rank=4), rec E (rank=5).
How can I do this ? Can this be solved with mysql only or should I write some important bunch of code in PHP (Yii2) ?
Assuming no rank is skipped you need to shift existing ranks before saving the new record. To do that you can use beforeSave() method of your ActiveRecord like this:
class MyModel extends \yii\db\ActiveRecord
{
public function beforeSave($insert)
{
if (!parent::beforeSave($insert)) {
return false;
}
if ($insert) { //only if we are saving new record
{
$query = self::find()
->where(['rank' => $this->rank]);
if ($query->exists()) { //check if the rank is already present in DB
//we will create the query directly because yii2
// doesn't support order by for update
$command = static::getDb()->createCommand(
"UPDATE " . static::tableName() .
" SET rank = rank + 1 WHERE rank >= :rank ORDER BY rank DESC",
[':rank' => $this->rank]
);
$command->execute();
}
}
return true;
}
// ... other content of your model ...
}
MySQL allows use of ORDER BY in UPDATE query, that will help us deal with fact that doing UPDATE on table is not atomic and the UNIQUE constraint is checked after each row is updated.
It would be more problematic if there are skipped ranks. In that case you will need to shift ranks only until you hit first skipped rank.
Another option might be creating an before insert trigger on the table that would do the rank shifting.
Note:
It might be a good idea to also implement afterDelete method to shift the ranks in oposite direction when some record is removed to avoid skipped ranks.
Resources:
\yii\db\BaseActiveRecord::beforeSave()
\yii\db\ActiveRecord::updateAllCounters() - replaced with direct update
MySQL triggers
MySQL UPDATE syntax

Iterating over records in one table and adding them to another with calculations

I'm currently using PHP and MySQL to retrieve a set of 100,000 records in a table, then iterate over each of those records to do some calculations and then insert the result into another table. I'm wondering if I'd be able to do this in pure SQL and make the query run faster.
Here's what I"m currently using:
$stmt= $pdo->query("
SELECT Well_Permit_Num
, Gas_Quantity
, Gas_Production_Days
FROM DEP_OG_Production_Import
ORDER
BY id ASC
");
foreach ($stmt as $row) {
$data = array('well_id' => $row['Well_Permit_Num'],
'gas_quantity' => $row['Gas_Quantity'],
'gas_days' => $row['Gas_Production_Days'],
'gas_average' => ($row['Gas_Production_Days']);
$updateTot = $pdo->prepare("INSERT INTO DEP_OG_TOTALS
(Well_Permit_Num,
Total_Gas,
Total_Gas_Days,
Total_Gas_Avg)
VALUES (:well_id,
:gas_quantity,
:gas_days,
:gas_average)
ON DUPLICATE KEY UPDATE
Total_Gas = Total_Gas + VALUES(Total_Gas),
Total_Gas_Days = Total_Gas_Days + VALUES(Total_Gas_Days),
Total_Gas_Avg =(Total_Gas + VALUES(Total_Gas)) / (Total_Gas_Days + VALUES(Total_Gas_Days))");
}
I'd like to see if this can be done in pure MySQL instead of having to use PHP just for the fact of using it to hold the variables.
My Result should be 1 record that is a running total for each Well. The source table may house 60-70 records for the same well, but over a few thousand different Wells.
It's a constant import process that has to be run, so it's not like there is a final table which you can just do SUM(Gas_Quantity)... etc.. on
As commented by Uueerdo, you seem to need an INSERT ... SELECT query. The role of such query is to INSERT insert the resultset returned by an inner SELECT. The inner select is an aggregate query that computes the total sum of gas and days for each well.
INSERT INTO DEP_OG_TOTALS (Well_Permit_Num, Total_Gas, Total_Gas_Days, Total_Gas_Avg)
SELECT
t.Well_Permit_Num,
SUM(t.Gas_Quantity) Total_Gas,
SUM(t.Gas_Production_Days) Total_Gas_Days
FROM DEP_OG_Production_Import t
GROUP BY t.Well_Permit_Num
ON DUPLICATE KEY UPDATE
Total_Gas = Total_Gas + t.Total_Gas,
Total_Gas_Days = Total_Gas_Days + t.Total_Gas_Days,
Total_Gas_Avg =(Total_Gas + t.Total_Gas) / (Total_Gas_Days + t.Total_Gas_Days)

Stored Procedure With Function giving me errors in Oracle

I have stored procedure and function and I am calling the function in the stored procedure in ORACLE.The function CalculateIncomeTax is what is giving me errors.In MSSQL,this type of update is possible because I have done it before.I called the function in the stored procedure.When I read around the answer I get is to use a package before I cannot use a function to update a table from another table.Please if you have any idea,tell me.The error I get is
table string.string is mutating, trigger/function may not see it
Cause: A trigger (or a user defined plsql function that is referenced in this statement) attempted to look at (or modify) a table that was in the middle of being modified by the statement which fired it.
Action: Rewrite the trigger (or function) so it does not read that table.
This is function
CREATE OR REPLACE function CalculateIncomeTax(periodId NVARCHAR2,
employeeId NVARCHAR2, taxableIncome NUMBER)return NUMBER
AS
IncomeTax NUMBER (18,4);Taxable NUMBER(18,4);
BEGIN
SELECT SUM(CASE WHEN (taxableIncome > T.TAX_CUMMULATIVE_AMOUNT)
THEN (taxableIncome - T.TAX_CUMMULATIVE_AMOUNT)* T.TAX_PERCENTAGE/ 100
ELSE 0.00 END ) INTO IncomeTax
FROM TAX_LAW T JOIN PAY_GROUP P ON P.PAY_FORMULA_ID =T.TAX_FORMULA_ID
JOIN PAYROLL_MASTER PP ON P.PAY_CODE =PP.PAY_PAY_GROUP_CODE
WHERE PP.PAY_EMPLOYEE_ID = employeeId AND PP.PAY_PERIOD_CODE = periodId;
if IncomeTax IS NULL THEN IncomeTax :=0;
end if;
return IncomeTax;
end;/
This is the stored procedure
CREATE OR REPLACE PROCEDURE PROCESSPAYROLLMASTER (periodcode
VARCHAR2) AS BEGIN
INSERT INTO PAYROLL_MASTER
(
PAY_PAYROLL_ID,PAY_EMPLOYEE_ID ,PAY_EMPLOYEE_NAME,PAY_SALARY_GRADE_CODE
,PAY_SALARY_NOTCH_CODE,PAY_BASIC_SALARY,PAY_TOTAL_ALLOWANCE
,PAY_TOTAL_CASH_BENEFIT,PAY_MEDICAL_BENEFIT,PAY_TOTAL_BENEFIT
,PAY_TOTAL_DEDUCTION,PAY_GROSS_SALARY,PAY_TOTAL_TAXABLE,PAY_INCOME_TAX
,PAY_TAXABLE,PAY_PERIOD_CODE,PAY_BANK_CODE,PAY_BANK_NAME,PAY_BANK_ACCOUNT_NO
,PAY_PAY_GROUP_CODE )
SELECT
1,
E.EMP_ID AS PAY_EMPLOYEE_ID ,
E.EMP_FIRST_NAME || ' ' || E.EMP_LAST_NAME AS PAY_EMPLOYEE_NAME,
E.EMP_RANK_CODE,
'CODE',
(SC.SAL_MINIMUM_AMOUNT+( SN.SAL_SALARY_PERCENTAGE *
SC.SAL_MINIMUM_AMOUNT)/100) AS PAY_BASIC_SALARY,
0,
0,
0,
0,
0,
0,
0,
0,
0,
periodcode,
'BANKCODE',
'BANKNAME',
'BANKNUMBER',
'GENERAL'
FROM EMPLOYEE E
LEFT JOIN SALARY_SCALE SC ON SC.SAL_RANK_CODE = E.EMP_RANK_CODE
LEFT JOIN SALARY_NOTCH SN ON SC.SAL_ID = SN.SAL_SALARYSCALE_ID
WHERE E.EMP_RANK_CODE = SC.SAL_RANK_CODE AND E.EMP_STATUS=2;
CALCULATEALLOWANCE(v_payrollId,periodcode);
CALCULATECASHBENEFITS(v_payrollId,periodcode);
CALCULATEDEDUCTIONS(v_payrollId,periodcode);
-- UPDATE PAYROLL PAY_INCOME_TAX
UPDATE PAYROLL_MASTER PM SET PM.PAY_INCOME_TAX = CalculateIncomeTax(PM.PAY_PERIOD_CODE,PM.PAY_EMPLOYEE_ID,PM.PAY_TOTAL_TAXABLE) WHERE PM.PAY_PAYROLL_ID = v_payrollId;
UPDATE PAYROLL_PROCESS set PAY_CANCELLED = 1 WHERE PAY_PAY_GROUP_CODE='GENERAL' AND PAY_PERIOD_CODE=periodcode
AND PAY_ID<>v_payrollId;
COMMIT;
END ;
/
The function is querying the same table you are updating, which is what the error is reporting. As it happens you are not changing the value of the column you're querying, but Oracle doesn't check to that level - not least because there could be, for instance, a trigger that has less obvious side-effects.
The best solution really would be to not have to update at all, and to calculate and set all the value as part of the original insert, by joining to all the relevant tables. But you are already calling other procedures which are, presumably, updating some of the values you're inserting as zeros, including pay_total_taxable.
Unless you're able to reevaluate those as well, you may be stuck with doing a further update. In which case, you could remove the reference to the payroll_master table from the function and instead pass in the relevant data.
I think this is equivalent, though with out the table structures, sample data and what the other procedures are doing it's hard to be sure (so this is untested, obviously):
create or replace function calculateincometax (
p_periodid nvarchar2,
p_employeeid nvarchar2,
p_paypaygroupcode payroll_master.pay_pay_group_code%type,
p_taxableincome number
) return number as
l_incometax number(18, 4);
begin
select coalesce(sum(case when p_taxableincome > t.tax_cummulative_amount
then (taxableincome - t.tax_cummulative_amount) * t.tax_percentage / 100
else 0 end), 0)
into l_incometax
from tax_law t
join pay_group p
on p.pay_formula_id = t.tax_formula_id
where p.pay_code = p_paypaygroupcode;
return l_incometax;
end;
/
and then include the extra argument in your call:
update payroll_master pm
set pm.pay_income_tax = calculateincometax(pm.pay_period_code, pm.pay_employee_id,
pm.pay_pay_group_code, pm.pay_total_taxable)
where pm.pay_payroll_id = v_payrollid;
Although v_payrollid isn't defined in what you've shown, so even that isn't entirely clear.
I've also modified the function argument and local variable names with prefixes to remove potential ambiguity (which you seem to do by removing underscores from the names), removed the unused variable, and added a coalesce() call in place of the separate null check. Those things aren't directly relevant to the approach though.

Merge 2 tables in MySQL

I would like to merge to tables in MySQL. In SQL I would use the 'MERGE' command, but what is the equivalent command in MySQL? Lets say i have 3 columns in both tables. Then i want to match the rows by the first column, and if there is a match it needs to update 2nd column but keep the original 3rd column and if there isnt a match then it needs to insert the new row.
Here is the SQL code I would like to convert to MySQL.
MERGE [Synsbasen].[dbo].[Koeretoej] AS T
USING [Synsbasen].[dbo].[KoeretoejLoad] AS S ON (T.KoeretoejIdent = S.KoeretoejIdent)
WHEN NOT MATCHED BY TARGET
THEN INSERT(KoeretoejIdent, KoeretoejArtNavn, KoeretoejAnvendelseNavn, RegistreringNummerNummer, KoeretoejOplysningStatus, KoeretoejOplysningFoersteRegistreringDato, KoeretoejOplysningStelNummer, KoeretoejMaerkeTypeNavn, KoeretoejModelTypeNavn, KoeretoejVariantTypeNavn, DrivkraftTypeNavn, SynResultatSynsType, SynResultatSynsDato, SynResultatSynStatusDato, SidsteSynTjek)
VALUES(S.KoeretoejIdent, S.KoeretoejArtNavn, S.KoeretoejAnvendelseNavn, S.RegistreringNummerNummer, S.KoeretoejOplysningStatus, S.KoeretoejOplysningFoersteRegistreringDato, S.KoeretoejOplysningStelNummer, S.KoeretoejMaerkeTypeNavn, S.KoeretoejModelTypeNavn, S.KoeretoejVariantTypeNavn, S.DrivkraftTypeNavn, S.SynResultatSynsType, S.SynResultatSynsDato, S.SynResultatSynStatusDato, CONVERT(VARCHAR(10),'1900-01-01',110))
WHEN MATCHED
THEN UPDATE SET
T.KoeretoejArtNavn = S.KoeretoejArtNavn,
T.KoeretoejAnvendelseNavn = S.KoeretoejAnvendelseNavn,
T.RegistreringNummerNummer = S.RegistreringNummerNummer,
T.KoeretoejOplysningStatus = S.KoeretoejOplysningStatus,
T.KoeretoejOplysningFoersteRegistreringDato = S.KoeretoejOplysningFoersteRegistreringDato,
T.KoeretoejOplysningStelNummer = S.KoeretoejOplysningStelNummer,
T.KoeretoejMaerkeTypeNavn = S.KoeretoejMaerkeTypeNavn,
T.KoeretoejModelTypeNavn = S.KoeretoejModelTypeNavn,
T.KoeretoejVariantTypeNavn = S.KoeretoejVariantTypeNavn,
T.DrivkraftTypeNavn = S.DrivkraftTypeNavn,
T.SynResultatSynsType = S.SynResultatSynsType,
T.SynResultatSynsDato = S.SynResultatSynsDato,
T.SynResultatSynStatusDato = S.SynResultatSynStatusDato;
Look at: https://dev.mysql.com/doc/refman/5.7/en/insert-on-duplicate.html
Your query should be something like this:
INSERT into Koeretoej
(KoeretoejIdent, KoeretoejArtNavn, KoeretoejAnvendelseNavn,
RegistreringNummerNummer, KoeretoejOplysningStatus,
KoeretoejOplysningFoersteRegistreringDato, KoeretoejOplysningStelNummer,
KoeretoejMaerkeTypeNavn, KoeretoejModelTypeNavn, KoeretoejVariantTypeNavn,
DrivkraftTypeNavn, SynResultatSynsType, SynResultatSynsDato,
SynResultatSynStatusDato, SidsteSynTjek)
SELECT
S.KoeretoejIdent, S.KoeretoejArtNavn, S.KoeretoejAnvendelseNavn,
S.RegistreringNummerNummer, S.KoeretoejOplysningStatus,
S.KoeretoejOplysningFoersteRegistreringDato,
S.KoeretoejOplysningStelNummer, S.KoeretoejMaerkeTypeNavn,
S.KoeretoejModelTypeNavn, S.KoeretoejVariantTypeNavn,
S.DrivkraftTypeNavn, S.SynResultatSynsType, S.SynResultatSynsDato,
S.SynResultatSynStatusDato, DATE_FORMAT("19000101","%m-%d-%Y")
FROM KoeretoejLoad S LEFT JOIN Koeretoej T ON
T.KoeretoejIdent = S.KoeretoejIdent
ON DUPLICATE KEY UPDATE
KoeretoejArtNavn=S.KoeretoejArtNavn,
KoeretoejAnvendelseNavn=S.KoeretoejAnvendelseNavn,
RegistreringNummerNummer=S.RegistreringNummerNummer,
KoeretoejOplysningStatus=S.KoeretoejOplysningStatus,
KoeretoejOplysningFoersteRegistreringDato=S.KoeretoejOplysningFoersteRegistreringDato,
KoeretoejOplysningStelNummer=S.KoeretoejOplysningStelNummer,
KoeretoejMaerkeTypeNavn=S.KoeretoejMaerkeTypeNavn,
KoeretoejModelTypeNavn=S.KoeretoejModelTypeNavn,
KoeretoejVariantTypeNavn=S.KoeretoejVariantTypeNavn,
DrivkraftTypeNavn=S.DrivkraftTypeNavn,
SynResultatSynsType=S.SynResultatSynsType,
SynResultatSynsDato=S.SynResultatSynsDato,
SynResultatSynStatusDato=S.SynResultatSynStatusDato,
SidsteSynTjek=DATE_FORMAT("19000101","%m-%d-%Y")

Merge not inserting. No error

Can someone tell me why this insert is failing but not giving me an error either? How do I fix this?
merge table1 as T1
using(select p.1,p.2,p.3,p.4,p.5 from #parameters p
inner join table1 t2
on p.1 = t2.1
and p.2 = t2.2
and p.3 = t2.3
and p.4 = t2.4) as SRC on SRC.2 = T1.2
when not matched then insert (p.1,p.2,p.3,p.4,p.5)
values (SRC.1,SRC.2,SRC.3,SRC.4,SRC.5)
when matched then update set t1.5 = SRC.5;
The T1 table is currently empty so nothing can match. The parameters table does have data in it. I simply need to modify this merge so that it checks all 4 fields before deciding what to do.
You can't select from a variable: from #parameters
See the following post: Using a variable for table name in 'From' clause in SQL Server 2008
Actually, you can use a variable table. Check it out:
MERGE Target_table AS [Target]
USING #parameters AS [Source]
ON (
[Target].col1 = [Source].col1
AND [Target].col2 = [Source].col2
AND [Target].col3 = [Source].col3
AND [Target].col4 = [Source].col4
)
WHEN NOT MATCHED BY TARGET
THEN INSERT (col1,col2,col3,col4,col5)
VALUES (
[Source].col1
,[Source].col2
,[Source].col3
,[Source].col4
,[Source].col5
)
WHEN MATCHED
THEN UPDATE SET [Target].col5 = [Source].col5;