Count on various substrings from same column - mysql

I want to count the number of occurences of several substrings from the same column and with the same WHERE condition. For each substring I have a UNION SELECT statement including the repeating WHERE condition. The final result is a query with appox. 2500 lines of sql code, but it gives good result.
Simplified example of query:
SELECT ‘be’ AS country, count(destination) FROM demo
WHERE destination LIKE ‘%be%’ AND field_a=xzx AND field_b=bbb
UNION SELECT ‘de’ AS country, count(destination) FROM demo
WHERE destination LIKE ‘%de%’ AND field_a=xzx AND field_b=bbb
UNION SELECT ‘fr’ AS country, count(destination) FROM demo
WHERE destination LIKE ‘%fr%’ AND field_a=xzx AND field_b=bbb
UNION SELECT ‘nl’ AS country, count(destination) FROM demo
WHERE destination LIKE ‘%nl%’ AND field_a=xzx AND field_b=bbb
Is it possible to modify the query in such a way that the WHERE condition only appears once in the query?
Please find a simplified example of my question via this link:
https://docs.google.com/document/d/1otjZZlBy6au5E2I7T6NdSYmLayhNGWyXGxGo3gBx_-w/edit

DON'T store comma separated values in a single field. Instead, use a many:many relationship table and store one (id,country) combination per row. Your data structure is a SQL-Anti-Pattern and goes against all relational database design principles.
CREATE TABLE map (
demo_id INT,
country VARCHAR(2),
PRIMARY KEY (demo_id, country)
)
INSERT INTO map VALUES
(1, 'nl'), (1, 'de'), (1, 'be'), (1, 'fr'),
(2, 'de'), (2, 'fr'), (2, 'be'),
(3, 'fr'), (3, 'nl'),
(4, 'nl')
Then you will have this single query...
SELECT
map.country,
COUNT(*)
FROM
demo
INNER JOIN
map
ON map.demo_id = demo.id
WHERE
demo.field_a = 'xzx'
AND demo.field_b = 'bbb'

select a.country,count(*) from
(
select 'be' as country
union all
select 'de' as country
union all
select 'fr' as country
union all
select 'nl' as country
)a join demo d
on d.destination like '%'+a.country+'%'
AND d.field_a=xzx AND d.field_b=bbb
group by a.country
I am not sure whether mySQL supports derived table or not . If its not supported the you have to create a temp table with all different country values and replace it with table a in the above query

Use COUNT CASE WHEN ... THEN ... END
SELECT
COUNT(CASE WHEN destination LIKE "%be%" THEN 1 ELSE NULL END) AS "BE",
COUNT(CASE WHEN destination LIKE "%fr%" THEN 1 ELSE NULL END) AS "FR",
COUNT(CASE WHEN destination LIKE "%de%" THEN 1 ELSE NULL END) AS "DE",
COUNT(CASE WHEN destination LIKE "%nl%" THEN 1 ELSE NULL END) AS "NL"
FROM demo
WHERE field_a = "xzx" AND field_b = "bbb"
But you should really think about changing the structure of your table. Having comma-separated values in a single field is not really recommanded.
Instead, you should have for example here an ID for each travel (or whatever it is, I said travel because of the destination field). Then, make an entry for each destination within this travel. With this kind of structure, you'd have a request like this :
SELECT destination, COUNT(id)
FROM demo
WHERE field_a = "xzx" AND field_b = "bbb"
GROUP BY destination

Related

SQL- preserve order from 'IN' clause and return null for non matching clauses

