Tuesday, March 19, 2024

Updating Table Containing Xml Column via LinkedServer

If you are trying to update a table containing XML column via Linked Server in SQL Server, and you are not able to, you are not alone.

There are 2 main issues in this scenario:
1. Accessing a table containing XML column via Linked Server
2. Updating the XML value

When trying to select the XML column of a table via linked server:

SELECT col1, col2, XmlColumn
FROM [LinkedServer].[DatabaseName].dbo.TableName

Error encountered:

Xml data type is not supported in distributed queries. Remote object '[LinkedServer].[DatabaseName].dbo.TableName' has xml column(s).

There are a few workarounds to resolve this (we will focus on using view in this post):

1. Use OPENQUERY

2. Create a view that selects a converted non-XML value from the XML column in the destination DB.

--In destination DB
CREATE VIEW vw_NonXmlColumn
AS
SELECT col1, col2,
    XmlColumn = CONVERT(NVARCHAR(MAX), XmlColumn)
FROM TableName WITH(NOLOCK)

Then, on the source DB where you create the Linked Server, you can access the view instead:

--In source DB
select col1, col2, XmlColumn
from [LinkedServer].[DatabaseName].dbo.vw_NonXmlColumn

This resolves our first issue: accessing a table containing XML column via Linked Server.

Share:

Wednesday, May 12, 2021

Creating Dynamic Pivot Table using SQL

 In this post, I want to share SQL tips on how to create dynamic pivot table in SQL.

The columns in the pivot table is not fixed in this case. Hence, there is a need to use dynamic SQL to achieve this.

In this example, we will be forming a pivot table containing spending by outlet from spending data.

SQL Query:

--prepare data source
create table #TmpSpending
(
	MemberID nvarchar(100),
	Outlet nvarchar(100),
	TransactDate date,
	Spending numeric(18,4)
)

insert into #TmpSpending values ('M0001', 'Outlet A', '2021/01/05', 100)
insert into #TmpSpending values ('M0001', 'Outlet B', '2021/01/15', 200)
insert into #TmpSpending values ('M0002', 'Outlet A', '2021/02/09', 300)
insert into #TmpSpending values ('M0003', 'Outlet B', '2021/01/10', 400)
insert into #TmpSpending values ('M0003', 'Outlet B', '2021/02/15', 500)

select * from #tmpspending

--temporary table for output
create table #tmpOutput (
	Outlet nvarchar(100)
)

--initial insert into #tmpOutput
insert into #tmpOutput
select distinct outlet
from #TmpSpending
order by outlet

--store the list of [month year] first
select distinct [Year] = year(transactdate), 
	[Month] = month(transactdate),
	MonthYear = left(datename(month, transactdate), 3) + ' ' + convert(varchar, year(transactdate))
into #tmpDates
from #tmpspending

--construct the table with dynamic columns using cursor
declare @year int, @month int
declare @monthyear nvarchar(8)
declare @sql nvarchar(max) = '' --dynamic sql for columns addition

declare cur1 cursor for
select [year], [month], MonthYear
from #tmpDates
order by [year], [month]

open cur1

fetch next from cur1
into @year, @month, @monthyear

while @@FETCH_STATUS = 0
begin
	
	--add column to #tmpOutput
	set @sql = 'alter table #tmpOutput add [' + @monthyear + '] numeric(18,4)'
	exec (@sql)

	--update #tmpOutput
	set @sql = 'update o '
	set @sql += 'set [' + @monthyear + '] = s.spending '
	set @sql += 'from #tmpOutput o '
	set @sql += 'inner join (	select outlet, spending = sum(spending) '
	set @sql += '				from #tmpspending '
	set @sql += '				where year(transactdate) = ' + convert(varchar, @year) + ' '
	set @sql += '				and month(transactdate) = ' + convert(varchar, @month) + ' '
	set @sql += '				group by outlet '
	set @sql += ') s on s.outlet = o.outlet '
	exec(@sql)

	--fetch next record
	fetch next from cur1
	into @year, @month, @monthyear
end

close cur1
deallocate cur1

select * from #tmpOutput

drop table #tmpDates
drop table #TmpSpending
drop table #tmpOutput


First, we prepare the data source which looks like this.



Afterwards, we use cursor (or you can use while loop) to create additional columns on the table on the fly, to form up the pivot table.
This is the end result of the pivot table.


Happy querying! :)
Share:

Wednesday, March 17, 2021

Activate Tableau Server Offline

If you are doing Tableau server license activation offline, it may be a bit problematic, and confusing.

