Insert multiple rows after a check - sql-server

I have a use case where I have 200 tables. I need to get the latest record from all the 200 tables store them in staging table. Now using each staging record need to check if it is already existing
in Final table and status column for that record is open or closed.
Initial table:(generic schema for all 200 tables)
ID, timestamp, name
Staging Table:
ID, timestamp, name
Final Table:
ID, timestamp, name, status, count
My approach:
Ordering by timestamp and limit 1 will give latest record in each table
Union all those latest record from 200 tables( 200 select statements with union)
staging table will now have 200 records
check each record if it is already existing in Final table, if existing and status="open" need
to increment the increment the count, if status="closed" or didn't find any match it should be
inserted as new record in Final table
came across TSQL "IF NOT EXISTS () BEGIN END ELSE BEGIN END" and while loop (not sure how use in this case)
All this process happens every 15 mins.
Any better approach or suggestions and how can I handle the last step of checking and inserting each row.
I am new to SQL.
More Info:
Those initial tables are in hive, where 200 different process trying to write simultaneously into tables, So table lock will happen for each write and remaining process should wait, so I had each table for each process. there will not be 200 records in staging every time, I gave the worst case. ideally it will be of range 0 to 10 at any given point, but it has to check all the 200 tables every 15 mins. this staging table from hive is brought into sql server and pushed to Final table to server other purpose

Although it sounds very strange that you have 200 Tables all with the same scheme, the following MERGE-Statement should achieve what you want.
WITH STAGING_DATA ([ID], [TIMESTAMP], [NAME])
as
(
SELECT TOP 1 [ID], [TIMESTAMP], [NAME] FROM <TABLE_1> ORDER BY [TIMESTAMP] DESC
UNION ALL
SELECT TOP 1 [ID], [TIMESTAMP], [NAME] FROM <TABLE_2> ORDER BY [TIMESTAMP] DESC
UNION ALL
...
UNION ALL
SELECT TOP 1 [ID], [TIMESTAMP], [NAME] FROM <TABLE_N> ORDER BY [TIMESTAMP] DESC
)
MERGE INTO <FINAL_TABLE> AS TARGET
USING (
SELECT [ID], [TIMESTAMP], [NAME] FROM STAGING_DATA
)
AS SOURCE ([ID], [TIMESTAMP], [NAME])
ON TARGET.ID = SOURCE.ID AND TARGET.STATUS = 'OPEN'
WHEN MATCHED THEN
UPDATE SET [COUNT] = ISNULL([COUNT], 0) + 1
WHEN NOT MATCHED BY TARGET THEN
INSERT ([ID], [TIMESTAMP], [NAME], [STATUS], [COUNT]) VALUES ([ID], [TIMESTAMP], [NAME], 'OPEN', 0)
The STAGING_DATA CTE is collecting all the data (the top 1 datatset from each table ordered by timestamp) and the merge statement takes care of merging the result into your final table. The merge statement also checks if a dataset with the same ID and the Status 'OPEN' already exists, in which case it just updates the according dataset in the final table by incrementing the counter by 1. Should the dataset not be found (or have another status than 'OPEN') we add a new dataset to the final table.
ORDER BY with UNION ALL Statements:
The ORDER BY does work with the UNION ALL as long as they are within the CTE. At least when I tested it on SQL Server 2012, 2017 and 2019 with the following setup:
WITH STAGING_DATA ([ID], [TIMESTAMP], [NAME])
as
(
SELECT TOP 1 [ID], [TIMESTAMP], [NAME]
FROM (VALUES
('1', '2021-01-01 00:00:00.000', 'Käser'),
('74', '2021-01-01 00:00:00.000', 'Valérie Maier'),
('2', '2021-01-01 00:00:00.000', 'Jäggi'),
('84', '2021-01-01 00:00:00.000', 'D'),
('83', '2021-01-01 00:00:00.000', 'Wyss')
) as DATA ([ID], [TIMESTAMP], [NAME])
ORDER BY [ID] ASC
UNION ALL
SELECT TOP 1 [ID], [TIMESTAMP], [NAME]
FROM (VALUES
('1', '2021-01-01 00:00:00.000', 'Käser'),
('74', '2021-01-01 00:00:00.000', 'Valérie Maier'),
('2', '2021-01-01 00:00:00.000', 'Jäggi'),
('84', '2021-01-01 00:00:00.000', 'D'),
('83', '2021-01-01 00:00:00.000', 'Wyss')
) as DATA ([ID], [TIMESTAMP], [NAME])
ORDER BY [ID] DESC
UNION ALL
SELECT TOP 2 [ID], [TIMESTAMP], [NAME]
FROM (VALUES
('1', '2021-01-01 00:00:00.000', 'Käser'),
('74', '2021-01-01 00:00:00.000', 'Valérie Maier'),
('2', '2021-01-01 00:00:00.000', 'Jäggi'),
('84', '2021-01-01 00:00:00.000', 'D'),
('83', '2021-01-01 00:00:00.000', 'Wyss')
) as DATA ([ID], [TIMESTAMP], [NAME])
ORDER BY [ID] ASC
UNION ALL
SELECT TOP 2 [ID], [TIMESTAMP], [NAME]
FROM (VALUES
('1', '2021-01-01 00:00:00.000', 'Käser'),
('74', '2021-01-01 00:00:00.000', 'Valérie Maier'),
('2', '2021-01-01 00:00:00.000', 'Jäggi'),
('84', '2021-01-01 00:00:00.000', 'D'),
('83', '2021-01-01 00:00:00.000', 'Wyss')
) as DATA ([ID], [TIMESTAMP], [NAME])
ORDER BY [ID] DESC
)
SELECT [ID], [TIMESTAMP], [NAME] FROM STAGING_DATA

