SQLITE - combine a couples names (first and last name included) - sqlite

I have two tables: People and Clients. Here is a basic sample.
'People' columns:
cliend_id|first_name|last_name
------------------------------
1 Dan Black
1 Jenn Black
2 Rob White
2 Lisa Green
'Clients' columns:
client_id|address
1 43 address
2 89 address
In the case where there is two partners (people) living in the same house (which in combination make a client), I am trying to combine the two to output something like "Black, Jenn & Dan"
I have the following
SELECT
clients.address_google Address,
group_concat(people.last_name || ", " || people.first_name, " & ") Name
FROM clients
INNER JOIN people ON people.client_id = clients.client_id
Which outputs a single line - Address and Name. My question is specific to the Name part.
If the last name is the same I would want "Black, Dan & Jenn"
If the last name is not the same, I would want "White, Rob & Green, Lisa"
Is there a way to test if the last name is the same, and if so omit it for the second person?

As a starter: you need to add a group by clause to the query to make it a valid aggregation query.
Then: if there always is 1 or 2 personn at a given address (not more), as shown in your sample data, then an option is a conditional expression:
select
c.address_google address,
case when min(p.last_name) <> max(p.last_name)
max(p.last_name) || ', ' || group_concat(p..first_name, ' & ')
else group_concat(p.last_name || ', ' || p.first_name, ' & ')
end as name
from clients c
inner join people p on p.client_id = c.client_id
group by c.address_google

You can use ROW_NUMBER() window function to get the 1st of the clients that has a last_name same as others:
SELECT
c.address_google Address,
GROUP_CONCAT(CASE WHEN p.rn = 1 THEN p.last_name || ", " ELSE '' END || p.first_name, " & ") Name
FROM clients c
INNER JOIN (
SELECT *, ROW_NUMBER() OVER (PARTITION BY client_id, last_name ORDER BY first_name) rn
FROM people
) p ON p.client_id = c.client_id
GROUP BY c.client_id, c.address_google;
See the demo.
Results:
| Address | Name |
| ---------- | ------------------------ |
| 43 address | Black, Dan & Jenn |
| 89 address | Green, Lisa & White, Rob |

Related

misuse of aggregate: group_concat()

I have a problem with sqlite3 in this string
select group_concat(persons.name, ', ') as Актеры
from films_persons join persons on persons.id = films_persons.person_id
where films_persons.film_id = 1 and role = Актеры
films_persons looks like this:
film_id | person_id | role
1 | 1 | "Actors" (or "Актеры" in my case)
persons:
id | name | birth_date
1 | Leonardo | 11.11.1974
I need to group all persons.name in 1 string, and this call must return "Leonardo".
Full name of error is "sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) misuse of aggregate: group_concat()".
In mysql, you need to do:
group_concat(persons.name separator ', ')
instead of:
group_concat(persons.name, ', ')
https://dbfiddle.uk/?rdbms=mariadb_10.5&fiddle=20172b9b99dfcf7dcdf7ccf00efd49f4
But it appears from your error that you are using sqlite, not mysql, in which case your syntax is correct: https://dbfiddle.uk/?rdbms=sqlite_3.27&fiddle=691caef008dbade5c1dde83e4e495c7f

SQLite Join Causing Duplicate Data After First Correct Record

