How to do SELECT in a column with different parameter and aliases? - mysql

I'd like to create a report from this table. I need to get the data from different parameter in a same column.
ID_NUMBER ID_DOCUMENT DOCUMENT_NAME
A001 1 DOC_A_1
A001 2 DOC_A_2
A001 3 DOC_A_3
B001 1 DOC_B_1
B001 3 DOC_B_3
The SELECT parameter are 1 and 2
Then, the SELECT output should be:
ID_NUMBER DOCUMENT_1 DOCUMENT_2
A001 DOC_A_1 DOC_A_2
B001 DOC_B_1 NULL
my current query:
SELECT
tdoc_1.ID_NUMBER,
tdoc_1.DOCUMENT_NAME AS "DOCUMENT_1"
FROM `document` 'tdoc_1'
LEFT OUTER JOIN (
SELECT
tdoc_1.ID_NUMBER,
tdoc_2.DOCUMENT_NAME AS "DOCUMENT_2"
FROM `document` 'tdoc_2'
WHERE `ID_DOCUMENT` = '2'
) temp_doc ON tdoc_1.ID_NUMBER = temp_doc.ID_NUMBER
WHERE ID_NUMBER = 1
The data in the column is big, so, it's better to have a speedy query

You just need some aggregation with pivoting logic here:
SELECT
ID_NUMBER,
MAX(CASE WHEN ID_DOCUMENT = 1 THEN DOCUMENT_NAME END) AS DOCUMENT_1,
MAX(CASE WHEN ID_DOCUMENT = 2 THEN DOCUMENT_NAME END) AS DOCUMENT_2
FROM document
GROUP BY
ID_DOCUMENT
ORDER BY
ID_DOCUMENT;
If you want the document columns to be flexible, then you'll need a bit of dynamic SQL for that. It can't be hard coded.

If you can live with the data in one column, then GROUP_CONCAT() might do what you want:
select id_number,
group_concat(document_name order by id_document) as documents
from document
group by id_number;
The advantage to this approach is that you can use a where clause to select the specific documents you want. For your specific question (for 1 and 2):
select id_number,
group_concat(document_name order by id_document) as documents
from document
where id_document in (1, 2)
group by id_number;

Related

Mysql create table with sums of nulls and not nulls

Scenario: I am trying to create an output matrix where I have all the columns names (fields) of a source table in the first column, followed by the sum of all Null values of that original field column.
Ex:
Original Table:
Id1 Code Range
aa 33 null
ab 12 001
ac 53 001
ad null null
null 36 002
Wanted output:
Fields #ofnull #ofnonnull
Id1 1 4
Code 1 4
Range 2 3
For this I have a code that retrieves the names and positions of all the columns in the original matrix, and a snippet which counts my nulls/nonnulls.
Issue: I have no idea how to string those together and get this output with a single query. I tried searching around, but most answers were regarding just counting the nulls, not on the process on inputting a list of columns to the query.
Question: Is it possible to do this? or do I have to feed the query each column name manually?
Code so far:
select
`ordinal_position`,
`column_name`,
from `dev1`.`info`
where `table_schema` = 'dev1'
and `table_name` = 'data1'
order by `ordinal_position`;
select
count(1)
from `dev1`.`data1`
where Id1 is null;
-- where Id1 is not null;
One approach uses a series of unions:
SELECT
'Id1' AS Fields,
COUNT(CASE WHEN Id1 IS NULL THEN 1 END) AS NoNull,
COUNT(Id1) AS NoNonNull
FROM yourTable
UNION ALL
SELECT 'Code', COUNT(CASE WHEN Code IS NULL THEN 1 END), COUNT(Code)
FROM yourTable
UNION ALL
SELECT 'Range', COUNT(CASE WHEN `Range` IS NULL THEN 1 END), COUNT(`Range`)
FROM yourTable;
Demo
You can try using UNION ALL
SELECT
field,
COUNT(CASE WHEN val IS NULL THEN 1 END) AS `#ofnull`,
COUNT(CASE WHEN val IS NOT NULL THEN 1 END) AS `#ofnotnull`
FROM
(
SELECT 'Id1' AS field, Id1 AS val FROM yourTable
UNION ALL
SELECT 'Code', Code FROM yourTable
UNION ALL
SELECT 'Range', `Range` FROM yourTable
) a
GROUP BY field;

Select row based on identifier and value from another row

