Select All customers with exact name and birthdate - mysql

I want to select all the customers that have the same name and birth date on mysql table.
my query right now is close, but it seems to have a flaw:
SELECT
id,
customer.name,
date
FROM
customer
INNER JOIN (
SELECT
name
FROM
customer
GROUP BY
name,date
HAVING
COUNT(id) > 1
) temp ON customer.name = customer.name
ORDER BY
name;

Return a customer if there EXISTS another one with same name and date, but other id:
SELECT
id,
name,
date
FROM
customer c1
where exists (SELECT 1 from customer c2
where c2.name = c1.name
and c2.date = c1.date
and c2.id <> c1.id)
JOIN version:
SELECT
c1.id,
c1.name,
c1.date
FROM
customer c1
JOIN customer c2
ON c2.name = c1.name
and c2.date = c1.date
and c2.id <> c1.id

You can do it using EXISTS expression:
SELECT
id,
customer.name,
date
FROM customer c
WHERE EXISTS (
SELECT *
FROM customer cc
WHERE cc.name=c.name AND cc.date=c.date AND cc.id <> c.id
)
The meaning of this query can be derived by pretending it's plain English: we are looking for all customers c for which there exists another customer cc with the same name and birth date, but different id.

Something like this should do it:
select
c1.*
from
customer c1
join customer c2 on
c1.name = c2.name
and c1.birth_date = c2.birth_date
and c1.id != c2.id
order by name, birth_date, id
;
And here's the full example:
drop table customer;
create table customer (
id int,
name varchar(64),
birth_date date
);
insert into customer values (1, 'Joe', '2001-01-01');
insert into customer values (2, 'Joe', '2001-01-02');
insert into customer values (3, 'Joe', '2001-01-03');
insert into customer values (4, 'Jim', '2001-01-01');
insert into customer values (5, 'Jack', '2001-01-01');
insert into customer values (6, 'George', '2001-01-01');
insert into customer values (7, 'George', '2001-01-02');
insert into customer values (8, 'Jeff', '2001-01-02');
insert into customer values (10, 'Joe', '2001-01-01');
insert into customer values (60, 'George', '2001-01-01');
select * from customer;
select
c1.*
from
customer c1
join customer c2 on
c1.name = c2.name
and c1.birth_date = c2.birth_date
and c1.id != c2.id
order by name, birth_date, id
;
+ ------- + --------- + --------------- +
| id | name | birth_date |
+ ------- + --------- + --------------- +
| 6 | George | 2001-01-01 |
| 60 | George | 2001-01-01 |
| 1 | Joe | 2001-01-01 |
| 10 | Joe | 2001-01-01 |
+ ------- + --------- + --------------- +
4 rows

Assuming a table with only id, name and date, you can expand it after that.
The solution is going to be in using an aliased join or an exists clause.
First some code to setup a temporary table with some values for us to query against.
--Drop Temporary Test Table if it exists
IF OBJECT_ID('tempdb.dbo.#Customers_Test', 'U') IS NOT NULL
DROP TABLE #Customers_Test;
--Create a Temporary Test Table
CREATE TABLE #Customers_Test
(
[id] [int] IDENTITY(1,1) NOT NULL,
[name] [varchar](50) NULL,
[date] [datetime] NULL,
CONSTRAINT [PK_Customers_Test] PRIMARY KEY CLUSTERED
(
[id] ASC
)
WITH
(
PAD_INDEX = OFF,
STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON
) ON [PRIMARY]
) ON [PRIMARY]
--Put some temporary values into the table
INSERT INTO #Customers_Test(name, [date])
VALUES ('Joe', GETUTCDATE()),
('Mark', GETDATE()),
('Dan', '1/1/1990'),
('Dan', '1/1/1990')
Now for the actual selection styles, there are several ways to do this depending on mostly preferences, if this query had become more complex there may have been a significant run time advantage of one over another.
--First Select Version
SELECT #Customers_Test.id,
#Customers_Test.name,
#Customers_Test.[date]
FROM #Customers_Test
WHERE EXISTS (
SELECT 1
FROM #Customers_Test Duped_Customers
WHERE Duped_Customers.name = #Customers_Test.name
AND Duped_Customers.[date] = #Customers_Test.[date]
AND Duped_Customers.id <> #Customers_Test.id
)
Another way:
--Second Select Version
SELECT #Customers_Test.id,
#Customers_Test.name,
#Customers_Test.[date]
FROM #Customers_Test
INNER JOIN #Customers_Test AS Duped_Customers
ON #Customers_Test.name = Duped_Customers.name
AND #Customers_Test.[date] = Duped_Customers.[date]
AND #Customers_Test.id <> Duped_Customers.id
One Final Way:
--Third Select Version ( Similar to your current logic).
SELECT #Customers_Test.id,
#Customers_Test.name,
#Customers_Test.[date]
FROM #Customers_Test
WHERE #Customers_Test.name IN (
SELECT name
FROM #Customers_Test Duped_Customers
GROUP BY name, [date]
HAVING COUNT(name) > 1
)

