MySQL – Concatenate column values for each group with GROUP_CONCAT function

As I said in my earlier posts, MySQL has some interesting features that make the things simpler. GROUP_CONCAT is one of such functions

This function concatenates values from several rows into a single row separated by the delimiter specified.

For example consider the following example with simple data set

create table customer_visits(customer_id int, place_name varchar(100));

insert into customer_visits (customer_id,place_name)
select 1,'Chennai' union all
select 1,'Denmark' union all
select 1,'USA' union all
select 2,'UK' union all
select 2,'Argentina'

Now for each customer id, if you want to concatenate the visited places in a single string separated by comma, you can use the following code

select customer_id,
       GROUP_CONCAT(place_name) as visited_places
from
       customer_visits
group by
       customer_id

The result is

customer_id      visited_places
1                Chennai,Denmark,USA
2                UK,Argentina

Note that by default values are concatenated using comma. If you want to use different delimiter, say for example a semicolon (;), you can use SEPARATOR clause to do it as shown below.

select customer_id,
       GROUP_CONCAT(place_name SEPARATOR ';') as visited_places
from
       customer_visits
group by
      customer_id

The result is

customer_id      visited_places
1                Chennai;Denmark;USA
2                UK;Argentina

GROUP_CONCAT is really a quite handy function.

Advertisements

MySQL – UPSERT in a single statement

MySQL, an opensource RDBMS, has lot of interesting features. One of them is ON DUPLICATE KEY UPDATE statement. This statement adds a new row, if the unique or primary column is not available in the table, if available,UPDATE the row.

Consider the following dataset

create table test
(
       id int ,
       name varchar(100),
       status varchar(100)
);

CREATE unique INDEX id ON test (id);

Now add the following rows

insert into test(id,name,status)
values (1,'test1','') ;

insert into test(id,name,status)
values (2,'test2','') ;

If you select the rows

select * from test

It returns the following rows

id           name           status
1            test1
2            test2

When you try to add the following data

insert into test(id,name,status)
values (1,'test3','') ;

You get the following error

Duplicate entry ‘1’ for key ‘id’

Suppose if there exists a row for the id, you want update it, say status column with the value “duplicate” and not to add as new row. You can use ON DUPLICATE KEY UPDATE statement as shown below;

insert into test(id,name,status)
values (1,'test3','')
ON DUPLICATE KEY UPDATE status='duplicate';

If you select the rows

select * from test

It returns the following rows

id           name           status
1            test1         duplicate
2            test2

As you see new row is not added but status column of the row with id value 1 is updated with the value duplicate

Alias name should be preceded by “AS”

In my earlier post on Understanding Column alias I have shown different methods of using a column alias name

SELECT 100number
SELECT 100.number
SELECT 100'number'
SELECT 100"number"
SELECT 100[number]

As you see all works fine with number as the column alias name

But However you need to use AS before the alias name for better reading and standard
Now execute the following and see the result

drop table if exists #employee_master;
create table #employee_master
(
emp_id int, emp_name varchar(100), dob datetime
)
insert into #employee_master
select 1,'Sankar','19760910' union all
select 2,'Kamal','19680707' union all
select 3,'Rajesh','19661222'
Select emp_id, emp_name dob from #employee_master

The result is

emp_id      dob
----------- ------------
1           Sankar
2           Kamal
3           Rajesh

The SELECT statement lists out three columns but the output has only two columns. The data of emp_name are listed under the column name dob. It is because the missing comma between column names makes that dob becomes alias name for emp_name which is confusing and hard to detect if there are hundreds of lines in the procedure.

So in my opinion ,Alias name should be preceded by “AS” to avoid confusion and unexpected result.

What do you think of this?

Adding time values stored as numbers

One of my friends told me that he had a data set in which numbers are expressed in decimal format.

For example 3.45 means 3 hours and 45 minutes, 4.30 means 4 hours 30 minutes etc.

The challenge is to add these numbers not as decimal values but as time values so adding 3.45 and 4.30 should be 8.15 and not 7.75.

Let us create this data set

create table #t (gtime numeric(8,2)) 
insert into #t values (3.45),(4.30),(4.45),(4.45) 

There are two methods that I come up with

Method 1 : Use CONVERT function

select 
convert(varchar(10), 
	dateadd(minute,sum(floor(gtime)*60+gtime%1*100),0),
108)  
        as time_value
from 
	#t   

The result is

time_value
----------
17:45:00

Method 2 : CONVERT the sum into TIME

select 
	cast(dateadd(minute,sum(floor(gtime)*60+gtime%1*100),0) as time)  
        as time_value
