Use some row values as column SQL - mysql

I would like to have columns that look like this:
KILL, DEATH, WIN, playerName
this is the resultset that it should be applied on
and this is the query that i use to get that resultset:
SELECT SUM(amount) as amount, type.name as type, gamemode_statistics.Name as playerName
FROM gamemode_statistics INNER JOIN type ON gamemode_statistics.type_id = type.id
GROUP BY gamemode_statistics.type_id, playerName
i really have no clue on how to do this, i tried various ways but none of them solve my problem. Maybe i just configured my tables incorrectly?

If these are the only three values you want to show as column you can do so,this will give you the count of types
SELECT SUM(amount) as amount,
SUM(`type`='KILL') AS `KILL`,
SUM(`type`='DEATH') AS `DEATH`,
SUM(`type`='WIN') AS `WIN`,
gamemode_statistics.Name as playerName
FROM gamemode_statistics
INNER JOIN type ON gamemode_statistics.type_id = type.id
GROUP BY gamemode_statistics.type_id, playerName

You're looking for a pivot query, and MySQL doesn't support them directly. For a simple 3-column result, it's not too bad, but this cannot be made to work as a general solution for n-way arbitrary columns:
SELECT
SUM(IF type='KILL', amount, 0) AS KILL,
SUM(IF type='DEATH', amount, 0) AS DEATH
etc..
FROM ...

