Sub Query or Nested. I really dont know. maybe neither - mysql

OK, I may have missed this in the search, but I am unsure of what I am looking for. I have a couple of tables that I need ti generate mailing labels from. Main table has name and surname. Then a second table has multiple rows for data like phon-123415324, address 1- 1 james st etc...There is also a table that bind the data. Here is an example result and the query.
SELECT
users.name,
users.surname,
details.`field`,
details.value
FROM
users
Inner Join details ON details.user_id = users.id
Inner Join bookings ON bookings.guest_id = users.id
WHERE
bookings.sub_event_id = '78'
NAME, SURNAME, field, value
David Oden title Mr
David Oden sex Male
David Oden mobile 0534600594
David Oden company Fterns Group
David Oden position
David Oden address_1 Cnr wtrewr Rd & wert St
David Oden address_2
David Oden suburb wertt Mile wertw
David Oden state MAS
David Oden postcode 14113
David Oden country USA
WHAT I need I all the data i 1 row to export to excel for mailing labels. Sorry if this is really dumb. I have spent about 4 hours researching with no luck.
I need the type to be a column header and the value to be the data.
Shoud read
Title | Name | Surname | Mobile | Company | Address etc.....
MR | David | Oden | 0534600594 | Fterns Group | etc....
Any help would be appreciated/