from 
	#t 

The result is

time_value
----------------
17:45:00.0000000

How to know the start time of SQL Server?

One of my friends asked me “How do I find out when the SQL Server is started/restarted?”

Well. There can be many ways to know this. The two methods I know are querying the system views sysdatabases and dm_os_sys_info

Method 1 : Query sysdatabases


select
	crdate
from
	master..sysdatabases
where
	name='tempdb'

The result is

crdate
-----------------------
2017-09-01 15:58:28.417

Method 2 : Query dm_os_sys_info

select
	sqlserver_start_time
from
	sys.dm_os_sys_info

The result is


sqlserver_start_time
-----------------------
2017-09-01 15:58:12.613

Note : This shows the time that my SQL server instance is started. You will get different time

Usage of CTE and maxrecursion option in view

You know that it is possible to use Common Table Expression in a View. But some people wonder why the following is not possible.

Let us create the following view that creates a number table that will have values from 1 to 10000

create view numbers
as
with numbers(n) as
(
select 1 as n union all select n+1 from numbers where n<10000
)
select n from numbers option(maxrecursion 0)

Which when created results to the error

Msg 156, Level 15, State 1, Procedure numbers, Line 15
Incorrect syntax near the keyword 'option'.

The correct way of doing it is to create a view without option(maxrecursion 0) and use it when querying a view

create view numbers 
as 
with numbers(n) as 
( 
select 1 as n union all select n+1 from numbers where n<10000 
) 
select n from numbers 

GO
Now you can execute the view with option as shown below

select * from numbers option (maxrecursion 0)

Use IN clause with care

IN clause can be used as a JOIN to get data by matching the column values from other table. Although this is much similar to INNER JOIN, be careful about the usage of IN

Let us create the following data sets

use tempdb 

create table customer_master
(
cust_id int,
cust_name varchar(100)
);

insert into customer_master(cust_id,cust_name)
select 1,'Sankar' union all
select 2,'Madhivanan' union all
select 3,'Gopal';

create table premium_customers
(
cust_id int
);


insert into premium_customers(cust_id)
select 1 union all
select 2

Now to get all customers who are in premium_customers table, you can use this

select 
	* 
from 
	customer_master 
where 
	cust_id in 
		(
		select cust_id from premium_customers
		)

which returns the following result

cust_id     cust_name
----------- -----------
1           Sankar
2           Madhivanan

Now, let us re name cust_id of premium_customers into customer_id

exec sp_rename 'premium_customers.cust_id','customer_id','COLUMN'

Now execute the same query

select 
	* 
from 
	customer_master 
where 
	cust_id in 
		(
		select cust_id from premium_customers
		)

The result is

cust_id     cust_name
----------- -------------
1           Sankar
2           Madhivanan
3           Gopal

It returns ALL ROWS from customer_master table although there is no column named cust_id in premium_customers. Because it is a column in customer_master table, it is matched with customer_master table and gives all rows

How do we avoid this?

Always use table alias and qualify the columns using table alias

Executing the following code

select 
	t1.* 
from 
	customer_master as t1
where 
	t1.cust_id in 
		(
		select t2.cust_id from premium_customers as t2
		)

now throws an error

Msg 207, Level 16, State 1, Line 39
Invalid column name 'cust_id'.

So it is always a better practice to use table alias and qualify the columns with it.

Avoid view creation with “SELECT *”

A view is very useful in giving out only the required/restricted information to the users. However see what happens when you create a view with “SELECT * from table” definition.

Let us create this simple table

create table emp_master
(
emp_id int,
emp_name varchar(100),
joined_date date
)

Create a view based on this table using “SELECT *”

Create view emp_master_view as
select * from emp_master;

Now see the column information of this view using sp_columns system procedure

EXEC sp_columns 'emp_master_view'

The result is shown in the below imageview1

Now let us add a new column in the emp_master table

alter table emp_master add address varchar(200)

Now see the column information of view using sp_columns system procedure

EXEC sp_columns 'emp_master_view'

The result is shown in the below image. As you see the address column is not yet updated in the view.view1

So what is the proper way to update the column information of the view?

Use sp_refreshview system rocedure

EXEC sp_refreshview emp_master_view

After executing the sp_refreshview procedure, let us see the column information

EXEC sp_columns 'emp_master_view'

As you see in the following result, new column is added in the viewview2

Points to consider when creating a view

1 Do not use *. Explicitly type out the column names in the view definition. When new columns are required, alter the view to add them
2 Use schemabinding option when creating view which will prevent the usuage of *