I have a SQL Server 2016 database with "Always Encrypted" set to on. We have multiple encrypted columns and a stored procedure that handles updates.
CREATE PROCEDURE UpdateRecord
#ID INT,
#EncryptedParameter01 NVARCHAR(255) = NULL,
#EncryptedParameter02 NVARCHAR(255) = NULL,
#EncryptedParameter03 NVARCHAR(255) = NULL,
#EncryptedParameter04 NVARCHAR(255) = NULL,
#EncryptedParameter05 NVARCHAR(255) = NULL,
#EncryptedParameter06 NVARCHAR(255) = NULL,
#EncryptedParameter07 NVARCHAR(255) = NULL,
#EncryptedParameter08 NVARCHAR(255) = NULL,
#EncryptedParameter09 NVARCHAR(255) = NULL,
#EncryptedParameter10 NVARCHAR(255) = NULL,
#EncryptedParameter11 NVARCHAR(255) = NULL,
#EncryptedParameter12 NVARCHAR(255) = NULL,
#EncryptedParameter13 NVARCHAR(255) = NULL,
#EncryptedParameter14 NVARCHAR(255) = NULL,
#EncryptedParameter15 NVARCHAR(255) = NULL,
#EncryptedParameter16 NVARCHAR(255) = NULL,
#EncryptedParameter17 NVARCHAR(255) = NULL,
#EncryptedParameter18 NVARCHAR(255) = NULL,
#EncryptedParameter19 NVARCHAR(255) = NULL,
#EncryptedParameter20 NVARCHAR(255) = NULL
AS
BEGIN
UPDATE myTable
SET Field01 = CASE WHEN #EncryptedParameter01 IS NULL THEN Field01 ELSE #EncryptedParameter01 END,
Field02 = CASE WHEN #EncryptedParameter02 IS NULL THEN Field02 ELSE #EncryptedParameter02 END,
Field03 = CASE WHEN #EncryptedParameter03 IS NULL THEN Field03 ELSE #EncryptedParameter03 END,
Field04 = CASE WHEN #EncryptedParameter04 IS NULL THEN Field04 ELSE #EncryptedParameter04 END,
Field05 = CASE WHEN #EncryptedParameter05 IS NULL THEN Field05 ELSE #EncryptedParameter05 END,
Field06 = CASE WHEN #EncryptedParameter06 IS NULL THEN Field06 ELSE #EncryptedParameter06 END,
Field07 = CASE WHEN #EncryptedParameter07 IS NULL THEN Field07 ELSE #EncryptedParameter07 END,
Field08 = CASE WHEN #EncryptedParameter08 IS NULL THEN Field08 ELSE #EncryptedParameter08 END
, Field09 = CASE WHEN #EncryptedParameter09 IS NULL THEN Field09 ELSE #EncryptedParameter09 END
, Field10 = CASE WHEN #EncryptedParameter10 IS NULL THEN Field10 ELSE #EncryptedParameter10 END
, Field11 = CASE WHEN #EncryptedParameter11 IS NULL THEN Field11 ELSE #EncryptedParameter11 END
, Field12 = CASE WHEN #EncryptedParameter12 IS NULL THEN Field12 ELSE #EncryptedParameter12 END
, Field13 = CASE WHEN #EncryptedParameter13 IS NULL THEN Field13 ELSE #EncryptedParameter13 END
, Field14 = CASE WHEN #EncryptedParameter14 IS NULL THEN Field14 ELSE #EncryptedParameter14 END
, Field15 = CASE WHEN #EncryptedParameter15 IS NULL THEN Field15 ELSE #EncryptedParameter15 END
, Field16 = CASE WHEN #EncryptedParameter16 IS NULL THEN Field16 ELSE #EncryptedParameter16 END
, Field17 = CASE WHEN #EncryptedParameter17 IS NULL THEN Field17 ELSE #EncryptedParameter17 END
, Field18 = CASE WHEN #EncryptedParameter18 IS NULL THEN Field18 ELSE #EncryptedParameter18 END
, Field19 = CASE WHEN #EncryptedParameter19 IS NULL THEN Field19 ELSE #EncryptedParameter19 END
, Field20 = CASE WHEN #EncryptedParameter20 IS NULL THEN Field20 ELSE #EncryptedParameter20 END
WHERE ID = #ID
END
Our actual stored procedure is more complex, this just gives you an idea. If this is called with only a handful of parameters it works fine. However if it is called with more than 10 the procedure fails.
The error message is
Msg 206, Level 16, State 2, Procedure UpdateRecord, Line 0 [Batch Start Line 0]
Operand type clash: nvarchar is incompatible with nvarchar(255) encrypted with (encryption_type = 'DETERMINISTIC', encryption_algorithm_name = 'AEAD_AES_256_CBC_HMAC_SHA_256', column_encryption_key_name = 'MyNamedKey', column_encryption_key_database_name = 'MyDatabase')
This is not down to individual parameters failing, rather the number of characters being passed. Also this procedure was working fine before the encryption was added.
Any further info on why this is happening and work arounds gratefully received.
Many thanks.
Edited to include call:
I am seeing this both through VB and T-SQL. Simplest case being a direct call from SSMS. I do have "Enable Parameterization for Always Encrypted" checked. The parameters are being passed in correctly, and functions correctly, provided 10 or less parameters are sent. Example call:
DECLARE #PassedEncryptedParameter01 NVARCHAR(255) = 'Updated value 01'
EXEC UpdateRecord #Id=12345, #EncryptedParameter01 = #PassedEncryptedParameter01
As you can see I have the declared the type so it matches. The issue here isn't how the parameters are defined, rather the number of parameters being sent.
I have a table that stores an IP address masks, for validating if users are allowed to access a resource based on their IP Address. For me to validate against their IP Address, I break their address, and the masks, into octets and compare them. I have a way that works, but it isn't sitting well with me, as it seems overly heavy on string manipulation, which is slow in T-SQL. This strikes me as being a 'Brute Force' method, and not at all elegant. I'm hoping someone can offer some improvements, while keeping to the same output.
Some sample masks are as follows:
*.*.*.* - This is the most common, which is "for all possible IP
addresses"
172.16.*.* - This specifies IP addresses that start with 172.16
172.16.0-10.* - This specifies IP addresses that start with 172.16, but where the third octet is between 0 and 10
172.16.99.10 - This specifies an individual address.
To parse these out, I declare a temp table:
DECLARE #Rights TABLE(
IPAddr char(15)
, octet1 char(7)
, octet1max char(3)
, octet2 char(7)
, octet2max char(3)
, octet3 char(7)
, octet3max char(3)
, octet4 char(7)
, octet4max char(3)
, dot int
, tlen int
, tempaddr char(15)
)
I start by inserting the records from my database table into my temp table:
INSERT #Rights(IPAddress, dot, tlen, tempaddr)
SELECT IPAddress, CHARINDEX('.',IPAddress), LEN(IPAddress), IPAddress
FROM IPAccessRights_Info
Then, I run 4 UPDATE statements against this temp table:
UPDATE #Rights
SET octet1 = SUBSTRING(tempaddr, 0, dot)
, tempaddr = SUBSTRING(tempaddr, dot+1, LEN(tempaddr)-dot)
, dot = CHARINDEX('.', SUBSTRING(tempaddr, dot+1, LEN(tempaddr)-dot))
, tlen = LEN(SUBSTRING(tempaddr, dot+1, LEN(tempaddr)-dot))
UPDATE #Rights
SET octet2 = SUBSTRING(tempaddr, 0, dot)
, tempaddr = SUBSTRING(tempaddr, dot+1, LEN(tempaddr)-dot)
, dot = CHARINDEX('.', SUBSTRING(tempaddr, dot+1, LEN(tempaddr)-dot))
, tlen = LEN(SUBSTRING(tempaddr, dot+1, LEN(tempaddr)-dot))
UPDATE #Rights
SET octet3 = SUBSTRING(tempaddr, 0, dot)
, octet4 = SUBSTRING(tempaddr, dot+1, LEN(tempaddr)-dot)
, dot = NULL
, tlen = LEN(IPAddr)
, tempaddr = NULL
-- Parse out any ranges
UPDATE #Rights
SET octet1 = CASE WHEN CHARINDEX('-', octet1) > 0 THEN SUBSTRING(octet1, 0, CHARINDEX('-', octet1)) ELSE octet1 END
, octet1max = CASE WHEN CHARINDEX('-', octet1) > 0 THEN SUBSTRING(octet1, CHARINDEX('-', octet1)+1, LEN(octet1)) ELSE NULL END
, octet2 = CASE WHEN CHARINDEX('-', octet2) > 0 THEN SUBSTRING(octet2, 0, CHARINDEX('-', octet2)) ELSE octet2 END
, octet2max = CASE WHEN CHARINDEX('-', octet2) > 0 THEN SUBSTRING(octet2, CHARINDEX('-', octet2)+1, LEN(octet2)) ELSE NULL END
, octet3 = CASE WHEN CHARINDEX('-', octet3) > 0 THEN SUBSTRING(octet3, 0, CHARINDEX('-', octet3)) ELSE octet3 END
, octet3max = CASE WHEN CHARINDEX('-', octet3) > 0 THEN SUBSTRING(octet3, CHARINDEX('-', octet3)+1, LEN(octet3)) ELSE NULL END
, octet4 = CASE WHEN CHARINDEX('-', octet4) > 0 THEN SUBSTRING(octet4, 0, CHARINDEX('-', octet4)) ELSE octet4 END
, octet4max = CASE WHEN CHARINDEX('-', octet4) > 0 THEN SUBSTRING(octet4, CHARINDEX('-', octet4)+1, LEN(octet4)) ELSE NULL END
When I'm done, I can then use the values in octet1-octet4 and octet1max-octet4max to match up against my IP address.
Of course, when we all go IPv6, then this will be a whole different can of worms...
This Tip-of-the-day (and this one) describes how to use the T-SQL PARSENAME function
to separate IP addresses as well as 4-part database object names.
DECLARE #IPAddresses TABLE ( [IPAddress] VARCHAR(20))
INSERT INTO #IPAddresses VALUES ('10.0.0.1')
INSERT INTO #IPAddresses VALUES ('255.255.255.255')
INSERT INTO #IPAddresses VALUES ('192.123.545.12')
INSERT INTO #IPAddresses VALUES ('1.2.3.4')
SELECT * FROM #IPAddresses
ORDER BY CAST(PARSENAME([IPAddress], 4) AS INT),
CAST(PARSENAME([IPAddress], 3) AS INT),
CAST(PARSENAME([IPAddress], 2) AS INT),
CAST(PARSENAME([IPAddress], 1) AS INT)
IPAddress
----------------
1.2.3.4
10.0.0.1
192.123.545.12
255.255.255.255
Maybe I'm completely off here, but I can imagine that your orignal table isn't fit for the usage.
If would define the table IPAccessRights_Info like this:
CREATE TABLE [dbo].[IPAccessRights_Info](
[Id] [int] NOT NULL,
[IpAddress] [nvarchar](15) NOT NULL,
[Octet1Min] [int] NOT NULL,
[Octet1Max] [int] NOT NULL,
[Octet2Min] [int] NOT NULL,
[Octet2Max] [int] NOT NULL,
[Octet3Min] [int] NOT NULL,
[Octet3Max] [int] NOT NULL,
[Octet4Min] [int] NOT NULL,
[Octet4Max] [int] NOT NULL,
CONSTRAINT [PK_IPAccessRights_Info] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
And when you insert the IpAddress Rights from your management application (I making the assumption you have some kind of management layer for this) you do the nasty job of splitting the values of the IpAddress in octets, the task to query against this is far easier.
And I think you only need a OctetMin and OctetMax column. Translated from your examples:
*.*.*.* => Octet1Min = 0, Octet1Max = 255, Octet2Min = 0, etc.
172.16.*.* => Octet1Min = 172, Octet1Max = 172, etc.
172.16.0-10.* => Octet3Min = 0, Octet3Max = 10, etc.
172.16.99.10 => Octet4Min = 10, Octet4Max = 10, etc.
You can query against this, like this:
INSERT INTO dbo.IPAccessRights_Info VALUES (4,'172.16.9-10.*',172,172,16,16,9,10,0,255)
-- IpAddress to check
DECLARE #IpAddress nvarchar(15) = '172.16.10.5'
DECLARE #octet1 nvarchar(3) = SUBSTRING(#IpAddress, 0, CHARINDEX('.', #IpAddress))
SET #IpAddress = RIGHT(#IpAddress, LEN(#IpAddress)-LEN(#octet1) - 1)
DECLARE #octet2 nvarchar(3) = SUBSTRING(#IpAddress, 0, CHARINDEX('.', #IpAddress))
SET #IpAddress = RIGHT(#IpAddress, LEN(#IpAddress)-LEN(#octet2) - 1)
DECLARE #octet3 nvarchar(3) = SUBSTRING(#IpAddress, 0, CHARINDEX('.', #IpAddress))
SET #IpAddress = RIGHT(#IpAddress, LEN(#IpAddress)-LEN(#octet3) - 1)
DECLARE #octet4 nvarchar(3) = #IpAddress
SELECT Id
FROM dbo.IPAccessRights_Info
WHERE Octet1Min <= CAST(#octet1 AS int)
AND Octet1Max >= CAST(#octet1 AS int)
AND Octet2Min <= CAST(#octet2 AS int)
AND Octet2Max >= CAST(#octet2 AS int)
AND Octet3Min <= CAST(#octet3 AS int)
AND Octet3Max >= CAST(#octet3 AS int)
AND Octet4Min <= CAST(#octet4 AS int)
AND Octet4Max >= CAST(#octet4 AS int)
-- Results:
1
2
4
I have a list of tables that can be joined together by the same PK column. Since this list of tables can vary from project to project, I want to create a query that can be dynamic enough to pull all unique columns from these tables.
For example, I have three tables below:
Table A (PK field, column1, column 2)
Table B (PK field, column3, column 4)
Table C (PK field, column5, column 5)
These three tables are joined on "PK field" column, and I want the query output to be something like:
PK field column1 column2 column3 column4 column5
..data.. ..data.. ..data.. ..data.. ..data.. ..data..
At the end, this query will be part of a SQL function or SP, so the user can define a list of tables, and PK field at the beginning, then executing it shall return my expected output with dataset.
I think about use this query below but the result is not what I like:
SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = ''
Any advice about how I should design this SP or function, will be appreciated.
Thanks in advance.
DDL for two example tables:
CREATE TABLE [dbo].[G_bDEM](
[blaiseKey_code] [nvarchar](255) NULL,
[qSex] [int] NULL,
[qDOB] [datetime] NULL,
[qDOBNR] [int] NULL,
[qAge] [int] NULL,
[qAgeNR] [int] NULL,
[qAgeRange] [int] NULL,
[qAge15OrOver] [int] NULL,
[qNotEligible] [nvarchar](1) NULL,
[qBornInNZ] [int] NULL,
[qCountryOfBirth] [nvarchar](2) NULL,
[qArriveNZYr] [int] NULL,
[qArriveNZYrNR] [int] NULL,
[qArriveNZMth] [int] NULL,
[bDEM_BOP_qHowManyRaised] [int] NULL,
[bDEM_BOP_q1stParentBornNZ] [int] NULL,
[bDEM_BOP_q2ndParentBornNZ] [int] NULL,
[bDEM_BOP_qHowManyParentBornNZ] [int] NULL,
[qMaoriDescent] [int] NULL,
[qSchQual] [int] NULL,
[qSchQualOth] [nvarchar](200) NULL,
[qSchQualOthNR] [int] NULL,
[qSchQualYr] [int] NULL,
[qSchQualYrNR] [int] NULL,
[qPostSchQual] [int] NULL,
[q3MthsStudy] [int] NULL,
[qHighestQual] [int] NULL,
[qHighestQualOth] [nvarchar](200) NULL,
[qHighestQualOthNR] [int] NULL,
[qHighestQualYr] [int] NULL,
[qHighestQualYrNR] [int] NULL,
[qWorkIntro] [nvarchar](1) NULL,
[qDidPaidWork] [int] NULL,
[qAwayFromWork] [int] NULL,
[qFamilyBusWork] [int] NULL,
[bDEM_WOR_qPaidWorkIntro] [nvarchar](1) NULL,
[bDEM_WOR_qJobsNum] [int] NULL,
[bDEM_WOR_qJobsNumNR] [int] NULL,
[bDEM_WOR_tabDEM_T2_fTotMins] [int] NULL,
[bDEM_WOR_q2JobsNoHrsIntro] [nvarchar](1) NULL,
[bDEM_WOR_q2Jobs2HrsIntro] [nvarchar](1) NULL,
[bDEM_WOR_q2Jobs1HrsIntro] [nvarchar](1) NULL,
[bDEM_WOR_qOccupation] [nvarchar](200) NULL,
[bDEM_WOR_qOccupationNR] [int] NULL,
[bDEM_WOR_qMainTasks] [nvarchar](200) NULL,
[bDEM_WOR_qMainTasksNR] [int] NULL,
[bDEM_WOR_qFeelAboutJob] [int] NULL,
[bDEM_WOR_qEmployArrangement] [int] NULL,
[bDEM_WOR_qPermEmployee] [int] NULL,
[qHasJobToStart] [int] NULL,
[qLookedForWork] [int] NULL,
[qJobSearchA] [int] NULL,
[qJobSearchB] [int] NULL,
[qJobSearchC] [int] NULL,
[qJobSearchD] [int] NULL,
[qJobSearchE] [int] NULL,
[qJobSearchF] [int] NULL,
[qJobSearchG] [int] NULL,
[qJobSearchH] [int] NULL,
[qJobSearchI] [int] NULL,
[qJobSearchOth] [nvarchar](200) NULL,
[qJobSearchOthNR] [int] NULL,
[qCouldStartLastWk] [int] NULL,
[qIncTotalAmt] [int] NULL,
[fCountryName] [nvarchar](60) NULL
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[G_bLWW](
[blaiseKey_code] [nvarchar](255) NULL,
[qThingsWorthwhileScale] [int] NULL
) ON [PRIMARY]
This script generate dynamic SQL for any table with similar PK name.
Query:
SET NOCOUNT ON
IF OBJECT_ID (N'dbo.A') IS NOT NULL
DROP TABLE dbo.A
IF OBJECT_ID (N'dbo.B') IS NOT NULL
DROP TABLE dbo.B
IF OBJECT_ID (N'dbo.C') IS NOT NULL
DROP TABLE dbo.C
CREATE TABLE dbo.A (PK_field INT PRIMARY KEY, column1 INT, column2 INT)
CREATE TABLE dbo.B (PK_field INT PRIMARY KEY, column3 INT, column4 INT)
CREATE TABLE dbo.C (PK_field INT PRIMARY KEY, column5 INT, [column 6] INT)
INSERT INTO dbo.A (PK_field, column1, column2)
VALUES (1, 1, 2), (2, 1, 2)
INSERT INTO dbo.B (PK_field, column3, column4)
VALUES (2, 3, 4)
INSERT INTO dbo.C (PK_field, column5, [column 6])
VALUES (1, 5, 6), (3, 5, 6)
DECLARE #SQL NVARCHAR(MAX)
;WITH cte AS
(
SELECT
column_name = '[' + c.name + ']'
, table_name = '[' + s.name + '].[' + o.name + ']'
FROM sys.columns c WITH (NOLOCK)
JOIN sys.objects o WITH (NOLOCK) ON c.[object_id] = o.[object_id]
JOIN sys.schemas s WITH (NOLOCK) ON o.[schema_id] = s.[schema_id]
WHERE o.name IN ('A', 'B', 'C')
AND s.name = 'dbo'
AND o.[type] = 'U'
), unicol AS (
SELECT TOP 1 column_name
FROM cte
GROUP BY cte.column_name
HAVING COUNT(cte.column_name) > 1
), cols AS
(
SELECT DISTINCT column_name
FROM cte
), tbl AS
(
SELECT DISTINCT table_name
FROM cte
), rs AS
(
SELECT
tbl.table_name
, column_name = ISNULL(cte.column_name, cols.column_name + ' = NULL')
FROM cols
CROSS JOIN tbl
LEFT JOIN cte ON cols.column_name = cte.column_name AND cte.table_name = tbl.table_name
), rs2 AS (
SELECT uni = ' UNION ALL' + CHAR(13) + 'SELECT ' + STUFF((
SELECT ', ' + rs.column_name
FROM rs
WHERE tbl.table_name = rs.table_name
GROUP BY rs.column_name
ORDER BY rs.column_name
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 2, '') +
' FROM ' + table_name
FROM tbl
)
SELECT #SQL = 'SELECT
' + STUFF((
SELECT CHAR(13) + ', ' + ISNULL(unicol.column_name, cols.column_name + ' = MAX(' + cols.column_name + ')')
FROM cols
LEFT JOIN unicol ON cols.column_name = unicol.column_name
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 2, ' ')
+ '
FROM
(' + STUFF((
SELECT CHAR(10) + uni
FROM rs2
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 11, '') + CHAR(13) +
') t
GROUP BY ' + (SELECT column_name FROM unicol)
PRINT #SQL
EXECUTE sys.sp_executesql #SQL
Output:
SELECT
[column 6] = MAX([column 6])
, [column1] = MAX([column1])
, [column2] = MAX([column2])
, [column3] = MAX([column3])
, [column4] = MAX([column4])
, [column5] = MAX([column5])
, [PK_field]
FROM (
SELECT [column 6] = NULL, [column1], [column2], [column3] = NULL, [column4] = NULL, [column5] = NULL, [PK_field] FROM [dbo].[A]
UNION ALL
SELECT [column 6] = NULL, [column1] = NULL, [column2] = NULL, [column3], [column4], [column5] = NULL, [PK_field] FROM [dbo].[B]
UNION ALL
SELECT [column 6], [column1] = NULL, [column2] = NULL, [column3] = NULL, [column4] = NULL, [column5], [PK_field] FROM [dbo].[C]
) t
GROUP BY [PK_field]
Results:
column 6 column1 column2 column3 column4 column5 PK_field
----------- ----------- ----------- ----------- ----------- ----------- -----------
6 1 2 NULL NULL 5 1
NULL 1 2 3 4 NULL 2
6 NULL NULL NULL NULL 5 3
Update in script:
DECLARE #SQL NVARCHAR(2000) -> NVARCHAR(MAX)
Output for your DDL:
SELECT
[blaiseKey_code]
, [bDEM_BOP_q1stParentBornNZ] = MAX([bDEM_BOP_q1stParentBornNZ])
, [bDEM_BOP_q2ndParentBornNZ] = MAX([bDEM_BOP_q2ndParentBornNZ])
, [bDEM_BOP_qHowManyParentBornNZ] = MAX([bDEM_BOP_qHowManyParentBornNZ])
, [bDEM_BOP_qHowManyRaised] = MAX([bDEM_BOP_qHowManyRaised])
, [bDEM_WOR_q2Jobs1HrsIntro] = MAX([bDEM_WOR_q2Jobs1HrsIntro])
, [bDEM_WOR_q2Jobs2HrsIntro] = MAX([bDEM_WOR_q2Jobs2HrsIntro])
, [bDEM_WOR_q2JobsNoHrsIntro] = MAX([bDEM_WOR_q2JobsNoHrsIntro])
, [bDEM_WOR_qEmployArrangement] = MAX([bDEM_WOR_qEmployArrangement])
, [bDEM_WOR_qFeelAboutJob] = MAX([bDEM_WOR_qFeelAboutJob])
, [bDEM_WOR_qJobsNum] = MAX([bDEM_WOR_qJobsNum])
, [bDEM_WOR_qJobsNumNR] = MAX([bDEM_WOR_qJobsNumNR])
, [bDEM_WOR_qMainTasks] = MAX([bDEM_WOR_qMainTasks])
, [bDEM_WOR_qMainTasksNR] = MAX([bDEM_WOR_qMainTasksNR])
, [bDEM_WOR_qOccupation] = MAX([bDEM_WOR_qOccupation])
, [bDEM_WOR_qOccupationNR] = MAX([bDEM_WOR_qOccupationNR])
, [bDEM_WOR_qPaidWorkIntro] = MAX([bDEM_WOR_qPaidWorkIntro])
, [bDEM_WOR_qPermEmployee] = MAX([bDEM_WOR_qPermEmployee])
, [bDEM_WOR_tabDEM_T2_fTotMins] = MAX([bDEM_WOR_tabDEM_T2_fTotMins])
, [fCountryName] = MAX([fCountryName])
, [q3MthsStudy] = MAX([q3MthsStudy])
, [qAge] = MAX([qAge])
, [qAge15OrOver] = MAX([qAge15OrOver])
, [qAgeNR] = MAX([qAgeNR])
, [qAgeRange] = MAX([qAgeRange])
, [qArriveNZMth] = MAX([qArriveNZMth])
, [qArriveNZYr] = MAX([qArriveNZYr])
, [qArriveNZYrNR] = MAX([qArriveNZYrNR])
, [qAwayFromWork] = MAX([qAwayFromWork])
, [qBornInNZ] = MAX([qBornInNZ])
, [qCouldStartLastWk] = MAX([qCouldStartLastWk])
, [qCountryOfBirth] = MAX([qCountryOfBirth])
, [qDidPaidWork] = MAX([qDidPaidWork])
, [qDOB] = MAX([qDOB])
, [qDOBNR] = MAX([qDOBNR])
, [qFamilyBusWork] = MAX([qFamilyBusWork])
, [qHasJobToStart] = MAX([qHasJobToStart])
, [qHighestQual] = MAX([qHighestQual])
, [qHighestQualOth] = MAX([qHighestQualOth])
, [qHighestQualOthNR] = MAX([qHighestQualOthNR])
, [qHighestQualYr] = MAX([qHighestQualYr])
, [qHighestQualYrNR] = MAX([qHighestQualYrNR])
, [qIncTotalAmt] = MAX([qIncTotalAmt])
, [qJobSearchA] = MAX([qJobSearchA])
, [qJobSearchB] = MAX([qJobSearchB])
, [qJobSearchC] = MAX([qJobSearchC])
, [qJobSearchD] = MAX([qJobSearchD])
, [qJobSearchE] = MAX([qJobSearchE])
, [qJobSearchF] = MAX([qJobSearchF])
, [qJobSearchG] = MAX([qJobSearchG])
, [qJobSearchH] = MAX([qJobSearchH])
, [qJobSearchI] = MAX([qJobSearchI])
, [qJobSearchOth] = MAX([qJobSearchOth])
, [qJobSearchOthNR] = MAX([qJobSearchOthNR])
, [qLookedForWork] = MAX([qLookedForWork])
, [qMaoriDescent] = MAX([qMaoriDescent])
, [qNotEligible] = MAX([qNotEligible])
, [qPostSchQual] = MAX([qPostSchQual])
, [qSchQual] = MAX([qSchQual])
, [qSchQualOth] = MAX([qSchQualOth])
, [qSchQualOthNR] = MAX([qSchQualOthNR])
, [qSchQualYr] = MAX([qSchQualYr])
, [qSchQualYrNR] = MAX([qSchQualYrNR])
, [qSex] = MAX([qSex])
, [qThingsWorthwhileScale] = MAX([qThingsWorthwhileScale])
, [qWorkIntro] = MAX([qWorkIntro])
FROM
(
SELECT [bDEM_BOP_q1stParentBornNZ], [bDEM_BOP_q2ndParentBornNZ], [bDEM_BOP_qHowManyParentBornNZ], [bDEM_BOP_qHowManyRaised], [bDEM_WOR_q2Jobs1HrsIntro], [bDEM_WOR_q2Jobs2HrsIntro], [bDEM_WOR_q2JobsNoHrsIntro], [bDEM_WOR_qEmployArrangement], [bDEM_WOR_qFeelAboutJob], [bDEM_WOR_qJobsNum], [bDEM_WOR_qJobsNumNR], [bDEM_WOR_qMainTasks], [bDEM_WOR_qMainTasksNR], [bDEM_WOR_qOccupation], [bDEM_WOR_qOccupationNR], [bDEM_WOR_qPaidWorkIntro], [bDEM_WOR_qPermEmployee], [bDEM_WOR_tabDEM_T2_fTotMins], [blaiseKey_code], [fCountryName], [q3MthsStudy], [qAge], [qAge15OrOver], [qAgeNR], [qAgeRange], [qArriveNZMth], [qArriveNZYr], [qArriveNZYrNR], [qAwayFromWork], [qBornInNZ], [qCouldStartLastWk], [qCountryOfBirth], [qDidPaidWork], [qDOB], [qDOBNR], [qFamilyBusWork], [qHasJobToStart], [qHighestQual], [qHighestQualOth], [qHighestQualOthNR], [qHighestQualYr], [qHighestQualYrNR], [qIncTotalAmt], [qJobSearchA], [qJobSearchB], [qJobSearchC], [qJobSearchD], [qJobSearchE], [qJobSearchF], [qJobSearchG], [qJobSearchH], [qJobSearchI], [qJobSearchOth], [qJobSearchOthNR], [qLookedForWork], [qMaoriDescent], [qNotEligible], [qPostSchQual], [qSchQual], [qSchQualOth], [qSchQualOthNR], [qSchQualYr], [qSchQualYrNR], [qSex], [qThingsWorthwhileScale] = NULL, [qWorkIntro] FROM [dbo].[G_bDEM]
UNION ALL
SELECT [bDEM_BOP_q1stParentBornNZ] = NULL, [bDEM_BOP_q2ndParentBornNZ] = NULL, [bDEM_BOP_qHowManyParentBornNZ] = NULL, [bDEM_BOP_qHowManyRaised] = NULL, [bDEM_WOR_q2Jobs1HrsIntro] = NULL, [bDEM_WOR_q2Jobs2HrsIntro] = NULL, [bDEM_WOR_q2JobsNoHrsIntro] = NULL, [bDEM_WOR_qEmployArrangement] = NULL, [bDEM_WOR_qFeelAboutJob] = NULL, [bDEM_WOR_qJobsNum] = NULL, [bDEM_WOR_qJobsNumNR] = NULL, [bDEM_WOR_qMainTasks] = NULL, [bDEM_WOR_qMainTasksNR] = NULL, [bDEM_WOR_qOccupation] = NULL, [bDEM_WOR_qOccupationNR] = NULL, [bDEM_WOR_qPaidWorkIntro] = NULL, [bDEM_WOR_qPermEmployee] = NULL, [bDEM_WOR_tabDEM_T2_fTotMins] = NULL, [blaiseKey_code], [fCountryName] = NULL, [q3MthsStudy] = NULL, [qAge] = NULL, [qAge15OrOver] = NULL, [qAgeNR] = NULL, [qAgeRange] = NULL, [qArriveNZMth] = NULL, [qArriveNZYr] = NULL, [qArriveNZYrNR] = NULL, [qAwayFromWork] = NULL, [qBornInNZ] = NULL, [qCouldStartLastWk] = NULL, [qCountryOfBirth] = NULL, [qDidPaidWork] = NULL, [qDOB] = NULL, [qDOBNR] = NULL, [qFamilyBusWork] = NULL, [qHasJobToStart] = NULL, [qHighestQual] = NULL, [qHighestQualOth] = NULL, [qHighestQualOthNR] = NULL, [qHighestQualYr] = NULL, [qHighestQualYrNR] = NULL, [qIncTotalAmt] = NULL, [qJobSearchA] = NULL, [qJobSearchB] = NULL, [qJobSearchC] = NULL, [qJobSearchD] = NULL, [qJobSearchE] = NULL, [qJobSearchF] = NULL, [qJobSearchG] = NULL, [qJobSearchH] = NULL, [qJobSearchI] = NULL, [qJobSearchOth] = NULL, [qJobSearchOthNR] = NULL, [qLookedForWork] = NULL, [qMaoriDescent] = NULL, [qNotEligible] = NULL, [qPostSchQual] = NULL, [qSchQual] = NULL, [qSchQualOth] = NULL, [qSchQualOthNR] = NULL, [qSchQualYr] = NULL, [qSchQualYrNR] = NULL, [qSex] = NULL, [qThingsWorthwhileScale], [qWorkIntro] = NULL FROM [dbo].[G_bLWW]
) t
GROUP BY [blaiseKey_code]
Try this :
DECLARE
#cols VARCHAR(MAX)
, #TableA VARCHAR(10)= 'TableA'
, #TableB VARCHAR(10)= 'TableB'
, #TableC VARCHAR(10)= 'TableC'
, #Pk VARCHAR(20)
SELECT
#cols = STUFF((
SELECT DISTINCT ', [' + c.column_name + ']'
FROM INFORMATION_SCHEMA.Columns c
WHERE c.table_name IN ( #TableA,#TableB,#TableC )
FOR XML PATH('')
), 1, 2, '');
SELECT #Pk = column_name
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
WHERE OBJECTPROPERTY(OBJECT_ID(constraint_name), 'IsPrimaryKey') = 1
AND table_name = #TableA
DECLARE #query VARCHAR(1000)
SET #query = 'SELECT ' + #cols + ' FROM ' + #TableA + ' JOIN ' + #TableB
+ ' ON ' + #TableA + '.' + #Pk + '=' + #TableB + '.' + #Pk
+ ' JOIN ' + #TableC + ' ON ' + #TableB + '.' + #Pk + '=' + #TableC
+ '.' + #Pk
EXEC (#query)
Do not forget the warning about special html characters in column name given by #Gordon.
You can only do this as a stored procedure. A SQL query returns a specified set of columns, no more, no less. The only way to get a variable number of columns is using dynamic SQL. And, functions don't support dynamic SQL.
You would need to construct a SQL statement, concatenating the column names from INFORMATION_SCHEMA.Columns. Something like this:
declare #cols varchar(max);
select #cols = stuff((select distinct ', ['+c.column_name+']'
from INFORMATION_SCHEMA.Columns c
where c.table_name in (<list of tables here>)
for xml path ('')
), 1, 2, '');
This will not work for column names that have special html characters, such as '<', '>', or '&'.
You can then construct the full query statement and execute it, with exec() or sp_executesql().
An alternative approach would be to create a view that has all the joins and all the columns. Let the SQL optimizer determine the best execution path.