Let's say I have a table Person with columns id, name, and phone. I want to fetch all records matching a list of pairs of names and phone numbers while preserving the order from the 'IN' clause and returning null or any default value for the mismatching clause.
For instance, if the Person table has the following records:
id
name
phone
1
Name1
1234
2
Name2
2345
3
Name3
4532
I want the query to return the ids of people matching pairs of names and phone numbers.
When queried with
('Name2', 2345), ('NonExistingName', 34543), ('Name1', 1234) should return a list [2, <null or a default value>, 1]
I am aware that I can use IN clause to find the matching rows,
SELECT id
FROM Person
WHERE (name, phone) in (('Name2', 2345),
('NonExistingName', 34543),
('Name1', 1234));
however, this alone doesn't fulfill what I want. The rows returned do not preserve the order and do not allow me to add a default value for nonexisting ids.
Relational databases explicitly disclaim any responsibility to ever preserve order unless you specify an ORDER BY clause. Therefore you will need to include the order information as part of the data in a way where you can reference it in the ORDER BY clause.
For example:
WITH source AS (
SELECT 'Name2' Name, 2345 Phone, 0 Ordinal
UNION
SELECT 'NonExistingName', 34543, 1
UNION
SELECT 'Name1', 1234, 2
)
SELECT p.id
FROM source s
LEFT JOIN Person p ON s.Name = p.Name and s.Phone = p.Phone
ORDER BY s.Ordinal
Or:
SELECT p.id
FROM (VALUES
ROW ('Name2', 2345, 0),
ROW ('NonExistingName', 34543, 1),
ROW ('Name1', 1234, 2)
) s
LEFT JOIN Person p ON s.column_0 = p.Name and s.column_1 = p.Phone
ORDER BY s.column_2

How to display multiple rows for one id in MySql? [duplicate]

