Insert into SELECT with composite primary key failing SQL Server 2008 - sql

I have one very large, un-normalized table which I am in the process of fixing. From that large table I'm normalizing the data. I used the SQL statement
INSERT INTO smallTable(patientID, admissionDate, dischargeDate)
select distinct patientID, admissionDate, dischargeDate
FROM largeTable
So my smallTable is populated with the correct number of rows. There's another column, drgCode that I want to add to my smallTable. I tried the following query to do that
INSERT INTO smallTable(drgCode)
select drgCode from
(
SELECT DISTINCT patientID, admissionDate, dischargeDate, drgCode from largeTable) as t
The error I was given reads cannot insert the value NULL into patientID, column does not alloq nulls, insert fails.
The only way that the drgCode will be chosen correctly is if some variant of the select distinct query is used. How can I insert only one field, when the other fields must be included to narrow down the search.
I know I could do this if I emptied out my smallTable, but I figured there's gotta be a way around it.

with drg as (SELECT DISTINCT patientID, admissionDate, dischargeDate, drgCode from largeTable)
update s
set s.drgCode = l.drgCode
from smallTable s join drg l on
s.patientId = l.patientId and
s.admissionDate = l.admissionDate and
s.dischargeDate = l.dischargeDate

As per my understanding, if you have "PatientID" to be unique in both the tables, you can do something like below.
Update S
SET S.drgCode = L.drgCode
FROM
SmallTable S
INNER JOIN
LargeTable T
ON S.PatientID = T.PatientID
Hope this Helps!!

When you perform an insert to a table, any values not specified in the query are poulated with the default value for the column. If there is no default value on the column, NULL will be used. You recieved that particular error message because your column does not allow NULL and does not have a default.
Given your reply to Praveen, perhaps you should be further normalizing and put the drgCodes into a separate table.

Related

Is the GROUP BY needed to insert

INSERT INTO NEWTABLE
(Street,
Number,
NuDate,
XValue)
SELECT
a1.Street,
a2.Number,
a2.NuDate,
a2.XValue
FROM
ABC.dbo.Faculty a1 INNER JOIN
ABC.dbo.Faculty2 a2
ON a1.NameID = a2.NameID
WHERE
a1.Bologna = 'True'
GROUP BY
a1.Street,
a2.Number,
a2.NuDate,
a2.XValue
In this completely fictitious SQL statement, is the GROUP by needed to insert properly into NEWTABLE? and/or does the group by need to match up perfectly with the INSERT INTO for this statement to work properly?
EDIT: Sorry, I realized I had the wrong values for the GROUP BY statement, they're supposed to match the INSERT INTO
In this completely fictitious SQL statement, is the GROUP by needed to insert properly into NEWTABLE?
It's not necessary if you don't mind duplicates
If you don't want duplicate rows then yes, you'll need to use GROUP BY (or DISTINCT):
SELECT DISTINCT
a1.Street,
a2.Number,
a2.NuDate,
a2.XValue
does the group by need to match up perfectly with the INSERT INTO for this statement to work properly?
Yes, the selected columns must match.
There are cases when what you group by doesn't match what you select but that's when you aggregating:
SELECT
column1, -- no aggregation, must match
sum(column2) -- aggregation, so does not need to match
FROM a
GROUP BY column1

alternatives to using IN clause

I am running the below query:
SELECT
ReceiptVoucherId,
VoucherId,
ReceiptId,
rvtransactionAmount,
AmountUsed,
TransactionTypeId
FROM
[Scratch].[dbo].[LoyaltyVoucherTransactionDetails]
WHERE
VoucherId IN
(2000723,
2000738,
2000774,
2000873,
2000888,
2000924,
2001023,
2001038,
2001074,
2001173)
the aim being to extract the ReceiptVoucherId / VoucherId / ReceiptId / rvtransactionAmount / AmountUsed / TransactionTypeId data for the list of voucherId's that I have.
My problem here is that my list of VoucherID's is 187k long so an IN clause is not possible as it returns the error:
Internal error: An expression services limit has been reached
Can anyone advise on a alternative to doing it this way?
I am using SSMS 2014
You can try the approach:
select from mytable where id in (select id from othertable)
or left join:
select from othertable left join mytable using id
not sure what has better performance, also second query could give you empty rows if it is not declared as foreign key.
fly-by-post, feel free to improve it.
Just create a table containing all this Vouchers (Hopefully you already have one) and then use IN() selecting from the table :
SELECT
ReceiptVoucherId,
VoucherId,
ReceiptId,
rvtransactionAmount,
AmountUsed,
TransactionTypeId
FROM
[Scratch].[dbo].[LoyaltyVoucherTransactionDetails]
WHERE
VoucherId IN (SELECT VoucherId FROM VourchersTable)
insert the vouchers to lookup in a seperate table . lets call it Voucher.
Then this query should do the trick. It does not use the IN Clause. but instead it uses Inner join which will be faster.
SELECT
L.ReceiptVoucherId,
L.VoucherId,
L.ReceiptId,
L.rvtransactionAmount,
L.AmountUsed,
L.TransactionTypeId
FROM
[Scratch].[dbo].[LoyaltyVoucherTransactionDetails] L
INNER JOIN dbo.Vouchers V ON L.VoucherId = V.VoucherId
Maybe the following works for you:
First of all, declare a variable of type table (or alternatively a temp table) and insert your IDs into it.
Modify your Query to
WHERE VoucherID in (SELECT VoucherID FROM #t)
Alternatively (but similar write-intensive for your Hands ;-) ) is the creation of a CTE:
WITH cte AS (SELECT 2000723 UNION ALL SELECT ...)
and again the redesign of your "WHERE... IN..." section.