I have a mySQL dataset that looks like this:
ID PARENT_ID VALUE
1 100 This comment should be approved
2 100 Y
3 101 Another approved comment
4 101 Y
5 102 This comment is not approved
6 102 N
I need to construct an SQL query to select the rows that have a matching parent_id and corresponding value of Y (but ignore the rows with single letters as a value in the result) to result in:
ID PARENT_ID VALUE
1 100 This comment should be approved
3 101 Another approved comment
My idea is to use GROUP BY to combine the columns, but I can't work out how to select based on the Y/N values.
There is possibly a solution here How do I select a row based on a priority value in another row? but I don't think it is asking quite the same question.
Any ideas?
Although you can express this as an aggregation, you can express this using exists:
select d.*
from dataset d
where d.value <> 'Y' and
exists (select 1
from dataset d2
where d2.parent_id = d.parent_id and d2.value = 'Y'
);
This version is probably more efficient.
First, if you possibly can, change your table schema. Your table is storing two kinds of data in the same field (yes no flags and comments). This breaks normality and will haunt you later.
But if its not your table to change, you will need to self join. Try this.
SELECT a.id, a.parent_Id, a.value
FROM table a inner join table b
ON a.parent_id =b.parent_id
WHERE a.value <> 'Y' and b.value ='Y'

How to bypass a reference to an outer table in subquery?

I've been dealing with these two tables:
Document
id company_id etc
=======================
1 2 x
2 2 x
Version
id document_id version date_created date_issued date_accepted
==========================================================================
1 1 1 2013-04-29 2013-04-30 NULL
2 2 1 2013-05-01 NULL NULL
3 1 2 2013-05-01 2013-05-01 2013-05-03
There's a page where I want to list all documents with their attributes.
And I would like to add a single have status from each document.
The status can be derived from the most present date that corresponding Versions have.
It is possible that an older version is being accepted.
The query result I am looking for is like this:
id company_id etc status
==================================
1 2 x accepted
2 2 x created
I started out by making a query which combines all dates and add a status next to it.
It works as expected and when I add the document_id things look alright.
SELECT `status`
FROM (
SELECT max(date_created) as `date`,'created' as `status` FROM version WHERE document_id = 1
UNION
SELECT max(date_issued),'issued' FROM version WHERE document_id = 1
UNION
SELECT max(date_accepted),'accepted' FROM version WHERE document_id = 1
ORDER BY date DESC
LIMIT 1
) as maxi
When I try to incorporate this query as a subquery, I can't make it work.
SELECT *, (
SELECT `status` FROM (
SELECT max(date_created) as `date`,'created' as `status`FROM version WHERE document_id = document.id
UNION
SELECT max(date_issued),'issued' FROM version WHERE document_id = document.id
UNION
SELECT max(date_accepted),'accepted' FROM version WHERE document_id = document.id
ORDER BY date DESC
LIMIT 1
) as maxi
) as `status`
FROM `document`
This will get me the error Unknown column 'document.id' in 'where clause'. So I've read around at SO and figured it simply can't reach the value offer.id since it's a subquery in a subquery. So I tried to take another approach and get all the statuses at once, to avoid the WHERE statement, and JOIN them. I ended up with the next query.
SELECT MAX(`date`),`status`, document_id
FROM (
SELECT datetime_created as `date`, 'created' as `status`,document_id FROM `version`
UNION
SELECT datetime_issued, 'issued',document_id FROM `version`
UNION
SELECT datetime_accepted, 'accepted',document_id FROM `version`
) as dates
GROUP BY offer_id
No error this time but I realized that the status couldn't be the correct one since it got lost during the GROUP BY. I've tried combinations of the two but both flaws keep hindering me. Could any one suggest how to do this in a single query without changing my database? (I know that saving the dates in a separate table would simply things)
I have not tested this, but you can do it like this (you might need to tweak the details)
It is basically looking at it from a completely different angle.
select
d.*,
(CASE GREATEST(ifnull(v.date_created, 0), ifnull(v.date_issued,0), ifnull(v.date_accepted,0) )
WHEN null THEN 'unknown'
WHEN v.date_accepted THEN 'accepted'
WHEN v.date_issued THEN 'issued'
WHEN v.date_created THEN 'created'
END) as status
from document d
left join version v on
v.document_id = d.document_id and
not exists (select 1 from (select * from version) x where x.document_id = v.document_id and x.id <> v.id and x.version > v.version)
Can you normalise your table designs to move the status / dates onto a different table from the Versions?
If no possibly something like this:-
SELECT Document.id, Document.company_id, Document.etc, CASE WHEN Sub1.status = 3 THEN 'accepted' WHEN Sub1.status = 2 THEN 'issued' WHEN Sub1.status = 1 THEN 'created' ELSE NULL END AS status
FROM Document
INNER JOIN (
SELECT document_id, MAX(CASE WHEN date_accepted IS NOT NULL THEN 3 WHEN date_issued IS NOT NULL THEN 2 WHEN date_created IS NOT NULL THEN 1 ELSE NULL END) AS status
FROM Version
GROUP BY document_id
) Sub1
ON Document.id = Sub1.document_id
The subselect gets the highest status for any document from the version table. Each possible versions highest status is returned as a number, and by grouping that on the document id it will get the highest status of any version. This is joined back against the Document table and the number for the version number converted into the text description.
select Doc.document_id,Doc.company_id,Doc.etc,f.status
from Document Doc
inner join
(select Ver.document_id,
case when Ver.date_accepted is not null then 'Accepted'
when Ver.date_issued is not null then 'Issued'
when Ver.date_created is not null then 'Created'
end as status
from version Ver
inner join (
select document_id,MAX(version) VersionId
from version
group by document_id
)t on t.document_id=Ver.document_id
where t.VersionId=Ver.version
)f on Doc.document_id=f.document_id
SQL Fiddle

