SQL records with common ID - update all with single user defined function call - sql

ClientInfo Table
------------------------------------------------------------
||ClientInfoID | ClientID | FName | MName | LName ||
||1 | 1 | Don | A | Smith||
||2 | 1 | Dan | A | Smith||
||3 | 1 | Dan | G | Smith||
||4 | 2 | John | D | Doe ||
------------------------------------------------------------
Trying to get an sql call right in SQL Server. I've written a user defined function that generates a random first/middle/last names which is working fine. My challenge is that I want ALL records with the same ClientID to get updated with the result of a single call to my rename function (actually 3 calls = 1 for first, middle, and last name).
My attempt below is chaning EVERY record in ClientInfo DIFFERENT names instead of giving all ClientID = 1 records the SAME f/m/last names, ClientID = 2 the SAME f/m/last names, etc.
DESIRED RESULT:
------------------------------------------------------------
||ClientInfoID | ClientID | FName | MName | LName ||
||1 | 1 | Bill | X | Brown ||
||2 | 1 | Bill | X | Brown ||
||3 | 1 | Bill | X | Brown ||
||4 | 2 | Kate | Q | Ramirez ||
------------------------------------------------------------
ACTUAL RESULT:
----------------------------------------------------------------
|| ClientInfoID |ClientID | FName | MName | LName ||
|| 1 | 1 | Bill | X | Brown ||
|| 2 | 1 | Sue | R | Henderson||
|| 3 | 1 | Phil | S | Anders ||
|| 4 | 2 | Kate | Q | Ramirez ||
----------------------------------------------------------------
My SQL call
UPDATE ClientInfo
SET FirstName = X.NewFirstName
,MiddleName = X.NewMiddleName
,LastName = X.NewLastName
FROM (
SELECT UniqueClientID
,LastClientInfoID
,NewFirstName
,NewMiddleName
,NewLastName
FROM (
(
SELECT ClientID AS UniqueClientID
,MAX(ClientInfoID) AS LastClientInfoID
FROM ClientInfo
GROUP BY ClientID
) A INNER JOIN (
SELECT ClientInfoID
,NewFirstName = dbo.fnSampleFnameMnameLname(0, #MaxName, '')
,NewMiddleName = dbo.fnSampleFnameMnameLname(1, #MaxName, MiddleName)
,NewLastName = dbo.fnSampleFnameMnameLname(2, #MaxName, '')
FROM ClientInfo
) B ON A.LastClientInfoID = B.ClientInfoID
)
) X
WHERE ClientID = X.UniqueClientID

Solved it. Moved the creation of names attached to each clientid into a temp table first. Then just joined on clientinfo on that temp table to pull in the new sample names.
SELECT ClientID, NewFirstName, NewMiddleName, NewLastName
INTO #TempSampleNames
FROM (
(
SELECT ClientID,
MAX(ClientInfoID) MaxClientInfoID
FROM ClientInfo
GROUP BY ClientID
) A
INNER JOIN (
SELECT ClientInfoID
,NewFirstName = dbo.fnSampleFnameMnameLname(0, #MaxName, '')
,NewMiddleName = dbo.fnSampleFnameMnameLname(1, #MaxName, MiddleName)
,NewLastName = dbo.fnSampleFnameMnameLname(2, #MaxName, '')
FROM ClientInfo
) B ON A.MaxClientInfoID = B.ClientInfoID
)
UPDATE ClientInfo
SET
FirstName = B.NewFirstName
,MiddleName = B.NewMiddleName
,LastName = B.NewLastName
FROM ClientInfo A
INNER JOIN #TempSampleNames B ON A.ClientID = B.ClientID
DROP TABLE #TempSampleNames

Assuming that you have a Client table, and ClientID is a primary or unique key in that table, and ClientInfo has a one-to-many relationship with it on ClientID, then you could simply do this:
UPDATE A
SET
FirstName = B.NewFirstName
,MiddleName = B.NewMiddleName
,LastName = B.NewLastName
FROM ClientInfo A
INNER JOIN (
SELECT ClientID
,NewFirstName = dbo.fnSampleFnameMnameLname(0, #MaxName, '')
,NewMiddleName = dbo.fnSampleFnameMnameLname(1, #MaxName, MiddleName)
,NewLastName = dbo.fnSampleFnameMnameLname(2, #MaxName, '')
FROM Client
) B ON A.ClientID = B.ClientID

I would change the function from scalar to table valued
That's just a proof of concept. I fill the existing FirstName into the function to proof that it's always the same with one client...
ATTENTION: I used your data as given in your question. One 'Don' should rather be a 'Dan' I assume...
EDIT: expand example...
CREATE FUNCTION dbo.fnSampleFNameMnameLname
(
#Inp VARCHAR(30) --don't quite understand the #Maxname...
)
RETURNS TABLE
AS
--Create your Names in one go (this is inline syntax, maybe you'd do it with multi statement syntax...)
RETURN SELECT #Inp+'FName1' AS FName,#Inp+'MName1' AS MName,#Inp+'LName1' AS LName;
GO
DECLARE #ClientInfo TABLE(ClientInfoID INT,ClientID INT, FName VARCHAR(30),MName VARCHAR(30),LName VARCHAR(30));
INSERT INTO #ClientInfo VALUES
(1,1,'Don','A','Smith')
,(1,1,'Dan','A','Smith')
,(1,1,'Dan','A','Smith')
,(1,1,'John','D','Doe');
SELECT * FROM #ClientInfo AS ci
CROSS APPLY dbo.fnSampleFNameMnameLname(ci.FName) AS NewNames

Related

SQL Query Find Exact and Near Dupes

I have a SQL table with FirstName, LastName, Add1 and other fields. I am working to get this data cleaned up. There are a few instances of likely dupes -
All 3 columns are the exact same for more than 1 record
The First and Last are the same, only 1 has an address, the other is blank
The First and Last are similar (John | Doe vs John C. | Doe) and the address is the same or one is blank
I'm wanting to generate a query I can provide to the users, so they can check these records out, compare their related records and then delete the one they don't need.
I've been looking at similarity functions, soundex, and such, but it all seems so complicated. Is there an easy way to do this?
Thanks!
Edit:
So here is some sample data:
FirstName | LastName | Add1
John | Doe | 1 Main St
John | Doe |
John A. | Doe |
Jane | Doe | 2 Union Ave
Jane B. | Doe | 2 Union Ave
Alex | Smith | 3 Broad St
Chris | Anderson | 4 South Blvd
Chris | Anderson | 4 South Blvd
I really like Critical Error's query for identifying all different types of dupes. That would give me the above sample data, with the Alex Smith result not included, because there are no dupes for that.
What I want to do is take that result set and identify which are dupes for Jane Doe. She should only have 2 dupes. John Doe has 3, and Chris Anderson has 2. Can I get at that sub-result set?
Edit:
I figured it out! I will be marking Critical Error's answer as the solution, since it totally got me where I needed to go. Here is the solution, in case it might help others. Basically, this is what we are doing.
Selecting the records from the table where there are dupes
Adding a WHERE EXISTS sub-query to look in the same table for exact dupes, where the ID from the main query and sub-query do not match
Adding a WHERE EXISTS sub-query to look in the same table for similar dupes, using a Difference factor between duplicative columns, where the ID from the main query and sub-query do not match
Adding a WHERE EXISTS sub-query to look in the same table for dupes on 2 fields where a 3rd may be null for one of the records, where the ID from the main query and sub-query do not match
Each subquery is connected with an OR, so that any kind of duplicate is found
At the end of each sub-query add a nested requirement that either the main query or sub-query be the ID of the record you are looking to identify duplicates for.
DECLARE #CID AS INT
SET ANSI_NULLS ON
SET NOCOUNT ON;
SET #CID = 12345
BEGIN
SELECT
*
FROM #Customers c
WHERE
-- Exact duplicates.
EXISTS (
SELECT * FROM #Customers x WHERE
x.FirstName = c.FirstName
AND x.LastName = c.LastName
AND x.Add1 = c.Add1
AND x.Id <> c.Id
AND (x.ID = #CID OR c.ID = #CID)
)
-- Match First/Last name are same/similar and the address is same.
OR EXISTS (
SELECT * FROM #Customers x WHERE
DIFFERENCE( x.FirstName, c.FirstName ) = 4
AND DIFFERENCE( x.LastName, c.LastName ) = 4
AND x.Add1 = c.Add1
AND x.Id <> c.Id
AND (x.ID = #CID OR c.ID = #CID)
)
-- Match First/Last name and one address exists.
OR EXISTS (
SELECT * FROM #Customers x WHERE
x.FirstName = c.FirstName
AND x.LastName = c.LastName
AND x.Id <> c.Id
AND (
x.Add1 IS NULL AND c.Add1 IS NOT NULL
OR
x.Add1 IS NOT NULL AND c.Add1 IS NULL
)
AND (x.ID = #CID OR c.ID = #CID)
);
Assuming you have a unique id between records, you can give this a try:
DECLARE #Customers table ( FirstName varchar(50), LastName varchar(50), Add1 varchar(50), Id int IDENTITY(1,1) );
INSERT INTO #Customers ( FirstName, LastName, Add1 ) VALUES
( 'John', 'Doe', '123 Anywhere Ln' ),
( 'John', 'Doe', '123 Anywhere Ln' ),
( 'John', 'Doe', NULL ),
( 'John C.', 'Doe', '123 Anywhere Ln' ),
( 'John C.', 'Doe', '15673 SW Liar Dr' );
SELECT
*
FROM #Customers c
WHERE
-- Exact duplicates.
EXISTS (
SELECT * FROM #Customers x WHERE
x.FirstName = c.FirstName
AND x.LastName = c.LastName
AND x.Add1 = c.Add1
AND x.Id <> c.Id
)
-- Match First/Last name are same/similar and the address is same.
OR EXISTS (
SELECT * FROM #Customers x WHERE
DIFFERENCE( x.FirstName, c.FirstName ) = 4
AND DIFFERENCE( x.LastName, c.LastName ) = 4
AND x.Add1 = c.Add1
AND x.Id <> c.Id
)
-- Match First/Last name and one address exists.
OR EXISTS (
SELECT * FROM #Customers x WHERE
x.FirstName = c.FirstName
AND x.LastName = c.LastName
AND x.Id <> c.Id
AND (
x.Add1 IS NULL AND c.Add1 IS NOT NULL
OR
x.Add1 IS NOT NULL AND c.Add1 IS NULL
)
);
Returns
+-----------+----------+-----------------+----+
| FirstName | LastName | Add1 | Id |
+-----------+----------+-----------------+----+
| John | Doe | 123 Anywhere Ln | 1 |
| John | Doe | 123 Anywhere Ln | 2 |
| John | Doe | NULL | 3 |
| John C. | Doe | 123 Anywhere Ln | 4 |
+-----------+----------+-----------------+----+
Initial resultset:
+-----------+----------+------------------+----+
| FirstName | LastName | Add1 | Id |
+-----------+----------+------------------+----+
| John | Doe | 123 Anywhere Ln | 1 |
| John | Doe | 123 Anywhere Ln | 2 |
| John | Doe | NULL | 3 |
| John C. | Doe | 123 Anywhere Ln | 4 |
| John C. | Doe | 15673 SW Liar Dr | 5 |
+-----------+----------+------------------+----+

One result from 2 rows same table

I have the following table
ID | Person | Type | Function
-----------------------------
1 | John | 1 | 1
2 | Smith | 1 | 2
I want do a query to get a single result from both rows. The first column is Person as ProjectLead where function = 1 and type = 1 and the second column is Person as Stakeholder where function = 2 and type = 1
ProjectLead | Stakeholder
-----------------------------
John | Smith
Does anyone have any suggestions?
You could use a self join on the type:
SELECT projectlead, stakeholder
FROM (SELECT person AS projectlead, type
FROM mytable
WHERE function = 1) p
JOIN (SELECT person AS stakeholder, type
FROM mytable
WHERE function = 2) s ON p.type = s.type
SelfJoin on your table
SELECT ProjectLead = ProjectLead.Person,
Stakeholder = StakeHolder.Person
FROM [YourTableName] AS ProjectLead
LEFT JOIN [YourTableName] AS StakeHolder ON StakeHolder [Function] = 2 AND StakeHolder.[TYPE] = 1
WHERE ProjectLead.[Function] = 1 AND ProjectLead.[TYPE] = 1

SQL SELECT multiple keys/values

I've got a table PERSON_PROPERTIES that resembles the following :
| ID | KEY | VALUE | PERSON_ID |
| 1 | fname | robert | 1 |
| 2 | lname | redford | 1 |
| 3 | fname | robert | 2 |
| 4 | lname | de niro | 2 |
| 5 | fname | shawn | 3 |
| 6 | nname | redford | 3 |
I would like to SELECT (in JPQL or in PSQL) the PERSON_ID that matches the given fname and lname.
I've tried
`SELECT DISTINCT *
FROM PERSON_PROPERTIES t0
WHERE ((((t0.key = 'fname')
AND (t0.value = 'robert'))
AND ((t0.key = 'lname')
AND (t0.value = 'redford'))))`
but it returns me no value.
I've also tried
`SELECT DISTINCT *
FROM PERSON_PROPERTIES t0
WHERE ((((t0.key = 'fname')
AND (t0.value = 'robert'))
OR ((t0.key = 'lname')
AND (t0.value = 'redford'))))`
but this way it returns me all values. I don't know how to turn the query properly for it to give me only value 1.
SELECT PERSON_ID
FROM PERSON_PROPERTIES
group by PERSON_ID
having sum(case when key = 'fname' and value = 'robert' then 1 else 0 end) > 0
and sum(case when key = 'lname' and value = 'redford' then 1 else 0 end) > 0
Groupy by the person and select only those having both values.
Another approach would be with subselect (caution, it's MS SQL 2012)
SELECT PERSON_ID
FROM PERSON_PROPERTIES
WHERE [Key] = 'fname' AND value = 'robert'
AND PERSON_ID in
(SELECT PERSON_ID FROM PERSON_PROPERTIES WHERE [Key] = 'lname' AND value = 'redford')
Fiddle Demo
Along with some colleagues we came to this answer :
SELECT p.PERSON_ID
FROM PERSON_PROPERTIES p
WHERE (p.key = 'fname' AND p.value = 'robert')
OR (p.key = 'lname' AND p.value = 'redford')
GROUP BY p.PERSON_ID
HAVING count(*) = 2
What do you think about it?
SELF JOIN also does the trick. DISTINCT for duplicate person_id:
SELECT DISTINCT a.PERSON_ID
FROM PERSON_PROPERTIES a JOIN PERSON_PROPERTIES b ON a.PERSON_ID = b.PERSON_ID
WHERE a.the_key = 'fname' AND a.value = 'robert'
AND b.the_key = 'lname' AND b.value = 'redford';
Demo
OK I will be marking this as the correct answer. The only thing I did was modified it a bit
SELECT Y.*, M.* FROM wp_postmeta as Y JOIN wp_postmeta AS M USING (`post_id`)
WHERE (Y.meta_key = 'agam_post_options_year' AND Y.meta_value = 2013)
AND (M.meta_key = 'agam_post_options_month' AND M.meta_value BETWEEN 0 AND 12 )
GROUP BY Y.meta_value, M.meta_value ORDER BY M.meta_value+0 DESC
So I get that DESC order.. however.. I noticed that it does not duplicates results... I had two posts with the same year and same month... now I don't see it... is there anything there that's preventing this ?

Combining multiple rows in SQL Server

I can't figure this out myself. Hope someone could help me.
I have three tables: users, cards, referees and users_cards
Users:
id | userName | referee_id
----------------------------
1 | u1 | 1
2 | u2 | 1
3 | u3 | 2
Referees:
id | refName
--------------
1 | ref1
2 | ref2
Cards:
id | cardName
--------------
1 | card1
2 | card2
Users_Cards:
user_id | card_id | color | number
-------------------------------------------
1 | 1 | red | 123
1 | 2 | yellow | 312
2 | 2 | yellow | 523
3 | 1 | red | 344
The result I want to get is:
id | userName | refName | cards
1 | u1 | ref1 | card1: red (123), card2: yellow (312)
2 | u2 | ref1 | card2: yellow (523)
3 | u3 | ref2 | card1: red (344)
And so on...
All I can get is multiple rows when user has more than a one card. So how can I combine them like this.
Thank you to anyone, who could help me!ยด
EDIT:
Currently I'm just using LEFT JOINs
SELECT UserName, refName cardName, color, number,
FROM users
LEFT JOIN referees ON users.referee_id = referees.id
LEFT JOIN users_cards ON users.id = users_cards.user_id
LEFT JOIN cards ON dbo.users_cards.card_id = cards.id
SELECT users.id, UserName, refName,
cards = (
SELECT
STUFF((
SELECT ', card' + CAST(ROW_NUMBER() OVER (ORDER BY cards.id) AS VARCHAR)
+': '+ color + '(' + CAST(number AS VARCHAR) +')'
FROM
users_cards
LEFT JOIN cards ON dbo.users_cards.card_id = cards.id
WHERE users.id = users_cards.user_id
FOR XML PATH('')), 1, 1, '')
)
FROM users
LEFT JOIN referees ON users.refereeid = referees.id
I think that there is no easy way to format the result of a table and append it to your result.
You can try doing a stored procedure with the following content:
CREATE TABLE #temp
(
user_id int,
CardString nvarchar(50)
)
CREATE TABLE #userCards
(
user_id int,
CardsList nvarchar(50)
)
DECLARE #UserID int,
#cardName nvarchar(50),
#cardList nvarchar(250)
INSERT INTO #temp
SELECT user_id, cardName + ': ' + color + ' (' + number + ')'
FROM User_Cards AS UC
INNER JOIN Cards AS C ON C.card_id = UC.card_id
WHILE EXISTS (SELECT * FROM #temp)
BEGIN
SELECT TOP(1) #UserID = user_id FROM #temp
SET #cardList = ''
WHILE EXISTS (SELECT * FROM #temp WHERE user_id = #UserID)
BEGIN
SELECT TOP(1) #cardName = [CardString] FROM #temp WHERE user_id = #UserID
IF #cardList <> ''
SET #cardList = #cardList + ', '
SET #cardList = #cardList + #cardName
END
INSERT INTO #userCards
VALUES (user_id, #cardList)
END
SELECT users.id, users.userName, referees.refName, CardsList
FROM users
LEFT JOIN referees ON users.referee_id = referees.id
LEFT JOIN #userCards ON #userCards.card_id = users.user_id
DROP TABLE #userCards
DROP TABLE #temp
This will return the content you wanted.
You can also define an aggregate function (see http://msdn.microsoft.com/en-us/library/ms190678.aspx)

rows as columns sql

I have 2 tables like this
table 1
profile
--------
id
---
1 | XXX
2 | zzz
table 2
profile_details
-----------------
id | K | V
---------------------------
1 | first_name | XXX
1 | last_name | YYY
1 | gender | female
2 | name | zzzzz
2 | gender | male
2 | phone | 8999xxxx
2 | location | india
2 | spoken_language | hindi
I use this query to fetch the rows as cols
select profiles.id,
max( decode( k, 'first_name', v, NULL )) first_name,
max(decode(k, 'last_name', v, null))as last_name ,
max( decode( k, 'gender', v, NULL)) gender
from profile_details , profiles
where
profile_details.id = profiles.id
and
profile_details.id=1
group by profiles.id
fetched me
id | first_name| last_name | gender
--------------------------------------------
1 | XXX | YYY | female
this works to fetch the rows as columns. but how can this query be changed to include the columns dynamically, as the K values can be anything possible.
ex, for id 2, it should be
id | name | gender | mobile | location | spoken_language
------------------------------------------------------------------
2 | zzz | male | 8999xxxx | india | hindi
thanks
V
What you have here is an Entity-Attribute-Value schema, often used for providing flexibility in the schema.
The downside is that everything you do from now on will be unutterably painful and difficult, including this, for which there is no easy solution.
Here's a lesson on the the subject: https://www.simple-talk.com/opinion/opinion-pieces/bad-carma/
You can use dynamic SQL to generate the string that you want to execute.
In Oracle, I would use a procedure that I could pass in the necessary id value and then return a sys_refcursor.
The procedure would be similar to the following:
CREATE OR REPLACE procedure dynamic_pivot_profile(p_cursor in out sys_refcursor, p_id in number)
as
sql_query varchar2(1000) := 'select p.id ';
begin
for x in (select distinct k from profile_details where id=p_id order by 1)
loop
sql_query := sql_query ||
' , max(case when pd.k = '''||x.k||''' then pd.v end) as "'||x.k||'"';
dbms_output.put_line(sql_query);
end loop;
sql_query := sql_query || ' from profile p
inner join profile_details pd
on P.ID = pd.id
where PD.ID = '||p_id||'
group by p.id';
dbms_output.put_line(sql_query);
open p_cursor for sql_query;
end;
/
Then you return the results, I use the following in TOAD:
variable x refcursor
exec dynamic_pivot_profile(:x, 1) -- pass in your id
print x
This will return the desired result that you provided.