Mysql - how to sort results by values in columns? - 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
1 birthday "2019-08-20 09:17:58"
1 company Idaho
2 surname Bloom
2 name Orlando
3 name Milton
3 city Milan
3 birthday "2011-10-20 11:17:58"
3 company Chicago
I want to create a query to get all customer_id where name=Milton and city=Milan sorted by birthday and company. So in my example the results would be:
mysql> SELECT customer_id FROM ....
customer_id
1
3
I have a query which gets all the customers_id where name=Milton and city=Milan
SELECT cf.* FROM customers_facts cf
WHERE cf.customer_id IN (
SELECT cf.customer_id FROM customers_facts cf
WHERE (cf.fact_name,cf.fact_value) IN (('name','Milton'),('city','Milan'))
GROUP BY cf.customer_id
HAVING COUNT(*) = 2
)
But I have no idea on how to sort the results by fact_value How to do it ? Is it even possible with such scheme ?

This is a little tricky. You can't filter easily before aggregating. So, do the filtering in the having clause:
SELECT customer_id
FROM customers_facts
GROUP BY customer_id
HAVING SUM( fact_name = 'name' AND fact_value = 'Milton' ) > 0 AND
SUM( fact_name = 'city' AND fact_value = 'Milan' ) > 0
ORDER BY MAX(CASE WHEN fact_name = 'birthday' THEN fact_value END) DESC,
MAX(CASE WHEN fact_name = 'company' THEN fact_value END)

Ues pivoting logic to turn out the birthday for each matching customer group:
SELECT customer_id
FROM customers_facts
WHERE (fact_name, fact_value) IN (('name', 'Milton'), ('city', 'Milan'))
GROUP BY customer_id
HAVING MIN(fact_name) <> MAX(fact_name)
ORDER BY MAX(CASE WHEN fact_name = 'birthday' THEN fact_value END);
Other than the ORDER BY clause, I used a HAVING clause which ensures that both the matching name and city were present in each matching customer group.
Edit:
Here is your desired ORDER BY clause:
ORDER BY
MAX(CASE WHEN fact_name = 'birthday' THEN fact_value END) DESC,
MAX(CASE WHEN fact_name = 'company' THEN fact_value END); -- ASC is default

Related

SQL Select with Conditional Fields (Subquerys maybe?)

I'm trying to set up an SQL View that returns all details of a contact. Name and Last name are saved in a Table "Person", the contact info is saved in "contact" and the type of contact info (email, phone 1, phone2) is saved in "contact_types".
I want to return all the information in 1 row but I can't really figure it out. So far my best result is with:
SELECT
Person.ID, Person.Title, Person.Firstname, Person.Lastname,
( SELECT MAX(ContactInfo.InfoText) FROM ContactInfo WHERE ContactInfo.ContactTypID = '1' AND ContactInfo.PersonID = Person.ID ) AS Phone_Business,
( SELECT MAX(ContactInfo.InfoText) FROM ContactInfo WHERE ContactInfo.ContactTypID = '2' AND ContactInfo.PersonID = Person.ID ) AS Phone_Private,
( SELECT MAX(ContactInfo.InfoText) FROM ContactInfo WHERE ContactInfo.ContactTypID = '3' AND ContactInfo.PersonID = Person.ID ) AS Phone_Mobile,
( SELECT MAX(ContactInfo.InfoText) FROM ContactInfo WHERE ContactInfo.ContactTypID = '5' AND ContactInfo.PersonID = Person.ID ) AS Email
FROM Person
This statement results in duplicate outputs - 4 identical rows, even with MAX(). It is apparently one row per subquery. How can I only receive 1 row per ID?
I'm quite new to SQL, any suggestions would be helpful!
Edit:
Sample Data:
Table Person:
ID
Title
Firstname
Lastname
1
Mr.
Tom
Selleck
2
Mr.
Fred
Miller
Table ContactInfo
PersonID
InfoText
ContactTypeID
1
tom.selleck#gmail.com
5
2
+1 12345 678
1
1
+1 98765 432
2
Table ContactTypeID
ID
InfoText
1
phone_business
2
phone_private
5
email
Expected Result:
ID
Title
Firstname
Lastname
Phone_Business
Phone_Private
Phone_Mobile
Email
1
Mr.
Tom
Selleck
NULL
+1 98765 432
NULL
tom.selleck#gmail.com
2
Mr.
Fred
Miller
+1 12345 678
NULL
NULL
NULL
It works so far, but I'd get each row 4 times.
You can do it like this:
SELECT P.ID, P.Title, P.Firstname, P.Lastname,
MAX(CASE WHEN C.ContactTypeID = '1' THEN C.InfoText END) AS Phone_Business,
MAX(CASE WHEN C.ContactTypeID = '2' THEN C.InfoText END) AS Phone_Private,
MAX(CASE WHEN C.ContactTypeID = '3' THEN C.InfoText END) AS Phone_Mobile,
MAX(CASE WHEN C.ContactTypeID = '5' THEN C.InfoText END) AS Email
FROM Person P
LEFT JOIN ContactInfo C
ON P.ID=C.PersonID
GROUP BY P.ID, P.Title, P.Firstname, P.Lastname;
Just a single LEFT JOIN between Person table and ContactInfo. The Person table here acts as a reference table. Then use MAX() with CASE expression (also possible with GROUP_CONCAT()) in SELECT.
Here's a demo fiddle

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;