SELECT
ISNULL(SUM(CASE WHEN type='KILL'THEN amount ELSE 0 END),0) AS KILL,
ISNULL(SUM(CASE WHEN type='DEATH'THEN amount ELSE 0 END) AS DEATH
etc..
FROM ...

Consider putting your database into 3rd normal form. http://www.andrewrollins.com/2009/08/11/database-normalization-first-second-and-third-normal-forms/
CREATE TABLE players (
id int not null primary key auto_increment,
name text not null,
... other player data
);
Create TABLE player_stats(
player_id int not null primary key, -- this is a foreign key to the players table
kills int not null default 0
deaths not null default 0,
wins not null default 0,
... other stats
);
then the query becomes something like
SELECT name, wins
FROM players join player_stats on player_id = players.id
WHERE name = 'fred';

Related

Insert empty row after group

I have a table that contains transaction data. The rows with the same 'group_id' are a part of the same transaction. I am running the following SQL query to show all the transactions:
SELECT * FROM transactions
When I run this query I get as expected a list of all the transactions. But this large list makes it difficult to seperate the data with a different group_id from the other data.
For that reason I want to add an empty row at the end of the group_id, so I get:
1
1
(empty row)
2
2
2
instead of:
1
1
2
2
2
Can someone help me with this?
Here is my database:
http://sqlfiddle.com/#!9/b9bf79/1
I do not suggest you do this at all but if you just want to separate two groups you could do this:
SELECT * FROM transactions WHERE group_id = 1
UNION ALL
(SELECT '','','','','','')
UNION ALL
SELECT * FROM transactions WHERE group_id = 2
Obviously this can added to if there are more group ids in the future but it is not a general purpose solution you are really better off dealing with appearance issues like this in application code.
you can use (abuse) rollup.
SELECT *
FROM transactions
group by group_id, id
with rollup
having group_id is not null
this will insert a row with id set to null after each group_id.
mysql will also sort by group_id because of the group by.
The group by id` makes sure that all rows are shown (your schema does not show it, but I assume id is unique? Otherwise you need to add other fields)
However only id will be null in the extra rows. The other columns repeat the value above.
You can filter them like this:
SELECT
id,
case id is not null when true then date else null end as date,
case id is not null when true then group_id else null end as group_id
-- ....
FROM transactions
group by group_id, id
with rollup
having group_id is not null
Alternatively:
select * from
(SELECT *
FROM transactions
union all
select distinct null, null, group_id, null, null,null from transactions
) as t
order by 3,1
but null values are sorted first, so the "gap" is in front of each section

SQL Query Still having duplicates after group by

SELECT *
FROM `eBayorders`
WHERE (`OrderIDAmazon` IS NULL
OR `OrderIDAmazon` = "null")
AND `Flag` = "True"
AND `TYPE` = "GROUP"
AND (`Carrier` IS NULL
OR `Carrier` = "null")
AND LEFT(`SKU`, 1) = "B"
AND datediff(now(), `TIME`) < 4
AND (`TrackingInfo` IS NULL
OR `TrackingInfo` = "null")
AND `STATUS` = "PROCESSING"
GROUP BY `Name`,
`SKU`
ORDER BY `TIME` ASC LIMIT 7
I am trying to make sure that none of the names and skus will show up in the same result. I am trying to group by name and then sku, however I ran into the problem where a result showed up that has the same name and different skus, which I dont want to happen. How can I fix this query to make sure that there is always distinct names and skus in the result set?!
For example say I have an Order:
Name: Ben Z, SKU : B000334, oldest
Name: Ben Z, SKU : B000333, second oldest
Name: Will, SKU: B000334, third oldest
Name: John, SKU: B000036, fourth oldest
The query should return only:
Name: Ben Z, SKU : B000334, oldest
Name: John, SKU: B000036, fourth oldest
This is because all of the Names should only have one entry in the set along with SKU.
There are two problems here.
The first is the ANSI standard says that if you have a GROUP BY clause, the only things you can put in the SELECT clause are items listed in GROUP BY or items that use an aggregate function (SUM, COUNT, MAX, etc). The query in your question selects all the columns in the table, even those not in the GROUP BY. If you have multiple records that match a group, the table doesn't know which record to use for those extra columns.
MySql is dumb about this. A sane database server would throw an error and refuse to run that query. Sql Server, Oracle and Postgresql will all do that. MySql will make a guess about which data you want. It's not usually a good idea to let your DB server make guesses about data.
But that doesn't explain the duplicates... just why the bad query runs at all. The reason you have duplicates is that you group on both Name and SKU. So, for example, for Ben Z's record you want to see just the oldest SKU. But when you group on both Name and SKU, you get a seperate group for { Ben Z, B000334 } and { Ben Z, B000333 }... that's two rows for Ben Z, but it's what the query asked for, since SKU is also part of what determines a group.
If you only want to see one record per person, you need to group by just the person fields. This may mean building that part of the query first, to determine the base record set you need, and then JOINing to this original query as part of your full solution.
SELECT T1.*
FROM eBayorders T1
JOIN
( SELECT `Name`,
`SKU`,
max(`TIME`) AS MAX_TIME
FROM eBayorders
WHERE (`OrderIDAmazon` IS NULL OR `OrderIDAmazon` = "null") AND `Flag` = "True" AND `TYPE` = "GROUP" AND (`Carrier` IS NULL OR `Carrier` = "null") AND LEFT(`SKU`, 1) = "B" AND datediff(now(), `TIME`) < 4 AND (`TrackingInfo` IS NULL OR `TrackingInfo` = "null") AND `STATUS` = "PROCESSING"
GROUP BY `Name`,
`SKU`) AS dedupe ON T1.`Name` = dedupe.`Name`
AND T1.`SKU` = dedupe.`SKU`
AND T1.`Time` = dedupe.`MAX_TIME`
ORDER BY `TIME` ASC LIMIT 7
Your database platform should have complained because your original query had items in the select list which were not present in the group by (generally not allowed). The above should resolve it.
An even better option would be the following if your database supported window functions (MySQL doesn't, unfortunately):
SELECT *
FROM
( SELECT *,
row_number() over (partition BY `Name`, `SKU`
ORDER BY `TIME` ASC) AS dedupe_rank
FROM eBayorders
WHERE (`OrderIDAmazon` IS NULL OR `OrderIDAmazon` = "null") AND `Flag` = "True" AND `TYPE` = "GROUP" AND (`Carrier` IS NULL OR `Carrier` = "null") AND LEFT(`SKU`, 1) = "B" AND datediff(now(), `TIME`) < 4 AND (`TrackingInfo` IS NULL OR `TrackingInfo` = "null") AND `STATUS` = "PROCESSING" ) T
WHERE dedupe_rank = 1
ORDER BY T.`TIME` ASC LIMIT 7
You are trying to obtain a result set which doesn't have repeats in either the SKU nor the Name column.
You might have to add a subquery to your query, to accomplish that. The inner query would group by Name, and the Outer query would group by SKU, such that you won't have repeats in either column.
Try this :
SELECT *
FROM
(SELECT *
FROM eBayorders
WHERE (`OrderIDAmazon` IS NULL
OR `OrderIDAmazon` = "null")
AND `Flag` = "True"
AND `TYPE` = "GROUP"
AND (`Carrier` IS NULL
OR `Carrier` = "null")
AND LEFT(`SKU`, 1) = "B"
AND datediff(now(), `TIME`) < 4
AND (`TrackingInfo` IS NULL
OR `TrackingInfo` = "null")
AND `STATUS` = "PROCESSING"
GROUP BY Name)
GROUP BY `SKU`
ORDER BY `TIME` ASC LIMIT 7
With this approach you just filter out rows that do not contain the largest/latest value for TIME.
SELECT SKU, Name
FROM eBayOrders o
WHERE NOT EXISTS (SELECT 0 FROM eBayOrders WHERE Name = o.name and Time > o.Time)
GROUP BY SKU, Name
Note: If two records have exactly the same Name and Time values, you may still end up getting duplicates, because the logic you have specified does not provide any way to break up a tie.

MySQL logic for prioritizing matching pattern/text

I have a table of incidents that have a short_description. I'm trying to assign them to a category based on the text in that short_description (not ideal, I know, but I'm working with an existing system and don't have much control). So I created a lookup table with search_text to look for, and the category value that should be assigned to the incident. In some cases more than one search_text value matches the short_description. I want to use the priority field to choose the highest priority (lowest priority value, such as 1) when this happens. I feel like maybe this involves a window function or something, but I'm not sure how to approach it.
Can someone help me with the changes needed in the logic below?
Thanks!
The query below returns two results, but I want it to just return one (Cluster) because Cluster is priority 1, and Disk is priority 2. I only want one record per snumber (incident).
CREATE TABLE snow_incident_s_2 (
SNUMBER varchar(40) DEFAULT NULL,
SHORT_DESCRIPTION varchar(200) DEFAULT NULL
);
INSERT INTO snow_incident_s_2 (snumber, short_description) values ('INC15535802','Prognosis::ADMINCLUSTER [5251]::CmaDiskPartitionNearFull, PROGNOSIS:ADMINCLUSTER');
CREATE TABLE lkp_incident_category_2 (
incident_category_id smallint(6) NOT NULL AUTO_INCREMENT,
incident_type varchar(10) DEFAULT NULL,
category varchar(100) NOT NULL,
search_text varchar(200) NOT NULL,
priority smallint(6) DEFAULT NULL,
PRIMARY KEY (incident_category_id)
);
INSERT INTO lkp_incident_category_2 (incident_type, category, search_text, priority) values ('INC','Cluster','Cluster',1);
INSERT INTO lkp_incident_category_2 (incident_type, category, search_text, priority) values ('INC','Disk','Disk',2);
SELECT
inc.snumber,
inc.short_description,
ic.search_text,
ic.category
FROM
snow_incident_s_2 inc
LEFT JOIN
lkp_incident_category_2 ic ON inc.short_description LIKE CONCAT('%', ic.search_text, '%')
AND ic.incident_type = 'INC'
window functions don't exist in mysql. but you can mimic them using variables.
I have been trying to test this but sqlfiddle is not cooperating so here is what I believe should work for you give it a try.
SELECT *
FROM (
SELECT
inc.snumber
,inc.short_description
,ic.search_text
,ic.category
,(#rownum := IF(#incnum=inc.snumber,#rownum+1,1)) as IncidentRowNum
,#incnum := inc.snumber
FROM
snow_incident_s_2 inc
LEFT JOIN lkp_incident_category_2 ic
ON inc.short_description LIKE CONCAT('%', ic.search_text, '%')
AND ic.incident_type = 'INC'
CROSS JOIN (SELECT #rownum := 0, #incnum := '') var
ORDER BY
ic.priority
) t
WHERE
t.IncidentRowNum = 1
;
Fully tested and functional here is a sqlfiddle of it http://sqlfiddle.com/#!9/75ff1/7
Here is how to use a window function.
I have to say ON inc.short_description LIKE CONCAT('%', ic.search_text, '%') will be slow. If this is is done once a while (like every day) as an ad-hoc it will be fine, but if this is done often (every hour, every minute or more often) you'll want to materialize the existence of these values since you can't make an index for them unless you use full a text query or NOSQL solution.
SELECT snumber, short_description, search_text, category
FROM (
SELECT snumber, short_description, search_text, category,
ROW_NUMBER() OVER (partition by snumber order by priority asc) as rn
FROM (
SELECT
inc.snumber,
inc.short_description,
ic.search_text,
ic.category,
ic.priority
FROM snow_incident_s_2 inc
LEFT JOIN lkp_incident_category_2 ic
ON inc.short_description LIKE CONCAT('%', ic.search_text, '%')
AND c.incident_type = 'INC'
) X
) Y
WHERE RN = 1

MySQL query optimization with group by clause

I want to calculate total and unique clickouts based on country,partner and retailer.
I have achieved the desired result but i think its not a optimal solution and for longer data sets it will take longer time. how can I improve this query?
here is my test table, designed query and expected output:
"country_id","partner","retailer","id_customer","id_clickout"
"1","A","B","100","XX"
"1","A","B","100","XX"
"2","A","B","100","XX"
"2","A","B","100","GG"
"2","A","B","100","XX"
"2","A","B","101","XX"
DROP TABLE IF EXISTS x;
CREATE TEMPORARY TABLE x AS
SELECT test1.country_id, test1.partner,test1.retailer, test1.id_customer,
SUM(CASE WHEN test1.id_clickout IS NULL THEN 0 ELSE 1 END) AS clicks,
CASE WHEN test1.id_clickout IS NULL THEN 0 ELSE 1 END AS unique_clicks
FROM test1
GROUP BY 1,2,3,4
;
SELECT country_id,partner,retailer, SUM(clicks), SUM(unique_clicks)
FROM x
GROUP BY 1,2,3
Output:
"country_id","partner","retailer","SUM(clicks)","SUM(unique_clicks)"
"1","A","B","2","1"
"2","A","B","4","2"
And here is DDL and input data:
CREATE TABLE test (
country_id INT(11) DEFAULT NULL,
partner VARCHAR(256) CHARACTER SET utf8 DEFAULT NULL,
retailer VARCHAR(256) CHARACTER SET utf8 DEFAULT NULL,
id_customer BIGINT(20) DEFAULT NULL,
id_clickout VARCHAR(256) CHARACTER SET utf8 DEFAULT NULL)
ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO test VALUES(1,'A','B','100','XX'),(1,'A','B','100','XX'),
(2,'A','B','100','XX'),(2,'A','B','100','GG'),
(2,'A','B','100','XX'),(2,'A','B','101','xx')
SELECT
country_id,
partner,
retailer,
COUNT(id_clickout) AS clicks,
COUNT(DISTINCT CASE WHEN id_clickout IS NOT NULL THEN id_customer END) AS unique_clicks
FROM
test1
GROUP BY
1,2,3
;
COUNT(a_field) won't count any NULL values.
So, COUNT(id_clickout) will only count the number of times that it is NOT NULL.
Equally, the CASE WHEN statement in the unique_clicks only returns the id_customer for records where they clicked, otherwise it returns NULL. This means that the COUNT(DISTINCT CASE) only counts distinct customers, and only when they clicked.
EDIT :
I just realised, it's potentially even simpler than that...
SELECT
country_id,
partner,
retailer,
COUNT(*) AS clicks,
COUNT(DISTINCT id_customer) AS unique_clicks
FROM
test1
WHERe
id_clickout IS NOT NULL
GROUP BY
1,2,3
;
The only material difference in the results will be that any country_id, partner, retailed that previously showed up with 0 clicks will now not appear in the results at all.
With an INDEX on country_id, partner, retailed, id_clickout, id_customer or country_id, partner, retailed, id_customer, id_clickout, however, this query should be significantly faster.
I think this is what you are after:
SELECT country_id,partner,retailer,COUNT(retailer) as `sum(clicks)`,count(distinct id_clickout) as `SUM(unique_clicks)`
FROM test1
GROUP BY country_id,partner,retailer
Result:
COUNTRY_ID PARTNER RETAILER SUM(CLICKS) SUM(UNIQUE_CLICKS)
1 A B 2 1
2 A B 4 2
See result in SQL Fiddle.

Select characters and group together with joins?

I have the following table setup in mysql:
CREATE TABLE `games_characters` (
`game_id` int(11) DEFAULT NULL,
`player_id` int(11) DEFAULT NULL,
`character_id` int(11) DEFAULT NULL,
KEY `game_id_key` (`game_id`),
KEY `character_id_key` (`character_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
My objective is to get a game_id where a list of character_ids are all present in this game_id.
An example set of data:
1, 1
1, 2
1, 3
2, 1
2, 2
3, 1
3, 4
Let's say i want to get the game_id where the character_id has 1, 2, and 3. How would I go about making an efficient query? Best idea I have had so far was joining the table to itself multiple times, but i assume there has to be a better way to do this.
Thanks
EDIT: for anyone curious this was the final solution I used as it proved the best query time:
SELECT game_ID
FROM (
SELECT DISTINCT character_ID, game_ID
FROM games_Characters
) AS T
WHERE character_ID
IN ( 1, 2, 3 )
GROUP BY game_ID
HAVING COUNT( * ) =3
Select game_ID from games_Characters
where character_ID in (1,2,3)
group by game_ID
having count(*) = 3
the above makes two assumptions
1) you know the characters your looking for
2) game_ID and character_ID are unique
I don't assume you can get the #3 for the count I knnow you can since you know the list of people you're looking for.
This ought to do it.
select game_id
from games_characters
where character_id in (1,2,3)
group by game_id
having count(*) = 3
If that's not dynamic enough for you you'll need to add a few more steps.
create temporary table character_ids(id int primary key);
insert into character_ids values (1),(2),(3);
select #count := count(*)
from character_ids;
select gc.game_id
from games_characters as gc
join character_ids as c
on (gc.character_id = c.id)
group by gc.game_id
having count(*) = #count;