Related

SQL - complex SELECT query to UPDATE

SQL noob here.
So i have a table with a lot of products. Some of these products are clothing, meaning i have e.g. six different SKU's for one product due to different sizes. I now want to set the first product variant as the parent for each group. Product IDs and therefore the parent IDs are UUIDs. I managed to write a SELECT query which is taking all the product numbers, finds all groups (by cutting off the last pair of numbers) and assigns all the respective parent (uu)IDs and parent product numbers (for human readable comparison) - it works absolutely fine.
But i have no clue on how to convert this rather complex SELECT into an UPDATE. Anyone having ideas? Version is MySQL 8
Table1 looks like this (with all unused columns cut out):
ID |product_number |parent_id
-------------------------------------------
[UUID]1-1 |123-456-01 | NULL
[UUID]1-2 |123-456-02 | NULL
[UUID]1-3 |123-456-03 | NULL
[UUID]1-4 |123-456-04 | NULL
[UUID]2-1 |987-65-43-21-01 | NULL
[UUID]2-2 |987-65-43-21-02 | NULL
[UUID]2-3 |987-65-43-21-03 | NULL
[UUID]2-4 |987-65-43-21-04 | NULL
My SELECT query:
SELECT ArticleNumber, ArticleGroup, ParentID, t3.id as ID
FROM (
SELECT t2.product_number as ArticleNumber, GroupTable.GroupNr as ArticleGroup, GroupTable.product_number as ParentID
FROM (
SELECT MIN(result.product_number) as product_number, result.GroupNr
FROM (
SELECT product_number,
SUBSTRING_INDEX(product_number, "-", (LENGTH(product_number) - LENGTH(REPLACE(product_number, "-", "")))) as GroupNr
FROM table1.product
) result
WHERE LENGTH(result.GroupNr) > 0
GROUP BY result.GroupNr
ORDER BY GroupNr
) as GroupTable
JOIN table1.product as t2
ON t2.product_number like concat(GroupTable.GroupNr, '%') AND t2.product_number != GroupTable.product_number
ORDER BY GroupTable.GroupNr
) as Energija
JOIN table1.product as t3
ON t3.product_number = Energija.ParentID
I want to update the parent_id so that Table1 looks like this:
ID |product_number |parent_id
-------------------------------------------
[UUID]1-1 |123-456-01 | NULL
[UUID]1-2 |123-456-02 | [UUID]1-2
[UUID]1-3 |123-456-03 | [UUID]1-2
[UUID]1-4 |123-456-04 | [UUID]1-2
[UUID]2-1 |987-65-43-21-01 | NULL
[UUID]2-2 |987-65-43-21-02 | [UUID]2-2
[UUID]2-3 |987-65-43-21-03 | [UUID]2-2
[UUID]2-4 |987-65-43-21-04 | [UUID]2-2
It works in the SELECT query, i just don't know how to make an UPDATE out of this.
Sample table with UUIDs switched for string:
CREATE TABLE table1.product (
id varchar(255),
product_number varchar(255),
parent_id varchar(255));
INSERT INTO Table1.product (
id, product_number, parent_id)
VALUES(
'1-1',
'123-456-01',
NULL),
(
'1-2',
'123-456-02',
NULL),
(
'1-3',
'123-456-03',
NULL),
(
'1-4',
'123-456-04',
NULL),
(
'2-1',
'987-65-43-21-01',
NULL),
(
'2-2',
'987-65-43-21-02',
NULL),
(
'2-3',
'987-65-43-21-03',
NULL),
(
'2-4',
'987-65-43-21-04',
NULL);
You just need to slightly adapt your query and set the parentUuid in the update statement, where the product uuid matches.
In the example code below I adapted your query to get a mapping between the products uuid and the parent uuid. Then I update the table setting the parent_id from the product-table where the products uuid matches the product uuid from the query.
UPDATE table1.product p
SET parent_id = (
SELECT parentUUID
FROM (SELECT t3.id as parentUUID, Energija.productuuid as productUuid
FROM (
SELECT t2.id as productuuid,
t2.product_number as ArticleNumber,
GroupTable.GroupNr as ArticleGroup,
GroupTable.product_number as ParentID
FROM (
SELECT MIN(result.product_number) as product_number, result.GroupNr
FROM (
SELECT product_number,
SUBSTRING_INDEX(product_number, "-",
(LENGTH(product_number) - LENGTH(REPLACE(product_number, "-", "")))) as GroupNr
FROM table1.product
) result
WHERE LENGTH(result.GroupNr) > 0
GROUP BY result.GroupNr
ORDER BY GroupNr
) as GroupTable
JOIN table1.product as t2
ON t2.product_number like concat(GroupTable.GroupNr, '%') AND
t2.product_number != GroupTable.product_number
ORDER BY GroupTable.GroupNr
) as Energija
JOIN table1.product as t3
ON t3.product_number = Energija.ParentID) parentMapping
where parentMapping.productuuid = p.id);