There is already an article published by Tableau showing how to do Tableau server license activation offline. However, the last part about how to process the activation.tlf is not mentioned.

Hence, I am creating some guide on how to do that.

After you upload the offline.tlf file on Tableau site, and get the activation.tlf back, you need to:

1. Place the activation.tlf file somewhere in the server where you have your Tableau server installed. e.g. D:\activation.tlf

2. Open command prompt as Administrator from Start menu.

3. Go to the Tableau server directory where tabadmin.bat is located. Usually it is located at C:\Program Files\Tableau\Tableau Server\<version>\bin.

cd\Program Files\Tableau\Tableau Server\<version>\bin

4. Use this command to activate using the activation.tlf file:

tabadmin activate -tlf D:\activation.tlf

5. That's all, folks.

Share:

Friday, February 5, 2021

Using Macro in Notepad++

 Sometimes, you need to bulk-edit text with the same patterns. However, you can't simply use text editor to find and replace the content. In this case, you can try using macro in Notepad++ to achieve that.

This is a simple video on how you can do that using Notepad++:





Share:

Wednesday, November 4, 2020

Split Comma-Delimited Column into Multiple Rows

We had post about combining multiple rows into a column in the past. Sometimes, on the contrary, we would want to split character-delimited column into multiple rows. In this post, we will see try to split comma-delimited column into multiple rows. Sample Data:
End result after split into rows:
Query:

-- Create temporary table to store UserGroup
create table #UserGroup (GroupID int, UserGroup nvarchar(1000), Users nvarchar(1000))
insert into #UserGroup values (1, 'Group 1', 'Himura,Selvi,Superman')
INSERT INTO #UserGroup VALUES ( 2, 'Group 2', 'Luck,Smarty')
INSERT INTO #UserGroup VALUES ( 3, 'Group 3', 'Lucky')

--Display the sample data
select * from #UserGroup

--Split comma-delimited column into multiple rows
;WITH tmp(GroupID, UserGroup, Username, Users) AS
(
	SELECT GroupID, 
		UserGroup,
		convert(nvarchar(1000), LEFT(Users, CHARINDEX(',', Users + ',') - 1)),
		convert(nvarchar(1000), STUFF(Users, 1, CHARINDEX(',', Users + ','), ''))
	FROM #UserGroup
	where Users is not null
	UNION all

	SELECT GroupID, 
		UserGroup,
		convert(nvarchar(1000), LEFT(Users, CHARINDEX(',', Users + ',') - 1)),
		convert(nvarchar(1000), STUFF(Users, 1, CHARINDEX(',', Users + ','), ''))
	FROM tmp
	WHERE
		Users > ''
)
select GroupID, UserGroup, Username
from tmp
order by GroupID, UserGroup
Share:

Thursday, September 19, 2019

Using SQL Cursor for Looping

If you ever heard of SQL cursor, this is probably not a new thing to you. If you never heard or use cursor in SQL before, we usually use cursor to replace the ordinary looping in SQL. The performance is generally better compared to using WHILE or ordinary looping method, depending on how it is implemented. Reason being, cursor is stored in memory.

I have a good example on usage of cursor in SQL.
In CRM & loyalty, we are looking at membership and loyalty points. Customers earn points when they transact or purchase something.
In this scenario, there is a points cap for members transactions, in which customers can only earn x number of points from their purchase.

We want to retrospectively calculate the correct points earned given customer's transactions and the points they earned.

Example of the records:

Points is capped at member-level. This means, different customer / member can have different points cap, denoted in the last column in the example above.
Since we are looking at member-level, we will look at the re-arranged version of the records.

This is the end result we are looking at. Observe the last field in the table. The content in the Correct_PointsEarned is always within the points cap for each member, in the order of the transaction sequence. Once the points cap has been met, the Correct_PointsEarned will be 0 for that member.

You should familiarize yourself with how to create a cursor first.
This is a template of how a cursor looks like:
--START CURSOR
declare cur1 cursor for
select *
from 
order by 

open cur1

fetch next from cur1
into @zzz, @sss, ......

while @@FETCH_STATUS = 0
BEGIN
 --do the necessary processing here
 
 
 --fetch next record
 fetch next from cur1
 into @zzz, @sss, ......
END

close cur1
deallocate cur1
--END CURSOR


This is the script to create temp table to populate the data:
create table #TmpPointsCap (
 TransactAutoID bigint identity(1,1),
 MemberID nvarchar(100),
 TransactDate date,
 TransactPoints numeric(18,4),
 PointsCap numeric(18,4),
 Correct_PointsEarned numeric(18,4) default(0.00),
 AddedOn datetime default(getdate())
)