SELECT DISTINCT field1, field2, field3, ......
FROM table;
I am trying to accomplish the following SQL statement, but I want it to return all columns.
Is this possible?
Something like this:
SELECT DISTINCT field1, *
FROM table;
You're looking for a group by:
select *
from table
group by field1
Which can occasionally be written with a distinct on statement:
select distinct on field1 *
from table
On most platforms, however, neither of the above will work because the behavior on the other columns is unspecified. (The first works in MySQL, if that's what you're using.)
You could fetch the distinct fields and stick to picking a single arbitrary row each time.
On some platforms (e.g. PostgreSQL, Oracle, T-SQL) this can be done directly using window functions:
select *
from (
select *,
row_number() over (partition by field1 order by field2) as row_number
from table
) as rows
where row_number = 1
On others (MySQL, SQLite), you'll need to write subqueries that will make you join the entire table with itself (example), so not recommended.
From the phrasing of your question, I understand that you want to select the distinct values for a given field and for each such value to have all the other column values in the same row listed. Most DBMSs will not allow this with neither DISTINCT nor GROUP BY, because the result is not determined.
Think of it like this: if your field1 occurs more than once, what value of field2 will be listed (given that you have the same value for field1 in two rows but two distinct values of field2 in those two rows).
You can however use aggregate functions (explicitely for every field that you want to be shown) and using a GROUP BY instead of DISTINCT:
SELECT field1, MAX(field2), COUNT(field3), SUM(field4), ....
FROM table GROUP BY field1
If I understood your problem correctly, it's similar to one I just had. You want to be able limit the usability of DISTINCT to a specified field, rather than applying it to all the data.
If you use GROUP BY without an aggregate function, which ever field you GROUP BY will be your DISTINCT filed.
If you make your query:
SELECT * from table GROUP BY field1;
It will show all your results based on a single instance of field1.
For example, if you have a table with name, address and city. A single person has multiple addresses recorded, but you just want a single address for the person, you can query as follows:
SELECT * FROM persons GROUP BY name;
The result will be that only one instance of that name will appear with its address, and the other one will be omitted from the resulting table. Caution: if your fileds have atomic values such as firstName, lastName you want to group by both.
SELECT * FROM persons GROUP BY lastName, firstName;
because if two people have the same last name and you only group by lastName, one of those persons will be omitted from the results. You need to keep those things into consideration. Hope this helps.
That's a really good question. I have read some useful answers here already, but probably I can add a more precise explanation.
Reducing the number of query results with a GROUP BY statement is easy as long as you don't query additional information. Let's assume you got the following table 'locations'.
--country-- --city--
France Lyon
Poland Krakow
France Paris
France Marseille
Italy Milano
Now the query
SELECT country FROM locations
GROUP BY country
will result in:
--country--
France
Poland
Italy
However, the following query
SELECT country, city FROM locations
GROUP BY country
...throws an error in MS SQL, because how could your computer know which of the three French cities "Lyon", "Paris" or "Marseille" you want to read in the field to the right of "France"?
In order to correct the second query, you must add this information. One way to do this is to use the functions MAX() or MIN(), selecting the biggest or smallest value among all candidates. MAX() and MIN() are not only applicable to numeric values, but also compare the alphabetical order of string values.
SELECT country, MAX(city) FROM locations
GROUP BY country
will result in:
--country-- --city--
France Paris
Poland Krakow
Italy Milano
or:
SELECT country, MIN(city) FROM locations
GROUP BY country
will result in:
--country-- --city--
France Lyon
Poland Krakow
Italy Milano
These functions are a good solution as long as you are fine with selecting your value from the either ends of the alphabetical (or numeric) order. But what if this is not the case? Let us assume that you need a value with a certain characteristic, e.g. starting with the letter 'M'. Now things get complicated.
The only solution I could find so far is to put your whole query into a subquery, and to construct the additional column outside of it by hands:
SELECT
countrylist.*,
(SELECT TOP 1 city
FROM locations
WHERE
country = countrylist.country
AND city like 'M%'
)
FROM
(SELECT country FROM locations
GROUP BY country) countrylist
will result in:
--country-- --city--
France Marseille
Poland NULL
Italy Milano
SELECT c2.field1 ,
field2
FROM (SELECT DISTINCT
field1
FROM dbo.TABLE AS C
) AS c1
JOIN dbo.TABLE AS c2 ON c1.field1 = c2.field1
Great question #aryaxt -- you can tell it was a great question because you asked it 5 years ago and I stumbled upon it today trying to find the answer!
I just tried to edit the accepted answer to include this, but in case my edit does not make it in:
If your table was not that large, and assuming your primary key was an auto-incrementing integer you could do something like this:
SELECT
table.*
FROM table
--be able to take out dupes later
LEFT JOIN (
SELECT field, MAX(id) as id
FROM table
GROUP BY field
) as noDupes on noDupes.id = table.id
WHERE
//this will result in only the last instance being seen
noDupes.id is not NULL
Try
SELECT table.* FROM table
WHERE otherField = 'otherValue'
GROUP BY table.fieldWantedToBeDistinct
limit x
You can do it with a WITH clause.
For example:
WITH c AS (SELECT DISTINCT a, b, c FROM tableName)
SELECT * FROM tableName r, c WHERE c.rowid=r.rowid AND c.a=r.a AND c.b=r.b AND c.c=r.c
This also allows you to select only the rows selected in the WITH clauses query.
For SQL Server you can use the dense_rank and additional windowing functions to get all rows AND columns with duplicated values on specified columns. Here is an example...
with t as (
select col1 = 'a', col2 = 'b', col3 = 'c', other = 'r1' union all
select col1 = 'c', col2 = 'b', col3 = 'a', other = 'r2' union all
select col1 = 'a', col2 = 'b', col3 = 'c', other = 'r3' union all
select col1 = 'a', col2 = 'b', col3 = 'c', other = 'r4' union all
select col1 = 'c', col2 = 'b', col3 = 'a', other = 'r5' union all
select col1 = 'a', col2 = 'a', col3 = 'a', other = 'r6'
), tdr as (
select
*,
total_dr_rows = count(*) over(partition by dr)
from (
select
*,
dr = dense_rank() over(order by col1, col2, col3),
dr_rn = row_number() over(partition by col1, col2, col3 order by other)
from
t
) x
)
select * from tdr where total_dr_rows > 1
This is taking a row count for each distinct combination of col1, col2, and col3.
select min(table.id), table.column1
from table
group by table.column1
SELECT *
FROM tblname
GROUP BY duplicate_values
ORDER BY ex.VISITED_ON DESC
LIMIT 0 , 30
in ORDER BY i have just put example here, you can also add ID field in this
Found this elsewhere here but this is a simple solution that works:
WITH cte AS /* Declaring a new table named 'cte' to be a clone of your table */
(SELECT *, ROW_NUMBER() OVER (PARTITION BY id ORDER BY val1 DESC) AS rn
FROM MyTable /* Selecting only unique values based on the "id" field */
)
SELECT * /* Here you can specify several columns to retrieve */
FROM cte
WHERE rn = 1
In this way can get 2 unique column with 1 query only
select Distinct col1,col2 from '{path}' group by col1,col2
you can increase your columns if need
Add GROUP BY to field you want to check for duplicates
your query may look like
SELECT field1, field2, field3, ...... FROM table GROUP BY field1
field1 will be checked to exclude duplicate records
or you may query like
SELECT * FROM table GROUP BY field1
duplicate records of field1 are excluded from SELECT
Just include all of your fields in the GROUP BY clause.
It can be done by inner query
$query = "SELECT *
FROM (SELECT field
FROM table
ORDER BY id DESC) as rows
GROUP BY field";
SELECT * from table where field in (SELECT distinct field from table)
SELECT DISTINCT FIELD1, FIELD2, FIELD3 FROM TABLE1 works if the values of all three columns are unique in the table.
If, for example, you have multiple identical values for first name, but the last name and other information in the selected columns is different, the record will be included in the result set.
I would suggest using
SELECT * from table where field1 in
(
select distinct field1 from table
)
this way if you have the same value in field1 across multiple rows, all the records will be returned.