Adding one extra row to the result of MySQL select query

I have a MySQL table like this
id Name count
1 ABC 1
2 CDF 3
3 FGH 4
using simply select query I get the values as
1 ABC 1
2 CDF 3
3 FGH 4
How I can get the result like this
1 ABC 1
2 CDF 3
3 FGH 4
4 NULL 0
You can see Last row. When Records are finished an extra row in this format
last_id+1, Null ,0 should be added. You can see above. Even I have no such row in my original table. There may be N rows not fixed 3,4
The answer is very simple
select (select max(id) from mytable)+1 as id, NULL as Name, 0 as count union all select id,Name,count from mytable;
This looks a little messy but it should work.
SELECT a.id, b.name, coalesce(b.`count`) as `count`
FROM
(
SELECT 1 as ID
UNION
SELECT 2 as ID
UNION
SELECT 3 as ID
UNION
SELECT 4 as ID
) a LEFT JOIN table1 b
ON a.id = b.id
WHERE a.ID IN (1,2,3,4)
UPDATE 1
You could simply generate a table that have 1 column preferably with name (ID) that has records maybe up 10,000 or more. Then you could simply join it with your table that has the original record. For Example, assuming that you have a table named DummyRecord with 1 column and has 10,000 rows on it
SELECT a.id, b.name, coalesce(b.`count`) as `count`
FROM DummyRecord a LEFT JOIN table1 b
ON a.id = b.id
WHERE a.ID >= 1 AND
a.ID <= 4
that's it. Or if you want to have from 10 to 100, then you could use this condition
...
WHERE a.ID >= 10 AND
a.ID <= 100
To clarify this is how one can append an extra row to the result set
select * from table union select 123 as id,'abc' as name
results
id | name
------------
*** | ***
*** | ***
123 | abc
Simply use mysql ROLLUP.
SELECT * FROM your_table
GROUP BY Name WITH ROLLUP;
select
x.id,
t.name,
ifnull(t.count, 0) as count
from
(SELECT 1 AS id
-- Part of the query below, you will need to generate dynamically,
-- just as you would otherwise need to generate 'in (1,2,3,4)'
UNION ALL SELECT 2
UNION ALL SELECT 3
UNION ALL SELECT 4
UNION ALL SELECT 5
) x
LEFT JOIN YourTable t
ON t.id = x.id
If the id does not exist in the table you're selecting from, you'll need to LEFT JOIN against a list of every id you want returned - this way, it will return the null values for ones that don't exist and the true values for those that do.
I would suggest creating a numbers table that is a single-columned table filled with numbers:
CREATE TABLE `numbers` (
id int(11) unsigned NOT NULL
);
And then inserting a large amount of numbers, starting at 1 and going up to what you think the highest id you'll ever see plus a thousand or so. Maybe go from 1 to 1000000 to be on the safe side. Regardless, you just need to make sure it's more-than-high enough to cover any possible id you'll run into.
After that, your query can look like:
SELECT n.id, a.*
FROM
`numbers` n
LEFT JOIN table t
ON t.id = n.id
WHERE n.id IN (1,2,3,4);
This solution will allow for a dynamically growing list of ids without the need for a sub-query with a list of unions; though, the other solutions provided will equally work for a small known list too (and could also be dynamically generated).

Is it possible to add conditions to a MAX() call in an aggregated query?