How display the count of associates at each rating?

I have a table called associate_ratings with the below structure:
id int(11) NO PRI auto_increment
associate varchar(10) NO
skill_id int(11) NO MUL
rating int(11) NO
updated_time datetime NO
This table holds the skills(skill_id) of the associate and their corresponding rating in that skill.
Rating column can take values (1,2,3)
I want to get the in each skill how many associates have got a particular rating, please find below output table structure:
Skill_id Rating1_count Rating2_count Rating3_count
Java 2 1 4
C# 3 2 2
This says in Java there are 2 associates with rating 1, 1 associates with rating 2 & 4 associates with rating 3
I tried the below query, but the output is not in the format I expect:
SELECT skill_id, rating, count(*) FROM associate_ratings a
WHERE updated_time = (
SELECT max(updated_time)
FROM skill_set.associate_ratings b
WHERE a.associate = b.associate
) GROUP BY a.skill_id, a.rating order by a.skill_id, a.rating;
Could you please let me know how to get the output in the format I want?
Use temporary table and case
SELECT skill_id, sum(rating_1), sum(rating_2), sum(rating_3)
FROM (
SELECT a.skill_id as skill_id,
case a.rating when '1' then 1 else 0 end as rating_1,
case a.rating when '2' then 1 else 0 end as rating_2,
case a.rating when '3' then 1 else 0 end as rating_3
FROM associate_ratings a
WHERE updated_time = (
SELECT max(updated_time)
FROM skill_set.associate_ratings b
WHERE a.associate = b.associate
) ) as t
GROUP BY skill_id
ORDER BY skill_id;
select Skill_id ,
count(case when rating = 1 then 1 else null end) as Rating1_count ,
count(case when rating = 2 then 1 else null end) as Rating2_count ,
count(case when rating = 3 then 1 else null end) as Rating3_count
from associate_ratings b
left join associate_ratings a
on b.Skill_id = a.Skill_id
group by Skill_id
That would be something like this:
SELECT
skill_id,
sum(IF(rating=1,1,0)) as Rating1_count,
sum(IF(rating=2,1,0)) as Rating2_count,
sum(IF(rating=3,1,0)) as Rating3_count
FROM associate_ratings
GROUP BY skill_id
ORDER BY skill_id;
I think it's the most simple solution possible here.

MySQL SELECT to Rank A Number out of a set of numbers