Your approach to insert into the staging table would work logically, unfortunately in SQL Server you cannot UNION queries that contain an ORDER BY, so the following WILL NOT WORK
SELECT TOP(1) ID,[timestamp], [name] FROM dbo.TblA ORDER BY timestamp
UNION ALL
SELECT TOP(1) ID,[timestamp], [name] FROM dbo.TblB ORDER BY timestamp
UNION ALL
SELECT TOP(1) ID,[timestamp], [name] FROM dbo.TblC ORDER BY timestamp
If you want to do the UNION, you have to put the ORDER BY in a subquery and then do the UNION. It looks like this:
--INSERT INTO dbo.Staging (ID, [timestamp], [name])
SELECT q1.ID, q1.[timestamp], q1.[name] FROM
(SELECT TOP(1) ID, [timestamp], [name] FROM dbo.TblA ORDER BY [timestamp] DESC) AS q1
UNION ALL
SELECT q2.ID, q2.[timestamp], q2.[name] FROM
(SELECT TOP(1) ID, [timestamp], [name] FROM dbo.TblB ORDER BY [timestamp] DESC) AS q2
UNION ALL
SELECT q3.ID, q3.[timestamp], q3.[name] FROM
(SELECT TOP(1) ID, [timestamp], [name] FROM dbo.TblC ORDER BY [timestamp] DESC) AS q3
It is very ugly for sure and I don't know if you would be better off with 200 separate INSERT statements, but let's just stick with this approach for now. So you can stage those records now:
INSERT INTO dbo.Staging (ID, [timestamp], [name])
SELECT q1.ID, q1.[timestamp], q1.[name] FROM
(SELECT TOP(1) ID, [timestamp], [name] FROM dbo.TblA ORDER BY [timestamp] DESC) AS q1
UNION ALL
SELECT q2.ID, q2.[timestamp], q2.[name] FROM
(SELECT TOP(1) ID, [timestamp], [name] FROM dbo.TblB ORDER BY [timestamp] DESC) AS q2
UNION ALL
SELECT q3.ID, q3.[timestamp], q3.[name] FROM
(SELECT TOP(1) ID, [timestamp], [name] FROM dbo.TblC ORDER BY [timestamp] DESC) AS q3
I assume you TRUNCATE the staging table before each run so it will only contain the records you are about to load into the final table. I myself prefer to use a combination of INNER JOINs and LEFT OUTER JOINs to find what doesn't exist and what already exists (makes debugging and development easier in my opinion, but others may disagree), but there is the MERGE approach (I will not show that here).
So to load the final table you can do something like:
-- increment existing open records
-- the INNER JOIN guarantees an existing record that matches ID
UPDATE final SET final.[count] = final.[count] + 1
FROM dbo.Staging AS stage
INNER JOIN dbo.Final AS final ON final.ID = stage.ID AND final.[status] = 'open';
-- add closed records
-- same comment about the INNER JOIN
INSERT INTO dbo.Final(ID, [timestamp], [name], [status], [count])
SELECT final.ID, final.[timestamp], final.[name], 'open', 1
FROM dbo.Staging AS stage
INNER JOIN dbo.Final AS final ON final.ID = stage.ID AND final.[status] = 'closed'
-- no match, insert these records
-- the LEFT OUTER JOIN with the WHERE clause guarantees no matching record
INSERT INTO dbo.Final(ID, [timestamp], [name], [status], [count])
SELECT stage.ID, stage.[timestamp], stage.[name], 'open', 1
FROM dbo.Staging AS stage
LEFT OUTER JOIN dbo.Final AS final ON final.ID = stage.ID
WHERE final.ID IS NULL;
I just matched on the ID value, but you can modify what is considered a match easily in the ON clause.

