Postgresql - Copy row and set uid to new column - sql

I'm working on a user management system and I need to copy a user to a "backup" table before the user will be deleted. How can I set the id to the new column userid while id on both tables are unique?
users
+----+------+-------------+--+
| id | lang | email | |
+----+------+-------------+--+
| 20 | en | test#ya.hoo | |
+----+------+-------------+--+
delusers
+----+--------+------+-------------+
| id | userid | lang | email |
+----+--------+------+-------------+
| 1 | 20 | en | test#ya.hoo |
+----+--------+------+-------------+

First, if an user cannot be deleted twice, the delusers.id could be the PRIMARY KEY of delusers table, and you could use the value of the id of the user itself. There is no need of id and userid on delusers table.
Then, you can just INSERT on delusers and DELETE on users (inside the same transaction, of course):
BEGIN;
INSERT INTO delusers(id,lang,email)
SELECT id,lang,email FROM users WHERE id = 20;
DELETE FROM users WHERE id = 20;
COMMIT;
You could also do that on same command (using CTE):
WITH deleted AS (
DELETE FROM users WHERE id = 20 RETURNING id, lang, email
)
INSERT INTO delusers(id,lang,email)
SELECT id,lang,email FROM deleted;
The last is good for some reasons:
You don't need to explicit open a transaction if this is all you are going to do (it would make no harm in opening it though);
You can delete lots of users at same time;
PostgreSQL does not need to find for the users at users table more than once (one for SELECT and other for DELETE). The same could be achieved with cursors though.

Related

Is there a way to insert a record in SQL server if it does not match the latest version of the record based on three of the columns?