I am having an issue where after the first record in a Select statement the data in a certain column is being duplicated within the Select statement. I will provide the statement in which I am working with:
SELECT Call.CallID AS [Call #],
(Members.FirstName || ' ' || Members.LastName) AS OIC,
Alarm.AlarmID,
Alarm.AlarmDesc AS Alarm,
--Group_Concat(m2.FirstName || ' ' || m2.LastName) AS [Line Officers],
Group_Concat(m1.FirstName || ' ' || m1.LastName) AS Members
FROM Call
LEFT JOIN
Members ON Call.OIC = Members.MemberID
LEFT JOIN
Alarm ON Call.AlarmID = Alarm.AlarmID
LEFT JOIN
CallToMembers ON Call.CallID = CallToMembers.CallID
--LEFT JOIN
--CallToLineOfficers on Call.CallID = CallToLineOfficers.CallID
LEFT JOIN
Members AS m1 ON CallToMembers.MemberID = m1.MemberID
--LEFT JOIN
--Members as m2 on CallToLineOfficers.MemberID = m2.MemberID
GROUP BY Call.CallID;
Ok, so this statement returns everything that I need and is working perfectly. However, whenever I uncomment the lines that are commented out so that I can obtain "Line Officers" from the Bridge Table "CallToLineOfficers", data begins to duplicate from within the columns like so:
Incorrect Complete Select Statement
- Nothing commented out.
Correct Line Officers
- If I comment Joins for CallToMembers.
Correct Members
- If I comment Joins for CallToLineOfficers.
As you can see that once I introduce both "Members" and "Line Officers", things go wrong.
When you join the calls with the line officers, you get an intermediate result like this:
Call # Line Officers
------ -------------
54 Bob Clark
54 Rob Catalano
When you then join with the members, the database matches the call number again, so for each row in the intermediate result, you get all combinations with the members:
Call # Line Officers Members
------ ------------- -----------
54 Bob Clark Matt Butler
54 Rob Catalano Matt Butler
54 Bob Clark Tom Cramer
54 Rob Catalano Tom Cramer
...
So you cannot use joins when you have multiple independent tables with 1:N relationships.
You don't actually want to have multiple line officer/member rows in the result anyway, so you can just use a subquery to aggregate those values:
SELECT CallID,
(SELECT group_concat(FirstName || ' ' || LastName)
FROM CallToLineOfficers
JOIN Members USING (MemberID)
WHERE CallID = Call.CallID
) AS "Line Officers",
(SELECT group_concat(FirstName || ' ' || LastName)
FROM CallToMembers
JOIN Members USING (MemberID)
WHERE CallID = Call.CallID
) AS Members
FROM Call;

split the regular expression and loop through

I need to loop through inside a join thats what I think I have written.
I am posting the code.
select listagg(request_num,',') within group (order by request_num) as request_num,segmentation_name from (
select MST.REQUEST_NUM,seg_dtls.SEGMENT_NAME,LAST_UPDATED_date,seg_dtls.segmentation_name from
(select * from rp_sr_master ) Mst,
(select SUBSTR(ANSWER,1,INSTR (ANSWER, '~', 1)-1) AS SM_ID,sr_id from rp_sR_details
WHERE Q_ID in (SELECT Q_ID FROM RP_QUESTIONS WHERE field_id='LM_LRE_Q6')
) Dtls, (select SM_ID, SQL_STATEMENT, CREATION_DATE, UPDATED_DATE, SEGMENT_NAME,segmentation_name ,TOTAL_COUNT
from rp_sEGMENT_master ) seg_dtls
where Dtls.SM_ID=seg_dtls.SM_ID
and Dtls.sr_id=Mst.sr_id)
group by segmentation_name;
The problem I am facing here is in the following,
(select SUBSTR(ANSWER,1,INSTR (ANSWER, '~', 1)-1) AS SM_ID,sr_id from rp_sR_details
WHERE Q_ID in (SELECT Q_ID FROM RP_QUESTIONS WHERE field_id='LM_LRE_Q6')
)
In the above code, answer will be something like this:
2603~NG non IaaS IT Professional^2600~NG non IaaS Senior IT^2598~NG data profiling SENIOR IT professional^2595~Nigeria data profiling IT professiona
It only picks the first number that is 2603 and others will be left out.
Is there any way I can loop through all the number in that 'ANSWER'.
I am looking for ideas.
Thanks.
One idea is to use a method for splitting a comma delimited string into rows, you can find examples of this method in the following answers:
Splitting comma separated values in Oracle
How can I use regex to split a string, using a string as a delimiter?
The above solutions use regexp_substr function.
If you dig into details of Oracle's REGEXP_SUBSTR function you wil find that there is optional position parameter there.
This parameter can be combined with a sulution shown in this answer:
SQL to generate a list of numbers from 1 to 100
(that is SELECT LEVEL n FROM DUAL CONNECT BY LEVEL <= 100) in the below way:
with xx as (
select '2603~NG non IaaS IT Professional^2600~NG non IaaS Senior '
|| 'IT^2598~NG data profiling SENIOR IT professional^2595~Nigeria '
|| 'data profiling IT professiona' as answer
from dual
)
select LEVEL AS n, regexp_substr( answer, '\d+', 1, level) as nbr
from xx
connect by level <= 6
;
The above query produces the following result:
N |NBR |
--|-----|
1 |2603 |
2 |2600 |
3 |2598 |
4 |2595 |
5 | |
6 | |
What we need is to eliminate null values from the resultset, it can be done using a simple condition IS NOT NULL
with xx as (
select '2603~NG non IaaS IT Professional^2600~NG non IaaS Senior '
|| 'IT^2598~NG data profiling SENIOR IT professional^2595~Nigeria '
|| 'data profiling IT professiona' as answer
from dual
)
select LEVEL AS n, regexp_substr( answer, '\d+', 1, level) as nbr
from xx
connect by regexp_substr( answer, '\d+', 1, level) IS NOT NULL
;
N |NBR |
--|-----|
1 |2603 |
2 |2600 |
3 |2598 |
4 |2595 |
The above query works perfect for a single record, but gets confused when we try to parse 2 or more rows. Luckily there is another answer on SO that helps to solve this issue:
Is there any alternative for OUTER APPLY in Oracle?
-- source data
WITH xx as (
select 1 AS id,
'2603~NG non IaaS IT Professional^2600~NG non IaaS Senior '
|| 'IT^2598~NG data profiling SENIOR IT professional^2595~Nigeria '
|| 'data profiling IT professiona' as answer
from dual
UNION ALL
select 2 AS id,
'11111~NG non IaaS IT Professional^22222~NG non IaaS Senior '
|| 'IT^2598~NG data 33333 profiling SENIOR IT professional^44~Nigeria '
|| 'data profiling 5 IT professiona 66' as answer
from dual
)
-- end of source data
SELECT t.ID, t1.n, t1.nbr
FROM xx t
CROSS JOIN LATERAL (
select LEVEL AS n, regexp_substr( t.answer, '\d+', 1, level) as nbr
from dual
connect by regexp_substr( t.answer, '\d+', 1, level) IS NOT NULL
) t1;
the above query parses numbers from two records and displays then in the following form:
ID |N |NBR |
---|--|------|
1 |1 |2603 |
1 |2 |2600 |
1 |3 |2598 |
1 |4 |2595 |
2 |1 |11111 |
2 |2 |22222 |
2 |3 |2598 |
2 |4 |33333 |
2 |5 |44 |
2 |6 |5 |
2 |7 |66 |
I belive you will manage to merge this simple "parsing" query into your main query.

how to concatenate multiple column values into a single column?

i have the following table friend
id | first_name | last_name | gender | age | mobile
1 | bobby | roe | male | 21 | 541-5780
how to concatenate multiple column (first_name & last_name) values into a single column to get the following result?
full_name
bobby roe
i have writen the following query but it does not work
declare #full_name varchar(max)
select #full_name = COALESCE(#full_name + ', ', '') + first_name, last_name
from friend
select #full_name
More than one way to achieve this:
SELECT CONCAT(first_name, ' ' ,last_name) AS full_name;
For earlier versions (Where CONCAT is not a built in function):
SELECT first_name + ISNULL(' ' + last_name, '') as Full_Name from [YourTable]
This as well should give you the same result
SELECT COALESCE(first_name, '') + COALESCE(last_name, '') as FullName FROM [YourTable]

Creating a view in a not relational database

I had an issue and I hope that someone could help me out. In fact, I work on a poorly designed database and I have no control to change things in it. I have a table "Books", and each book can have one or more author. Unfortunately the database is not fully relational (please don't ask me why because I am asking the same question from the beginning). In the table "Books" there is a field called "Author_ID" and "Author_Name", so when a book was written by 2 or 3 authors their IDs and Their names will be concatenated in the same record separated by an star. Here is a demonstration:
ID_BOOK | ID_AUTHOR | NAME AUTHOR | Adress | Country |
----------------------------------------------------------------------------------
001 |01 | AuthorU | AdrU | CtryU |
----------------------------------------------------------------------------------
002 |02*03*04 | AuthorX*AuthorY*AuthorZ | AdrX*NULL*AdrZ | NULL*NULL*CtryZ |
----------------------------------------------------------------------------------
I need to create a view against this table that would give me this result:
ID_BOOK | ID_AUTHOR | NAME AUTHOR | Adress | Country |
----------------------------------------------------------------------------------
001 |01 | AuthorU | AdrU | CtryU |
----------------------------------------------------------------------------------
002 |02 | AuthorX | AdrX | NULL |
----------------------------------------------------------------------------------
002 |03 | AuthorY | NULL | NULL |
----------------------------------------------------------------------------------
002 |04 | AuthorZ | AdrZ | CtryZ |
----------------------------------------------------------------------------------
I will continue trying to do it and I hope that someone could help me with at least some hints. Many thanks guys.
After I applied the solution given by you guys I got this problem. I am trying to solve it and hopefully you can help me. In fact, when the sql query run, the CLOB fields are disorganized when some of them contain NULL value. The reslut should be like above, but i got the result below:
ID_BOOK | ID_AUTHOR | NAME AUTHOR | Adress | Country |
----------------------------------------------------------------------------------
001 |01 | AuthorU | AdrU | CtryU |
----------------------------------------------------------------------------------
002 |02 | AuthorX | AdrX | CtryZ |
----------------------------------------------------------------------------------
002 |03 | AuthorY | AdrZ | NULL |
----------------------------------------------------------------------------------
002 |04 | AuthorZ | NULL | NULL |
----------------------------------------------------------------------------------
Why does it put the NULL values in the end? Thank you.
in 11g you can use a factored recursive sub query for this:
with data (id_book, id_author, name, item_author, item_name, i)
as (select id_book, id_author, name,
regexp_substr(id_author, '[^\*]+', 1, 1) item_author,
regexp_substr(name, '[^\*]+', 1, 1) item_name,
2 i
from books
union all
select id_book, id_author, name,
regexp_substr(id_author, '[^\*]+', 1, i) item_author,
regexp_substr(name, '[^\*]+', 1, i) item_name,
i+1
from data
where regexp_substr(id_author, '[^\*]+', 1, i) is not null)
select id_book, item_author, item_name
from data;
fiddle
A couple weeks ago I answered a similar question here. That answer has an explanation (I hope) of the general approach so I'll skip the explanation here. This query will do the trick; it uses REGEXP_REPLACE and leverages its "occurrence" parameter to pick the individual author ID's and names:
SELECT
ID_Book,
REGEXP_SUBSTR(ID_Author, '[^*]+', 1, Counter) AS AuthID,
REGEXP_SUBSTR(Name_Author, '[^*]+', 1, Counter) AS AuthName
FROM Books
CROSS JOIN (
SELECT LEVEL Counter
FROM DUAL
CONNECT BY LEVEL <= (
SELECT MAX(REGEXP_COUNT(ID_Author, '[^*]+'))
FROM Books))
WHERE REGEXP_SUBSTR(Name_Author, '[^*]+', 1, Counter) IS NOT NULL
ORDER BY 1, 2
There's a Fiddle with your data plus another row here.
Addendum: OP has Oracle 9, not 11, so regular expressions won't work. Following are instructions for doing the same task without regexes...
Without REGEXP_COUNT, the best way count authors is to count the asterisks and add one. To count asterisks, take the length of the string, then subtract its length when all the asterisks are sucked out of it: LENGTH(ID_Author) - LENGTH(REPLACE(ID_Author, '*')).
Without REGEX_SUBSTR, you need to use INSTR to find the position of the asterisks, and then SUBSTR to pull out the author IDs and names. This gets a little complicated - consider these Author columns from your original post:
Author U
Author X*Author Y*Author Z
AuthorX lies between the beginning the string and the first asterisk.
AuthorY is surrounded by asterisks
AuthorZ lies between the last asterisk and the end of the string.
AuthorU is all alone and not surrounded by anything.
Because of this, the opening piece (WITH AuthorInfo AS... below) adds an asterisk to the beginning and the end so every author name (and ID) is surrounded by asterisks. It also grabs the author count for each row. For the sample data in your original post, the opening piece will yield this:
ID_Book AuthCount ID_Author Name_Author
------- --------- ---------- -------------------------
001 1 *01* *AuthorU*
002 3 *02*03*04* *AuthorX*AuthorY*AuthorZ*
Then comes the join with the "Counter" table and the SUBSTR machinations to pull out the individual names and IDs. The final query looks like this:
WITH AuthorInfo AS (
SELECT
ID_Book,
LENGTH(ID_Author) -
LENGTH(REPLACE(ID_Author, '*')) + 1 AS AuthCount,
'*' || ID_Author || '*' AS ID_Author,
'*' || Name_Author || '*' AS Name_Author
FROM Books
)
SELECT
ID_Book,
SUBSTR(ID_Author,
INSTR(ID_Author, '*', 1, Counter) + 1,
INSTR(ID_Author, '*', 1, Counter+1) - INSTR(ID_Author, '*', 1, Counter) - 1) AS AuthID,
SUBSTR(Name_Author,
INSTR(Name_Author, '*', 1, Counter) + 1,
INSTR(Name_Author, '*', 1, Counter+1) - INSTR(Name_Author, '*', 1, Counter) - 1) AS AuthName
FROM AuthorInfo
CROSS JOIN (
SELECT LEVEL Counter
FROM DUAL
CONNECT BY LEVEL <= (SELECT MAX(AuthCount) FROM AuthorInfo))
WHERE AuthCount >= Counter
ORDER BY ID_Book, Counter
The Fiddle is here
If you have an authors table, you can do:
select b.id_book, a.id_author, a.NameAuthor
from books b left outer join
authors a
on '*'||NameAuthor||'*' like '%*||a.author||'*%'
In addition:
SELECT distinct id_book,
, trim(regexp_substr(id_author, '[^*]+', 1, LEVEL)) id_author
, trim(regexp_substr(author_name, '[^*]+', 1, LEVEL)) author_name
FROM yourtable
CONNECT BY LEVEL <= regexp_count(id_author, '[^*]+')
ORDER BY id_book, id_author
/
ID_BOOK ID_AUTHOR AUTHOR_NAME
------------------------------------
001 01 AuthorU
002 02 AuthorX
002 03 AuthorY
002 04 AuthorZ
003 123 Jane Austen
003 456 David Foster Wallace
003 789 Richard Wright
No REGEXP:
SELECT str, SUBSTR(str, substr_start_pos, substr_end_pos) final_str
FROM
(
SELECT str, substr_start_pos
, (CASE WHEN substr_end_pos <= 0 THEN (Instr(str, '*', 1)-1)
ELSE substr_end_pos END) substr_end_pos
FROM
(
SELECT distinct '02*03*04' AS str
, (Instr('02*03*04', '*', LEVEL)+1) substr_start_pos
, (Instr('02*03*04', '*', LEVEL)-1) substr_end_pos
FROM dual
CONNECT BY LEVEL <= length('02*03*04')
)
ORDER BY substr_start_pos
)
/
STR FINAL_STR
---------------------
02*03*04 02
02*03*04 03
02*03*04 04

Resources