MySQL: Merge rows and returning default values - mysql

Imagine the following table:
id | variant | name
-----------------------
1 | 1 | a
1 | 2 | b
1 | 3 | c
2 | 1 | d
2 | 2 | e
2 | 3 | NULL
3 | 1 | g
Which SQL statement do I need to run to get this:
For a given id and a given variant get the name of this combination.
But if the name is NULL get the name of the row with the given id but with variant 1 (the name of variant 1 is never NULL).
If there is no row with the given variant, again, use the row where variant is 1 with the same id.
Variant 1 is never requested directly.
This is like a fall back mechanism. Or you could consider it as overriding values of rows with variant = 1.
Examples:
id = 1, variant = 2: b
id = 1, variant = 3: c
id = 2, variant = 3: d
id = 3, variant = 5: g
Is this possible with SQL? And is it performing well if the mechanism is applied on many more fields?
Thanks!
Update:
Please note that I would like to have this mechanism not only for the name field. There should be further columns which should have the same behaviour - but each column should be treated on its own.

This should do what you need using a LEFT JOIN to get the optional value.
SELECT COALESCE(b.Name,a.Name)
FROM Table1 a
LEFT JOIN Table1 b
ON a.id=b.id AND b.variant=#y
WHERE a.id=#x AND a.variant=1
An SQLFiddle to test with.
Performance wise, it would depend on how you need to apply the query to get multiple fields. If you can solve your column choice using COALESCE from the existing join, I can't see a big problem, but if you end up with multiple self joins to solve it, you may have a problem.

As always, performance will depend on your data structure, how many rows you have, and what indexes you have created. I would recommend a query like this:
SELECT name FROM table WHERE id=#1 AND ((variant=#2 AND name IS NOT NULL) OR variant=1) ORDER BY variant DESC LIMIT 1

Related

Complex SQL query with where and where not in

I have 3 tables (candidates, candidate_notes and candidate_events)
CANDIDATES | CANDIDATE_NOTES | CANDIDATE_EVENTS
id | id | id
name | candidate_event_id | type
surname | candidate_id
city | note
...
One candidate can have many notes.
One note has one kind of event.
I would like to obtain all the candidates that have notes of kind of event 1 but only that.
For example, if the candidate 1 have 5 notes, one of type 1, other 3 of type 3 and another one of type 6, I don't want to see it in the results.
Can someone help me with this query?
Thanks
One method uses group by and having:
select cn.candidate_id
from candidate_notes cn join
candidate_events e
on cn.candidate_event_id = e.candidate_event_id
group by cn.candidate_id
having min(type) = 1 and min(type) = max(type);

2 inner joins between same 2 tables

