How to create a table using "With" clause in SQL - sql

I'm attempting to create a persistent table using the WITH clause however, I'm getting an error.
For context the answer that I currently find is
CREATE TABLE my_table
AS
WITH my_tables_data AS (
SELECT another_table.data1 AS some_value
FROM another_table
)
SELECT *
FROM some_data;
However, I am getting an error
Msg 319, Level 15, State 1, Line 5 Incorrect syntax near the keyword
'with'. If this statement is a common table expression, an
xmlnamespaces clause or a change tracking context clause, the previous
statement must be terminated with a semicolon.
My code is
CREATE TABLE SalesOrdersPerYear
WITH t1 AS (
-- Define the CTE expression name and column list.
WITH Sales_CTE (SalesPersonID, BaseSalary)
AS
-- Define the CTE query.
(
SELECT SALES_PERSON.SALES_PERSON_ID, SALES_PERSON.BASE_SALARY
FROM SALES_PERSON
WHERE SALES_PERSON_ID IS NOT NULL
)
-- Define the outer query referencing the CTE name.
SELECT SalesPersonID, BaseSalary AS TotalSales
FROM Sales_CTE
ORDER BY SalesPersonID, BaseSalary;
)
Would anyone be able to provide some guidance on this?
Many thanks in advance!

This is not valid syntax for sql server. you can either create a table using CREATE TABLE and specifying the column names and types, or you can do a SELECT INTO statement including data.
Approach 1 : Create the table and then populate:
CREATE TABLE SalesOrdersPerYear
( SalesPersonID int, BaseSalary float)
;
WITH Sales_CTE (SalesPersonID, BaseSalary)
AS
(
SELECT SALES_PERSON.SALES_PERSON_ID, SALES_PERSON.BASE_SALARY
FROM SALES_PERSON
WHERE SALES_PERSON_ID IS NOT NULL
)
insert into SalesOrdersPerYear
SELECT SalesPersonID, BaseSalary AS TotalSales
FROM Sales_CTE
ORDER BY SalesPersonID, BaseSalary;
Approach 2 - all in one step
WITH Sales_CTE (SalesPersonID, BaseSalary)
AS
(
SELECT SALES_PERSON.SALES_PERSON_ID, SALES_PERSON.BASE_SALARY
FROM SALES_PERSON
WHERE SALES_PERSON_ID IS NOT NULL
)
select SalesPersonID, BaseSalary AS TotalSales
into SalesOrdersPerYear
FROM Sales_CTE
ORDER BY SalesPersonID, BaseSalary;
Use approach 1 when you need to specify more about the table (primary keys, indexes, foregin keys etc.
Use approach 2 for things that are more temporary. (you would normally use a temporary table such as #SalesOrdersPerYear here).
Either way, the data is now stored in your table, and you can use it again.
Using temporary tables:
-- Check for existence and drop first to avoid errors if it already exists.
if OBJECT_ID('tempdb..#SalesOrdersPerYear') is not null
drop table #SalesOrdersPerYear
WITH Sales_CTE (SalesPersonID, BaseSalary)
AS
(
SELECT SALES_PERSON.SALES_PERSON_ID, SALES_PERSON.BASE_SALARY
FROM SALES_PERSON
WHERE SALES_PERSON_ID IS NOT NULL
)
select SalesPersonID, BaseSalary AS TotalSales
into #SalesOrdersPerYear
FROM Sales_CTE
ORDER BY SalesPersonID, BaseSalary;
You could also define it as a table variable, which is a bit of a cross between the approaches:
declare #SalesOrdersPerYear table
( SalesPersonID int, BaseSalary float)
;
WITH Sales_CTE (SalesPersonID, BaseSalary)
AS
(
SELECT SALES_PERSON.SALES_PERSON_ID, SALES_PERSON.BASE_SALARY
FROM SALES_PERSON
WHERE SALES_PERSON_ID IS NOT NULL
)
insert into #SalesOrdersPerYear
SELECT SalesPersonID, BaseSalary AS TotalSales
FROM Sales_CTE
ORDER BY SalesPersonID, BaseSalary;
This option will only persist with this batch, and does not need dropping - just like any other variable.

Creating a view using WITH and SELECT :
CREATE VIEW SCHEMA.DEMO
AS
WITH STAGING AS (
SELECT col1 as id,col2 as name from tbl1
UNION ALL
SELECT col1 as id,col3 as name from tbl2
)
SELECT distinct id,name
FROM STAGING;

CREATE VIEW AS my_view
AS
WITH my_tables_data AS (
SELECT another_table.data1 AS some_value
FROM another_table
)
SELECT *
FROM some_data;
CREATE TABLE my_table AS
SELECT * FROM my_view;

Related

Populate table variable with ordered results

I would like to populate table variable with results from CADEPA table. The only problem is that those results must be ordered.
The error I am receiving is:
The ORDER BY clause is invalid in views, inline functions, derived tables, subqueries, and common table expressions, unless TOP, OFFSET or FOR XML is also specified.
Query is:
DECLARE #DEPARTMENTS_TBL TABLE
(
DEPA_KEY INT
)
INSERT INTO #DEPARTMENTS_TBL(DEPA_KEY)
SELECT DEPA_KEY
FROM (
SELECT DISTINCT DEPA_KEY
FROM CADEPA
WHERE DEPA_STA = '1'
ORDER BY DEPA_NME
) P
Tables represent unordered sets. To do what you want, you need a key to represent the ordering:
DECLARE #DEPARTMENTS_TBL TABLE (
ID IDENTITY(1, 1) PRIMARY KEY
DEPA_KEY INT
);
INSERT INTO #DEPARTMENTS_TBL (DEPA_KEY)
SELECT DEPA_KEY
FROM CADEPA
WHERE DEPA_STA = '1'
GROUP BY DEPA_KEY
ORDER BY MAX(DEPA_NME);
I also think you should include DEPA_NME in the table, but the id column will keep the ordering.
Be sure you query with order by:
select d.*
from DEPARTMENTS_TBL d
order by id;
Move order by clause at the end after you table alias (P)
INSERT INTO #DEPARTMENTS_TBL(DEPA_KEY)
SELECT DISTINCT DEPA_KEY
FROM (
SELECT DEPA_KEY, DEPA_NME
FROM CADEPA
WHERE DEPA_STA = '1'
) P
ORDER BY DEPA_NME

Join Two Temp Table With Full Outer Join

I want join two temp tables with Full outer join but doesn't work properly
and allways just show #RMS values without #RMB !!
where and what's wrong in this code?
( #RMS without null )
create table #RMS
(
[Year] int,
[Month] int,
sTAccount bigint,
sRemaining bigint
)
insert into #RMS(Year,Month,sTAccount,sRemaining)
select
YEAR(Date) [Year],
DATEPART(MONTH,Date) [Month],
sum(TAccount) sTAccount,
sum(Remaining) sRemaining
from
SaleInvoices
group by YEAR(Date),DATEPART(MONTH,Date)
order by YEAR(Date),DATEPART(MONTH,Date)
( #RMB without null but sometimes #RMB Month Column Value and #RMS Month Column value is Different )
create table #RMB
(
[Year] int,
[Month] int,
bTAccount bigint,
bRemaining bigint
)
insert into #RMB(Year,Month,bTAccount,bRemaining)
select
YEAR(Date) [Year],
DATEPART(MONTH,Date) [Month],
sum(TAccount) bTAccount,
sum(Remaining) bRemaining
from
BuyInvoices
group by YEAR(Date),DATEPART(MONTH,Date)
order by YEAR(Date),DATEPART(MONTH,Date)
select * from #RMS
Full Outer Join #RMB
on #RMS.Year=#RMB.Year and #RMS.Month=#RMB.Month
group by #RMS.Year, #RMS.Month
order by #RMS.Year, #RMS.Month
Thanks For Your Answers
You have the wrong SELECT list. Replace * with #RMS.*, #RMB.* or (better) an explicit list of the fields you want, prefixing their names with the name of the table they come from. This also allows not to repeat the fields you've joined on.

Use column defined in a subquery

Sorry if the title is not clear, I'm a beginner and I didn't know exactly how to formule it...
I have this query working with Oracle :
SELECT
( SELECT COUNT(*)
FROM CATEGORY
) AS NBCATEGORIES,
( SELECT ROUND(AVG(FINANCIALOPERATIONBYPERSON),2)
FROM
(
SELECT SUM(AMOUNT) AS FINANCIALOPERATIONBYPERSON
FROM FINANCIALOPERATION
WHERE PERSONID IS NOT NULL
GROUP BY PERSONID
)
) AS AVERAGELOADAMOUNTBYPERSON
FROM DUAL
I'm looking for the equivalent for Sql Server...
The goal is to have multiple queries in a single query.
So I removed the "FROM DUAL" but I get an error on "FINANCIALOPERATIONBYPERSON" (Invalid column name), certainly because it's defined in the subquery...
How can I modify the query for SQL-Server ?
SQL Server requires aliases for subqueries. So, you can rewrite this as:
SELECT (SELECT COUNT(*)
FROM CATEGORY
) AS NBCATEGORIES,
(SELECT ROUND(AVG(FINANCIALOPERATIONBYPERSON),2)
FROM (SELECT SUM(AMOUNT) AS FINANCIALOPERATIONBYPERSON
FROM FINANCIALOPERATION
WHERE PERSONID IS NOT NULL
GROUP BY PERSONID
) t
) AS AVERAGELOADAMOUNTBYPERSON;
In both databases, though, I would be inclined to write this as:
SELECT c.NBCATEGORIES, ROUND(fo.AVERAGELOADAMOUNTBYPERSON, 2) AS AVERAGELOADAMOUNTBYPERSON
FROM (SELECT COUNT(*) as NBCATEGORIES
FROM CATEGORY c
) c CROSS JOIN
(SELECT SUM(AMOUNT) / COUNT(DISTINCT PERSONID) AS AVERAGELOADAMOUNTBYPERSON
FROM FINANCIALOPERATION fo
WHERE PERSONID IS NOT NULL
) fo;
One note for both these forms: SQL Server does integer arithmetic on integers. So, if AMOUNT is an integer, then you should convert it to an appropriate floating or fixed point numeric type.
You need to add a table alias for the subquery.
SELECT
( SELECT COUNT(*)
FROM CATEGORY
) AS NBCATEGORIES,
( SELECT ROUND(AVG(RESULTS.FINANCIALOPERATIONBYPERSON),2)
FROM
(
SELECT SUM(AMOUNT) AS FINANCIALOPERATIONBYPERSON
FROM FINANCIALOPERATION
WHERE PERSONID IS NOT NULL
GROUP BY PERSONID
) RESULTS
) AS AVERAGELOADAMOUNTBYPERSON

Select a Column in SQL not in Group By

I have been trying to find some info on how to select a non-aggregate column that is not contained in the Group By statement in SQL, but nothing I've found so far seems to answer my question. I have a table with three columns that I want from it. One is a create date, one is a ID that groups the records by a particular Claim ID, and the final is the PK. I want to find the record that has the max creation date in each group of claim IDs. I am selecting the MAX(creation date), and Claim ID (cpe.fmgcms_cpeclaimid), and grouping by the Claim ID. But I need the PK from these records (cpe.fmgcms_claimid), and if I try to add it to my select clause, I get an error. And I can't add it to my group by clause because then it will throw off my intended grouping. Does anyone know any workarounds for this? Here is a sample of my code:
Select MAX(cpe.createdon) As MaxDate, cpe.fmgcms_cpeclaimid
from Filteredfmgcms_claimpaymentestimate cpe
where cpe.createdon < 'reportstartdate'
group by cpe.fmgcms_cpeclaimid
This is the result I'd like to get:
Select MAX(cpe.createdon) As MaxDate, cpe.fmgcms_cpeclaimid, cpe.fmgcms_claimid
from Filteredfmgcms_claimpaymentestimate cpe
where cpe.createdon < 'reportstartdate'
group by cpe.fmgcms_cpeclaimid
The columns in the result set of a select query with group by clause must be:
an expression used as one of the group by criteria , or ...
an aggregate function , or ...
a literal value
So, you can't do what you want to do in a single, simple query. The first thing to do is state your problem statement in a clear way, something like:
I want to find the individual claim row bearing the most recent
creation date within each group in my claims table
Given
create table dbo.some_claims_table
(
claim_id int not null ,
group_id int not null ,
date_created datetime not null ,
constraint some_table_PK primary key ( claim_id ) ,
constraint some_table_AK01 unique ( group_id , claim_id ) ,
constraint some_Table_AK02 unique ( group_id , date_created ) ,
)
The first thing to do is identify the most recent creation date for each group:
select group_id ,
date_created = max( date_created )
from dbo.claims_table
group by group_id
That gives you the selection criteria you need (1 row per group, with 2 columns: group_id and the highwater created date) to fullfill the 1st part of the requirement (selecting the individual row from each group. That needs to be a virtual table in your final select query:
select *
from dbo.claims_table t
join ( select group_id ,
date_created = max( date_created )
from dbo.claims_table
group by group_id
) x on x.group_id = t.group_id
and x.date_created = t.date_created
If the table is not unique by date_created within group_id (AK02), you you can get duplicate rows for a given group.
You can do this with PARTITION and RANK:
select * from
(
select MyPK, fmgcms_cpeclaimid, createdon,
Rank() over (Partition BY fmgcms_cpeclaimid order by createdon DESC) as Rank
from Filteredfmgcms_claimpaymentestimate
where createdon < 'reportstartdate'
) tmp
where Rank = 1
The direct answer is that you can't. You must select either an aggregate or something that you are grouping by.
So, you need an alternative approach.
1). Take you current query and join the base data back on it
SELECT
cpe.*
FROM
Filteredfmgcms_claimpaymentestimate cpe
INNER JOIN
(yourQuery) AS lookup
ON lookup.MaxData = cpe.createdOn
AND lookup.fmgcms_cpeclaimid = cpe.fmgcms_cpeclaimid
2). Use a CTE to do it all in one go...
WITH
sequenced_data AS
(
SELECT
*,
ROW_NUMBER() OVER (PARITION BY fmgcms_cpeclaimid ORDER BY CreatedOn DESC) AS sequence_id
FROM
Filteredfmgcms_claimpaymentestimate
WHERE
createdon < 'reportstartdate'
)
SELECT
*
FROM
sequenced_data
WHERE
sequence_id = 1
NOTE: Using ROW_NUMBER() will ensure just one record per fmgcms_cpeclaimid. Even if multiple records are tied with the exact same createdon value. If you can have ties, and want all records with the same createdon value, use RANK() instead.
You can join the table on itself to get the PK:
Select cpe1.PK, cpe2.MaxDate, cpe1.fmgcms_cpeclaimid
from Filteredfmgcms_claimpaymentestimate cpe1
INNER JOIN
(
select MAX(createdon) As MaxDate, fmgcms_cpeclaimid
from Filteredfmgcms_claimpaymentestimate
group by fmgcms_cpeclaimid
) cpe2
on cpe1.fmgcms_cpeclaimid = cpe2.fmgcms_cpeclaimid
and cpe1.createdon = cpe2.MaxDate
where cpe1.createdon < 'reportstartdate'
Thing I like to do is to wrap addition columns in aggregate function, like max().
It works very good when you don't expect duplicate values.
Select MAX(cpe.createdon) As MaxDate, cpe.fmgcms_cpeclaimid, MAX(cpe.fmgcms_claimid) As fmgcms_claimid
from Filteredfmgcms_claimpaymentestimate cpe
where cpe.createdon < 'reportstartdate'
group by cpe.fmgcms_cpeclaimid
What you are asking, Sir, is as the answer of RedFilter.
This answer as well helps in understanding why group by is somehow a simpler version or partition over:
SQL Server: Difference between PARTITION BY and GROUP BY
since it changes the way the returned value is calculated and therefore you could (somehow) return columns group by can not return.
You can use as below,
Select X.a, X.b, Y.c from (
Select X.a as a, sum (b) as sum_b from name_table X
group by X.a)X
left join from name_table Y on Y.a = X.a
Example;
CREATE TABLE #products (
product_name VARCHAR(MAX),
code varchar(3),
list_price [numeric](8, 2) NOT NULL
);
INSERT INTO #products VALUES ('paku', 'ACE', 2000)
INSERT INTO #products VALUES ('paku', 'ACE', 2000)
INSERT INTO #products VALUES ('Dinding', 'ADE', 2000)
INSERT INTO #products VALUES ('Kaca', 'AKB', 2000)
INSERT INTO #products VALUES ('paku', 'ACE', 2000)
--SELECT * FROM #products
SELECT distinct x.code, x.SUM_PRICE, product_name FROM (SELECT code, SUM(list_price) as SUM_PRICE From #products
group by code)x
left join #products y on y.code=x.code
DROP TABLE #products

SQL alias for SELECT statement

I would like to do something like
(SELECT ... FROM ...) AS my_select
WHERE id IN (SELECT MAX(id) FROM my_select GROUP BY name)
Is it possible to somehow do the "AS my_select" part (i.e. assign an alias to a SELECT statement)?
(Note: This is a theoretical question. I realize that I can do it without assign an alias to a SELECT statement, but I would like to know whether I can do it with that.)
Not sure exactly what you try to denote with that syntax, but in almost all RDBMS-es you can use a subquery in the FROM clause (sometimes called an "inline-view"):
SELECT..
FROM (
SELECT ...
FROM ...
) my_select
WHERE ...
In advanced "enterprise" RDBMS-es (like oracle, SQL Server, postgresql) you can use common table expressions which allows you to refer to a query by name and reuse it even multiple times:
-- Define the CTE expression name and column list.
WITH Sales_CTE (SalesPersonID, SalesOrderID, SalesYear)
AS
-- Define the CTE query.
(
SELECT SalesPersonID, SalesOrderID, YEAR(OrderDate) AS SalesYear
FROM Sales.SalesOrderHeader
WHERE SalesPersonID IS NOT NULL
)
-- Define the outer query referencing the CTE name.
SELECT SalesPersonID, COUNT(SalesOrderID) AS TotalSales, SalesYear
FROM Sales_CTE
GROUP BY SalesYear, SalesPersonID
ORDER BY SalesPersonID, SalesYear;
(example from http://msdn.microsoft.com/en-us/library/ms190766(v=sql.105).aspx)
You can do this using the WITH clause of the SELECT statement:
;
WITH my_select As (SELECT ... FROM ...)
SELECT * FROM foo
WHERE id IN (SELECT MAX(id) FROM my_select GROUP BY name)
That's the ANSI/ISO SQL Syntax. I know that SQL Server, Oracle and DB2 support it. Not sure about the others...
Yes, but you can select only one column in your subselect
SELECT (SELECT id FROM bla) AS my_select FROM bla2
You could store this into a temporary table.
So instead of doing the CTE/sub query you would use a temp table.
Good article on these here http://codingsight.com/introduction-to-temporary-tables-in-sql-server/