I have rows of data from a SELECT query with a few prices (say three for this example). One is our price, one is competitor1 price, one is competitor2 price. I want to add a column that spits out the rank of our price as compared to the other two prices; if our price is the lowest it would spit out the number 1 if the highest it would spit out the number it is out of.
Something like this:
Make | Model | OurPrice | Comp1Price | Comp2Price | Rank | OutOf
MFG1 MODEL1 350 100 500 2 3
MFG1 MODEL2 50 100 100 1 3
MFG2 MODEL1 100 NULL 50 2 2
MFG2 MODEL2 9999 500 NULL 2 2
-Sometimes the competitor price will be NULL as seen above, and I believe this is where my issue lies. I have tried a CASE and it works when only on one competitor but when I add a AND statement it spits out the ranks as all NULL. Is there a better way of doing this through a MySQL query?
SELECT
MT.MAKE as Make,
MT.MODEL as Model,
MT.PRICE as OurPrice,
CT1.PRICE as Comp1Price,
CT2.PRICE as Comp2Price,
CASE
WHEN MT.PRICE < CT1.PRICE AND MT.PRICE < CT2.PRICE
THEN 1 END AS Rank
(CT1.PRICE IS NOT NULL) + (CT2.PRICE IS NOT NULL) + 1 as OutOf
FROM mytable MT
LEFT JOIN competitor1table as CT1 ON CT1.MODEL = MT.MODEL
LEFT JOIN competitor2table as CT2 ON CT2.MODEL = MT.MODEL
ORDER BY CLASS
Not tested, but you can try:
SELECT
a.MAKE AS Make,
a.MODEL AS Model,
a.PRICE AS OurPrice
MAX(CASE WHEN a.compnum = 1 THEN pricelist END) AS Comp1Price,
MAX(CASE WHEN a.compnum = 2 THEN pricelist END) AS Comp2Price,
FIND_IN_SET(a.PRICE, GROUP_CONCAT(a.pricelist ORDER BY a.pricelist)) AS Rank,
COUNT(a.pricelist) AS OutOf
FROM
(
SELECT MAKE, MODEL, PRICE, PRICE AS pricelist, 0 AS compnum
FROM mytable
UNION ALL
SELECT a.MAKE, a.MODEL, a.PRICE, CT1.PRICE, 1
FROM mytable a
LEFT JOIN competitor1table CT1 ON a.MODEL = CT1.MODEL
UNION ALL
SELECT a.MAKE, a.MODEL, a.PRICE, CT2.PRICE, 2
FROM mytable a
LEFT JOIN competitor2table CT2 ON a.MODEL = CT2.MODEL
) a
GROUP BY
a.MAKE, a.MODEL
(CT1.PRICE IS NOT NULL AND CT1.PRICE < MT.PRICE) + (CT2.PRICE IS NOT NULL AND CT2.PRICE < MT.PRICE) + 1 as Rank

COUNT from single column and seperate it into 2 column

This is my table "rating" in my database.
attraction customer rate
------------------------------------
attrac1 cust1 like
attrac2 cust1 dislike
attrac1 cust2 like
What SQL should i write to make the output become like this
attraction like dislike
----------------------------------
attrac1 2 0
attrac2 0 1
I tried this
SELECT a_id,
(SELECT COUNT(rate) FROM rating WHERE rate = 'like') as 'Like',
(SELECT COUNT(rate) FROM rating WHERE rate = 'dislike') as 'Dislike'
FROM rating
But I cant get the result that I want.
SELECT Attraction,
COUNT(CASE WHEN Rate = 'Like' THEN Customer END) [Like],
COUNT(CASE WHEN Rate = 'DisLike' THEN Customer END) [DisLike]
FROM Rating
GROUP BY Attraction
This is one approach:
SELECT a_id,
SUM(Case when rate='like' then 1 else 0 end) as 'Like',
SUM(Case when rate='dislike' then 1 else 0 end) as 'Dislike'
FROM rating
Group BY A_id