Specify Which Column Comes First SQL - sql

I am processing a large list of church members in order to send them a letter. We want the letter to say "Dear John & Jane Smith". We will use Word to do the mail merge from an Excel sheet. The important thing is the male name has to always come first.
Each individual has their own row in the table I am using. They have a unique ID as well as a family ID. I am using that family ID to put families together on the same row. Currently I have the male name and the female name separated using MAX(CASE WHEN) in order to specify what goes where. It looks something like this:
+-----------+------------+--------------------------+
| family id | male name | female name | last name |
+-----------+------------+--------------------------+
| 1234 | john | jane | doe |
| 1235 | bob | cindy | smith |
| 1236 | NULL | susan | jones |
| 1237 | jim | NULL | taylor |
+-----------+------------+--------------------------+
But I run into a problem when the family only has one member.
Here's a part of the query I have:
SELECT
fm.family_id AS 'Family ID',
MAX(CASE WHEN PB.gender like 'm' and FM.role_luid=29 THEN PB.nick_name END)
AS 'Male Name',
MAX(CASE WHEN PB.gender like 'f' and FM.role_luid=29 THEN PB.nick_name END)
AS 'Female Name',
PB.last_name AS 'Last Name',
FROM core_family F
I was thinking that I need to combine rows using STUFF or something like that, but I'd need some way of specifying which column comes first so that the male name always comes first. Essentially, as stated above, I need the letter to read "Dear John & Jane Smith" for families with two people and "Dear John Smith" for families with one person. So I am hoping my results might look like:
+-----------+--------------+-----------+
| family id | First name | last name |
+-----------+--------------+-----------+
| 1234 | john & jane | doe |
| 1235 | bob & cindy | smith |
| 1236 | susan | jones |
| 1237 | jim | taylor |
+-----------+--------------+-----------+

You can use your intermediate table (assuming you don't have 3 names for a family id).
From the table you indicated use:
select
id
, coalesce(male_name+' & '+female_name,male_name, female_name)
, last_name
from F;
Here is an example with your data
Basically if you concatenate using + in Sql Server you will get null. So if either male or female name is NULL, you get NULL. Coalesce will move on to the next value if it sees NULL. This way you either get a pair with '&' or a single name for each family.

I've created some test data. This technique works with the test data.
CREATE TABLE #Temp (FamID INT,
MaleName VARCHAR(20),
FemaleName VARCHAR(20),
LName VARCHAR(20))
INSERT #Temp
VALUES (1234, 'John' ,'Jane' , 'Doe' ),
(1235, 'Bob' , 'Cindy' , 'Smith'),
(1236 , NULL , 'Susan' , 'Jones'),
(1237 , 'Jim' , NULL , 'Taylor')
Here is your query.
SELECT FamID,
ISNULL(MaleName+' ','') +
CASE WHEN MaleName IS NULL OR FemaleName IS NULL THEN '' ELSE 'and ' END+
ISNULL(FemaleName,'') AS FirstName,
LName
FROM #Temp

You can use like this
SELECT
fm.family_id AS 'Family ID',
MAX(CASE WHEN PB.gender like 'm' and FM.role_luid=29 THEN PB.nick_name END)
+ '&'+
MAX(CASE WHEN PB.gender like 'f' and FM.role_luid=29 THEN PB.nick_name END)
AS 'First Name',
PB.last_name AS 'Last Name',
FROM core_family F

Related

VSQL: Concatenate two values in same column from same table

I have a table that looks like the following:
email | first_name
----------------------+------------
------#diffem.com | Matthew
------#email.net | Susan
------#email.net | Thomas
------#email.com | Donald
------#email.com | Paula
I.e. I have records where there is only one value (name) per key (email), but in other instance I have two values per key.
I want the output to look like this:
email | first_name
----------------------+-----------------
------#diffem.com | Matthew
------#email.net | Susan and Thomas
------#email.com | Donald and Paula
I have tried the following, but it is not working due to grouping by an aggregate function:
CREATE TABLE user.table1 AS
(
select distinct email
, case when email_count = 1 then first_name
when email_count = 2 then (MIN(first_name))||' and '||MAX(first_name))
else null end as first_name_grouped
FROM (
SELECT email
, first_name
, count(email) over (partition by email) as email_count
FROM table
)
x
)
;
I've also tried partitioning by email, putting the two names into different columns and then concatenating that, but am ending up with blanks in my output table (see below)
email | name1 | name 2
----------------------+--------+-------
------#email.net | Susan | null
------#email.net | null | Donald
Is there a way to do this in SQL, without creating two separate name columns? Thanks in advance.
What you are trying to accomplish could be done in MYSQL like
SELECT email, GROUP_CONCAT(first_name)
FROM table
GROUP BY email
There is similar function in MS SQL server called STRING_AGG() , you can see more here https://database.guide/mysql-group_concat-vs-t-sql-string_agg/