insert into #TmpPointsCap (MemberID, TransactDate, TransactPoints, PointsCap)
values ('M0001', '2019/01/01', 100, 700)

insert into #TmpPointsCap (MemberID, TransactDate, TransactPoints, PointsCap)
values ('M0002', '2019/01/02', 200, 500)

insert into #TmpPointsCap (MemberID, TransactDate, TransactPoints, PointsCap)
values ('M0004', '2019/01/02', 400, 300)

insert into #TmpPointsCap (MemberID, TransactDate, TransactPoints, PointsCap)
values ('M0001', '2019/01/03', 500, 700)

insert into #TmpPointsCap (MemberID, TransactDate, TransactPoints, PointsCap)
values ('M0003', '2019/01/03', 700, 1000)

insert into #TmpPointsCap (MemberID, TransactDate, TransactPoints, PointsCap)
values ('M0001', '2019/01/05', 200, 700)

insert into #TmpPointsCap (MemberID, TransactDate, TransactPoints, PointsCap)
values ('M0002', '2019/01/06', 100, 500)

insert into #TmpPointsCap (MemberID, TransactDate, TransactPoints, PointsCap)
values ('M0003', '2019/01/06', 300, 1000)

insert into #TmpPointsCap (MemberID, TransactDate, TransactPoints, PointsCap)
values ('M0002', '2019/01/06', 400, 500)

insert into #TmpPointsCap (MemberID, TransactDate, TransactPoints, PointsCap)
values ('M0002', '2019/01/08', 200, 500)

insert into #TmpPointsCap (MemberID, TransactDate, TransactPoints, PointsCap)
values ('M0003', '2019/01/08', 100, 1000)

insert into #TmpPointsCap (MemberID, TransactDate, TransactPoints, PointsCap)
values ('M0004', '2019/01/09', 100, 300)

insert into #TmpPointsCap (MemberID, TransactDate, TransactPoints, PointsCap)
values ('M0005', '2019/01/10', 200, 200)

--Original table content
select * from #Tmppointscap

--The data sorted in the order we want
select TransactAutoID, MemberID, TransactDate, TransactPoints, PointsCap, Correct_PointsEarned = 0
from #TmpPointsCap
order by MemberID, TransactDate, AddedOn

And this is how we use cursor to achieve the correct point earned in the last column:
--Temporary variables to store the table fields
declare @MemberID nvarchar(100), @TransactAutoID bigint, @TransactPoints numeric(18,4), @PointsCap numeric(18,4)
declare @PrevMemberID nvarchar(100) = ''
declare @RunningPoints numeric(18,4) = 0


--START CURSOR
declare cur1 cursor for
select MemberID, TransactAutoID, TransactPoints, PointsCap
from #TmpPointsCap
order by MemberID, TransactDate, AddedOn

open cur1

fetch next from cur1
into @MemberID, @TransactAutoID, @TransactPoints, @PointsCap

while @@FETCH_STATUS = 0
BEGIN
 --do the necessary processing here
 if @MemberID <> @PrevMemberID
 begin
  --set initial running points as the 1st TransactPoints
  set @RunningPoints = 0
 end
 
 if @RunningPoints < @PointsCap and (@RunningPoints + @TransactPoints) > @PointsCap
 begin
  --if after adding the TransactPoints, it is bigger than the pointscap
  --only earn at most, as much as the pointscap
  update #TmpPointsCap
  set Correct_PointsEarned = @PointsCap - @RunningPoints
  where TransactAutoID = @TransactAutoID

  --Update RunningPoints = PointsCap
  set @RunningPoints += @TransactPoints
 end
 else if (@RunningPoints + @TransactPoints) = @PointsCap
 begin
  --earning all the transactpoints just good
  update #TmpPointsCap
  set Correct_PointsEarned = @TransactPoints
  where TransactAutoID = @TransactAutoID
  
  set @RunningPoints += @TransactPoints
 end
 else if @RunningPoints < @PointsCap
 begin
  --update the running points
  set @RunningPoints += @TransactPoints
  
  --update the correct points earned in the table
  update #TmpPointsCap
  set Correct_PointsEarned = TransactPoints
  where TransactAutoID = @TransactAutoID
 end
 else
 begin
  --not earning any points anymore
  update #TmpPointsCap
  set Correct_PointsEarned = 0
  where TransactAutoID = @TransactAutoID
 end
 
 set @PrevMemberID = @MemberID

 --fetch next record
 fetch next from cur1
 into @MemberID, @TransactAutoID, @TransactPoints, @PointsCap