I am trying to select columns from 2 tables,
The INNER JOIN conditions are $table1.idaction_url=$table2.idaction AND $table1.idaction_name=$table2.idaction.
However, From the query below, there is no output. It seems like the INNER JOIN can only take 1 condition. If I put AND to include both conditions as shown in the query below, there wont be any output. Please look at the picture below. Please advice.
$mysql=("SELECT conv(hex($table1.idvisitor), 16, 10) as visitorId,
$table1.server_time, $table1.idaction_url,
$table1.time_spent_ref_action,$table2.name,
$table2.type, $table1.idaction_name, $table2.idaction
FROM $table1
INNER JOIN $table2
ON $table1.idaction_url=$table2.idaction
AND $table1.idaction_name=$table2.idaction
WHERE conv(hex(idvisitor), 16, 10)='".$id."'
ORDER BY server_time DESC");
Short answer:
You need to use two separate inner joins, not only a single join.
E.g.
SELECT `actionurls`.`name` AS `actionUrl`, `actionnames`.`name` AS `actionName`
FROM `table1`
INNER JOIN `table2` AS `actionurls` ON `table1`.`idaction_url` = `actionurls`.`idaction`
INNER JOIN `table2` AS `actionnames` ON `table1`.`idaction_name` = `actionurls`.`idaction`
(Modify this query with any additional fields you want to select).
In depth: INNER JOIN, when done on a value unique to the second table (the table joined to the first in this operation) will only ever fetch one row. What you want to do is fetch data from the other table twice, into the same row, reading the select part of the statement.
INNER JOIN table2 ON [comparison] will, for each row selected from table1, grab any rows from table2 for which [comparison] is TRUE, then copy the row from table1 N times, where N is the amount of rows found in table2. If N = 0, then the row is skipped. In our case N=1 so INNER JOIN of idaction_name in table1 to idaction in table2 for example will allow you to select all the action names.
In order to get the action urls as well we have to INNER JOIN a second time. Now you can't join the same table twice normally, as SQL won't know which of the two joined tables is meant when you type table2.name in the first part of your query. This would be ambiguous if both had the same name. There's a solution for this, table aliases.
The output (of my answer above) is going to be something like:
+-----+------------------------+-------------------------+
| Row | actionUrl | actionName |
+-----+------------------------+-------------------------+
| 1 | unx.co.jp/ | UNIX | Kumamoto Home |
| 2 | unx.co.jp/profile.html | UNIX | Kumamoto Profile |
| ... | ... | ... |
+-----+------------------------+-------------------------+
While if you used only a single join, you would get this kind of output (using OR):
+-----+-------------------------+
| Row | actionUrl |
+-----+-------------------------+
| 1 | unx.co.jp/ |
| 2 | UNIX | Kumamoto Home |
| 3 | unx.co.jp/profile.html |
| 4 | UNIX | Kumamoto Profile |
| ... | ... |
+-----+-------------------------+
Using AND and a single join, you only get output if idaction_name == idaction_url is TRUE. This is not the case, so there's no output.
If you want to know more about how to use JOINS, consult the manual about them.
Sidenote
Also, I can't help but notice you're using variables (e.g. $table1) that store the names of the tables. Do you make sure that those values do not contain user input? And, if they do, do you at least whitelist a list of tables that users can access? You may have some security issues with this.
INNER JOIN does not put any restriction on number of conditions it can have.
The zero resultant rows means, there is no rows satisfying the two conditions simultaneously.
Make sure you are joining using correct columns. Try going step by step to identify from where the data is lost

Access SQL : How to create a crosstab query with a potentially empty row

Suppose I have a database that contains two different types of information about certain unique objects, say their 'State' and 'Condition' to give a name to their classifiers. The State can take the values A, B, C or D, the condition the values X or Y. Depending on where I am sourcing data from, sometimes this database lacks entries for a particular pair.
From this data, I'd like to make a crosstab query that shows the count of data with a given State and Condition combination, but to have it still yield a row even when a given row is a 0. For example, I'd like the following table:
Unit | State | Condition
1 | A | X
2 | B | Y
3 | C | X
4 | B | Y
5 | B | X
6 | B | Y
7 | C | X
To produce the following crosstab:
Count | X | Y
A | 1 | 0
B | 1 | 3
C | 2 | 0
D | 0 | 0
Any help that would leave blanks instead of zeroes is fit for purpose as well, these are being pasted into a template Excel document that requires each crosstab to have an exact dimension.
What I've Tried:
The standard crosstab SQL
TRANSFORM Count(Unit)
SELECT Condition
FROM Sheet
GROUP BY Count(Unit)
PIVOT State;
obviously doesn't work as it doesn't raise the possibility of a D occurring. PIVOTing by a nested IIf that explicitly names D as a possible value does nothing either, nor does combining it with an Nz() around the TRANSFORM clause variable.
TRANSFORM Count(sheet.unit) AS CountOfunit
SELECT AllStates.state
FROM AllStates LEFT JOIN sheet ON AllStates.state = sheet.state
GROUP BY AllStates.state
PIVOT sheet.condition;
This uses a table "AllStates" that has a row for each state you want to force into the result. It will produce an extra column for entries that are neither Condition X nor Condition Y - that's where the forced entry for state D ends up, even though the count is 0.
If you have a relatively small number of conditions, you can use this instead:
SELECT AllStates.state, Sum(IIf([condition]="x",1,0)) AS X, Sum(IIf([condition]="Y",1,0)) AS Y
FROM AllStates LEFT JOIN sheet ON AllStates.state = sheet.state
GROUP BY AllStates.state;
Unlike a crosstab, though, this won't automatically add new columns when new condition codes are added to the data. It can also be cumbersome if you have many condition codes.

SQLYOG - SQL - Merging two columns into 1 column

I have two columns displaying the same type of information but not necessarily the same data. Although some of the data overlaps each column may/may not contain information that will also include NULL values. Like so:
Company ID | Company Name | Company ID | Company Name
-----------+--------------+------------+-------------
1 | A | 1 | A
2 | B | NULL | NULL
NULL | NULL | 3 | C
I am trying to merge columns 1 and 2 to columns 3 and 4, respectively, so that I have two columns that look like this:
Company ID | Company Name
-----------+-------------
1 | A
2 | B
3 | C
Looking at similar stackoverflow questions, I have doubt this may be done easily. Is this possible? Please, let me know!
Anything helps.
As you don't seem to be around to answer questions for clarification right now, let's go ahead.
It seems, you do actually have the four columns in question in a single table - but than, there should be no duplicate column names. Once they are unique, the following should work:
UPDATE SomeTable
SET company_ID_1 = IFNULL(company_ID_1, company_ID_2)
, company_Name_1 = IFNULL(company_Name_1, company_Name_2)
WHERE
company_ID_1 IS NULL
OR
company_Name_1 IS NULL
;
If the presented is actually the output of a join, you could replace the same by:
SELECT
IFNULL(SomeTable1.company_ID, SomeTable2.company_ID) company_ID
, IFNULL(SomeTable1.company_Name, SomeTable2.company_Name) company_Name
FROM SomeTable1
LEFT JOIN SomeTable2
ON SomeTable1.company_ID = SomeTable2.company_ID
UNION ALL
SELECT
IFNULL(SomeTable1.company_ID, SomeTable2.company_ID) company_ID
, IFNULL(SomeTable1.company_Name, SomeTable2.company_Name) company_Name
FROM SomeTable1
RIGHT JOIN SomeTable2
ON SomeTable1.company_ID = SomeTable2.company_ID
WHERE SomeTable1.company_ID IS NULL
ORDER BY company_ID
;
See it in action: SQL Fiddle
Please comment, if and as this requires adjustment / further detail.

Return the query when count of a query is greater than a number?

I want to return all rows that have a certain value in a column and have more than 5 instances in which a number is that certain value. For example, I would like to return all rows of the condition in which if the value in the column M has the number 1 in it and there are 5 or more instances of M having the number 1 in it, then it will return all rows with that condition.
select *
from tab
where M = 1
group by id --ID is the primary key of the table
having count(M) > 5;
EDIT: Here is my table:
id | M | price
--------+-------------+-------
1 | | 100
2 | 1 | 50
3 | 1 | 30
4 | 2 | 20
5 | 2 | 10
6 | 3 | 20
7 | 1 | 1
8 | 1 | 1
9 | 1 | 1
10 | 1 | 1
11 | 1 | 1
Originally I just want to insert into a trigger so that if the number of M = 1's is greater than 5, then I want to create an exception. The query I asked for would be inserted into the trigger. END EDIT.
But my table is always empty. Can anyone help me out? Thanks!
Try this :
select *
from tab
where M in (select M from tab where M = 1 group by M having count(id) > 5);
SQL Fiddle Demo
please try
select *,count(M) from table where M=1 group by id having count(M)>5
Since you group on your PK (which seems a futile excercise), you are counting per ID, whicg will indeed always return 1.
As i explain after this code, this query is NOT good, it is NOT the answer, and i also explain WHY. Please do not expect this query to run correctly!
select *
from tab
where M = 1
group by M
having count(*) > 5;
Like this, you group on what you are counting, which makes a lot more sense. At the same time, this will have unexpected behaviour, as you are selecting all kinds of columns that are not in the group by or in any aggregate. I know mySQL is lenient on that, but I don;t even want to know what it will produce.
Try indeed a subquery along these lines:
select *
from tab
where M in
(SELECT M
from tab
group by M
having count(*) > 5)
I've built a SQLFiddle demo (i used 'Test' as table name out of habit) accomplishing this (I don't have a mySQL at hand now to test it).
-- Made up a structure for testing
CREATE TABLE Test (
id INT NOT NULL AUTO_INCREMENT,
PRIMARY KEY(id),
M int
);
SELECT id, M FROM tab
WHERE M IN (
SELECT M
FROM Test
WHERE M = 1
GROUP BY M
HAVING COUNT(M) > 5
)
The sub-query is a common "find the duplicates" kind of query, with the added condition of a specific value for the column M, also stating that there must be at least 5 dupes.
It will spit out a series of values of M which you can use to query the table against, ending with the rows you need.
You shouldn't use SELECT * , it's a bad practice in general: don't retrieve data you aren't actually using, and if you are using it then take the little time needed to type in a list of field, you'll likely see faster querying and on the other hand the code will be way more readable.