MySQL Rows Based On Prority Values - mysql

Lets say I have a table with four columns: FirstName, LastName, Number (not a primary key) and Status. If a there are a persons with the same First name, Last name and number, but differing status (where status is a string such as "King" or "Queen" or "Jack").
I want to retrieve all the values from the table for all columns, but if there are duplicates where there are those with the same first name, last name, and number, I want to get those with status of "King", and if there a duplicates without the "King" status then get those with "Queen" and if there is no duplicates with "King" or "Queen" than only get one of those with "Jack".
Basically, the order of priority is King, Queen, and then Jack. so, I want all values from the table but if there are duplicates only include the one with the highest priority. I did some research and it appears that SQL implementations other than MYSql provide functions such as dense_rank, but I need to implement this in MYSql and I cannot find any way how.

try using session variable:
SET #row_number:=1;
SELECT
FirstName,
LastName,
Number,
Status
FROM(
SELECT
#row_number:=
CASE
WHEN #FirstName = FirstName AND #LastName = LastName AND #Number = Number
THEN #row_number + 1
ELSE 1
END AS num,
#FirstName := FirstName as FirstName,
#LastName := LastName as LastName,
#Number := Number as Number,
Status
FROM
t1
ORDER BY
FirstName,
lastName,
Number,
CASE
WHEN STATUS = 'King' THEN '1'
WHEN STATUS = 'Queen' THEN '2'
WHEN STATUS = 'Jack' THEN '3'
END
) as ttt
WHERE num = 1;

One method of doing this involves union all:
select t.*
from t
where t.status = 'King'
union all
select t.*
from t
where t.status = 'Queen' and
not exists (select 1 from t t2 where t2.name = t.name andt2.status in ('King'))
union all
select t.*
from t
where t.status = 'Jack' and
not exists (select 1 from t t2 where t2.name = t.name and t2.status in ('King', 'Queen'));
Another method uses a correlated subquery:
select t.*
from t
where (name, field(t.status, 'King', 'Queen', 'Jack')) in
(select t2.name, max(field(t2.status, 'King', 'Queen', 'Jack'))
from t t2
where t2.status in ('King', 'Queen', 'Jack')
group by t2.name
);

Related

Sum of two counts from different table with different conditions

Basically I need to merge these into one single query:
SELECT COUNT( DISTINCT id ) AS totalRows1
FROM other_events WHERE status = "approved"
AND Location = 1
SELECT COUNT( DISTINCT Id ) AS totalRows2
FROM core_events WHERE Status = "Active"
AND Location_id = 1
When I do it like below, if there is no event with Location_id = 1 query returns 0. In that condition I need it to return the count of the first table only.
SELECT COUNT( DISTINCT t1.id ) + COUNT( DISTINCT t2.Id ) AS total
FROM other_events AS t1, core_events AS t2
WHERE t1.status = "approved"
AND t1.Location = 1
AND t2.Location_id = 1
AND t2.Status = 'Active'
ps. column names are exactly like above
Use a UNION statement to merge the result like this:
SELECT SUM(total) FROM (
SELECT COUNT(DISTINCT id) AS total
FROM other_events
WHERE (status = "approved" AND Location = 1)
UNION ALL
SELECT COUNT(DISTINCT Id) AS total
FROM core_events
WHERE (Location_id = 1 AND Status = 'Active')
) union_result

MySQL Conditioning Query

