Mysql - How to create view representing dynamic pivot - mysql

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;

Related

Group_Concat with multiple joined tables

I have two main tables that comprise bookings for events.
A Registrants table (Bookings) R and an Events table E.
There are also two connected tables, Field_Values V and Event_Categories C
This diagram shows the relationship
What I am trying to do is create an Invoice query that mirrors the user's shopping cart. Often a user will book multiple events in one transaction, so my invoice should have columns for the common items e.g. User Name, User Email, Booking Date, Transaction ID and aggregated columns for the invoice line item values e.g. Quantity "1,2" Description "Desc1, Desc2" Price "10.00, 20.00" where there are two line items in the shopping cart.
The Transaction ID (dcea4_eb_registrant.transaction_id) is unique per Invoice and repeated per line item in that sale.
I have the following query which produces rows for each line item
SELECT
R.id as ID,
E.event_date as ServiceDate,
E.event_date - INTERVAL 1 DAY as DueDate,
Concat('Ad-Hoc Booking:',E.title) as ItemProductService,
Concat(R.first_name, ' ',R.last_name) as Customer,
R.first_name as FirstName,
R.last_name as LastName,
R.email,
R.register_date as InvoiceDate,
R.amount as ItemAmount,
R.comment,
R.number_registrants as ItemQuantity,
R.transaction_id as InvoiceNo,
R.published as Status,
E.event_date AS SERVICEDATE,
Concat('Ad-Hoc Booking:',E.title) AS DESCRIPTION,
R.number_registrants AS QUANTITY,
FORMAT(R.amount / R.number_registrants,2) AS RATE,
R.amount AS AMOUNT,
C.category_id as CLASS,
Concat(Group_Concat(V.field_value SEPARATOR ', '),'. ',R.comment) as Memo
FROM dcea4_eb_events E
LEFT JOIN dcea4_eb_registrants R ON R.event_id = E.id
LEFT JOIN dcea4_eb_field_values V ON V.registrant_id = R.id
LEFT JOIN dcea4_eb_event_categories C ON C.event_id = R.event_id
WHERE 1=1
AND V.field_id IN(14,26,27,15)
AND R.published <> 2 /*Including this line omits Cancelled Invoices */
AND R.published IS NOT NULL
AND (R.published = 1 OR R.payment_method = "os_offline")
AND (R.register_date >= CURDATE() - INTERVAL 14 DAY)
GROUP BY E.event_date, E.title, R.id, R.first_name, R.last_name, R.email,R.register_date, R.amount, R.comment
ORDER BY R.register_date DESC, R.transaction_id
This produces output like this
I'm using the following query to try to group together the rows with a common transaction_ID (rows two and three in the last picture) - I add group_concat on the columns I want to aggregate and change the Group By to be the transaction_id
SELECT
R.id as ID,
E.event_date as ServiceDate,
E.event_date - INTERVAL 1 DAY as DueDate,
Concat('Ad-Hoc Booking:',E.title) as ItemProductService,
Concat(R.first_name, ' ',R.last_name) as Customer,
R.first_name as FirstName,
R.last_name as LastName,
R.email,
R.register_date as InvoiceDate,
R.amount as ItemAmount,
R.comment,
R.number_registrants as ItemQuantity,
R.transaction_id as InvoiceNo,
R.published as Status,
Group_ConCat( E.event_date) AS SERVICEDATE,
Group_ConCat( Concat('Ad-Hoc Booking:',E.title)) AS DESCRIPTION,
Group_ConCat( R.number_registrants) AS QUANTITY,
Group_ConCat( FORMAT(R.amount / R.number_registrants,2)) AS RATE2,
Group_ConCat( R.amount) AS AMOUNT,
Group_ConCat( C.category_id) as CLASS,
Concat(Group_Concat(V.field_value SEPARATOR ', '),'. ',R.comment) as Memo
FROM dcea4_eb_events E
LEFT JOIN dcea4_eb_registrants R ON R.event_id = E.id
LEFT JOIN dcea4_eb_field_values V ON V.registrant_id = R.id
LEFT JOIN dcea4_eb_event_categories C ON C.event_id = R.event_id
WHERE 1=1
AND V.field_id IN(14,26,27,15)
AND R.published <> 2 /*Including this line omits Cancelled Invoices */
AND R.published IS NOT NULL
AND (R.published = 1 OR R.payment_method = "os_offline")
AND (R.register_date >= CURDATE() - INTERVAL 14 DAY)
GROUP BY R.transaction_id
ORDER BY R.register_date DESC, R.transaction_id
But this produces this output
It seems to be multiplying the rows. The Quantity column in the first row should just be 1 and in the second row it should be 2,1 .
I've tried using Group_Concat with DISTINCT but this doesn't work because often the values being concatenated are the same (e.g. the price for two events being booked are both the same) and the query only returns one value e.g. 10 and not 10, 10. The latter being what I need.
I'm guessing the issue is around the way the tables are joined but I'm struggling to work out how to get what I need.
Pointers in the right direction most appreciated.
You seem determined to go in what seems to me to be the wrong direction, so here's a gentle nudge down that hill...
Consider the following...
CREATE TABLE users
(user_id SERIAL PRIMARY KEY
,username VARCHAR(12) UNIQUE
);
INSERT INTO users VALUES
(101,'John'),(102,'Paul'),(103,'George'),(104,'Ringo');
DROP TABLE IF EXISTS sales;
CREATE TABLE sales
(sale_id SERIAL PRIMARY KEY
,purchaser_id INT NOT NULL
,item_code CHAR(1) NOT NULL
,quantity INT NOT NULL
);
INSERT INTO sales VALUES
( 1,101,'A',1),
( 2,103,'A',2),
( 3,103,'A',3),
( 4,104,'A',1),
( 5,104,'A',2),
( 6,104,'A',3),
( 7,103,'B',2),
( 8,103,'B',2),
( 9,104,'B',3),
(10,103,'B',2),
(11,104,'B',2),
(12,104,'B',1);
SELECT u.*
, x.sale_ids
, x.item_codes
, x.quantities
FROM users u
LEFT
JOIN
( SELECT purchaser_id
, GROUP_CONCAT(sale_id ORDER BY sale_id) sale_ids
, GROUP_CONCAT(item_code ORDER BY sale_id) item_codes
, GROUP_CONCAT(quantity ORDER BY sale_id) quantities
FROM sales
GROUP
BY purchaser_id
) x
ON x.purchaser_id = u.user_id;
+---------+----------+---------------+-------------+-------------+
| user_id | username | sale_ids | item_codes | quantities |
+---------+----------+---------------+-------------+-------------+
| 101 | John | 1 | A | 1 |
| 102 | Paul | NULL | NULL | NULL |
| 103 | George | 2,3,7,8,10 | A,A,B,B,B | 2,3,2,2,2 |
| 104 | Ringo | 4,5,6,9,11,12 | A,A,A,B,B,B | 1,2,3,3,2,1 |
+---------+----------+---------------+-------------+-------------+