END

close cur1
deallocate cur1
--END CURSOR

--Check the temp table again after updated
select TransactAutoID, MemberID, TransactDate, TransactPoints, PointsCap, Correct_PointsEarned
from #TmpPointsCap
order by MemberID, TransactDate, AddedOn

Good Luck, and Happy Query-ing!
Share:

Tuesday, September 10, 2019

Connecting to SQL Instance with Non-Default Port on Pentaho

If you are trying to connect to SQL instance on Pentaho, and your SQL instance is using non-default port, then you need to fill in the connection string a bit differently.

This is how we normally fill in the connection to SQL instance, when connecting to default port.

If you are connecting to some other ports, you need to explicitly type in the connection parameters inside the IP field.
Share:

Tuesday, December 19, 2017

Dual Axis not Working in Tableau

Problem:
When creating chart with Dual Axis, Synchronize Axis is disabled and cannot be clicked.

Cause:
Tableau identified this as a bug, though it is supposed to have been fixed on later version, but it somehow still happens.

Solution:
1. To enable Synchronize Axis, make sure that the data type of both the measures are the same.

2. If you are using Integer data type, problem persists even if the data types are the same. You should convert the measures to float.
Notice that now the Synchronize Axis is enabled after we convert the data type to float.

Format it with 0 decimal points if you want it to appear as integer.

And this is how the end result looks like.

PS: I know the graph is ugly, but I hope you get what you need :)
Share:

Thursday, November 23, 2017

Tableau Server Unable to Start due to Blocked Ports

Problem:
Sometimes you might need to use different (from the default port) port on Tableau. By default, SSL is using port 443. For my case, I use SSL and port 8443 for Tableau Server. After you changed the port, and tried to start Tableau service, you get this error:
*** Tableau Server Gateway requires port 443,
***    in use by process 4.
*** Tableau Server unable to start due to blocked ports.

Solution:
There are 2 files you need to change:
1. C:\Tableau\Tableau Server\data\tabsvc\config\httpd.conf
2. C:\Program Files\Tableau\Tableau Server\10.3\apache\conf\original\extra\httpd-ssl.conf

Change the port to listen to 8443 (replace 443 to 8443) on these 2 files.
Share:

Wednesday, November 8, 2017

Frame cannot Load - Content Security Policy Issue

Problem:
You are using iframe to load a site from within a website, and the frame does not load.
In my case, I am using https for both the website and the iframe.

Error:
If you press F12 on the browser, you will see error similar to:
Refused to frame 'https://iframe_site' because it violates the following Content Security Policy directive: default-src 'self' 'unsafe-inline' 'unsafe-eval'...... (this can be found in web.config). Note that 'script-src' was not explicitly set, so 'default-src' is used as a fallback.

Solution:
Add the iframe_site to content-security-policy on the web.config.
<httpProtocol>
 <customHeaders>
  <add name="Content-Security-Policy" value="default-src 'self' 'unsafe-inline' 'unsafe-eval' iframe_site;" />
 </customHeaders>
</httpProtocol>

PS: If you are using port for the iframe site, you would need to include the port when adding into web.config.
Share:

Monday, October 24, 2016

Cisco VPN not Working on Windows 10

Error:
Cisco VPN is not working (cannot login and connect to VPN connection).

Solution:
1. Right-click on the VPN client, and Run as Administrator.
2. Open regedit and go to HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\CVirtA and rename:
  • x86 - "@oem8.ifn,%CVirtA_Desc%;Cisco Systems VPN Adapter" to "Cisco Systems VPN Adapter"
  • x64 - "@oem8.ifn,%CVirtA_Desc%;Cisco Systems VPN Adapter for 64-bit Windows" to "Cisco Systems VPN Adapter for 64-bit Windows"

Source partially from: IT that should just work
Share:

You may be intersted in

Related Posts

Updating Table Containing Xml Column via LinkedServer

If you are trying to update a table containing XML column via Linked Server in SQL Server, and you are not able to, you are not alone. There...

About Me

My photo
Is an ordinary man, with a little knowledge to share and high dreams to achieve. I'd be glad if I can help others, 'coz the only thing for the triumph of evil is for a good man to do nothing.

About Blog

You can find a lot of debugging and deploying problems while developing applications in .NET and Visual Basic here. There are also some querying tips in SQL and typical source codes which might be useful shared here.

Popular Posts

Blogroll

Followers

Leave a Message