Mysql - How to create view representing dynamic pivot

I have two database tables customers which contains data about customers with the scheme like that:
mysql> SELECT * FROM customers;
customer_id created_at partner_id
1 "2019-08-20 09:17:58" cats
2 "2019-09-12 11:46:37" dogs
and customers_facts which keeps the customers facts in a form of fact_name and corresponding fact_value.
mysql> SELECT * FROM customers_facts;
customer_id fact_name fact_value
1, name Milton
1 city Milan
2 surname Bloom
2 name Orlando
I want to create a pivot table which in each row will have a customer and it's facts each as a separate column. Something like this:
mysql> SELECT * FROM pivot_table;
customer_id created_at partner_id name city surname
1 "2019-08-20 09:17:58" cats Milton Milan
2 "2019-09-12 11:46:37" dogs Orlando Bloom
I've found a script that allows me to create such table:
SET #sql = '';
SELECT
#sql := CONCAT(#sql,if(#sql='','',', '),temp.output)
FROM
(
SELECT
DISTINCT
CONCAT(
'MAX(IF(cf.fact_name = ''',
fact_name,
''', cf.fact_value, NULL)) AS ''',
fact_name,
''''
) as output
FROM
customers_facts
) as temp;
SET #sql = CONCAT('SELECT c.customer_id, c.created_at, c.partner_id, ', #sql, '
FROM customers c
LEFT JOIN customers_facts AS cf
ON cf.customer_id = c.customer_id
GROUP BY c.customer_id, c.created_at, c.partner_id');
but I have an issue of how to make it so:
a) I will be able to query the pivot table
b) When I add a new entry / update an old one in one of those two original tables the pivot table will be updated
How to solve ? Is it possible ?
Consider the following:
DROP TABLE IF EXISTS customers;
CREATE TABLE customers
(customer_id SERIAL PRIMARY KEY
,created_at DATETIME NOT NULL
,partner_id INT NOT NULL
);
INSERT INTO customers VALUES
(1,"2019-08-20 09:17:58",108),
(2,"2019-09-12 11:46:37",110);
DROP TABLE IF EXISTS customers_facts ;
CREATE TABLE customers_facts
(customer_id INT NOT NULL
,fact_name VARCHAR(20) NOT NULL
,fact_value VARCHaR(20) NOT NULL
,PRIMARY KEY(customer_id,fact_name)
);
INSERT INTO customers_facts VALUES
(1,'name','Milton'),
(1,'city','Milan'),
(2,'surname','Bloom'),
(2,'name','Orlando');
Now we can create a VIEW in the manner you describe...
DROP VIEW IF EXISTS my_pivot;
CREATE VIEW my_pivot AS
SELECT c.customer_id
, c.created_at
, c.partner_id
, MAX(CASE WHEN fact_name = 'name' THEN fact_value END) name
, MAX(CASE WHEN fact_name = 'surname' THEN fact_value END) surname
, MAX(CASE WHEN fact_name = 'city' THEN fact_value END) city
FROM customers c
LEFT
JOIN customers_facts f
ON f.customer_id = c.customer_id
GROUP
BY c.customer_id;
We can interrogate this VIEW with a simple query - e.g. SELECT customer_id FROM my_pivot WHERE name = 'Milton', however, this cannot use an index, so it's not very efficient.
Also, because of the way in which we created the VIEW, it cannot be updated...
UPDATE my_pivot SET name = 'Leonardo' WHERE customer_id = 1;
ERROR 1288 (HY000): The target table my_pivot of the UPDATE is not updatable
However, had we created the VIEW slightly differently, then it could be updated...
DROP VIEW IF EXISTS my_pivot;
CREATE VIEW my_pivot AS
SELECT c.customer_id
, c.created_at
, c.partner_id
, name.fact_value name
, surname.fact_value surname
, city.fact_value city
FROM customers c
LEFT
JOIN customers_facts name
ON name.customer_id = c.customer_id
AND name.fact_name = 'name'
LEFT
JOIN customers_facts surname
ON surname.customer_id = c.customer_id
AND surname.fact_name = 'surname'
LEFT
JOIN customers_facts city
ON city.customer_id = c.customer_id
AND city.fact_name = 'city';
UPDATE my_pivot SET name = 'Leonardo' WHERE customer_id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
SELECT * FROM customers_facts;
+-------------+-----------+------------+
| customer_id | fact_name | fact_value |
+-------------+-----------+------------+
| 1 | city | Milan |
| 1 | name | Leonardo |
| 2 | name | Orlando |
| 2 | surname | Bloom |
+-------------+-----------+------------+
...but this still cannot use an index.
EDIT: To answer the question asked in comments below your question, you can do...
SELECT customer_id
FROM customers_facts
WHERE
( fact_name,fact_value ) IN (('name','Orlando'),('surname','Bloom'))
GROUP
BY customer_id
HAVING COUNT(*) = 2;
...although I think MySQL can't use an index in this instance, so the longhand version might be better...
SELECT customer_id
FROM customers_facts
WHERE
( fact_name = 'name'
AND fact_value = 'Orlando'
)
OR
( fact_name = 'surname'
AND fact_value = 'Bloom'
)
GROUP
BY customer_id HAVING COUNT(*) = 2;

MySQL - Join if no duplicate

I want to Join to table. the condition is I want to only join those rows which have only one row to match. eg.
books:
id | name | price
1 | book1 | 19
2 | book2 | 19
3 | book3 | 30
price_offer:
id | offer | price
1 | offer1 | 19
2 | offer2 | 30
so now if I do select query on these table:
SELECT * FROM price_offer
JOIN books ON price_offer.price = books.price
I only want to join book with id 3 as it have only one match with price_offer table.
You could use a self join for books table to pick a book with only single match
select po.*, b1.*
from price_offer po
join books b1 on po.price = b1.price
join (
select price,max(id) id
from books
group by price
having count(*) = 1
) b2 on b1.id = b2.id
Demo
Try following query:
Sample data:
create table books(id int, name varchar(10), price int);
insert into books values
(1, 'book1', 19),
(2, 'book2', 19),
(3, 'book3', 30);
create table price_offer(id int, offer varchar(10), price int);
insert into price_offer values
(1, 'offer1', 19),
(2, 'offer2', 30);
Query:
select max(b.id)
from price_offer p
left join books b on b.price = p.price
where p.id is not null
group by b.price
having count(*) = 1;
If you want to avoid nesting queries where you have to use self-joins, you can use window-functions of MySQL 8.0.11, which are exactly for cases like this

MySQL - SELECT query - oldest and youngest date

I have to build one SQL SELECT query. Table structure is as follows:
[ID] [PHONE] [description] [DATE]
[1] [600898367] [main] [2016-01-23]
[2] [600898367] [] [2016-01-24]
[3] [600898367] [] [2016-01-26]
[4] [600898367] [] [2016-01-28]
[5] [662349093] [main] [2016-01-10]
[6] [662349093] [] [2016-01-21]
[7] [662349093] [] [2016-01-30]
[8] [662349093] [] [2016-01-31]
You have here different records grouped within the same telephone number. The first (the oldest) occurance is marked with [main] flag. There's no two identical numbers with [main] flag.
I want to select each [main] record and additionaly one youngest with the same phone number, so the result should give records 1,4,5,8.
Please help.
Use a WHERE clause to give you the records with the main flag. Use MAX to get the most recent record and JOIN to get the additional columns. Finally, do a UNION ALL to combine the result.
-- Get the main records first
SELECT *
FROM tbl
WHERE description = 'main'
UNION ALL
-- Get the most recent records
SELECT b.*
FROM (
SELECT
t.PHONE,
MAX(DATE) AS MaxDate
FROM tbl t
GROUP BY PHONE
) a
INNER JOIN tbl b -- Do a JOIN to get the additional columns
ON b.PHONE = a.PHONE
AND b.DATE = a.MaxDate
Try this;)
SQL Fiddle
MySQL 5.6 Schema:
CREATE TABLE table1
(`ID` int, `PHONE` int, `description` varchar(4), `DATE` varchar(11))
;
INSERT INTO table1
(`ID`, `PHONE`, `description`, `DATE`)
VALUES
(1, 600898367, 'main', '2016-01-23'),
(2, 600898367, NULL, '2016-01-24'),
(3, 600898367, NULL, '2016-01-26'),
(4, 600898367, NULL, '2016-01-28'),
(5, 662349093, 'main', '2016-01-10'),
(6, 662349093, NULL, '2016-01-21'),
(7, 662349093, NULL, '2016-01-30'),
(8, 662349093, NULL, '2016-01-31')
;
Query 1:
select t.*
from table1 t
inner join (
select `PHONE`, max(`DATE`) as `DATE` from table1 group by `PHONE`
) t1 on t.`PHONE` = t1.`PHONE` and (t.`DATE` = t1.`DATE` or t.`description` = 'main')
order by t.`ID`
Results:
| ID | PHONE | description | DATE |
|----|-----------|-------------|------------|
| 1 | 600898367 | main | 2016-01-23 |
| 4 | 600898367 | (null) | 2016-01-28 |
| 5 | 662349093 | main | 2016-01-10 |
| 8 | 662349093 | (null) | 2016-01-31 |
You can use the following query:
SELECT t1.*, t3.*
FROM mytable AS t1
LEFT JOIN (
SELECT PHONE, MAX(date) AS max_date
FROM mytable
GROUP BY PHONE
) AS t2 ON t1.PHONE = t2.PHONE
LEFT JOIN mytable AS t3 ON t1.PHONE = t3.PHONE AND t2.max_date = t3.`date`
WHERE t1.description = 'main'
With a GROUP BY we'll first group on both PHONE and description, thus getting 4 rows.
Next we'll create a comma separated set using GROUP_CONCAT. It can take an ORDER BY clause, to order the phone numbers by date.
Last we want to get the first item from the set, we can do this with SUBSTRING_INDEX.
SELECT
SUBSTRING_INDEX(GROUP_CONCAT(`PHONE` ORDER BY `DATE`), ',', 1) AS `PHONE`,
description
FROM table1
GROUP BY `PHONE`, `description`;
See Fiddle
Try the following query..
SELECT
A.*
FROM
`old_young` A
INNER JOIN
(SELECT
MIN(`DATE`) AS Res
FROM
old_young
WHERE description = 'main'
GROUP BY PHONE
UNION
ALL
SELECT
MAX(`DATE`) AS Res
FROM
old_young
WHERE description = ''
GROUP BY PHONE) B
ON A.DATE = B.Res ;
Check the FIDDLE

how to do subquery with 3 tables and using where clause in multi values?

I want to do subquery with 3 tables and using where in multi values but I always get syntax error. I have to do reporting in Report Builder 3.0
Table A: record_id, Surname, Given Name
Table C: row_id, competency_code, competency_name
Table PC: link_id, record_id, row_id, attainment_date
I would like to join the tables into 1 table. One person will have some completion of competency_code and different with other person. the completion of competency_code based on the attainment_date. I also think to use iff function for attainment_date in competency_code value as complete/yes.
The table that I would like to create is:
Record_Id | Surname | GivenName | Code 1 | Code 2 | Code 3 | Code 4 | Code 5
01 | AA | AA | Complete | Complete | Complete | | Complete
02 | BB | BB | Complete | Complete | | Complete |
03 | CC | CC | | Complete | Complete | | Complete
here is the query that I tried to do.
select distinct a.id, a.surname, a.given_name
from all a
join
(
select pc.attainment_date
from personnel_competency pc
join
(
select c.code, c.name
from competency c)
competency c on (c.row_no = pc.linkid)
)
personnel_competency pc on (pc.id = a.id)
where c.code in ('ABC', 'BCD', 'ABE', 'DEA', 'DEF', 'POS', 'SAQ', 'LOP')
and pc.attainment_date < now()
order by a.record_id
My skill in SQL is very basic. Whether other ways to make the table like that?
Are you looking for a SQL to get your result. If so I think this is what you are looking for ..
It would help if you posted some sample data.
You can test it at
SQLFiddle
Here is the script ..
-- Generate schema and data
create table tableA (id int, surname varchar(30), given_name varchar(30));
create table tablePC (link_id int, id int, attainment_date datetime);
create table tableC (row_id int, competency_code varchar(20), Competency_name varchar(30));
insert into tableA (id, surname, given_name)
values (1, 'AA', 'AAgn')
, (2, 'BB', 'BBgn')
insert into tablePC (link_id, id, attainment_date)
values (1, 1, '2014-09-11')
, (2, 1, '2014-09-10')
, (3, 2, '2014-09-11')
insert into tableC (row_id, competency_code, Competency_name)
values (1, 'ABC', 'completed\Yes')
, (1, 'BCD', 'completed')
, (1, 'ABE', 'completed')
, (2, 'ABC', 'completed')
, (2, 'BCD', 'completed')
, (3, 'ABC', 'completed')
, (3, 'ABE', 'completed')
-- ===============
select *
from tableA TA
inner join tablePC PC
on TA.id = PC.id
inner join
(
select row_id, [ABC] as ABC, [BCD] as BCD, [ABE] as ABE
from tableC TC
pivot
(
max(Competency_name)
for Competency_code in ([ABC], [BCD], [ABE])
) as TCPVT
) TC
on PC.link_id = TC.row_id
where PC.attainment_date < GETDATE()