Create trigger for several rows

I have table users AND orders. After every UPDATE row in orders. I want update DATA in users table namely concat(OLD.DATA + ID which was updated).
Table 'users'.
ID NAME DATA
1 John 1|2
2 Michael 3|4
3 Someone 5
Table 'orders'.
ID USER CONTENT
1 1 ---
2 1 ---
3 2 ---
4 2 ---
5 3 ---
For example:
SELECT `data` from `users` where `id` = 2; // Result: 3|4
UPDATE `orders` SET '...' WHERE `id` > 0;
**NEXT LOOP**
UPDATE `users` SET `data` = concat(OLD.data, ID.rowUpdated) WHERE `user` = 1;
UPDATE `users` SET `data` = concat(OLD.data, ID.rowUpdated) WHERE `user` = 1;
UPDATE `users` SET `data` = concat(OLD.data, ID.rowUpdated) WHERE `user` = 2;
UPDATE `users` SET `data` = concat(OLD.data, ID.rowUpdated) WHERE `user` = 2;
UPDATE `users` SET `data` = concat(OLD.data, ID.rowUpdated) WHERE `user` = 3;
Result:
SELECT data from users where id = 1; // Result: 1|2|1|2
SELECT data from users where id = 2; // Result: 3|4|3|4
SELECT data from users where id = 3; // Result: 5|5
How can I do it?
I think you are making the same mistake I made not too long ago, ie storing an array/object in a column.
I would recommend using the following tables in your scenario:
users
+-----------+-----------+
| id | user_name |
+-----------+-----------+
| 1 | John |
+-----------+-----------+
| 2 | Michael |
+-----------+-----------+
orders
+-----------+-----------+------------+
| id | user_id |date_ordered|
+-----------+-----------+------------+
| 1 | 1 | 2019-03-05 |
+-----------+-----------+------------+
| 2 | 2 | 2019-03-05 |
+-----------+-----------+------------+
Where user_id is the foreign key to users
sales
+-----------+-----------+------------+------------+------------+
| id | order_id | item_sku | qty | price |
+-----------+-----------+------------+------------+------------+
| 1 | 1 | 1001 | 1 | 2.50 |
+-----------+-----------+------------+------------+------------+
| 2 | 1 | 1002 | 2 | 3.00 |
+-----------+-----------+------------+------------+------------+
| 3 | 2 | 1001 | 2 | 2.00 |
+-----------+-----------+------------+------------+------------+
where order_id is the foreign key to orders
Now for the confusing part. You will need to use a series of JOINs to access the relevant data for each user.
SELECT
t3.id AS user_id,
t3.user_name,
t1.id AS order_id,
t1.date_ordered,
SUM((t2.price * t2.qty)) AS order_total
FROM orders t1
JOIN sales t2 ON (t2.order_id = t1.id)
LEFT JOIN users t3 ON (t1.user_id = t3.id)
WHERE user_id=1
GROUP BY order_id;
This will return:
+-----------+--------------+------------+------------+--------------+
| user_id | user_name | order_id |date_ordered| order_total |
+-----------+--------------+------------+------------+--------------+
| 1 | John | 1 | 2019-03-05 | 8.50 |
+-----------+--------------+------------+------------+--------------+
These type of JOIN statements should come up in basically any project using a relational database (that is, if you are designing your DB correctly). Typically I create a view for each of these complicated queries, which can then be accessed with a simple SELECT * FROM orders_view
For example:
CREATE
ALGORITHM = UNDEFINED
DEFINER = `root`#`localhost`
SQL SECURITY DEFINER
VIEW orders_view AS (
SELECT
t3.id AS user_id,
t3.user_name,
t1.id AS order_id,
t1.date_ordered,
SUM((t2.price * t2.qty)) AS order_total
FROM orders t1
JOIN sales t2 ON (t2.order_id = t1.id)
LEFT JOIN users t3 ON (t1.user_id = t3.id)
GROUP BY order_id
)
This can then be accessed by:
SELECT * FROM orders_view WHERE user_id=1;
Which would return the same results as the query above.
Depending on your needs, you will probably need to add a few more tables (addresses, products etc.) and several more rows to each of these tables. Very often you will find that you need to JOIN 5+ tables into a view, and sometimes you might need to JOIN the same table twice.
I hope this helps despite it not exactly answering your question!
It is probably a bad idea to update the USERS table after inserting into (or updating) the ORDERS table. Avoid storing data twice. In your case: you can always get all "order ids" for a user by querying the ORDERS table. Thus, you don't need to store them in the USERS table (again). Example (tested with MySQL 8.0, see dbfiddle):
Tables and data
create table users( id integer primary key, name varchar(30) ) ;
insert into users( id, name ) values
(1, 'John'),(2, 'Michael'),(3, 'Someone') ;
create table orders(
id integer primary key
, userid integer
, content varchar(3) references users (id)
);
insert into orders ( id, userid, content ) values
(101, 1, '---'),(102, 1, '---')
,(103, 2, '---'),(104, 2, '---'),(105, 3, '---') ;
Maybe a VIEW - similar to the one below - will do the trick. (Advantage: you don't need additional columns or tables.)
-- View
-- Inner SELECT: group order ids per user (table ORDERS).
-- Outer SELECT: fetch the user name (table USERS)
create or replace view userorders (
userid, username, userdata
)
as
select
U.id, U.name, O.orders_
from (
select
userid
, group_concat( id order by id separator '|' ) as orders_
from orders
group by userid
) O join users U on O.userid = U.id ;
Once the view is in place, you can just SELECT from it, and you will always get the current "userdata" eg
select * from userorders ;
-- result
userid username userdata
1 John 101|102
2 Michael 103|104
3 Someone 105
-- add some more orders
insert into orders ( id, userid, content ) values
(1000, 1, '***'),(4000, 1, '***'),(7000, 1, '***')
,(2000, 2, ':::'),(5000, 2, ':::'),(8000, 2, ':::')
,(3000, 3, '###'),(6000, 3, '###'),(9000, 3, '###') ;
select * from userorders ;
-- result
userid username userdata
1 John 101|102|1000|4000|7000
2 Michael 103|104|2000|5000|8000
3 Someone 105|3000|6000|9000

MySQL SELECT COUNT DISTINCT from 2 columns

I previously had 1 column country on top of which I performed COUNT(DISTINCT()) and GROUP_CONCAT(DISTINCT()).
SELECT
(SELECT COUNT(DISTINCT(country)) FROM flagevent AS f2
WHERE f2.user = f.user
) AS totalflags,
GROUP_CONCAT(DISTINCT(country) ORDER BY c.name) AS allcountries,
f.user, u.username
FROM flagevent AS f
INNER JOIN country AS c ON f.country = c.code
INNER JOIN user AS u ON f.user = u.id
WHERE f.user = 1
OR f.user in (1, 2, 3)
GROUP BY user
ORDER BY totalflags DESC;
That would give me this example result:
totalflags | allcountries | user | username
---------------------------------------------
5 | es,fr,it,de,pt | 1 | jagomf
Now instead of original country column, I have 2 columns country1 and country2 on top of which I have to perform same calculations, getting DISTINCT values of both 2 columns.
How can I apply same COUNT() and GROUP_CONCAT() on top of the distinct data of the 2 columns?
UPDATE: Table schemas:
flagevent (old):
- user: int(11)
- country: varchar(2)
flagevent (new):
- user: int(11)
- country1: varchar(2)
- country2: varchar(2)
user:
- username: varchar(45)
country:
- code: varchar(2)
- name: varchar(45)
Here is the easy way -- just change the new to look like the old:
Note, this was edited to use a CTE since you had the sub query with a count.
WITH flagevent_comb as (
SELECT user, country, COUNT(DISTINCT country) as cnt
FROM (
SELECT user, country1 AS country FROM flagevent WHERE country1 IS NOT NULL
UNION ALL
SELECT user, country2 AS country FROM flagevent WHERE country2 IS NOT NULL
) x
GROUP BY user, country
)
SELECT f.cnt as totalflags,
GROUP_CONCAT(DISTINCT(country) ORDER BY c.name) AS allcountries,
f.user, u.username
FROM flagevent_comb AS f
INNER JOIN country AS c ON f.country = c.code
INNER JOIN user AS u ON f.user = u.id
WHERE f.user = 1
OR f.user in (1, 2, 3)
GROUP BY user
ORDER BY totalflags DESC;
Note you might have to make the sub query more complicated. For example if sometimes country1 or country2 is null this would probably be better.
SELECT user, country1 AS country FROM flagevent WHERE country1 IS NOT NULL
UNION ALL
SELECT user, country2 AS country FROM flagevent WHERE country2 IS NOT NULL
Other business rules might apply.

Select All customers with exact name and birthdate

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
)