Convert columns into rows with inner join in mysql

Please take a look at this fiddle.
I'm working on a search filter select box and I want to insert the field names of a table as rows.
Here's the table schemea:
CREATE TABLE general
(`ID` int, `letter` varchar(21), `double-letters` varchar(21))
;
INSERT INTO general
(`ID`,`letter`,`double-letters`)
VALUES
(1, 'A','BB'),
(2, 'A','CC'),
(3, 'C','BB'),
(4, 'D','DD'),
(5, 'D','EE'),
(6, 'F','TT'),
(7, 'G','UU'),
(8, 'G','ZZ'),
(9, 'I','UU')
;
CREATE TABLE options
(`ID` int, `options` varchar(15))
;
INSERT INTO options
(`ID`,`options`)
VALUES
(1, 'letter'),
(2, 'double-letters')
;
The ID field in options table acts as a foreign key, and I want to get an output like the following and insert into a new table:
id field value
1 1 A
2 1 C
3 1 D
4 1 F
5 1 G
6 1 I
7 2 BB
8 2 CC
9 2 DD
10 2 EE
11 2 TT
12 2 UU
13 2 ZZ
My failed attempt:
select DISTINCT(a.letter),'letter' AS field
from general a
INNER JOIN
options b ON b.options = field
union all
select DISTINCT(a.double-letters), 'double-letters' AS field
from general a
INNER JOIN
options b ON b.options = field
Pretty sure you want this:
select distinct a.letter, 'letter' AS field
from general a
cross JOIN options b
where b.options = 'letter'
union all
select distinct a.`double-letters`, 'double-letters' AS field
from general a
cross JOIN options b
where b.options = 'double-letters'
Fiddle: http://sqlfiddle.com/#!2/bbf0b/18/0
A couple to things to point out, you can't join on a column alias. Because that column you're aliasing is a literal that you're selecting you can specify that literal as criteria in the WHERE clause.
You're not really joining on anything between GENERAL and OPTIONS, so what you really want is a CROSS JOIN; the criteria that you're putting into the ON clause actually belongs in the WHERE clause.
I just made this query on Oracle.
It works and produces the output you described :
SELECT ID, CASE WHEN LENGTH(VALUE)=2THEN 2 ELSE 1 END AS FIELD, VALUE
FROM (
SELECT rownum AS ID, letter AS VALUE FROM (SELECT DISTINCT letter FROM general ORDER BY letter)
UNION
SELECT (SELECT COUNT(DISTINCT LETTER) FROM general) +rownum AS ID, double_letters AS VALUE
FROM (
SELECT DISTINCT double_letters FROM general ORDER BY double_letters)
)
It should also run on Mysql.
I did not used the options table. I do not understand his role. And for this example, and this type of output it seems unnecessary
Hope this could help you to.