Related

T-SQL - Customer Linking

Please run the below code, these are all the same Customer because 2 of them have the same TaxNumber while another one matches one based on CompanyName. I need to link them all and set the ParentCompanyID based on who was created first. I am struggling to get them linked.
CREATE TABLE #Temp
(
CustomerID INT,
CustomerName VARCHAR(20),
CustomerTaxNumber INT,
CreatedDate DATE
)
INSERT INTO #Temp
VALUES (8, 'Company PTY',1234, '2019-09-20'),
(2, 'Company PT', 1234, '2019-09-24'),
(3, 'Company PTY',NULL, '2019-09-29')
SELECT * FROM #Temp
Below is the result that I require....
Any help will be appreciated.
Using case expression with first_value can give you the desired results:
SELECT CustomerID, CustomerName, CustomerTaxNumber, CreatedDate,
CASE WHEN CustomerTaxNumber IS NULL THEN
FIRST_VALUE(CustomerID) OVER(PARTITION BY CustomerName ORDER BY CreatedDate)
ELSE
FIRST_VALUE(CustomerID) OVER(PARTITION BY CustomerTaxNumber ORDER BY CreatedDate)
END As ParentCompanyID
FROM #Temp
Try this:
CREATE TABLE #Temp
(
CustomerID INT,
CustomerName VARCHAR(20),
CustomerTaxNumber INT,
CreatedDate DATE
)
INSERT INTO #Temp
VALUES (8, 'Company PTY',1234, '2019-09-20'),
(2, 'Company PT', 1234, '2019-09-24'),
(3, 'Company PTY',NULL, '2019-09-29')
SELECT DS.[CreatedDate] AS [FirstEntry]
,DS.[CustomerID] AS [ParentCompanyID]
,#Temp.*
FROM #Temp
CROSS APPLY
(
SELECT TOP 1 *
FROM #Temp
ORDER BY CreatedDate
) DS
DROP TABLE #Temp
You are condition is pretty simple - get the first record. If you need to group the records in some way, you can add additional filtering in the CROSS APPLY clause.

How can i handle Insert from select statement using trigger in sqlserver