Background
My typical use case:
# Table
id category dataUID
---------------------------
0 A (NULL)
1 B (NULL)
2 C text1
3 C text1
4 D text2
5 D text3
# Query
SELECT MAX(`id`) AS `id` FROM `table`
GROUP BY `category`
This is fine; it will strip out any "duplicate categories" in the recordset that's being worked on, giving me the "highest" ID for each category.
I can then go on use this ID to pull out all the data again:
# Query
SELECT * FROM `table` JOIN (
SELECT MAX(`id`) AS `id` FROM `table`
GROUP BY `category`
) _ USING(`id`)
# Result
id category dataUID
---------------------------
0 A (NULL)
1 B (NULL)
3 C text1
5 D text3
Note that this is not the same as:
SELECT MAX(`id`) AS `id`, `category`, `dataUID` FROM `table`
GROUP BY `category`
Per the documentation:
In standard SQL, a query that includes a GROUP BY clause cannot refer
to nonaggregated columns in the select list that are not named in the
GROUP BY clause. For example, this query is illegal in standard SQL
because the name column in the select list does not appear in the
GROUP BY:
SELECT o.custid, c.name, MAX(o.payment) FROM orders AS o, customers
AS c WHERE o.custid = c.custid GROUP BY o.custid;
For the query to be legal, the name column must be omitted from the
select list or named in the GROUP BY clause.
MySQL extends the use of GROUP BY so that the select list can refer to
nonaggregated columns not named in the GROUP BY clause. This means
that the preceding query is legal in MySQL. You can use this feature
to get better performance by avoiding unnecessary column sorting and
grouping. However, this is useful primarily when all values in each
nonaggregated column not named in the GROUP BY are the same for each
group.
[..]
This extension assumes that the nongrouped columns will have the same group-wise values. Otherwise, the result is indeterminate.
So I'd get an unspecified value for dataUID — as an example, either text2 or text3 for result with id 5.
This is actually a problem for other fields in my real case; as it happens, for the dataUID column specifically, generally I don't really care which value I get.
Problem
However!
If any of the rows for a given category has a NULL dataUID, and at least one other row has a non-NULL dataUID, I'd like MAX to ignore the NULL ones.
So:
id category dataUID
---------------------------
4 D text2
5 D (NULL)
At present, since I pick out the row with the maximum ID, I get:
5 D (NULL)
But, because the dataUID is NULL, instead I want:
4 D text2
How can I get this? How can I add conditional logic to the use of aggregate MAX?
I thought of maybe handing MAX a tuple and pulling the id out from it afterwards:
GET_SECOND_PART_SOMEHOW(MAX((IF(`dataUID` NOT NULL, 1, 0), `id`))) AS `id`
But I don't think MAX will accept arbitrary expressions like that, let alone tuples, and I don't know how I'd retrieve the second part of the tuple after-the-fact.
slight tweak to #ypercube's answer. To get the ids you can use
SELECT COALESCE(MAX(CASE
WHEN dataUID IS NOT NULL THEN id
END), MAX(id)) AS id
FROM table
GROUP BY category
And then plug that into a join
This was easier than I thought, in the end, because it turns out MySQL will accept an arbitrary expression inside MAX.
I can get the ordering I want by injecting a leading character into id to serve as an ordering hint:
SUBSTRING(MAX(IF (`dataUID` IS NULL, CONCAT('a',`id`), CONCAT('b',`id`))) FROM 2)
Walk-through:
id category dataUID IF (`dataUID` IS NULL, CONCAT('a',`id`), CONCAT('b',`id`)
--------------------------------------------------------------------------------------
0 A (NULL) a0
1 B (NULL) a1
2 C text1 b2
3 C text1 b3
4 D text2 b4
5 D (NULL) a5
So:
SELECT
`category`, MAX(IF (`dataUID` IS NULL, CONCAT('a',`id`), CONCAT('b',`id`)) AS `max_id_with_hint`
FROM `table`
GROUP BY `category`
category max_id_with_hint
------------------------------
A a0
B a1
C b3
D b4
It's then a simple matter to chop the ordering hint off again.
Thanks in particular to #JlStone for setting me, via COALESCE, on the path to embedding expressions inside the call to MAX and directly manipulating the values supplied to MAX.
From what I can remember you can use COALESCE inside of grouping statements. For example.
SELECT MAX(COALESCE(`id`,1)) ...
hm seems I read to quickly the first time. I think maybe you want something like this?
SELECT * FROM `table` JOIN (
SELECT MAX(`id`) AS `id` FROM `table`
WHERE `dataUID` IS NOT NULL
GROUP BY `category`
) _ USING(`id`)
or perhaps
SELECT MAX(`id`) AS `id`,
COALESCE (`dataUID`, 0) as `dataUID`
FROM `table`
GROUP BY `category`
select *
from t1
join (
select max(id) as id,
max(if(dataGUID is NULL, NULL, id)) as fallbackid,
category
from t1 group by category) as ids
on if(ids.id = fallbackid or fallbackid is null, id, fallbackid) = t1.id;
SELECT t.*
FROM table AS t
JOIN
( SELECT DISTINCT category
FROM table
) AS tdc
ON t.id =
COALESCE(
( SELECT MAX(id) AS id
FROM table
WHERE category = tdc.category
AND dataUID IS NOT NULL
)
, ( SELECT MAX(id) AS id
FROM table
WHERE category = tdc.category
AND dataUID IS NULL
)
)
you need clause OVER
SELECT id, category,dataUID
FROM
(
SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id desc, dataUID desc ) rn,
id, category,dataUID FROM table
) q
WHERE rn=1
Consider that sorting by desc moves null values at last.