SQL Server adding its own aliases and getting them wrong - sql-server

I wrote the following short, simple view:
--DROP VIEW ReportObjects.vStarts;
CREATE VIEW ReportObjects.vStarts AS
SELECT *
FROM ReportObjects.vLotHistory
WHERE
LotType = 'Some constant value'
AND CDOName = 'Some constant value'
AND SpecId = 'Some constant value'
When I execute this script then click "Design" on the view in SSMS, I get this:
SELECT ContainerName, SpecName, ProductName, LotStatus, CDOName, ResourceName AS LotType, EmployeeName AS WorkflowName, LotType AS DieQty,
WorkflowName AS LotQty, DieQty AS TxnDate, LotQty AS TxnDateGMT, TxnDate AS ContainerId, TxnDateGMT AS HistoryMainlineId, ContainerId AS ProductId,
HistoryMainlineId AS SpecId, ProductId AS WorkflowId, SpecId AS WorkflowBaseId, WorkflowId AS WorkflowStepId, WorkflowBaseId, WorkflowStepId, ResourceId,
EmployeeId
FROM ReportObjects.vLotHistory
WHERE (LotType = 'Some constant value') AND (CDOName = 'Some constant value') AND (SpecId = 'Some constant value')
See the problem? DieQty AS TxnDate, LotQty AS TxnDateGMT, TxnDate AS ContainerId
It's aliasing columnA with the name of columnC!
I've tried dropping/recreating the view several times.
I know it can be argued that SELECT * is icky, but that's beside the point (and it is necessary sometimes in production code).
The view above is selecting from another view defined as:
CREATE VIEW ReportObjects.vLotHistory AS
SELECT
lot.ContainerName,
hist.SpecName,
hist.ProductName,
lot.Status LotStatus,
hist.CDOName,
hist.ResourceName,
hist.EmployeeName,
lot.csiLotType LotType,
WorkFlowBase.WorkflowName,
hist.Qty DieQty,
hist.Qty2 LotQty,
hist.TxnDate,
hist.TxnDateGMT,
lot.ContainerId,
hist.HistoryMainlineId,
hist.ProductId,
hist.SpecId,
Workflow.WorkflowId,
Workflow.WorkflowBaseId,
hist.WorkflowStepId,
hist.ResourceId,
hist.EmployeeId
FROM ODS.MES_Schema.Container lot
LEFT JOIN ODS.MES_Schema.HistoryMainline hist
ON lot.ContainerId = hist.ContainerId
LEFT JOIN ODS.MES_Schema.WorkflowStep WS
ON hist.WorkflowStepId = WS.WorkflowStepId
LEFT JOIN ODS.MES_Schema.Workflow
ON WS.WorkflowId = Workflow.WorkflowId
LEFT JOIN ODS.MES_Schema.WorkflowBase
ON WorkflowBase.WorkflowBaseId = Workflow.WorkflowBaseId
Does anyone know why SSMS or SQL Server is eating my query and spitting out nonsense? Note that I am not using SSMS tools to create the view -- I am using CREATE VIEW as shown above.

Related

How to return T-SQL query with column names as first row

