mysql - satisfy composite primary key while using 'insert into xxx select' - mysql

I am importing data to a table structured: content_id|user_id|count - all integers all comprise the composite primary key
The table I want to select it from is structured: content_id|user_id
For reasons quite specific to my use case, I will need to fire quite a lot of data into this regularly enough to want a pure MySQL solution
insert into new_db.table
select content_id,user_id,xxx from old_db.table
I want each row to go in with xxx set to 0, unless this would create a duplicate key, in which case I wish to increment the number, for the current user_id/content_id combination
Not being a MySQL expert, I tried a few options like trying to populate xxx by selecting from the target table during insert, with no luck. Also tried using ON DUPLICATE KEY to increment counters instead of the usual UPDATE. But it all seemed a bit daft so I thought I would come here!
Any ideas anyone? I have a backup option of wrapping this in PHP, but it would drastically raise the overall running time of the script in which this would be the only non-pure MySQL part
Any help really appreciated. thanks in advance!
--edit
this may sound really awful in principle. but id settle for a way to do it in an update after entering random numbers (i have sent in random numbers to allow me to continue other work at the moment) - and this is a purely dev setup
--edit again
12|234
51|45
51|45
51|45
23|67
would ideally insert
12|234|0
51|45|0
51|45|1
51|45|2
23|67|0