Reorganize multiple rows in a new table with more columns

I have a table that looks like that:
+----------+----------+----------+----------+--------------------------+
| Club | Role | Name | Lastname | Email |
+----------+----------+----------+----------+--------------------------+
| Porto | 1 | Peter | Pan | peter.pan#mail.com |
| Porto | 2 | Michelle | Obama | michelle.obama#mail.com |
| Monaco | 1 | Serena | Williams | serena.williams#mail.com |
| Monaco | 2 | David | Beckham | david.beckham#mail.com |
+----------+----------+----------+----------+--------------------------+
and i want to get a table like that:
+----------+-----------------+-----------------+---------------------------------+-----------------+-----------------+---------------------------------+
| Club | Role 1 Name | Role 1 Lastname | Role 1 Email | Role 2 Name | Role 2 Lastname | Role 2 Email |
+----------+-----------------+-----------------+---------------------------------+-----------------+-----------------+---------------------------------+
| Porto | Peter | Pan | peter.pan#mail.com | Michelle | Obama | michelle.obama#mail.com |
| Monaco | Serena | Williams | serena.williams#mail.com | David | Beckham | david.beckham#mail.com |
+----------+-----------------+-----------------+---------------------------------+-----------------+-----------------+---------------------------------+
where the persons with different roles in each club puts in the same row.
I would ideally like to find a way to do that in Excel, but i am not sure if its possible. If not, SQL code would also help a lot.
Here is what I could come up with for an excel formula. Hopefully it can push you in the right direction.
This formula is assuming that your first table exists at the range A1:E5 and the second table exists at the range G1:M3. It is also assuming that the second table's column names are just repeating without the Role number attached to the front of it (same as the first table). This formula is an array formula, so you have to make sure to do CTRL+SHIFT+ENTER when inputting it.
{=INDEX($A$2:$E$5,
MATCH(1,($G2=$A$2:$A$5)*((FLOOR((COLUMN() - COLUMN($H$1) ) / 3,1) + 1)=$B$2:$B$5),0),
MATCH(H$1,$A$1:$E$1,0))}
The first part is using the INDEX forumla which pulls data from the range suppled ($A$2:$E$5) based on the row and column numbers supplied by the following MATCH formulas.
The first MATCH is supplying the row number for when the result of the lookup array section is equal to 1. I am checking two conditions, the first is to check for the "Club" name ($G2=$A$2:$A$5) and the second is to check for which "Role" we are currently on ((FLOOR((COLUMN() - COLUMN($H$1) ) / 3,1) + 1). This is using the FLOOR function to round the result down to the whole number and dividing by the number of columns (3 in this case: Name, Lastname, and Email).
The final MATCH is pulling the column number based on the header names from both tables. If you wanted to incorporate the changing names of the roles in the column headers, you could change this part to something like this:
{=INDEX($A$2:$E$5,
MATCH(1,($G2=$A$2:$A$5)*((FLOOR((COLUMN() - COLUMN($H$1) ) / 3,1) + 1)=$B$2:$B$5),0),
MOD(COLUMN() - COLUMN($H$1),3) + 3)}
I am adding 3 to the end of the mod because of the original range that was selected for table 1. The columns that we want to pull start at location 3 in the range.
If you want to do this in Oracle Sql, there's a nice approach in analytical sql.
To convert/swap rows to columns or columns to rows, we can use Pivot or Unpivot operators.
In you example use below query to covert data as you like,
select * from
(
with all_roles as
(select 1 role from dual union all
select 2 role from dual),
ddata as
(select 1 c_role, 'porto' club, 'peter' fname,'pan' lname,'peter.pan#mail.com' email from dual union all
select 2 c_role, 'porto' club, 'Michelle' fname, 'Obama' lname,'michelle.obama#mail.com' email from dual union all
select 1 c_role, 'monaco' club, 'Serena' fname, 'Williams' lname,'serena.williams#mail.com' email from dual union all
select 2 c_role, 'monaco' club, 'David' fname, 'Beckham' lname,'david.beckham#mail.com' email from dual )
(select role, club, fname,lname, email from ddata,all_roles
where all_roles.role=ddata.c_role)) all_data
pivot (
max(fname) fname,
max(lname) lname,
max(email) email
for role in ( 1 role1, 2 role2 )
)
order by club;

Similarity search for name surname

I have a column name which contains name surname (name space surname) and I would like to search it based on
name, surname but I would like to match cases where people accidentally inserted surname name in a different order
misspelled names surnames by 1-2 characters.
You should read about the pg_trgm extension and its function similarity(). A few examples below.
Example data:
create table my_table(id serial primary key, name text);
insert into my_table (name) values
('John Wilcock'),
('Henry Brown'),
('Jerry Newcombe');
create extension if not exists pg_trgm; -- install the extension
Example 1:
select *,
similarity(name, 'john wilcock') as "john wilcock",
similarity(name, 'wilcock john') as "wilcock john"
from my_table;
id | name | john wilcock | wilcock john
----+----------------+--------------+--------------
1 | John Wilcock | 1 | 1
2 | Henry Brown | 0 | 0
3 | Jerry Newcombe | 0.037037 | 0.037037
(3 rows)
Example 2:
select *,
similarity(name, 'henry brwn') as "henry brwn",
similarity(name, 'brovn henry') as "brovn henry"
from my_table;
id | name | henry brwn | brovn henry
----+----------------+------------+-------------
1 | John Wilcock | 0 | 0
2 | Henry Brown | 0.642857 | 0.6
3 | Jerry Newcombe | 0.04 | 0.0384615
(3 rows)
Example 3:
select *
from my_table
where similarity(name, 'J Newcombe') >= 0.6;
id | name
----+----------------
3 | Jerry Newcombe
(1 row)
To counter the exchanged parts of the name you could use split_part() to split the name in its two parts and compare both of them, something similar to the following:
SELECT *
FROM person
WHERE split_part(name, ' ', 1) IN ('<given_name_searched_for>'
'<surname_searched_for>')
OR split_part(name, ' ', 2) IN ('<given_name_searched_for>'
'<surname_searched_for>');
Or have a look at the other string functions and operators. -- there a variants of split functions using regular expressions, e.g..
Are there names like 'John F. Kennedy', that is, with more than one token? Are there names with more than one contiguous spaces? Bear in mind that these have to be addressed with further means if any. (Such things can get hairy. If possible consider revising your design and use a separate column for the surname.)
For the similarity part: PostgreSQL provides some modules, that might be useful here:
fuzzystrmatch
pg_trm

Concat multiple rows PSQL

id | name | Subject | Lectured_Times | Faculty
3258132 | Chris Smith | SATS1364 | 10 | Science
3258132 | Chris Smith | ECTS4605 | 9 | Engineering
How would I go about creating the following
3258132 Chris Smith SATS1364, 10, Science + ECTS4605, 9,Engineering
where the + is just a new line. Notice how after the '+'(new line) it doesnt concat the id,name
try
SELECT distinct concat(id,"name",string_agg(concat(subject, Lectured_Times , Faculty), chr(10)))
from tn
where id = 3258132
group by id;
As mentioned above string_agg is perfect solution for this.
select
id, name, string_agg(concat(subject, Lectured_Times, Faculty), '\n')
from table
group by id, name

Several conditions on the same table

I've inherited an excel file converted to a database and I try to figure out the people who went to several locations.
| Customer | email | ZIP | shop |
| John Smith | js#mail.com | 75016 | 1 |
| Mary King | mary#ymail.com | 97430 | 2 |
| John Smith | js#mail.com | 75016 | 3 |
| Ivan Turtle | ivan#mail.com | 56266 | 5 |
| Mary King | mary#ymail.com | 97430 | 5 |
| John Smith | js#mail.com | 75016 | 5 |
Eg : John Smith had been to 1, 3, 5
Mary King to 2, 3
I tried to use email as a key but can't figure out how to solve this one
assuming each row specifies a separate location,
select customer, count(*) as countOfLocation
from TABLE
group by customer
having count(*) > 1
order by countOfLocation desc
Is the simplest answer
I believe something like this will work for you.
Find all customers who have been to more than one shop, and then join back to get the details of each customer.
This will give back multiple rows per person, one for each shop they have been to. If all you care about is the names of customers, you can simply use the second SELECT
If you want one row per customer and a CSV of the shops, you will have to do a bit of joining.
SELECT a.*
FROM Customers a
INNER JOIN
-- Find all customers who have been to more than one shop.
(SELECT email
FROM Customers
GROUP BY email
HAVING COUNT(shop) > 1) b
ON a.email = b.email
Try with this if is Sql server:
select customer, count(distinct(shop))
from table
group by customer, shop
having count(distinct(shop)) > 1
or, you only want to know how many places they visited:
select customer, count(distinct(shop))
from table
group by customer, shop
if you want to show the user name and shop seperating by comma in one row use this
SELECT Customer
,STUFF((SELECT ', ' + CAST(shop AS VARCHAR(10)) [text()]
FROM 'ur table'
WHERE customer = t.customer
FOR XML PATH(''), TYPE)
.value('.','NVARCHAR(MAX)'),1,2,' ') List_Output
FROM 'ur table' t
GROUP BY customer