SQL Trigger to record UPDATE into Audit Table

I have a table with columns businessname, sortcode and accountnumber all populated, name, nationality and DOB all currently unpopulated. I need to create a trigger to shoot every update to an audit table when any of the null fields are updated so if I change just the name I'll get a timestamp, userid, the field changed, the old value and the new value.. If I changed all 3 null fields I'd like to send 3 rows to the audit table.
Can someone give me a pointer on the logic of this please?
In a very rudimentary format for testing I've got
CREATE TRIGGER RM_UPDATE_TRIGGER ON RM_BASE
ON UPDATE
AS
INSERT INTO RM_AUDITLOG
SELECT CURRENT_TIMESTAMP, SRT_CD
FROM RM_BASE
but this is sending all the current rows across after an UPDATE to any of them, I only want the row that has been affected. I'm not sure if I should be building more tables to join together to get the final answer or using the INSERT/DELETE tables.. I've seen an audit table in this format in previous roles so I know it works but can't figure it out!
Thanks
Yes, you need to use the INSERTED and/or DELETED pseudo-tables as those contain only the rows that have been modified. As in:
CREATE TRIGGER RM_UPDATE_TRIGGER ON RM_BASE
ON UPDATE
AS
INSERT INTO RM_AUDITLOG
SELECT CURRENT_TIMESTAMP, SRT_CD
FROM INSERTED
The INSERTED table has the "new" or "current" version of each row while the DELETED table has the "old" version that has been replaced via the UPDATE operation. This is the case for all versions of SQL Server, at least going back as far as SQL Server 2000.
In order to track the change itself (both "old" and "new" values), then you need to JOIN those two pseudo-tables, as in:
INSERT INTO RM_AUDITLOG
SELECT CURRENT_TIMESTAMP, ins.SRT_CD AS [SRT_CD_new], del.SRT_CD AS [SRT_CD_old]
FROM INSERTED ins
INNER JOIN DELETED del
ON del.PKfield = ins.PKfield
This is the basic operation for capturing changes (unless, of course, you use Change Data Capture) in a DML trigger.
If you want to unpivot this data such that each set of "old" and "new" columns becomes a row, that should be easily adaptable from the above. In that case, you could also add WHERE ISNULL(ins.column, '~~~~') <> ISNULL(del.column, '~~~~') COLLATE Latin1_General_BIN to avoid capturing fields that have not changed. The COLLATE ensures case-sensitive / accent-sensitive / etc comparisons.
Of course, unpivoting makes it really hard to reconstruct the entire row as you are then required to keep the full history forever. You would need to start with the base values for all fields and apply the changes incrementally. The typical audit scenario is to just to capture the row that has fields for both old and new for each source field (like I have already shown). If your audit table looks more like:
PKfield, DateModified, businessname_old, businessname_new, sortcode_old, sortcode_new
then you can write a query to identify which fields actually changed by comparing each set (given that more than 1 field can change in the same UPDATE operation), something like:
SELECT PKfield,
DateModified,
CASE
WHEN ISNULL(businessname_old, '~~~~') <> ISNULL(businessname_new, '~~~~')
COLLATE Latin1_General_BIN THEN 'BusinessName ' ELSE ''
END +
CASE
WHEN ISNULL(sortcode_old, '~~~~') <> ISNULL(sortcode_new, '~~~~')
COLLATE Latin1_General_BIN THEN 'SortCode ' ELSE ''
END AS [FieldsChanged]
FROM AuditTable
ORDER BY DateModified DESC;
BUT, if you really want to unpivot the data to have one row per actual changed field, then the following structure should work:
;WITH ins AS
(
SELECT PKfield, FieldName, Value
FROM (
SELECT PKfield, businessname, sortcode, accountnumber, name,
nationality, DOB
FROM INSERTED
) cols
UNPIVOT (Value FOR FieldName IN
(businessname, sortcode, accountnumber, name, nationality, DOB)
) colvals
), del AS
(
SELECT PKfield, FieldName, Value
FROM (
SELECT PKfield, businessname, sortcode, accountnumber, name,
nationality, DOB
FROM DELETED
) cols
UNPIVOT (Value FOR FieldName IN
(businessname, sortcode, accountnumber, name, nationality, DOB)
) colvals
)
INSERT INTO AuditTable (PKfield, DateModified, FieldName, OldValue, NewValue)
SELECT ins.PKfield, CURRENT_TIMESTAMP, ins.FieldName, del.Value, ins.Value
FROM ins
INNER JOIN del
ON del.PKfield = ins.PKfield
AND del.FieldName = ins.FieldName
WHERE ISNULL(del.Value, '~~~~') <>
ISNULL(ins.Value, '~~~~') COLLATE Latin1_General_BIN;
You might need to add a CONVERT(VARCHAR(1000), field) to the WHERE condition if DOB is a DATE or DATETIME field, or if SRT_CD is an INT or other type of number field:
WHERE ISNULL(CONVERT(VARCHAR(1000), del.Value), '~~~~') <>
ISNULL(CONVERT(VARCHAR(1000), ins.Value), '~~~~')
COLLATE Latin1_General_BIN;