INSERT INTO new_db.table (content_id, user_id, cnt)
SELECT old.content_id, old.user_id, COUNT(old.*) - 1 FROM old_db.table old
GROUP BY old.content_id, old.user_id
this would be the way I would go, so if 1 entry it would put 0 on cnt, for more it would just put 1-2-3 etc.
Edit:
Your correct answer would be somewhat complicated but I tested it and it works:
INSERT INTO newtable(user_id,content_id,cnt)
SELECT o1.user_id, o1.content_id,
CASE
WHEN COALESCE(#rownum, 0) = 0
THEN #rownum:=c-1
ELSE #rownum:=#rownum-1
END as cnt
FROM
(SELECT user_id, content_id, COUNT(*) as c FROM oldtable
GROUP BY user_id, content_id ) as grpd
LEFT JOIN
(SELECT oldtable.* FROM oldtable) o1 ON
(o1.user_id = grpd.user_id AND o1.content_id = grpd.content_id)
;

Assuming that in the old db table (source), you will not have the same (content_id, user_id) combination, then you can import using this query
insert newdbtable
select o.content_id, o.user_id, ifnull(max(n.`count`),-1)+1
from olddbtable o
left join newdbtable n on n.content_id=o.content_id and n.user_id=o.user_id
group by o.content_id, o.user_id;

Related

Auto Incremental serial number for MySQL View

Having a issue with my project need to insert an auto incremental value for my MySQL view, I would be nice if you guys help in solving this obstacle, Here is the code in which I wanna have auto incremental serial number (say S.No) as the first column.
CREATE
ALGORITHM = UNDEFINED
DEFINER = `srems_admin`#`localhost`
SQL SECURITY DEFINER
VIEW `emp_elec_consumption_view` AS
SELECT
`t1`.`PFNUMBER` AS `PFNUMBER`,
`emp`.`EMPNAME` AS `EMPNAME`,
`t1`.`MonthAndYear` AS `MonthAndYear`,
`qt`.`QTRSCODE` AS `QTRSCODE`,
`t1`.`UNITS_CONSUMED` AS `UNITS_CONSUMED`,
(`t2`.`FIXED_COMPONENT` + (`t1`.`UNITS_CONSUMED` * `t2`.`RATE_COMPONENT`)) AS `Amount`
FROM
(((`srems`.`mstqtroccu` `qt`
JOIN `srems`.`mstemp` `emp`)
JOIN `srems`.`msttariffrate` `t2`)
JOIN (SELECT
`srems`.`tranmeterreading`.`PFNUMBER` AS `PFNUMBER`,
(`srems`.`tranmeterreading`.`CLOSINGREADING` - `srems`.`tranmeterreading`.`OPENINGREADING`) AS `UNITS_CONSUMED`,
CONCAT(CONVERT( IF((LENGTH(MONTH(`srems`.`tranmeterreading`.`READINGDATE`)) > 1), MONTH(`srems`.`tranmeterreading`.`READINGDATE`), CONCAT('0', MONTH(`srems`.`tranmeterreading`.`READINGDATE`))) USING UTF8), '/', RIGHT(YEAR(`srems`.`tranmeterreading`.`READINGDATE`), 2)) AS `MonthAndYear`,
(SELECT
`t`.`TRANSACTIONID`
FROM
`srems`.`msttariffrate` `t`
WHERE
(`t`.`TORANGE` > (`srems`.`tranmeterreading`.`CLOSINGREADING` - `srems`.`tranmeterreading`.`OPENINGREADING`))
LIMIT 1) AS `tariffplanid`
FROM
`srems`.`tranmeterreading`) `t1`)
WHERE
((`t1`.`tariffplanid` = `t2`.`TRANSACTIONID`)
AND (`t1`.`PFNUMBER` = `qt`.`PFNUMBER`)
AND (`t1`.`PFNUMBER` = `emp`.`PFNUMBER`))
Pls insert the things at the correct place and post it as an comment to get S.No which should be auto-incremental starting from 1 and also it should be the first column, ty in advance
Your view has no chance of working in MySQL anyway so you might as well give up.
MySQL does not allow subqueries in the FROM clause. And your query is pretty complicated with lots of subqueries.
It also does not allow variables, so getting a row number is rather complicated.

MySQL INSERT-SELECT a non-mandatory field with JOIN

I have a table (netStream), that has 2 foreign keys: (logSessions_logSessionID) and (accountSessions_accountSessionID).
The (logSessions_logSessionID) is mandatory the (accountSessions_accountSessionID) is NOT mandatory.
Here is the part of the block-scheme that shows the connections and the non-mandatory status:
(The background: logSessions are the sessions that every visitor have, accountSessions are the login sessions. Everybody has a logSession (since everybody is a visitor), but not everybody is logged in, so they do not have accountSession)
I want to insert a row into (netStream), in every case there is a (logSession), but it is not the same with (accountSession). So, when there is an (accountSession), I want to insert that ID too, if there is no (accountSession), then just leave that field in (netStream) NULL.
The hash values are stored in Binary(x), this is why I use UNHEX().
This is the MySQL I wrote, there is no error message, but it does not work. What is the problem?
INSERT INTO `test-db`.`netStream` (`netStreamHash`, `logSessions_logSessionID`, `accountSessions_accountSessionID`)
SELECT UNHEX("1faab"), `logSessions`.`logSessionID`, NULL FROM `logSessions` CROSS JOIN `accountSessions`
WHERE `logSessions`.`logSessionHash` = UNHEX("aac") AND
`accountSessions`.`accountSessionHash` = UNHEX("2fb");
If understand you correctly you are probably looking for something like this
INSERT INTO `test-db`.`netStream` (
`netStreamHash`,
`logSessions_logSessionID`,
`accountSessions_accountSessionID`)
SELECT UNHEX("1faab"),
(SELECT `logSessionID` FROM `logSessions`
WHERE `logSessionHash` = UNHEX("aac")),
(SELECT `accountSessionID` FROM `accountSessions`
WHERE `accountSessionHash` = UNHEX("2fb"));
If there is no matching row in accountSessions then you'll get NULL inserted in accountSessions_accountSessionID in netStream table

Adding Data Values inside a data table

Hey guys how do you add two values on separate fields but on the same table
for example:
tblbooks
Quantity
Borrowed
each time a user issue a book to a borrower the Quantity its reduce by 1 and Borrowed is added by 1....
INSERT INTO tablename(field1,field2)
VALUES(v1,v2)
In your case I guess you need to update.
Update yourtable
SET Quantity =Quantity-1,
Borrowed=Borrowed+1
Where userid=1
The way I generally do it is select the row I want to update using LinQ and then just update the values.
For example:
With (From rw In tblBooks Select rw Where rw.Item("MyCondition").ToString = "Condition").First
.Item("Quantity") = .Item("Quantity") - 1
.Item("Borrowed") = .Item("Borrowed") + 1
End With
... I didn't test this code, and it doesn't take into account conversion, error checking, etc, but I hope it conveys the idea...

How can I sanitize my DB from these duplicates

I have a table with the following fields:
id | domainname | domain_certificate_no | keyvalue
An example for the output of a select statement can be as:
'57092', '02a1fae.netsolstores.com', '02a1fae.netsolstores.com_1', '55525772666'
'57093', '02a1fae.netsolstores.com', '02a1fae.netsolstores.com_2', '22225554186'
'57094', '02a1fae.netsolstores.com', '02a1fae.netsolstores.com_3', '22444356259'
'97168', '02aa6aa.netsolstores.com', '02aa6aa.netsolstores.com_1', '55525772666'
'97169', '02aa6aa.netsolstores.com', '02aa6aa.netsolstores.com_2', '22225554186'
'97170', '02aa6aa.netsolstores.com', '02aa6aa.netsolstores.com_3', '22444356259’
I need to sanitize my db such that: I want to remove the domain names that have repeated keyvalue for the first domain_certificate_no (i.e, in this example, I look for the field domain_certificate_no: 02aa6aa.netsolstores.com_1, since it is number 1, and has repeated value for the key, then I want to remove the whole chain which is 02aa6aa.netsolstores.com_2 and 02aa6aa.netsolstores.com_3 and this by deleting the domain name that this chain belongs to which is 02aa6aa.netsolstores.com.
How can I automate the checking process for the whole DB. So, I have a query that checks any domain name in the pattern ('%.%.%) EDIT: AND they have share domain name (in this ex: netsolstores.com) , if it finds cert no. 1 that belongs to this domain name has a repeated key value, then delete. Otherwise no. Please, note tat, it is ok for domain_certificate_no to have repeated value if it is not number 1.
EDIT: I only compare the repeated valeues for the same second level domain name. Ex: in this question, I compare the values that share the domain name: .netsolstores.com. If I have another domain name, with sublevel domains, I do the same. But the point is that I don't need to compare the whole DB. Only the values with shared domain name (but different sub domain).
I'm not sure what happens with '02aa6aa.netsolstores.com_1' in your example.
The following keeps only the minimum id for any repeated key:
with t as (
select t.*,
substr(domain_certificate_no,
instr(domain_certificate_no, '_') + 1, 1000) as version,
left(domain_certificate_no, instr(domain_certificate_no, '_') - 1) as dcn
from t
)
select t.*
from t join
(select keyvalue, min(dcn) as mindcn
from t
group by keyvalue
) tsum
on t.keyvalue = tsum.keyvalue and
t.dcn = tsum.mindcn
For the data you provide, this seems to do the trick. This will not return the "_1" version of the repeats. If that is important, the query can be pretty easily modified.
Although I prefer to be more positive (thinking about the rows to keep rather than delete), the following should delete what you want:
with t as (
select t.*,
substr(domain_certificate_no,
instr(domain_certificate_no, '_') + 1, 1000) as version,
left(domain_certificate_no, instr(domain_certificate_no, '_') - 1) as dcn
from t
),
tokeep as (
select t.*
from t join
(select keyvalue, min(dcn) as mindcn
from t
group by keyvalue
) tsum
on t.keyvalue = tsum.keyvalue and
t.dcn = tsum.mindcn
)
delete from t
where t.id not in (select id from tokeep)
There are other ways to express this that are possibly more efficient (depending on the database). This, though, keeps the structure of the original query.
By the way, when trying new DELETE code, be sure that you stash a copy of the table. It is easy to make a mistake with DELETE (and UPDATE). For instance, if you leave out the WHERE clause, all the rows will disappear, after the long painful process of logging all of them. You might find it faster to simply select the desired results into a new table, validate them, then truncate the old table and re-insert them.

Increment string with %name%+(num) in mysql

Is there way to realize this algorithm with mysql without 100500 queries and lots of resources?
if (exists %name% in table.name) {
num = 2;
while(exists %newname%+(num) in table.name) num++;
%name% = newname+(num);
}
Thanks
I don't know how much better you can do with a stored procedure in MySql, but you can definitely do better than 100500 queries:
SELECT name FROM table WHERE name LIKE 'somename%' ORDER BY name DESC LIMIT 1
At that point, you know that you can increment the number at the end of name and the result will be unused.
I 'm glossing over some fine print (this approach will never find and fill any "holes" in the naming scheme that may exist, and it's still not guaranteed that the name will be available due to race conditions), but in practice it can be made to work quite easily.
The simpliest way I can see of doing it is to create a table of sequential numbers
then cross join on to it....
SELECT a.name,b.id
FROM table a
WHERE a.name = 'somename'
CROSS JOIN atableofsequentialnumbers b
WHERE NOT EXISTS (SELECT 1 FROM table x WHERE x.name = CONCAT(a.name,b.id))
LIMIT 10
This will return the first 10 available numbers/names