SELECT WHERE IN - mySQL

let's say I have the following Table:
ID, Name
1, John
2, Jim
3, Steve
4, Tom
I run the following query
SELECT Id FROM Table WHERE NAME IN ('John', 'Jim', 'Bill');
I want to get something like:
ID
1
2
NULL or 0
Is it possible?
How about this?
SELECT Id FROM Table WHERE NAME IN ('John', 'Jim', 'Bill')
UNION
SELECT null;
Start by creating a subquery of names you're looking for, then left join the subquery to your table:
SELECT myTable.ID
FROM (
SELECT 'John' AS Name
UNION SELECT 'Jim'
UNION SELECT 'Bill'
) NameList
LEFT JOIN myTable ON NameList.Name = myTable.Name
This will return null for each name that isn't found. To return a zero instead, just start the query with SELECT COALESCE(myTable.ID, 0) instead of SELECT myTable.ID.
There's a SQL Fiddle here.
The question is a bit confusing. "IN" is a valid operator in SQL and it means a match with any of the values (see here ):
SELECT Id FROM Table WHERE NAME IN ('John', 'Jim', 'Bill');
Is the same as:
SELECT Id FROM Table WHERE NAME = 'John' OR NAME = 'Jim' OR NAME = 'Bill';
In your answer you seem to want the replies for each of the values, in order. This is accomplished by joining the results with UNION ALL (only UNION eliminates duplicates and can change the order):
SELECT max(Id) FROM Table WHERE NAME = 'John' UNION ALL
SELECT max(Id) FROM Table WHERE NAME = 'Jim' UNION ALL
SELECT max(Id) FROM Table WHERE NAME = 'Bill';
The above will return 1 Id (the max) if there are matches and NULL if there are none (e.g. for Bill). Note that in general you can have more than one row matching some of the names in your list, I used "max" to select one, you may be better of in keeping the loop on the values outside the query or in using the (ID, Name) table in a join with other tables in your database, instead of making the list of ID and then using it.

Sort certain values to the top

I have a MySQL table with the following data (simplified):
INSERT INTO `stores` (`storeId`, `name`, `country`) VALUES
(1, 'Foo', 'us'),
(2, 'Bar', 'jp'),
(3, 'Baz', 'us'),
(4, 'Foo2', 'se'),
(5, 'Baz2', 'jp'),
(6, 'Bar3', 'jp');
Now, I want to be able to get a paginated list of stores that begins with the customers country.
For example, an american customer would see the following list:
Foo
Baz
Bar
Foo2
Baz2
Bar3
The naive solution I'm using right now (example with an american customer and page size 3):
(SELECT * FROM stores WHERE country = "us") UNION (SELECT * FROM stores WHERE country != "us") LIMIT 0,3
Are there any better ways of doing this? Could ORDER BY be used and told to put a certain value at the top?
Try this:
SELECT * FROM stores ORDER BY country = "us" DESC, storeId
To get the searched-for country first, and the remainder alphabetically:
SELECT *
FROM stores
ORDER BY country = 'us' DESC, country ASC
You have to link each value of a country with a numeric, with a case :
select *
from stores
order by case when country = "us" then 1
else 0
end desc
Create a table of country codes and orders, join to it in your query, and then order by the country code's order.
So you would have a table that looks like
CountryOrder
Code Ord
---- ---
us 1
jp 2
se 3
and then code that looks like:
SELECT s.*
FROM Stores s
INNER JOIN CountryOrder c
ON c.Code = s.Country
ORDER BY c.Ord;
How about using IF to assign the top value to US rows.
select if(country_cd='us,'aaaUS',country_cd) sort_country_cd, country_cd from stores Order by sort_country_cd
This will give you a pseudo column called sort_country_cd .
Here you can map "US" to "aaaUS".
JP can still be mapped to JP.
That puts US on the top of your sort list.