I'm writing a SSIS package to output data from a SQL Server 2012 database to a .CSV file for a client. The requirement is that the first row be the column names. Below is the query I've written for the Source in the Data Flow Task. The problem is, it always returns the column names as the LAST row, not the first. Why? How do I achieve this?
DECLARE #Today AS DateTime= GETDATE()
DECLARE #NextPayrollDate AS DateTime
EXEC mobile.getNextPayrollDate #Today, #NextPayrollDate OUTPUT
;WITH LatestEligible (EmployeeID, LatestBillVerified) AS
(
SELECT
EmployeeID, MAX(DateBillVerified) AS LatestBillVerified
FROM
Inv_DataReimbursement
GROUP BY
EmployeeID
)
SELECT
'Edit Set' AS 'Edit Set',
'Employee No.' AS 'Employee No.'
FROM
LatestEligible
UNION
SELECT
NULL AS 'Edit Set',
d.EmployeeID AS 'Employee No.'
FROM
LatestEligible d
INNER JOIN
Employee e ON d.EmployeeID = e.EmployeeID
INNER JOIN
Inv_DataReimbursement dr ON d.EmployeeID = dr.EmployeeID
AND d.LatestBillVerified = dr.DateBillVerified
WHERE
(dr.MonthlyServiceEligible = 'true'
OR (dr.MonthlyServiceEligible = 'false'
AND e.DateEnd IS NOT NULL
AND e.DateEnd > #NextPayrollDate))
AND dr.ActualAmount > 0
How do I achieve this?
Don't do it through SQL.
Just tick the Column names in the first data row box in the Flat File Connection Manager and leave the original query untouched.
The column headers will then be added automatically without you needing to union this additional metadata (and potentially cast everything as a string on the SQL side).
You could try UNION ALL:
SELECT
'Edit Set' AS 'Edit Set', 'Employee No.' AS 'Employee No.'
FROM LatestEligible
UNION ALL
SELECT DISTINCT
NULL AS 'Edit Set',
d.EmployeeID AS 'Employee No.'
FROM LatestEligible d
INNER JOIN Employee e
ON d.EmployeeID = e.EmployeeID
INNER JOIN Inv_DataReimbursement dr
ON d.EmployeeID = dr.EmployeeID AND d.LatestBillVerified =
dr.DateBillVerified
WHERE (dr.MonthlyServiceEligible = 'true'
OR (dr.MonthlyServiceEligible = 'false' AND e.DateEnd IS NOT NULL AND
e.DateEnd > #NextPayrollDate))
AND dr.ActualAmount > 0

SQL Server Cannot Update Table with Subqueries

I'm trying to update a temporary table called #deletedRecords which looks like this:
With the data from a table called log that looks like this:
The KeyValue in the log table is the same as the ID in #deletedRecords.
There is a column in #deletedRecords for every FieldName for any particular key value.
I tried to extract the values using the following query:
UPDATE #deletedRecords
SET PatientName = (SELECT ACL.OldValue WHERE ACL.FieldName = 'CptCode'),
ChargeNotes = (SELECT ACL.OldValue WHERE ACL.FieldName = 'ChargeNotes'),
Units = (SELECT ACL.OldValue WHERE ACL.FieldName = 'Units'),
ChargeStatusID = (SELECT ACL.OldValue WHERE ACL.FieldName = 'Units')
FROM Log ACL
JOIN #deletedRecords DR ON ACL.KeyValue = DR.ID
WHERE ACL.TableName = 'BillingCharge'
AND ACL.EventType = 'DELETE'
However when I run the query all of the columns to be updated in #deletedRecords are null. Can somebody please help explain what I'm missing?
Thanks in advance.
EDIT:
In response to #Yogesh Sharma's answer, I elected to use the CTE method. I would this that using the values from the CTE to join to additional tables and extract their values during the update.
e.g. The Log table doesn't contain an old value for the StatusName but it does contain the ChargeStatusID which could be used to join to another table that contains that information such as this table ChargeStatus:
Thus I modified #Yogesh Sharma's code to the following:
WITH cte AS
...
UPDATE d
SET d.PatientName = c.PatientName
, d.StatusName = cs.StatusName
FROM #deletedBillingChargeTemp d
JOIN cte c ON c.KeyValue = d.chargeID
JOIN ChargeStatus cs ON c.ChargeStatusID = cs.ChargeStatusID
However, once I add that secondary join, all of the updated values return to null as they were before #Yogesh Sharma's suggestions were implemented.
Your query does not work because the UPDATE is executed multiple times for each row in DR, considering only the conditions specified in the last three rows of your query (not the ones specified in the subqueries). The values that remain in the table are the ones that correspond to the ACL row used in the last execution (and the order of execution cannot be controlled). If for ACL row used in the last execution the subqueries return NULL, you will get a NULL result.
See the example in the https://learn.microsoft.com/en-us/sql/t-sql/queries/update-transact-sql topic, where it says "The results of an UPDATE statement are undefined if the statement includes a FROM clause that is not specified in such a way that only one value is available for each column occurrence that is updated, that is if the UPDATE statement is not deterministic.".
You should rewrite your query like this:
UPDATE #deletedRecords
SET PatientName = (
SELECT ACL.OldValue FROM Log ACL
WHERE ACL.FieldName = 'CptCode' AND ACL.KeyValue = DR.ID
AND ACL.TableName = 'BillingCharge' AND ACL.EventType = 'DELETE'
),
ChargeNotes = (
SELECT ACL.OldValue FROM Log ACL
WHERE ACL.FieldName = 'ChargeNotes' AND ACL.KeyValue = DR.ID
AND ACL.TableName = 'BillingCharge' AND ACL.EventType = 'DELETE'
),
Units = (
SELECT ACL.OldValue FROM Log ACL
WHERE ACL.FieldName = 'Units' AND ACL.KeyValue = DR.ID
AND ACL.TableName = 'BillingCharge' AND ACL.EventType = 'DELETE'
),
ChargeStatusID = (
SELECT ACL.OldValue FROM Log ACL
WHERE ACL.FieldName = 'Units' AND ACL.KeyValue = DR.ID
AND ACL.TableName = 'BillingCharge' AND ACL.EventType = 'DELETE'
)
FROM #deletedRecords DR
You would required to do some conditional aggregation for log table and do the JOINs in order to update the temporary table #deletedRecords records
So, the conditional approach could be achieve via CTE or Subquery
WITH cte AS
(
SELECT KeyValue,
MAX(CASE WHEN FieldName = 'CptCode' THEN OldValue END) PatientName,
MAX(CASE WHEN FieldName = 'ChargeNotes' THEN OldValue END) ChargeNotes,
...
FROM Log
WHERE TableName = 'BillingCharge' AND EventType = 'DELETE'
GROUP BY KeyValue
)
UPDATE d
SET d.PatientName = c.PatientName,
...
FROM #deletedRecords d
INNER JOIN cte c ON c.KeyValue = d.ID
The other way is to update your temporary table via correlation approach
UPDATE d
SET d.PatientName = (SELECT TOP 1 OldValue FROM Log WHERE KeyValue = d.ID AND
TableName = 'BillingCharge' AND EventType = 'DELETE' AND FieldName = 'CptCode'),
d.ChargeNotes= (SELECT TOP 1 OldValue FROM Log WHERE KeyValue = d.ID AND
TableName = 'BillingCharge' AND EventType = 'DELETE' AND FieldName = 'ChargeNotes'),
...
FROM #deletedRecords d
If your updated columns are NULL, these are it's possible causes:
Since you are doing a INNER JOIN, records might not be joining correctly by their joining column. Make sure both tables have the same values on the joining columns.
Since you are filtering in a WHERE clause, records might not fulfill your TableName and EventType filters. Make sure there are records that sucessfully INNER JOIN between them and they have the supplied TableName and EventType.
The values you are asigning are NULL. Make sure your subqueries return a not null value.
Table reference is off. When updating a table in SQL Server, always use the updating table alias if you are using one.
Use
UPDATE DR SET
YourColumn = Value
FROM
Log ACL
JOIN #deletedRecords DR ON -...
Instead of
UPDATE #deletedRecords SET
YourColumn = Value
FROM
Log ACL
JOIN #deletedRecords DR ON -...
Make sure you are NOT checking the variable table values on another batch, script or procedure. Variable tables scope are limited to current batch or procedure, while temporary tables remain as long as the session is alive.
Make sure that there isn't another statement that is setting those values as NULL after your update. Also keep an eye on your transactions (might not be commited or rolled back).

How do I join a column onto a selected query in T-SQL

I have the following view and I need to add on the characterName column from the character table as reference without changing the structure of the query results. Please help.
CREATE VIEW vwTopStackedItems
AS
SELECT TOP 20 MAX(inventorySlot.quantity) AS 'Top Stacked', item.itemName AS 'Item Name'
FROM inventorySlot
JOIN item
ON inventorySlot.itemID = item.itemID
JOIN character
ON inventorySlot.characterID = character.characterID
WHERE quantity > 1
GROUP BY itemName
ORDER BY MAX(quantity) DESC
GO
SELECT * FROM vwTopStackedItems
GO
You could use function CONCAT() to concatenate the two values. For more information: https://learn.microsoft.com/en-us/sql/t-sql/functions/concat-transact-sql
CREATE VIEW vwTopStackedItems
AS
SELECT TOP 20 MAX(inventorySlot.quantity) AS 'Top Stacked', CONCAT(item.itemName, '-', character.characterName) AS 'Item Name'
FROM inventorySlot
JOIN item
ON inventorySlot.itemID = item.itemID
JOIN character
ON inventorySlot.characterID = character.characterID
WHERE quantity > 1
GROUP BY CONCAT(item.itemName, '-', character.characterName)
ORDER BY MAX(quantity) DESC
GO
SELECT * FROM vwTopStackedItems
GO
since itemName is your key to this view, you need another view like this:
CREATE VIEW vwTopStackedItemsWCharacter
AS
SELECT itemName, characterName
FROM inventorySlot
JOIN item
ON inventorySlot.itemID = item.itemID
JOIN character
ON inventorySlot.characterID = character.characterID
GROUP BY itemName, characterName
this second view will bring on the character name so you join accordingly. this way you get the info without any change on vwTopStackedItems

SQL Server - IN clause with multiple fields

Is it possible to include in a IN clause multiple fields? Something like the following:
select * from user
where code, userType in ( select code, userType from userType )
I'm using ms sql server 2008
I know this can be achieved with joins and exists, I just wanted to know if it could just be done with the IN clause.
Not the way you have posted. You can only return a single field or type for IN to work.
From MSDN (IN):
test_expression [ NOT ] IN
( subquery | expression [ ,...n ]
)
subquery - Is a subquery that has a result set of one column.
This column must have the same data type as test_expression.
expression[ ,... n ] - Is a list of expressions to test for a match.
All expressions must be of the same type as
test_expression.
Instead of IN, you could use a JOIN using the two fields:
SELECT U.*
FROM user U
INNER JOIN userType UT
ON U.code = UT.code
AND U.userType = UT.userType
You could use a form like this:
select * from user u
where exists (select 1 from userType ut
where u.code = ut.code
and u.userType = ut.userType)
Only with something horrific, like
select * from user
where (code + userType) in ( select code + userType from userType )
Then you have to manage nulls and concatenating numbers rather than adding them, and casting, and a code of 12 and a usertype of 3 vs a code of 1 and a usertype of 23, and...
..which means you start heading into perhaps something like:
--if your SQLS supports CONCAT
select * from user
where CONCAT(code, CHAR(9), userType) in ( select CONCAT(code, CHAR(9), userType) from ... )
--if no concat
select * from user
where COALESCE(code, 'no code') + CHAR(9) + userType in (
select COALESCE(code, 'no code') + CHAR(9) + userType from ...
)
CONCAT will do a string concatenation of most things, and won't zip the whole output to NULL if one element is NULL. If you don't have CONCAT then you'll string concat using + but anything that might be null will need a COALESCE/ISNULL around it.. And in either case you'll need something like CHAR(9) (a tab) between the fields to prevent them mixing.. The thing between the fields should be southing that is not naturally present in the data..
Tis a shame SQLS doesn't support this, that Oracle does:
where (code, userType) in ( select code, userType from userType )
but it's probably not worth switching DB for; I'd use EXISTS or a JOIN to achieve a multi column filter
So there ya go: a solution that doesn't use joins or exists.. and a bunch of reasons why you shouldn't use it ;)
How about this instead:
SELECT user.* FROM user JOIN userType on user.code = userType.code AND user.userType = userType.userType
You can either use joins
SELECT * FROM user U
INNER JOIN userType UT on U.code = UT.code
AND U.userType = UT.userType
I had to do something very similar but EXISTS didn't work in my situation. Here is what worked for me:
UPDATE tempFinalTbl
SET BillStatus = 'Non-Compliant'
WHERE ENTCustomerNo IN ( SELECT DISTINCT CustNmbr
FROM tempDetailTbl dtl
WHERE dtl.[Billing Status] = 'NEEDS FURTHER REVIEW'
AND dtl.CustNmbr = ENTCustomerNo
AND dtl.[Service] = [Service])
AND [Service] IN ( SELECT DISTINCT [Service]
FROM tempDetailTbl dtl
WHERE dtl.[Billing Status] = 'NEEDS FURTHER REVIEW'
AND dtl.CustNmbr = ENTCustomerNo
AND dtl.[Service] = [Service])
EDIT: Now that I look, this is very close to #v1v3kn's answer
I don't think that query is quite portable,it would be safer to use something like
select * from user
where code in ( select code from userType ) and userType in (select userType from userType)
select * from user
where (code, userType) in ( select code, userType from userType );

How can I create multiple columns from one DB Field in SQL Server?

I have a field called PropertyValue in the UserProfile table that can contain information for address, phone number, first name, last name, user name, city, etc... each record in this table is associated to a user by UserId, it is also associated to a ProfilePropertyDefinition which contains the definition for each of the properties (ie. PropertyName).
Through this relationship I can get all of the property values along with their property names. What I would like to do it to extract the data from these two columns (PropertyValue, PropertyName) and create a table similar to this:
First Name | Last Name | Email | Phone | City | Country
-------------------------------------------------------
| | | | |
So, I wanted to know if I can use a SQL statement to do this, here's my go at it:
SELECT FirstName = (SELECT PropertyValue FROM UserProfile WHERE PropertyDefinitionID = (SELECT PropertyDefinitionID WHERE PropertyName = 'first name')),
LastName = (SELECT PropertyValue FROM UserProfile WHERE PropertyDefinitionID = (SELECT PropertyDefinitionID WHERE PropertyName = 'last name')),
Email = (SELECT PropertyValue FROM UserProfile WHERE PropertyDefinitionID = (SELECT PropertyDefinitionID WHERE PropertyName = 'email'))
But that didn't work and something seems really weird about it... Anyone know how to take one column and display it's values in several different columns?
SELECT fn.PropertyValue FirstName,
ln.PropertyValue LastName,
etc...
From UserProfile fN
Join UserProfile lN
On fN.PropertyName = 'first name'
And ln.PropertyName = 'last name'
And fn.user = ln.user
Join UserProfile eM
On fN.PropertyName = 'first name'
And eM.PropertyName = 'email'
And eM.user = fn.user
(
Personally, I would stop right now and consider how bad this design will be for performance. This is in general a very poor technique to use to store this type of data. If you have 20 proerties you want to display you will have to join (And left join at that as you can't guarantee each property will be represented) to this table 20 times. Further, if this is central to your data structure (As it sounds like it is from the type of data you seem to be storing) virtually every query will need to do something simliar and performance will be atrocious. There are time when this is the best model (when you have no way of knowing in advance what properties will need to be stored), but most of the time, it's use is a sign of a bad design.
http://en.wikipedia.org/wiki/Entity-Attribute-Value_model
I guess you could do a select from the same table multiple times.
Let say tA is the name table with UserProfileID,PropertyDefinition and PropertyValue
You could do
select
t1.PropertyValue as FirstName,
t2.PropertyValue as LastName,
...
FROM
tA as t1, tA as t2, ....
WHERE
t1.PropertyDefinition Like 'FirstName' AND
t2.PropertyDefinition Like 'LastName' AND
....
AND
t1.UserId = #user AND
t2.UserID = #user ....
Not ideal, but it would work
You need to join the tables many times over (as many times as you have fields):
SELECT UPFN.PropertyValue AS FirstName, UPLN.PropertyValue AS LastName, ...
FROM UserProfile UPFN
INNER JOIN ProfilePropertyDefinition PPDFN ON PPDFN.PropertyDefinitionID = UPFN.PropertyDefinitionID AND PPDFN.PropertyName = 'first name'
INNER JOIN UserProfile UPLN ON UPLN.id = UPFN.id
INNER JOIN ProfilePropertyDefinition PPDLN ON PPDLN.PropertyDefinitionID = UPLN.PropertyDefinitionID AND PPDLN.PropertyName = 'last name'
...
Note that this relies on their being some ID field in the UserProfile that you can use to tie all the rows for the same user together.
Assuming schema like
UserProfile#
{userid,
ProfileName,
propertyValue
}
You would want to do
SELECT
FirstName.PropertyValue FirstNAme,
LastName.PropertyValue LastName,
FROM
users
JOIN (USERPROFILE) FirstName ON
FirstName.userid = users.userid
and PropertName ='FirstName'
JOIN (USERPROFILE) LastName ON
LastName.userid = users.userid
and PropertName ='LastName'
I would write the query like this:
Select
aa.userId,
Coalesce(Max(Case when PropertyName = 'First Name' then PropertyValue else '' end),'') as FirstName,
and so on
from
UserTable as aa
left join
UserProfile as bb
on
aa.UserId = bb.UserId
left join
ProfilePropertyDefinition as cc
on bb.PropertyDefinitionId = cc.PropertdefinitionId
group by
aa.UserId
I would need to know more about your table sturtures and what you are trying to achomplish but an option may be to create a SQL Scalar Function to retieve the values of the properties. I am making some assumptions on table names and database setup but try this on...
CREATE FUNCTION [dbo].[UserProperty]
(
#UserProfileID UNIQUEIDENTIFIER, #Property VARCHAR(200)
)
RETURNS VARCHAR(max)
AS
BEGIN
-- Declare the return variable here
DECLARE #Value AS VARCHAR(MAX)
SELECT #Value = PropertyValue FROM UserProfile up INNER JOIN PropertyDefinitions pd ON
up.PropertyDefinitionID = pd.PropertyDefinitionID
WHERE pd.PropertyName = #Property AND up.UserProfileID=#UserProfileID
RETURN ISNULL(#Value,'')
END
SELECT
[dbo].[UserProperty](UserProfileID, 'first name') AS [First Name],
[dbo].[UserProperty](UserProfileID, 'last name') AS [Last Name],
[dbo].[UserProperty](UserProfileID, 'email') AS [Email]
FROM
[Users]

Resources