I have the following TSQL statements to insert data from one database Table to another database tables, how can i mange this using Trigger?
the Source table is [DISCS] and the destination tables are,
[WIBOMH] is a Master table, [WIBOMH] is a header table, [WIBOMD] is detail table so
the orders have to be first insert into [WIITEM] then create a header [WIBOMH]
I want to fire this AFTER INSERT. when i added a new DISC into DISC Table i would like to fire the following insert statement
-- Insert into [WIITEM] Table---
INSERT INTO [WIITEM]
([itemId], [descr],[xdesc],[sales] ,[uOfM] ,[poUOfM] ,[uConvFact],[ref],[type],[status],[unitWgt] )
SELECT [itemId], [PURCHASE_DESCRIPTION], [SALES_DESCRIPTION], [sales] ,[uOfM] ,[poUOfM] ,[uConvFact],'TESTING', '2','0',[unitWgt]
FROM [DISCS]
WHERE [itemId] NOT IN (SELECT [itemId] FROM [WIITEM])
AND [makebuy]='Make';
-- Insert into [WIBOMH] Table---
DECLARE #d DATETIME = GETDATE();
INSERT INTO [WIBOMH]
([bomItem], [bomRev], [rollup], [mult], [autoBuild], [assyLead],[revCmnt],[author],[descr],[qPerLead],[lstMainDt],[revDate],[effStartDate],[ovride] )
SELECT DISTINCT [bomitem], 'B', '1', '1', '1', '3','TESTING','USER NAME','TESTING','0', FORMAT(#d, 'yyyy-MM-dd HH\:mm\:ss\.fff', 'en-US') AS 'Format#1',FORMAT(#d, 'yyyyMMdd' , 'en-US') AS 'Format#2',FORMAT(#d, 'yyyyMMdd' , 'en-US') AS 'Format#2','0'
FROM [DISCS]
WHERE [bomitem] IN (SELECT [ItemId] FROM [WIITEM] where type='2')
AND [bomitem] NOT IN (SELECT [bomItem] FROM [WIBOMH]);
-- Insert into [WIBOMD] Table---
INSERT INTO [WIBOMD]
([bomItem], [bomRev], [bomEntry], [partId], [qty],[cmnt],[srcLoc],[dType],[lead],[lineNbr])
SELECT [ItemID], 'B', [bomEntry], [partid], [qty],'TESTING','WSD-DS','0','0', [lineNbr]
FROM [DISCS]
WHERE [ItemID] IN (SELECT [ItemId] FROM [WIITEM] where type='2')
AND [ItemID] NOT IN (SELECT [bomItem] FROM [WIBOMD]);
i tried
INSERT INTO [MITESTCO].[WIITEM]
([itemId], [descr],[xdesc],[sales] ,[uOfM] ,[poUOfM] ,[uConvFact],[ref],[type],[status])
SELECT
[CALC STOCK PN], [PURCHASE DESCRIPTION], [SALES DESCRIPTION], [CALC STOCK PN] ,'EA' ,'EA' ,'1','SYNC FROM PDM', '2','0'--,[APPROX. WGT.]
FROM inserted
WHERE [MAKE / BUY]='Make' [CALC STOCK PN] NOT IN (SELECT [itemId] FROM [MIITEM] WHERE itemId NOT LIKE '*-CI');
but i get
invalid object [MITESTCO].[WIITEM]
here i got the first trigger working
create TRIGGER [dbo].[PNSETUP]
ON [dbo].[DISCS]
AFTER insert,UPDATE
AS
BEGIN
IF TRIGGER_NESTLEVEL() > 1
RETURN
SET ANSI_WARNINGS OFF;
INSERT INTO MITESTCO.dbo.WIITEM
([itemId], [descr],[xdesc],[sales] ,[uOfM] ,[poUOfM] ,[uConvFact],[ref],[type],[status])--,[unitWgt]
SELECT [CALC STOCK PN], [PURCHASE DESCRIPTION], [SALES DESCRIPTION], [CALC STOCK PN] ,'EA' ,'EA' ,'1','SYNC FROM PDM', '2','0'--,[APPROX. WGT.]
FROM [DISCS]
WHERE [CALC STOCK PN] NOT IN (SELECT [itemId] FROM MITESTCO.dbo.[WIITEM] WHERE itemId NOT LIKE '*-CI')
AND [MAKE / BUY]='Make';
SET ANSI_WARNINGS ON;
how can i add the next two insert select statement to same trigger
You can use after insert trigger to update the count after each insertion.
Do you need a trigger ? Do you need to copy the new rows from the table in real time ?
Triggers will KILL the insert performance of your table.
Consider adding a column DateLastCopied to the source table, and then once a minute
declare #now as datetime= getdate()
update sourcetable set DateLastCopied=#now where DateLastCopied is null
insert into targetTable(..)
select ...
FROM sourcetable where DateLastCopied=#now

Left join results in extra records

This is a basic left join problem and I have read many articles explaining what is going on but somehow the resolution is not clicking in my head. My left table has unique records. My right table has several records for each record in the left.
In the articles I have been reading this is often explained as left table has customers and right table has orders. That is very similar but not exactly what I am facing.
In my situation the left table has unique records and the right has repetitive data to be migrated into db the left table is in. So I am trying to write a query that will join on the key shared by both but I only need one record from the right. The results I am getting of course have multiple records since the single left matches multiple times on the right.
I am thinking I need to add some sort of filtering such as Top(1) but still reading / learning and wanted to get feedback / direction from the brainiacs on this list.
Here is a simple schema of what I am working with:
DECLARE #Customer TABLE
(
Id int,
Name varchar(50),
email varchar(50)
)
INSERT #Customer VALUES(1, 'Frodo', 'frodo#middleearth.org')
INSERT #Customer VALUES(2, 'Bilbo', 'Bilbo#middleearth.org')
INSERT #Customer VALUES(3, 'Galadriel', 'Galadriel#middleearth.org')
INSERT #Customer VALUES(4, 'Arwen', 'Arwen#middleearth.org')
INSERT #Customer VALUES(5, 'Gandalf', 'Gandalf#middleearth.org')
DECLARE #CustomerJobs TABLE
(
Id int,
email varchar(50),
jobname varchar(50)
)
INSERT #CustomerJobs VALUES(1, 'frodo#middleearth.org', 'RingBearer')
INSERT #CustomerJobs VALUES(2, 'frodo#middleearth.org', 'RingBearer')
INSERT #CustomerJobs VALUES(3, 'frodo#middleearth.org', 'RingBearer')
INSERT #CustomerJobs VALUES(4, 'frodo#middleearth.org', 'RingBearer')
INSERT #CustomerJobs VALUES(5, 'frodo#middleearth.org', 'RingBearer')
INSERT #CustomerJobs VALUES(6, 'Bilbo#middleearth.org', 'Burglar')
INSERT #CustomerJobs VALUES(7, 'Bilbo#middleearth.org', 'Burglar')
INSERT #CustomerJobs VALUES(8, 'Bilbo#middleearth.org', 'Burglar')
INSERT #CustomerJobs VALUES(9, 'Galadriel#middleearth.org', 'MindReader')
INSERT #CustomerJobs VALUES(10, 'Arwen#middleearth.org', 'Evenstar')
INSERT #CustomerJobs VALUES(10, 'Arwen#middleearth.org', 'Evenstar')
INSERT #CustomerJobs VALUES(11, 'Gandalf#middleearth.org', 'WhiteWizard')
INSERT #CustomerJobs VALUES(12, 'Gandalf#middleearth.org', 'WhiteWizard')
SELECT
Cust.Name,
Cust.email,
CJobs.jobname
FROM
#Customer Cust
LEFT JOIN #CustomerJobs CJobs ON
Cjobs.email = Cust.email
I'm toying with row_number over partition() as maybe I should be joining to a cte with the row_number over partition instead of the table itself???
One other constraint I have is I can't delete the duplicates from the right table.
So again my apologies for the simplistic question and thank you for the help.
Instead of using a left join, use an outer apply... you can then use the top clause to limit the rows returned...
select
Cust.Name
, Cust.email
, CJobs.jobname
from #Customer Cust
outer apply (
select top 1 *
from #CustomerJobs CJobs
where Cjobs.email = Cust.email
) cjobs;
You have to come up with some artificial method of reducing the second table to one row per email. For example:
SELECT
Cust.Name,
Cust.ID,
Cust.email,
CJobs.jobname
FROM
#Customer Cust
LEFT JOIN
(select min(id) as id,email, jobname
from
#CustomerJobs
group by email, jobname) as CJobs ON
Cjobs.email = Cust.email
But that's pretty much random. Is there a way to determine which row from your CustomerJobs table is the "right" one?
SELECT DISTINCT
Cust.Name,
Cust.email,
CJobs.jobname
FROM
#Customer Cust
LEFT JOIN #CustomerJobs CJobs ON
Cjobs.email = Cust.email
The additional of the DISTINCT keyword should get you what you want.
This will work:
SELECT
Cust.Name,
Cust.ID,
Cust.email,
CJobs.jobname
FROM #Customer Cust
LEFT JOIN
(SELECT DISTINCT email, jobname
FROM #CustomerJobs) C2 ON C2.email = C.email

How do I find records out of order - SQL?

Let's say I have a table with an ID Identity column, some data, and a datestamp. Like this:
1 data 5/1/2013 12:30
2 data 5/2/2013 15:32
3 data 5/2/2013 16:45
4 data 5/3/2013 9:32
5 data 5/5/2013 8:21
6 data 5/4/2013 9:36
7 data 5/6/2013 11:42
How do I write a query that will show me the one record that is timestamped 5/4? The table has millions of records. I've done some searching, but I don't know what to call what I'm searching for. :/
declare #t table(id int, bla char(4), timestamp datetime)
insert #t values
(1,'data','5/1/2013 12:30'),
(2,'data','5/2/2013 15:32'),
(3,'data','5/2/2013 16:45'),
(4,'data','5/3/2013 9:32'),
(5,'data','5/5/2013 8:21'),
(6,'data','5/4/2013 9:36'),
(7,'data','5/6/2013 11:42')
select timestamp
from
(
select rn1 = row_number() over (order by id),
rn2 = row_number() over (order by timestamp), timestamp
from #t
) a
where rn1 not in (rn2, rn2-1)
in 2008 r2, this would be a way
DECLARE #Table AS TABLE
(id INT , ladate DATETIME)
INSERT INTO #Table VALUES (1, '2013-05-01')
INSERT INTO #Table VALUES (2, '2013-05-02')
INSERT INTO #Table VALUES (3, '2013-05-03')
INSERT INTO #Table VALUES (4, '2013-05-05')
INSERT INTO #Table VALUES (5, '2013-05-04')
INSERT INTO #Table VALUES (6, '2013-05-06')
INSERT INTO #Table VALUES (7, '2013-05-07')
INSERT INTO #Table VALUES (8, '2013-05-08')
--I added the records in the sort order but if not just make sure you are sorted in the query
SELECT t2.ladate FROM #Table T1
INNER JOIN #Table T2 ON T1.Id = T2.Id + 1
INNER JOIN #Table t3 ON t2.id = t3.id + 1
WHERE t3.ladate < t2.ladate AND t2.ladate > t1.ladate
-- I made the assumption that your Id are all there, 1,2,3,4,5.... none missing... if there are rownumbers missing, you can use row_number()

sum COALESCE 0 instead of null

i cant add zero values instead of null, here is my sql:
SELECT
S.STOCK_ID,
S.PRODUCT_NAME,
SUM(COALESCE(AMOUNT,0)) AMOUNT,
DATEPART(MM,INVOICE_DATE) AY
FROM
#DSN3_ALIAS#.STOCKS S
LEFT OUTER JOIN DAILY_PRODUCT_SALES DPS ON S.STOCK_ID = DPS.PRODUCT_ID
WHERE
MONTH(INVOICE_DATE) >= #attributes.startdate# AND
MONTH(INVOICE_DATE) < #attributes.finishdate+1#
GROUP BY
DATEPART(MM,INVOICE_DATE),
S.STOCK_ID,
S.PRODUCT_NAME
ORDER BY
S.PRODUCT_NAME
and my table:
<cfoutput query="get_sales_total" group="stock_id">
<tr height="20" class="color-row">
<td>#product_name#</td>
<cfoutput group="ay"><td><cfif len(amount)>#amount#<cfelse>0</cfif></td></cfoutput>
</tr>
</cfoutput>
the result i want:
and the result i get:
thank you all for the help!
+ EDIT :
I have used the cross join technique, rewrote the sql:
SELECT
SUM(COALESCE(AMOUNT,0)) AMOUNT,S.STOCK_ID,S.PRODUCT_NAME,DPS.AY
FROM
#DSN3_ALIAS#.STOCKS S
CROSS JOIN (SELECT DISTINCT <cfif attributes.time_type eq 2>DATEPART(MM,INVOICE_DATE) AY<cfelse>DATEPART(DD,INVOICE_DATE) AY</cfif>
FROM DAILY_PRODUCT_SALES) DPS
LEFT OUTER JOIN DAILY_PRODUCT_SALES DP ON S.STOCK_ID = DP.PRODUCT_ID AND
<cfif attributes.time_type eq 2>DATEPART(MM,DP.INVOICE_DATE)<cfelse>DATEPART(DD,DP.INVOICE_DATE)</cfif> = DPS.AY
WHERE
<cfif attributes.time_type eq 2>
MONTH(INVOICE_DATE) >= #attributes.startdate# AND
MONTH(INVOICE_DATE) < #attributes.finishdate+1#
<cfelse>
MONTH(INVOICE_DATE) = #attributes.startdate#
</cfif>
<cfif len(trim(attributes.product_cat)) and len(attributes.product_code)>
AND S.STOCK_CODE LIKE '#attributes.product_code#%'
</cfif>
GROUP BY DPS.AY,S.STOCK_ID,S.PRODUCT_NAME
ORDER BY DPS.AY,S.STOCK_ID,S.PRODUCT_NAME
and the result is:
Use CASE instead
SUM(CASE WHEN A IS NULL THEN 0 ELSE A END)
You can do it in the database as Lasse suggested, or you can wrap each output value in a Val function, like so:
<cfoutput group="ay"><td>#Val(amount)#</td></cfoutput>
The Val function will convert any non-numeric value to 0.
Can you use ISNULL instead, ie;
SUM(ISNULL(AMOUNT,0)) AMOUNT,
?
EDIT: okay, given that the problem seems to be missing values rather than nulls as such. try something like this.
First, create a permanent reporting_framework table. This one is based on months and years but you could extend it into days if you wished.
create table reporting_framework
([month] smallint, [year] smallint);
go
declare #year smallint;
declare #month smallint;
set #year=2000;
while #year<2500
begin
set #month=1;
while #month<13
begin
insert into reporting_framework ([month], [year]) values (#month, #year);
set #month=#month+1;
end
set #year=#year+1;
end
select * from reporting_framework;
(this gives you 6000 rows, from 2000 to 2499 - adjust to taste!)
Now we'll make a table of parts and a table of orders
create table parts
([part_num] integer, [description] varchar(100));
go
insert into parts (part_num, [description]) values (100, 'Widget');
insert into parts (part_num, [description]) values (101, 'Sprocket');
insert into parts (part_num, [description]) values (102, 'Gizmo');
insert into parts (part_num, [description]) values (103, 'Foobar');
create table orders
([id] integer, part_num integer, cost numeric(10,2), orderdate datetime);
go
insert into orders ([id], part_num, cost, orderdate) values
(1, 100, 49.99, '2011-10-30');
insert into orders ([id], part_num, cost, orderdate) values
(2, 101, 109.99, '2011-10-31');
insert into orders ([id], part_num, cost, orderdate) values
(3, 100, 47.99, '2011-10-31');
insert into orders ([id], part_num, cost, orderdate) values
(4, 102, 429.99, '2011-11-01');
insert into orders ([id], part_num, cost, orderdate) values
(5, 101, 111.17, '2011-11-01');
insert into orders ([id], part_num, cost, orderdate) values
(6, 101, 111.17, '2011-11-01');
insert into orders ([id], part_num, cost, orderdate) values
(7, 103, 21.00, '2011-09-15');
Now this is the table you base your query on, eg;
select rf.month, rf.year, p.description, sum(isnull(o.cost,0))
from reporting_framework rf cross join parts p
full outer join orders o
on rf.year=year(o.orderdate) and rf.month=month(o.orderdate)
and p.part_num=o.part_num
where rf.year='2011'
group by p.description, rf.month, rf.year
order by rf.year, rf.month, p.description
Does this example help? There are probably loads of better ways of doing this (hello StackOverflow) but it might get you started thinking about what your problem is.
Not the CROSS JOIN to get all parts/dates combinations and then the FULL OUTER JOIN to get the orders into it.
The 'where' clause is just controlling your date range.

Resources