Consider the following table named UserAttributes:
+----+--------+----------+-----------+
| Id | UserId | AttrName | AttrValue |
+----+--------+----------+-----------+
| 4 | 1 | FavFood | Apples |
| 3 | 2 | FavFood | Burgers |
| 2 | 1 | FavShape | Circle |
| 1 | 1 | FavFood | Chicken |
+----+--------+----------+-----------+
I would like to insert a new record in this table if the latest version of a particular attribute for a user has a value that does not match the latest.
What I mean by the latest is, for example, if I was to do:
SELECT TOP(1) * FROM [UserAttributes] WHERE [UserId] = 1 AND [AttrName] = 'FavFood' ORDER BY [Id] DESC
I will be able to see that user ID 1's current favorite food is "Apples".
Is there a query safe for concurrency that will only insert a new favorite food if it doesn't match the current favorite food for this user?
I tried using the MERGE query with a HOLDLOCK, but the problem is that WHEN MATCHED/WHEN NOT MATCHED, and that works if I never want to insert a new record after a user has previously set their favorite food (in this example) to the new value. However, it does not consider that a user might switch to a new favorite food, then subsequently change back to their old favorite food. I would like to maintain all the changes as a historical record.
In the data set above, I would like to insert a new record if the user ID 1's new favorite food is "Burgers", but I do not want to insert a record if their new favorite food is "Apples" (since that is their current favorite food). I would also like to make this operation safe for concurrency.
Thank you for your help!
EDIT: I should probably also mention that when I split this operation into two queries (ie: first select their current favorite food, then do an insert query only if there is a new food detected) it works under normal conditions. However, we are observing race conditions (and therefore duplicates) since (as you may have guessed) the data set above is simply an example and there are many threads operating on this table at the same time.
A bit ugly, but to do it in one command, you could insert the user's (new) favorite food but filter with an EXCEPT of their current values.
e.g., (assuming the user's new data is in #UserID, #FavFood
; WITH LatestFavFood AS
(SELECT TOP(1) UserID, AttrName, AttrValue
FROM [UserAttributes]
WHERE [UserId] = #UserID AND [AttrName] = 'FavFood'
ORDER BY [Id] DESC
)
INSERT INTO UserAttributes (UserID, AttrName, AttrValue)
SELECT #UserID, 'FavFood', #FavFood
EXCEPT
SELECT UserID, AttrName, AttrValue
FROM LatestFavFood
Here's a DB_Fiddle with three runs.
EDIT: I have changed the above to assume varchar types for AttrName rather than nvarchar. The fiddle has a mixture. Would be good to ensure you get them correct (especially food as it may have special characters).

Business logic for identify SQL column update

I have a SQL table called contacts which as n number of rows where n is more than 10Lakh (1 million) rows.
Below is the table structure with dummy data
+---------------+------------+---------+-----------+---------+------------------------+
| email | department | title | state | country | cleansing_verification |
+---------------+------------+---------+-----------+---------+------------------------+
| xyz#email.com | h.r. | sr. Exe | telangana | Ind | - |
+---------------+------------+---------+-----------+---------+------------------------+
So, I have 4 schedulers to cleanse the data which is present in the above table, ie
department cleanser
title cleanser
state cleanser
country cleanser
Each cleanser will update the data of the respective columns. I have added one more column call cleansing_verification to identify which column is updated but not able to use properly.
One email can be touched by any of the cleansers. Which means all 4 can update the value or any 3 or any 2 or only 1.
So, the problem is I'm facing is How to identify which email is touched and which is not so that for remaining I can send an email notification.
If something more need let me know I will add in the question.
Thanks in advance.
So normally we don't do this in the world of database design, but you could use bitfields. So your cleansing_verification is a BIT(4) type column, and each cleanser gets a bit they can set:
department = B'1000'
title = B'0100'
state = B'0010'
country = B'0001'
When running i.e. state, you would then:
UPDATE contacts
SET cleansing_verification = cleansing_verification | B'0010'
WHERE -- whatever conditions you want to apply
If you wanted to check which rows were updated by a given cleanser, you check if the bit is set, e.g. for state:
SELECT * FROM contacts WHERE cleansing_verification & B'0010' = B'0010'
Working example on dbfiddle
Actually proper way to do it would be to introduce a new table with a foreign key back to the contacts table and a column for a cleanser, like (quick'n'dirty example):
CREATE TABLE contacts_verification
(
contact_id int references contacts(id),
cleanser int
)
Then if you want to mark a record you just insert the contact id and some sort of cleanser identification (1, 2, 3, 4), or you can use a text field and meaningful names if you really want:
INSERT INTO contacts_verification (contact_id, cleanser) VALUES (21386, 1)
Then just use JOIN to get back the records marked by a cleanser:
SELECT c.*
FORM contacts c
JOIN contacts_verification dep_verify
ON dep_verify.contact_id = c.id
AND dep_verify.cleanser = 1

Multiple records in a table matched with a column

The architecture of my DB involves records in a Tags table. Each record in the Tags table has a string which is a Name and a foreign kery to the PrimaryID's of records in another Worker table.
Records in the Worker table have tags. Every time we create a Tag for a worker, we add a new row in the Tags table with the inputted Name and foreign key to the worker's PrimaryID. Therefore, we can have multiple Tags with different names per same worker.
Worker Table
ID | Worker Name | Other Information
__________________________________________________________________
1 | Worker1 | ..........................
2 | Worker2 | ..........................
3 | Worker3 | ..........................
4 | Worker4 | ..........................
Tags Table
ID |Foreign Key(WorkerID) | Name
__________________________________________________________________
1 | 1 | foo
2 | 1 | bar
3 | 2 | foo
5 | 3 | foo
6 | 3 | bar
7 | 3 | baz
8 | 1 | qux
My goal is to filter WorkerID's based on an inputted table of strings. I want to get the set of WorkerID's that have the same tags as the inputted ones. For example, if the inputted strings are foo and bar, I would like to return WorkerID's 1 and 3. Any idea how to do this? I was thinking something to do with GROUP BY or JOINING tables. I am new to SQL and can't seem to figure it out.
This is a variant of relational division. Here's one attempt:
select workerid
from tags
where name in ('foo', 'bar')
group by workerid
having count(distinct name) = 2
You can use the following:
select WorkerID
from tags where name in ('foo', 'bar')
group by WorkerID
having count(*) = 2
and this will retrieve your desired result/
Regards.
This article is an excellent resource on the subject.
While the answer from #Lennart works fine in Query Analyzer, you're not going to be able to duplicate that in a stored procedure or from a consuming application without opening yourself up to SQL injection attacks. To extend the solution, you'll want to look into passing your list of tags as a table-valued parameter since SQL doesn't support arrays.
Essentially, you create a custom type in the database that mimics a table with only one column:
CREATE TYPE list_of_tags AS TABLE (t varchar(50) NOT NULL PRIMARY KEY)
Then you populate an instance of that type in memory:
DECLARE #mylist list_of_tags
INSERT #mylist (t) VALUES('foo'),('bar')
Then you can select against that as a join using the GROUP BY/HAVING described in the previous answers:
select workerid
from tags inner join #mylist on tag = t
group by workerid
having count(distinct name) = 2
*Note: I'm not at a computer where I can test the query. If someone sees a flaw in my query, please let me know and I'll happily correct it and thank them.

How to get sum of values per id and update existing records in other table

I have two tables like:
ID | TRAFFIC
fd56756 | 4398
645effa | 567899
894fac6 | 611900
894fac6 | 567899
and
USER | ID | TRAFFIC
andrew | fd56756 | 0
peter | 645effa | 0
john | 894fac6 | 0
I need to get SUM ("TRAFFIC") from first table AND set column traffic to the second table where first table ID = second table ID. ID's from first table are not unique, and can be duplicated.
How can I do this?
Table names from your later comment. Chances are, you are reporting table and column names incorrectly.
UPDATE users u
SET "TRAFFIC" = sub.sum_traffic
FROM (
SELECT "ID", sum("TRAFFIC") AS sum_traffic
FROM stats.traffic
GROUP BY 1
) sub
WHERE u."ID" = sub."ID";
Aside: It's unwise to use mixed-case identifiers in Postgres. Use legal, lower-case identifiers, which do not need to be double-quoted, to make your life easier. Start by reading the manual here.
Something like this?
UPDATE users t2 SET t2.traffic = t1.sum_traffic FROM
(SELECT sum(t1.traffic) t1.sum_traffic FROM stats.traffic t1)
WHERE t1.id = t2.id;

Selecting Unread Documents in PostgreSQL (JOINS?)

I have a PostgreSQL table containing users, a PostgreSQL table containing documents, and a table mapping the users to the documents they've read. Like so:
Table: users
------------
| oid |
| username |
| ... |
------------
Table: documents
------------
| oid |
| title |
| ... |
------------
Table: users_documents
-----------------
| oid |
| user_oid |
| document_oid |
-----------------
Whenever a user reads a document, a record is added to users_documents with their user id and the document id. This is all working fine.
What I want to do though is select a random unread document for a given user. I feel like I should be able to do this with a fairly simple JOIN, but I can't get my head around exactly what the query should look like.
Can someone help please? Thanks.
For most data, you can go to the users_documents table. For instance,
a query of the documents read by user_oid = ? would be
SELECT document_oid
FROM users_documents
WHERE user_oid = ?
However, you want the records which are missing.
You can do an outer join, and find the NULLs in the results.
SELECT users_documents.document_oid
FROM users_documents
RIGHT OUTER JOIN documents
ON users_documents.document_oid = documents.oid AND users_documents.user_oid = ?
It is important that the "users_documents.user_oid = ?" be in the join in the FROM clause rather than in a WHERE clause, because it would not work in the WHERE clause.