This is known as a PIVOT function but unfortunately MySQL does not have a PIVOT, so you will have to replicate it use an aggregate function an a CASE statement. If you know the values that you need to include in the query, then you can hard-code them similar to the following:
select
min(case when d.field = 'title' then d.value end) as Title,
u.name,
u.surname,
min(case when d.field = 'sex' then d.value end) as Sex,
min(case when d.field = 'mobile' then d.value end) as Mobile,
min(case when d.field = 'company' then d.value end) as Company,
min(case when d.field = 'position' then d.value end) as Position,
min(case when d.field = 'address_1' then d.value end) as Address_1,
min(case when d.field = 'address_2' then d.value end) as Address_2,
min(case when d.field = 'suburb' then d.value end) as suburb,
min(case when d.field = 'state' then d.value end) as State,
min(case when d.field = 'postcode' then d.value end) as Postcode,
min(case when d.field = 'country' then d.value end) as country
from users u
left join details d
on u.id = d.id
left join bookings b
on u.id = b.guest_id
where b.sub_event_id = 78
group by u.name, u.surname
See SQL Fiddle with demo
But if you have an unknown number of columns or if the value will change, then you will want to perform this dynamically, then you should read the following article on prepared statements:
Dynamic pivot tables (transform rows to columns)
Your code would look like this:
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'min(case when d.field = ''',
d.field,
''' then d.value end) AS ',
d.field
)
) INTO #sql
from users u
left join details d
on u.id = d.d_id;
SET #sql = CONCAT('SELECT u.name,
u.surname, ', #sql, '
from users u
left join details d
on u.id = d.d_id
left join bookings b
on u.id = b.guest_id
where b.sub_event_id = 78
group by u.name, u.surname');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
See SQL Fiddle with Demo

Related

How to find the shops whose customers are only men?

I have this schema:
PERSON(Name, Sex)
FREQUENTS(Name, Shop)
My question is, how do I find the shops whose clients are exclusively men?
You can use the following using a GROUP BY with HAVING:
SELECT frequents.shop
FROM frequents LEFT JOIN person ON frequents.name = person.name
GROUP BY frequents.shop
HAVING SUM(person.sex = 'female') = 0 AND SUM(person.sex = 'male') > 0
demo at dbfiddle.uk
select shop
from
(select shop , (case when sex = 'Male' then 1 else 2 end)s_cnt
from frequents a11
join person a12
on a11.name = a12.name
group by shop , (case when sex = 'Male' then 1 else 2 end)
) a11
group by shop
having sum(s_cnt) = 1
For example NOT EXISTS
select distinct shop
from frequents
where not exists (
select 1
from person
where person.name = frequents.name and person.sex = 'female'
)
However, according to this test is may be better to use IS NULL approach:
select distinct shop
from frequents
left join person on person.name = frequents.name and person.sex = 'female'
where person.name is null
If you want exclusively men, then I would think:
SELECT f.shop
FROM frequents f JOIN
person p
ON f.name = p.name
GROUP BY f.shop
HAVING MIN(p.sex) = MAX(p.sex) AND -- all sex values are the same or NULL
COUNT(p.sex) = COUNT(*) AND -- no NULL values
MIN(p.sex) = 'male' -- the value is male
This version does not assume that there are only two genders.

Mix SQL Rows into one

I am using the following SQL Query:
SELECT package_stats.developer
,xf_user.username
,xf_user.email
,xf_user_profile.homepage
,xf_user_profile.location
,xf_user_profile.about
,CASE WHEN field_id ='Github' THEN field_value ELSE '' END AS Github
,CASE WHEN field_id ='Repository' THEN field_value ELSE '' END AS Repository
,CASE WHEN field_id ='twitter' THEN field_value ELSE '' END AS Twitter
FROM package_stats, xf_user, xf_user_field_value, xf_user_profile
WHERE xf_user.username = package_stats.developer AND xf_user_profile.user_id = xf_user.user_id AND xf_user_field_value.user_id = xf_user.user_id AND xf_user_field_value.field_value <> ''
Which outputs the following:
developer username email about Github Repository Twitter
John Doe jdoe j#doe.com It's me https://github....
John Doe jdoe j#doe.com It's me
John Doe jdoe j#doe.com It's me https://link....
John Doe jdoe j#doe.com It's me https://twitter.com/...
How can I mix all those rows into a single row?
I believe you can use conditional aggregation:
SELECT ps.developer, u.username, u.email,
up.homepage, up.location, up.about,
MAX(CASE WHEN ufv.field_id = 'Github' THEN ufv.field_value END) AS Github,
MAX(CASE WHEN ufv.field_id = 'Repository' THEN ufv.field_value END) AS Repository,
MAX(CASE WHEN ufv.field_id = 'twitter' THEN ufv.field_value END) AS Twitter
FROM package_stats ps JOIN
xf_user u
ON u.username = ps.developer JOIN
xf_user_profile up
ON up.user_id = u.user_id JOIN
xf_user_field_value ufv
ON ufv.user_id = u.user_id
WHERE ufv.field_value <> ''
GROUP BY ps.developer, u.username, u.email,
up.homepage, up.location, up.about;
Notes:
Table aliases make the query easier to write and to read.
All columns should be qualified with a table alias, to avoid ambiguity.
There is no reason to have an ELSE clause for this purpose. If there are no matches, then the column is NULL (a reasonable value).
If there could be more than one field for a given category, use GROUP_CONCAT().
You could use a (fake) aggregation function and group by
SELECT package_stats.developer
,xf_user.username
,xf_user.email
,xf_user_profile.homepage
,xf_user_profile.location
,xf_user_profile.about
,min(CASE WHEN field_id ='Github' THEN field_value END) AS Github
,min(CASE WHEN field_id ='Repository' THEN field_value END) AS Repository
,min(CASE WHEN field_id ='twitter' THEN field_value END ) AS Twitter
FROM package_stats
INNER JOIN xf_user ON xf_user.username = package_stats.developer
INNER JOIN xf_user_profile ON xf_user_profile.user_id = xf_user.user_id
INNER JOIN xf_user_field_value v1 ON xf_user_field_value.user_id = xf_user.user_id
WHERE xf_user_field_value.field_value <> ''
GROUP BY package_stats.developer
,xf_user.username
,xf_user.email
,xf_user_profile.homepage
,xf_user_profile.location
,xf_user_profile.about
and don't use implict joins are not clear ..
the implict join is an arcaic sintax

Rows are represented as columns in a View

for my sense, I've a creative way of saving userdata. Let me explain:
I do not know which data is going to be saved in this database. For example, someone wants to save his icq number and i didn't know it before, where could he write it into? He dynamically creates a new field and in background there is an insert done in fields and an insert in user_fields where the new value of the new option is stored.
Table user:
id username
1 rauchmelder
Table fields:
id name
1 firstname
2 lastname
Table user_fields: (old values are stored as well as current, only youngest entry should be used)
id user_id fields_id value date
1 1 1 Chris 1.Mai
1 1 2 Rauch 1.Mai
1 1 1 Christopher 2.Mai
Result should be a View:
user.id user.username fields.firstname fields.lastname
1 rauchmelder Christopher Rauch
Firstly, does it make sense at all?
Secondly, should I solve it in MySQL or within the application?
Thridly, how to solve this in MySQL as a View?
In order to get the data into your columns, you can use an aggregate function with a CASE expression to convert the row data into columns.
If your fields are known ahead of time, then you can hard-code the values in your query:
select u.id,
u.username,
max(case when f.name = 'firstname' then uf.value end) firstname,
max(case when f.name = 'lastname' then uf.value end) lastname
from user u
left join
(
select uf1.*
from user_fields uf1
inner join
(
select max(date) maxDate, user_id, fields_id
from user_fields
group by user_id, fields_id
) uf2
on uf1.date = uf2.maxdate
and uf1.user_id = uf2.user_id
and uf1.fields_id = uf2.fields_id
) uf
on u.id = uf.user_id
left join fields f
on uf.fields_id = f.id
group by u.id, u.username;
See SQL Fiddle with Demo
But since you are going to have unknown fields, then you will need to use a prepared statement to generate dynamic SQL to execute. The syntax will be similar to this:
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'max(CASE WHEN f.name = ''',
name,
''' THEN uf.value END) AS `',
name, '`'
)
) INTO #sql
FROM fields;
SET #sql
= CONCAT('SELECT u.id,
u.username, ', #sql, '
from user u
left join
(
select uf1.*
from user_fields uf1
inner join
(
select max(date) maxDate, user_id, fields_id
from user_fields
group by user_id, fields_id
) uf2
on uf1.date = uf2.maxdate
and uf1.user_id = uf2.user_id
and uf1.fields_id = uf2.fields_id
) uf
on u.id = uf.user_id
left join fields f
on uf.fields_id = f.id
group by u.id, u.username');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
See SQL Fiddle with Demo

Get data from 3 tables and show nice table

I have 3 tables: users, rooms, room_access.
in users is: user1, user2, user3
in rooms: room1 room2 roomN (i have about 10 rooms)
room_access (room,uid): room1, 1; room2,1; roomN,1; room2,3; room1,3;
So. User3 have access to room1, user2 have access to roome, etc.
In admin area i want to display that:
Basically table rooms content is displayed table header and table users content is displayed table rows. Maybe there is way that i can select all that data in single query? And where is check symbol, there i want to place checkboxes. So. I don't know how to made that table.
Essentially you are trying to return the data via a PIVOT. MySQL does not have a PIVOT function so you can use a combination of aggregate functions and a CASE statement. If you know ahead of time the number of rooms that you will have you can use:
select u.uname,
max(case when r.rname = 'room 1' then 'y' else 'n' end) room1,
max(case when r.rname = 'room 2' then 'y' else 'n' end) room2,
max(case when r.rname = 'room 3' then 'y' else 'n' end) room3
from users u
left join room_access ra
on u.uid = ra.userid
left join rooms r
on ra.roomid = r.rid
group by u.uname
see SQL Fiddle with Demo
But if you have an unknown number of rooms then you can use a prepared statement to create a dynamic version
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'MAX(CASE WHEN rname = ''',
rname,
''' THEN ''Y'' ELSE ''N'' END) AS ',
replace(rname, ' ', '')
)
) INTO #sql
FROM rooms;
SET #sql = CONCAT('SELECT u.uname, ', #sql, '
from users u
left join room_access ra
on u.uid = ra.userid
left join rooms r
on ra.roomid = r.rid
group by u.uname');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
see SQL Fiddle with Demo

MYSQL How to count elements grouped by type

I have a problem with a query:
I have a list of stores, each of these stores has members and there are various categories of membership (Bronze, silver, gold ...)
The tables are: 'shops', 'members', 'membership_cards'.
shops: id, name
members: id, shops_id, membership_id, first_name, last_name
membership_cards: id, description
I need to extract the count of members, grouped by membership of each stores. Can I do this without using a server side language?
The final result should be something like:
Store's name, n°bronze members, n°silver_members, n°gold_members ....
Based on what you provided, you want a query like:
select shopid,
sum(case when c.cardtype = 'Bronze' then 1 else 0 end) as Bronze,
sum(case when c.cardtype = 'Silver' then 1 else 0 end) as Silver,
sum(case when c.cardtype = 'Gold' then 1 else 0 end) as Gold
from shops s left outer join
members m
on s.shopid = m.shopid left outer join
cards c
on c.memberid = m.memberid
group by shopid
If you want to know the number of members, rather than of cards in each group (if members can have more than one card), then replace the sum() expression with:
count(case when c.cardtype = 'Bronze' then m.memberid end)
Without knowing your database schema, it's a bit hard to answer that question, but something like the following should do the job:
SELECT shop.name,
SUM(CASE WHEN membership_cards.category = 'Bronze' THEN 1 ELSE 0 END) AS Bronze,
SUM(CASE WHEN membership_cards.category = 'Silver'THEN 1 ELSE 0 END) AS Silver,
SUM(CASE WHEN membership_cards.category = 'Gold' THEN 1 ELSE 0 END) AS Gold
FROM shops
INNER JOIN members
ON shop.id = members.shopid
INNER JOIN membership_cards
ON members.id = membership_cards.memberid
GROUP BY shop.name
Just change the column names to the names you are using.
SELECT B.name,A.Bronze,A.Silver,A.Gold
FROM
(
SELECT S.id,
SUM(IF(IFNULL(C.cardtype,'')='Bronze',1,0)) Bronze,
SUM(IF(IFNULL(C.cardtype,'')='Silver',1,0)) Silver,
SUM(IF(IFNULL(C.cardtype,'')='Gold' ,1,0)) Gold
FROM shops S
LEFT JOIN members M ON S.id = M.shops_id
LEFT JOIN membership_cards C ON M.membership_id = C.id
GROUP BY S.id
) A
INNER JOIN shops B USING (id);
I used the IFNULL function in case any member has no cards