I was trying with writing a query to select a row from the table (attached screenshot). This is something peculiar, where * means any value. I need to select a row where Amount should be between Start Amount and End Amount and Department should be IT.
The condition for Country and Sub Department is a bit tricky. If the selected country is not in the Country column then the query should return me the record with * and same is the case with sub department.
I tried with a approach of selecting columns based on Department and amount like this
Select * from table_name where Department = 'IT'
and 1000 BETWEEN Start Amount AND End Amount
But, after this I am not sure how to get the result with below condition.
If country is not India then all * results I should get.
I believe you want something like:
SELECT *
FROM table_name
WHERE Department = 'IT'
AND 1000 BETWEEN `Start Amount` AND `End Amount`
AND country IN ('India','*')
AND `Sub Department` IN ('SD2','*')
ORDER BY country = 'India' DESC,
`Sub Department` = 'SD2' DESC
LIMIT 1
Use a union all to assign a group number in order of preference to every permitted combination of country/sub_department i.e. (India,SD1) (India,*) (*,*) then only select the rows with the lowest group number.
select t1.* from (
Select t1.* ,
if(#minGroup > groupNumber, #minGroup := groupNumber, #minGroup) minGroupNumber
from (
Select t1.*, 1 groupNumber from table_name t1
where Department = 'IT'
and 1000 BETWEEN `Start Amount` AND `End Amount`
and country = 'India'
and sub_department = 'SD1'
union all
Select t1.*, 2 groupNumber from table_name t1
where Department = 'IT'
and 1000 BETWEEN `Start Amount` AND `End Amount`
and country = 'India'
and sub_department = '*'
union all
Select t1.*, 3 groupNumber from table_name t1
where Department = 'IT'
and 1000 BETWEEN `Start Amount` AND `End Amount`
and country = '*'
and sub_department = '*'
) t1 cross join (select #minGroup := 3) t2
) t1 where groupNumber = #minGroup
Re-sending bcz, star is truncated
Select NVL(tc.Country, '*') Country_Column_name,
NVL(tsd.Sub_Dept, '*') Sub_Dept_Column_name
from table_name tn
LEFT OUTER JOIN table_country tc
ON tn.Country = tc.Country
LEFT OUTER JOIN table_sub_dept tsd
ON tn.Sub Dept = tsd.Sub Dept
where Department = 'IT'
and 1000 BETWEEN Start Amount AND End Amount
Note: Country & Sub Dpt table should have unique column and that should be used in the join.

How to insert a row in mysql before union?

Say I have the following:
CREATE TABLE newtable AS (
SELECT #rownum:=#rownum+1 as rownum, name, age FROM (
SELECT name, age FROM clubAmembers
UNION
SELECT name, age FROM clubBmembers
)
) AS atable
How can I make it such that I can "stick in a new row at the beginning of the table" prior to the SELECT union such that it would start with:
rownum | name| age
1 | "Jordan" | 6 <-- This is an arbitrarily inserted record with name="Jordan" age="6" that is not a part of any of the clubAmembers or clubBmembers table.
The rest of the table (rownum 2 and onwards) would contain the actual result form the union with clubAmembers then clubBmembers.
Basically I am looking for:
CREATE TABLE
INSERT a row "Jordan" | 6
Perform select with union such that the rows after the first would start with "rownum=2", all the data from clubAmembers, etc.
How to best do this?
"At the beginning of the table" is not truly meaningful to relational databases because the order results are returned are not guaranteed until you use an ORDER BY clause, at which point the order on disk becomes a moot point anyway.
In your case, since you want to guarantee an order in your result clause (and therefore ordering #rownum, you will have to use ORDER BY. Something like:
CREATE TABLE newtable AS (
SELECT #rownum:=#rownum+1 as rownum, name, age
FROM (
SELECT 'Jordan' AS name, 6 AS age, 0 AS ord
UNION
SELECT name, age, 1 AS ord FROM clubAmembers
UNION
SELECT name, age, 1 AS ord FROM clubBmembers
ORDER BY ord
)
) AS atable
Note that at no point does this guarantee that rows in clubAmembers will have a lower rownum than rows in clubBmembers. If you want to guarantee that clubAmembers have a lower rownum, while keeping the semantics of UNION (versus UNION ALL), you can use the following:
CREATE TABLE newtable AS (
SELECT #rownum:=#rownum+1 as rownum, name, age
FROM (
SELECT 'Jordan' AS name, 6 AS age, 0 AS ord
UNION ALL
SELECT name, age, 1 AS ord FROM clubAmembers
UNION ALL
SELECT name, age, 2 AS ord FROM clubBmembers AS b
WHERE NOT EXISTS(SELECT 1 FROM clubAmembers AS a
WHERE a.name = b.name AND a.age = b.age)
ORDER BY ord
)
) AS atable
Note if {name, age} could be duplicated within the clubXmembers table, you will need to add DISTINCT:
...
SELECT DISTINCT name, age, 1 AS ord FROM clubAmembers
UNION ALL
...
As per the request in the comments, if you had a clubCmembers table, you would do:
CREATE TABLE newtable AS (
SELECT #rownum:=#rownum+1 as rownum, name, age
FROM (
SELECT 'Jordan' AS name, 6 AS age, 0 AS ord
UNION ALL
SELECT name, age, 1 AS ord FROM clubAmembers
UNION ALL
SELECT name, age, 2 AS ord FROM clubBmembers AS b
WHERE NOT EXISTS(SELECT 1 FROM clubAmembers AS a
WHERE a.name = b.name AND a.age = b.age)
SELECT name, age, 3 AS ord FROM clubCmembers AS c
WHERE NOT EXISTS(SELECT 1 FROM clubAmembers AS a
WHERE a.name = c.name AND a.age = c.age)
AND NOT EXISTS(SELECT 1 FROM clubBmembers AS b
WHERE b.name = c.name AND b.age = c.age)
ORDER BY ord
)
) AS atable
I'm not sure if I got it right. But why don't you just add another union like this:
CREATE TABLE newtable AS (
SELECT #rownum:=#rownum+1 as rownum, name, age FROM (
SELECT 1, "Jordan", 6
UNION ALL
SELECT name, age FROM clubAmembers
UNION ALL
SELECT name, age FROM clubBmembers
)
) AS atable
You can separate the create table statmenet from the insert statmenet:
Create the table (you must know which colums are gona be there)
Insert your 1st record (INSERT INTO .... Values(...))
Use your statement but with insert into instead of create table like: INSERT INTO YourNewTable.... Values(YourSubQuery) (Nr and tye of columns must match your subquery)
This should do, I believe:
CREATE TABLE newtable AS (
SELECT (#rownum:=IFNULL(#rownum,0)+1)+1 as rownum, name, age FROM (
SELECT name, age FROM clubAmembers
UNION
SELECT name, age FROM clubBmembers
) AS s
UNION ALL
SELECT 1, 'Jordan', 6
) AS atable
Demo at SQL Fiddle: http://sqlfiddle.com/#!2/ab825/6

Is it possible for a mysql query to return true/false instead of values?

I have a table:
custID orderID orderComponent
=====================================
1 123 pizza
1 123 wings
1 234 breadsticks
1 239 salad
2 456 pizza
2 890 salad
I have a list of values - pizza, wings, breadsticks, and salad. I need a way to just get a true/false value if a customer has at least one record containing each of these. Is that possible with a mysql query, or do I just have to do a select distinct(orderComponent) for each user and use php to check the results?
If you are just looking to see if the customer has ordered all items, then you can use:
select t1.custid,
case when t2.total is not null
then 'true'
else 'false'
end OrderedAll
from yourtable t1
left join
(
select custid, count(distinct orderComponent) Total
from yourtable
where orderComponent in ('pizza', 'wings', 'breadsticks', 'salad')
group by custid
having count(distinct orderComponent) = 4
) t2
on t1.custid = t2.custid
See SQL Fiddle with Demo
If you want to expand this out, to see if the custid has ordered all items in a single order, then you can use:
select t1.custid,
t1.orderid,
case when t2.total is not null
then 'true'
else 'false'
end OrderedAll
from yourtable t1
left join
(
select custid, orderid, count(distinct orderComponent) Total
from yourtable
where orderComponent in ('pizza', 'wings', 'breadsticks', 'salad')
group by custid, orderID
having count(distinct orderComponent) = 4
) t2
on t1.custid = t2.custid
and t1.orderId = t2.orderid
See SQL Fiddle with Demo
If you only want the custid and the true/false value, then you can add distinct to the query.
select distinct t1.custid,
case when t2.total is not null
then 'true'
else 'false'
end OrderedAll
from yourtable t1
left join
(
select custid, count(distinct orderComponent) Total
from yourtable
where orderComponent in ('pizza', 'wings', 'breadsticks', 'salad')
group by custid
having count(distinct orderComponent) = 4
) t2
on t1.custid = t2.custid
See SQL Fiddle with Demo
Or by custid and orderid:
select distinct
t1.custid,
t1.orderid,
case when t2.total is not null
then 'true'
else 'false'
end OrderedAll
from yourtable t1
left join
(
select custid, orderid, count(distinct orderComponent) Total
from yourtable
where orderComponent in ('pizza', 'wings', 'breadsticks', 'salad')
group by custid, orderID
having count(distinct orderComponent) = 4
) t2
on t1.custid = t2.custid
and t1.orderId = t2.orderid
See SQL Fiddle with Demo
select case when
count(distinct orderComponent) = 4
then 'true'
else 'false'
end as bool
from tbl
where custID=1
Here's one approach. This approach does not require an inline view (derived table), and can be effective if you want to include flags for multiple conditions:
EDIT:
This returns custID that has a row for all four items:
SELECT t.custID
, MAX(IF(t.orderComponent='breadsticks',1,0))
+ MAX(IF(t.orderComponent='pizza',1,0))
+ MAX(IF(t.orderComponent='salad',1,0))
+ MAX(IF(t.orderComponent='wings',1,0)) AS has_all_four
FROM mytable t
GROUP BY t.custID
HAVING has_all_four = 4
ORIGINAL ANSWER:
(This checked for a customer "order" that had all four items, rather than just a "custID".)
SELECT t.custID
, t.orderID
, MAX(IF(t.orderComponent='breadsticks',1,0))
+ MAX(IF(t.orderComponent='pizza',1,0))
+ MAX(IF(t.orderComponent='salad',1,0))
+ MAX(IF(t.orderComponent='wings',1,0)) AS has_all_four
-- , MAX(IF(t.orderComponent='breadsticks',1,0)) AS has_breadsticks
-- , MAX(IF(t.orderComponent='pizza',1,0)) AS has_pizza
-- , MAX(IF(t.orderComponent='salad',1,0)) AS has_salad
-- , MAX(IF(t.orderComponent='wings',1,0)) AS has_wings
FROM mytable t
GROUP BY t.custID, t.orderID
HAVING has_all_four = 4
That will get the "orders" that have all four items. If you want to return just values for custID, then use the query above as an inline view (wrap it in another query)
SELECT s.custID
FROM (
SELECT t.custID
, t.orderID
, MAX(IF(t.orderComponent='breadsticks',1,0))
+ MAX(IF(t.orderComponent='pizza',1,0))
+ MAX(IF(t.orderComponent='salad',1,0))
+ MAX(IF(t.orderComponent='wings',1,0)) AS has_all_four
FROM mytable t
GROUP BY t.custID, t.orderID
HAVING has_all_four = 4
) s
GROUP BY s.custID
#EmmyS: you can do it both ways.
If you want to check using MySql use:
SELECT #rowcount:=COUNT(*) FROM orderComponent Where (Your Conditions);
IF (#rowcount > 0) THEN
'True'
ELSE
'False'
END IF

Get column name which has the max value in a row sql

I have a a table in my database where I store categories for newsarticles and each time a user reads an article it increments the value in the associated column. Like this:
Now I want to execute a query where I can get the column names with the 4 highest values for each record. For example for user 9, it would return this:
I've tried several things, searched a lot but don't know how to do it. Can anyone help me?
This should do it:
select
userid,
max(case when rank=1 then name end) as `highest value`,
max(case when rank=2 then name end) as `2nd highest value`,
max(case when rank=3 then name end) as `3rd highest value`,
max(case when rank=4 then name end) as `4th highest value`
from
(
select userID, #rownum := #rownum + 1 AS rank, name, amt from (
select userID, Buitenland as amt, 'Buitenland' as name from newsarticles where userID = 9 union
select userID, Economie, 'Economie' from newsarticles where userID = 9 union
select userID, Sport, 'Sport' from newsarticles where userID = 9 union
select userID, Cultuur, 'Cultuur' from newsarticles where userID = 9 union
select userID, Wetenschap, 'Wetenschap' from newsarticles where userID = 9 union
select userID, Media, 'Media' from newsarticles where userID = 9
) amounts, (SELECT #rownum := 0) r
order by amt desc
limit 4
) top4
group by userid
Demo: http://www.sqlfiddle.com/#!2/ff624/11
A very simple way of doing this is shown below
select userId, substring_index(four_highest,',',1) as 'highest value', substring_index(substring_index(four_highest,',',2),',',-1) as '2th highest value', substring_index(substring_index(four_highest,',',3),',',-1) as '3 rd highest value', substring_index(four_highest,',',-1) as '4th highest value' from
(
select userid, convert(group_concat(val) using utf8) as four_highest from
(
select userId,Buitenland as val,'Buitenland' as col from test where userid=9 union
select userId,Economie as val,' Economie' as col from test where userid=9 union
select userId,Sport as val ,'Sport' as col from test where userid=9 union
select userId,Cultuur as val,'Cultuur' as col from test where userid=9 union
select userId,Wetenschap as val,'Wetenschap' as col from test where userid=9 union
select userId,Media as val,'Media' as col from test where userid=9 order by val desc limit 4
) inner_query
)outer_query;
PL/SQL, maybe? Set user_id, query your table, store the returned row in an nx2 array of column names and values (where n is the number of columns) and sort the array based on the values.
Of course, the correct thing to do is redesign your database in the manner that #octern suggests.
This will get you started with the concept of grabbing the highest value from multiple columns on a single row (modify for your specific tables - I created a fake one).
create table fake
(
id int Primary Key,
col1 int,
col2 int,
col3 int,
col4 int
)
insert into fake values (1, 5, 9, 27, 10)
insert into fake values (2, 3, 5, 1, 20)
insert into fake values (3, 89, 9, 27, 6)
insert into fake values (4, 17, 40, 1, 20)
SELECT *,(SELECT Max(v)
FROM (VALUES (col1), (col2), (col3), (col4) ) AS value(v))
FROM fake