The multi-part identifier "field" could not be bound

I'm trying to use data stored in a temporary result set (SOURCE in the code) to fill another table with SQL Server 2012. When executing the below code I get the error "The multi-part identifier "SOURCE.JnlDetoaId" could not be bound".
SELECT Journaldet.*, Agency.ID_Agency INTO SOURCE
FROM Journaldet
inner join Agency
ON Agency.Agency_ID = Journaldet.AgenceId
IF ((SELECT COUNT(Journal.Journal_ID) FROM dbo.Journal, SOURCE WHERE Journal_ID = SOURCE.JournalId)=0)
INSERT INTO Discarded.JournalDet(JournalDet_ID, Amount, Sensoa, DetoaId, ID_Agency, JournalId, Appli_Source, ReasonDiscarded, DateDiscarded)
VALUES (SOURCE.JnlDetoaId, SOURCE.Amount, SOURCE.Sensoa, SOURCE.DetoaId, SOURCE.ID_Agency, JournalId, 'GameApps','Member not yet inserted', GETDATE());
I read some threads about here but didn't see how to apply them to my case.
Any help please?
SELECT Journaldet.*, Agency.ID_Agency INTO sourceTable
FROM Journaldet
inner join Agency
ON Agency.Agency_ID = Journaldet.AgenceId;
IF ((SELECT COUNT(j.Journal_ID) FROM dbo.Journal as j, sourceTable s WHERE j.Journal_ID = s.JournalId) = 0)
INSERT INTO Discarded.JournalDet(JournalDet_ID, Amount, Sensoa, DetoaId,ID_Agency, JournalId, Appli_Source, ReasonDiscarded, DateDiscarded)
VALUES (select JnlDetoaId, Amount, Sensoa, DetoaId, ID_Agency, JournalId, 'GameApps','Member not yet inserted', GETDATE() FROM sourceTable)
The problem was in you insert () values().
To insert values into your Discarded.JournalDet table. You cannot just use the above fields from source table. You have to select from the source table.
U cannot just user the source.JournalDet .. and soo on , directly only because they are defined few line above.
Below is how I solved my issue. The SOURCE was not seen in the INSERT as a result set as I wanted. It was nothing for the INSERT. I just rewrote the queries in such a way that the result set be seen in the INSERT. Thanks a lot user2919277.
INSERT INTO Discarded.JournalDet
(JournalDet_ID, Amount, Sensoa, DetoaId, ID_Agency, JournalId, Appli_Source, ReasonDiscarded, DateDiscarded)
SELECT SOURCE1.JnlDetoaId, Amount,Sensoa,DetoaId,ID_Agency,JournalId, 'GameApps', 'Member not yet inserted', GETDATE()
FROM Journaldet AS SOURCE1
inner join Agency AS SOURCE2 ON SOURCE2.Agency_ID = SOURCE1.AgenceId
WHERE ((SELECT COUNT(Journal.Journal_ID) FROM dbo.Journal WHERE dbo.Journal.Journal_ID = SOURCE1.JournalId)=0)

SQL Query to return rows even if it is not present in the table

This is a specific problem .
I have an excel sheet containing data. Similar data is present in a relational database table. Some rows may be absent or some additional rows may be present. The goal is to verify the data in the excel sheet with the data in the table.
I have the following query
Select e_no, start_dt,end_dt
From MY_TABLE
Where e_no In
(20231, 457)
In this case, e_no 457 is not present in the database (and hence not returned). But I want my query to return a row even if it not present (457 , null , null). How do I do that ?
For Sql-Server: Use a temporary table or table type variable and left join MY_TABLE with it
Sql-Server fiddle demo
Declare #Temp Table (e_no int)
Insert into #Temp
Values (20231), (457)
Select t.e_no, m.start_dt, m.end_dt
From #temp t left join MY_TABLE m on t.e_no = m.e_no
If your passing values are a csv list, then use a split function to get the values inserted to #Temp.
Why not simply populate a temporary table in the database from your spreadsheet and join against that? Any other solution is probably going to be both more work and more difficult to maintain.
You can also do it this way with a UNION
Select
e_no, start_dt ,end_dt
From MY_TABLE
Where e_no In (20231, 457)
UNION
Select 457, null, null