Attempting to concatenate multiple rows.. unexpected results

Issue:
I'm trying to return a list of contacts with a column containing all the parent item names concatenated together. Please don't bother telling me this violates 1st normal form, I already know. I've been trying to get this working for so long that it is probably some stupid mistake, but I need some help at this point.
Query:
SELECT C.ID, C.SequenceNumber, C.ContactType, C.GUID, C.ContactCategory, MAP.Parents, C.LastName, C.FirstName, C.CompanyName, C.Title, C.Email, C.Phone, C.MobilePhone, C.Fax, C.Comments, C.StandardConfirmation, C.Active FROM
(
SELECT ContactTypeMapping.Contact_GUID, STUFF( ( SELECT ','+ [NAME]
FROM ContactParents a
WHERE a.GUID =b.GUID
FOR XML PATH('')),1 ,1, '') Parents
FROM ContactParents b, ContactTypeMapping
WHERE ContactTypeMapping.Parent_GUID=b.GUID
) MAP
INNER JOIN
(
SELECT Contact.ID, Contact.GUID, Contact.SequenceNumber,
Contact.ContactType, Contact.ContactCategory, Contact.LastName, Contact.FirstName,
Contact.CompanyName, Contact.Title, Contact.Email, Contact.Phone,
Contact.MobilePhone, Contact.Fax, Contact.Comments, Contact.StandardConfirmation, Contact.Active
FROM Contact
)C
ON (MAP.Contact_GUID=C.GUID)
Current results:
ID SequenceNumber ContactType GUID ContactCategory Parents LastName FirstName CompanyName Title Email Phone MobilePhone Fax Comments StandardConfirmation Active
15 4 2 95A566D0-DB83-4853-9CB7-E6CF3B1FF814 0 AParent Beard Kirk NULL Business Kirk_Beard#someplace.com 913-906-3333 NULL (913) 906-3434 NULL 0 1
15 4 2 95A566D0-DB83-4853-9CB7-E6CF3B1FF814 0 AnotherParent Beard Kirk NULL Business Kirk_Beard#someplace.com 913-906-3333 NULL (913) 906-3434 NULL 0 1
Desired results:
15 4 2 95A566D0-DB83-4853-9CB7-E6CF3B1FF814 0 AParent,AnotherParent Beard Kirk NULL Business Kirk_Beard#someplace.com 913-906-3333 NULL (913) 906-3434 NULL 0 1
Data:
ContactParents Table:
GUID NAME TYPE
C40A6F7E-F760-48D6-8BAF-E55EC7DC900D AParent Place
A651A0A3-5A50-45F1-AB4B-2B7FDCE9734C AnotherParent Place
ContactTypeMapping Table:
Contact_GUID Parent_GUID ParentTable
95A566D0-DB83-4853-9CB7-E6CF3B1FF814 C40A6F7E-F760-48D6-8BAF-E55EC7DC900D Place
95A566D0-DB83-4853-9CB7-E6CF3B1FF814 A651A0A3-5A50-45F1-AB4B-2B7FDCE9734C Place
Contact Table:
ID GUID SequenceNumber ContactType LastName FirstName Title Email Phone MobilePhone Fax Comments Active ContactCategory CompanyName StandardConfirmation
15 95A566D0-DB83-4853-9CB7-E6CF3B1FF814 4 2 Beard Kirk Business Kirk_Beard#someplace.com 913-906-3333 NULL (913) 906-3434 NULL 1 0 NULL 0
;WITH cp AS -- contact parents - initial join
(
SELECT cp.NAME, ctm.Parent_GUID
FROM ContactParents AS cp
INNER JOIN ContactTypeMapping AS ctm
ON cp.GUID = ctm.Contact_GUID
),
cm AS -- contact mapping with concatenated values
(
SELECT Parent_GUID, Parents = (
SELECT STUFF ((SELECT ','+ [NAME] FROM cp AS cp2
WHERE cp2.Parent_GUID = cp.Parent_GUID
FOR XML PATH(''),
TYPE).value(N'./text()[1]',N'nvarchar(max)'),1 ,1, '')
)
FROM cp GROUP BY Parent_GUID
)
SELECT c.[GUID], cm.Parents --, other columns from c
FROM Contact AS c
INNER JOIN cm
ON c.[GUID] = cm.Parent_GUID;
To demonstrate how I verified that this query returns the right results, here are the table variables I created locally, how I populated them, and a slightly different query that references the table variables:
DECLARE #ContactParents TABLE
(
[GUID] UNIQUEIDENTIFIER,
NAME VARCHAR(32),
[TYPE] VARCHAR(32)
);
INSERT #ContactParents VALUES
('C40A6F7E-F760-48D6-8BAF-E55EC7DC900D','AParent','Place'),
('A651A0A3-5A50-45F1-AB4B-2B7FDCE9734C','AnotherParent','Place');
DECLARE #ContactTypeMapping TABLE
(
Contact_GUID UNIQUEIDENTIFIER,
Parent_GUID UNIQUEIDENTIFIER,
ParentTable VARCHAR(32)
);
INSERT #ContactTypeMapping VALUES
('A651A0A3-5A50-45F1-AB4B-2B7FDCE9734C','95A566D0-DB83-4853-9CB7-E6CF3B1FF814','Place'),
('C40A6F7E-F760-48D6-8BAF-E55EC7DC900D','95A566D0-DB83-4853-9CB7-E6CF3B1FF814','Place');
DECLARE #Contact TABLE
(
ID INT,
[GUID] UNIQUEIDENTIFIER,
LastName VARCHAR(32),
FirstName VARCHAR(32)
--, other columns...
);
INSERT #Contact VALUES
(15, '95A566D0-DB83-4853-9CB7-E6CF3B1FF814', 'Beard', 'Kirk');
;WITH cp AS
(
SELECT cp.NAME, ctm.Parent_GUID
FROM #ContactParents AS cp
INNER JOIN #ContactTypeMapping AS ctm
ON cp.GUID = ctm.Contact_GUID
),
cm AS
(
SELECT Parent_GUID, Parents = (
SELECT STUFF ((SELECT ','+ [NAME] FROM cp AS cp2
WHERE cp2.Parent_GUID = cp.Parent_GUID
FOR XML PATH('')),1 ,1, '')
)
FROM cp
GROUP BY Parent_GUID
)
SELECT c.[GUID], cm.Parents --, other columns from c
FROM #Contact AS c
INNER JOIN cm
ON c.[GUID] = cm.Parent_GUID;
Results
GUID Parents
95A566D0-DB83-4853-9CB7-E6CF3B1FF814 AParent,AnotherParent