I am aware that Snowflake is very similar to Oracle. I am doing a job that requires me to move some SQL Server code over into snowflake , however I can't seem to get these date functions converted over. In SSMS this SELECT DATEADD(YEAR,DATEDIFF(YEAR,0,'1912-01-01'),0) statement would return 1912-01-01 00:00:000. since the 0 indicates "The beginning of time" what would be Snowflakes equivalent to this 0 be and better yet how would you convert this to say Oracle code?
Thank you ahead of time!
Sharing few examples from Snowflake -
select date_trunc(year,'1912-01-05'::date);
+-------------------------------------+
| DATE_TRUNC(YEAR,'1912-01-05'::DATE) |
|-------------------------------------|
| 1912-01-01 |
+-------------------------------------+
select date_trunc(year,'1912-10-05'::date);
+-------------------------------------+
| DATE_TRUNC(YEAR,'1912-10-05'::DATE) |
|-------------------------------------|
| 1912-01-01 |
+-------------------------------------+
select date_trunc(year,'1912-12-01 10:00:000'::timestamp);
+----------------------------------------------------+
| DATE_TRUNC(YEAR,'1912-12-01 10:00:000'::TIMESTAMP) |
|----------------------------------------------------|
| 1912-01-01 00:00:00.000 |
+----------------------------------------------------+
I am familiar with the method you are talking about. It was a workaround to make up for the fact that SQL Server did not have a function to truncate date/time.
In Snowflake, just use DATE_TRUNC( <date_or_time_part>, <date_or_time_expr> )
https://docs.snowflake.com/en/sql-reference/functions/date_trunc.html
select to_date('2015-05-08T23:39:20.123-07:00') as "DATE1",
date_trunc('YEAR', "DATE1") as "TRUNCATED TO YEAR",
date_trunc('MONTH', "DATE1") as "TRUNCATED TO MONTH",
date_trunc('DAY', "DATE1") as "TRUNCATED TO DAY";
+------------+-------------------+--------------------+------------------+
| DATE1 | TRUNCATED TO YEAR | TRUNCATED TO MONTH | TRUNCATED TO DAY |
|------------+-------------------+--------------------+------------------|
| 2015-05-08 | 2015-01-01 | 2015-05-01 | 2015-05-08 |
+------------+-------------------+--------------------+------------------+
select to_timestamp('2015-05-08T23:39:20.123-07:00') as "TIMESTAMP1",
date_trunc('HOUR', "TIMESTAMP1") as "TRUNCATED TO HOUR",
date_trunc('MINUTE', "TIMESTAMP1") as "TRUNCATED TO MINUTE",
date_trunc('SECOND', "TIMESTAMP1") as "TRUNCATED TO SECOND";
+-------------------------+-------------------------+-------------------------+-------------------------+
| TIMESTAMP1 | TRUNCATED TO HOUR | TRUNCATED TO MINUTE | TRUNCATED TO SECOND |
|-------------------------+-------------------------+-------------------------+-------------------------|
| 2015-05-08 23:39:20.123 | 2015-05-08 23:00:00.000 | 2015-05-08 23:39:00.000 | 2015-05-08 23:39:20.000 |
+-------------------------+-------------------------+-------------------------+-------------------------+
Snowflake and Oracle both have a date truncate function
Snowflake:
DATE_TRUNC has many supported periods it can truncate to:
SELECT '1921-12-23'::date as d
,DATE_TRUNC('year', d) as d_trunc_to_year;
gives:
D
D_TRUNC_TO_YEAR
1921-12-23
1921-01-01
Oracle:
Oracle has TRUNC
Which there example looks to solve a rather similar way:
SELECT TRUNC(TO_DATE('27-OCT-92','DD-MON-YY'), 'YEAR')
"New Year" FROM DUAL;
New Year
---------
01-JAN-92
Related
I have an interesting conundrum and I am using SQL Server 2012 or SQL Server 2016 (T-SQL obviously). I have a list of products, each with their own UPC code. These products have a discontinue date and the UPC code gets recycled to a new product after the discontinue date. So let's say I have the following in the Item_UPCs table:
Item Key | Item Desc | UPC | UPC Discontinue Date
123456 | Shovel | 0009595959 | 2018-04-01
123456 | Shovel | 0007878787 | NULL
234567 | Rake | 0009595959 | NULL
As you can see, I have a UPC that gets recycled to a new product. Unfortunately, I don't have an effective date for the item UPC table, but I do in an items table for when an item was added to the system. But let's ignore that.
Here's what I want to do:
For every inventory record up to the discontinue date, show the unique UPC associated with that date. An inventory record consists of the "Inventory Date", the "Purchase Cost", the "Purchase Quantity", the "Item Description", and the "Item UPC".
Once the discontinue date is over with (e.g.: it's the next day), start showing only the UPC that is in effect.
Make sure that no duplicate data exists and the UPCs are truly being "attached" to each row per whatever the date is in the query.
Here is an example of the inventory details table:
Inv_Key | Trans_Date | Item_Key | Purch_Qty | Purch_Cost
123 | 2018-05-12 | 123456 | 12.00 | 24.00
108 | 2018-03-22 | 123456 | 8.00 | 16.00
167 | 2018-07-03 | 234567 | 12.00 | 12.00
An example query:
SELECT DISTINCT
s.SiteID
,id.Item_Key
,iu.Item_Desc
,iu.Item_Department
,iu.Item_Category
,iu.Item_Subcategory
,iu.UPC
,iu.UPC_Discontinue_Date
,id.Trans_Date
,id.Purch_Cost
,id.Purch_Qty
FROM Inventory_Details id
INNER JOIN Item_UPCs iu ON iu.Item_Key = id.Item_Key
INNER JOIN Sites s ON s.Site_Key = id.Site_Key
The real query I have is far too long to post here. It has three CTEs and the resultant query. This is simply a mockup. Here is an example result set:
Site_ID | Item_Key | Item_Desc | Item_Department | Item_Category | UPC | UPC_Discontinue Date | Trans_Date | Purch_Cost | Purch_Qty
2457 | 123456 | Shovel | Digging Tools | Shovels | 0009595959 | 2018-04-01 | 2018-03-22 | 16.00 | 8.00
2457 | 123456 | Shovel | Digging Tools | Shovels | 0007878787 | NULL | 2018-03-22 | 16.00 | 8.00
2457 | 234567 | Rakes | Garden Tools | Rakes | 0009595959 | NULL | 2018-07-03 | 12.00 | 12.00
2457 | 123456 | Shovel | Digging Tools | Shovels | 0007878787 | NULL | 2018-05-12 | 24.00 | 12.00
Do any of you know how I can "assign" a UPC to a specific range of dates in my query and then "assign" an updated UPC to the item for every effective date thereafter?
Many thanks!
Given your current Item_UPC table, you can generate effective start dates from the Discontinue Date using the LAG analytic function:
With Effective_UPCs as (
select [Item_Key]
, [Item_Desc]
, [UPC]
, coalesce(lag([UPC_Discontinue_Date])
over (partition by [Item_Key]
order by coalesce( [UPC_Discontinue_Date]
, datefromparts(9999,12,31))
),
lag([UPC_Discontinue_Date])
over (partition by [UPC]
order by coalesce( [UPC_Discontinue_Date]
, datefromparts(9999,12,31))
)) [UPC_Start_Date]
, [UPC_Discontinue_Date]
from Item_UPCs i
)
select * from Effective_UPCs;
Which yields the following Results:
| Item_Key | Item_Desc | UPC | UPC_Start_Date | UPC_Discontinue_Date |
|----------|-----------|------------|----------------|----------------------|
| 123456 | Shovel | 0007878787 | 2018-04-01 | (null) |
| 123456 | Shovel | 0009595959 | (null) | 2018-04-01 |
| 234567 | Rake | 0009595959 | 2018-04-01 | (null) |
This function produces a fully open ended interval where both the start and discontinue dates could be null indicating that it's effective for all time. To use this in your query simply reference the Effective_UPCs CTE in place of the Item_UPCs table and add a couple additional predicates to take the effective dates into consideration:
SELECT DISTINCT
s.SiteID
,id.Item_Key
,iu.Item_Desc
,iu.Item_Department
,iu.Item_Category
,iu.Item_Subcategory
,iu.UPC
,iu.UPC_Discontinue_Date
,id.Trans_Date
,id.Purch_Cost
,id.Purch_Qty
FROM Inventory_Details id
INNER JOIN Effective_UPCs iu
ON iu.Item_Key = id.Item_Key
and (iu.UPC_Start_Date is null or iu.UPC_Start_Date < id.Trans_Date)
and (iu.UPC_Discontinue_Date is null or id.Trans_Date <= iu.UPC_Discontinue_Date)
INNER JOIN Sites s ON s.Site_Key = id.Site_Key
Note that the above query uses a partially open range (UPC_Start_Date < trans_date <= UPC_Discontinue_Date instead of <= for both inequalities) this prevents transactions occurring exactly on the discontinue date from matching both the prior and next Item_Key record. If transactions that occur exactly on the discontinue date should match the new record and not the old simply swap the two inequalities:
and (iu.UPC_Start_Date is null or iu.UPC_Start_Date <= id.Trans_Date)
and (iu.UPC_Discontinue_Date is null or id.Trans_Date < iu.UPC_Discontinue_Date)
instead of
and (iu.UPC_Start_Date is null or iu.UPC_Start_Date < id.Trans_Date)
and (iu.UPC_Discontinue_Date is null or id.Trans_Date <= iu.UPC_Discontinue_Date)
==Edited to include outputs from each server==
==Edited to include additional table definition information==
I am attempting to integrate an application running on a SQL 2008R2 database, with a new application that runs on a SQL Server 2017 database.
This is undertaken by SQL scripts that are run as stored procedures on the 2017 database to copy information across from the 2008 database.
The SQL script below works perfectly fine on the 2008R2 database (in management studio 2014) and uses the for XML command to produce a string list of 1's and 0's that correspond to a week that an activity occurs. 1= occurs, 0 = does not occur, with this script being part of a larger SQL script.
When I run this script within SQL management Studio 17 on a 2017 server with the 2008R2 database setup as a linked server, the script runs but the FOR XML export just returns a sting of 0's and is not working as expected.
I've looked into the For XML command and I am not aware of it acting any differently on different versions on SQL server.
I also have another 10-15 integration scripts (though none of the others use the for xml command), that work perfectly well between the 2008 and 2017 database where the 2008 database is a linked server.
I can individually return the information from the tables via the linked server, but when I attempt to run the query the activity id returns successfully but the code string does not.
I am having to use the for XML script as the old database records each occurrence of an activity as an individual line, while the new system records one record for the activity and then records a string of 0's and 1's that work as a week pattern to say if an activity occurs or not.
I don't know if it is the use of the for xml command itself or the fact that its being run via a linked server.
In the script below I have removed the references for the linked server and the database name for security reasons, but as mentioned the script works perfectly fine in my 2008R2 environment.
When run in 2008 I receive the below output
+------------+-------------------------------------------------+
| activityid | code |
+------------+-------------------------------------------------+
| 59936 | 11111110111111100000000000000000000000000000000 |
+------------+-------------------------------------------------+
When Run in 2017 I receive the following output
+------------+-------------------------------------------------+
| activityid | code |
+------------+-------------------------------------------------+
| 59936 | 00000000000000000000000000000000000000000000000 |
+------------+-------------------------------------------------+
The vw_AcademicWeeks element is a view which picks up the following information
+----------------+-------------+
| Field | Type |
+----------------+-------------+
| ay_code | varchar(4) |
| week_number | int |
| ay_start | date |
| ay_end | date |
+----------------+-------------+
This returns for each week within an academic year the start and end date of the week (example shown below)
+---------+---------+------------+------------+
| ay_code | week_no | ay_start | ay_end |
+---------+---------+------------+------------+
| 1718 | 1 | 01/08/2017 | 06/08/2017 |
| 1718 | 2 | 07/08/2017 | 13/08/2017 |
| 1718 | 3 | 14/08/2017 | 20/08/2017 |
| 1718 | 4 | 21/08/2017 | 27/08/2017 |
+---------+---------+------------+------------+
The TT_Activity table is setup as below
+----------------------+-----------+
| Colum Name | Data Type |
+----------------------+-----------+
| ActivityOccurrenceID | int |
| ActivityID | int |
| StartTime | datetime |
| EndTime | datetime |
+----------------------+-----------+
This table contains multiple rows for an activity, with different start and end times i.e. if an activity occurs every day at 9am, there would be five entries for a week
+----------------------+------------+---------------------+---------------------+
| ActivityOccurrenceID | ActivityID | StartTime | EndTime |
+----------------------+------------+---------------------+---------------------+
| 2214753 | 65577 | 12/07/2019 13:30:00 | 12/07/2019 14:30:00 |
| 2214752 | 65577 | 05/07/2019 13:30:00 | 05/07/2019 14:30:00 |
| 2214906 | 65583 | 02/07/2019 14:30:00 | 02/07/2019 16:00:00 |
| 2215967 | 65613 | 02/07/2019 14:30:00 | 02/07/2019 16:00:00 |
| 2226569 | 65949 | 02/07/2019 14:30:00 | 02/07/2019 16:00:00 |
| 2226754 | 65963 | 02/07/2019 14:30:00 | 02/07/2019 16:00:00 |
+----------------------+------------+---------------------+---------------------+
The TT_Activity field contains the basic information for an activity and contains a single record for each activity
+-------------+--------------+
| Colum Name | Data Type |
+-------------+--------------+
| ActivityID | int |
| Code | varchar(40) |
| Description | varchar(255) |
| PeriodID | int |
+-------------+--------------+
Which contains the following information
+------------+---------+-------------+----------+
| ActivityID | Code | Description | PeriodID |
+------------+---------+-------------+----------+
| 20668 | Maths | Maths | 2017 |
| 20669 | English | English | 2017 |
| 20670 | Science | Science | 2017 |
+------------+---------+-------------+----------+
==SQL Query Below==
select
tta2.activityid,
(
select
case when ttao.endtime is null then '0' else '1' end
from
vw_AcademicWeeks aw
left join
TT_ActivityOccurrence ttao
on
(dateadd(dd,datediff(dd,0,DATEADD(dd, -(DATEPART(dw, ttao.StartTime)-1), ttao.StartTime)),0)) = aw.ay_start
and ay_code='1718'
and ttao.ActivityID=tta2.ActivityID
where
aw.week_no>=6
group by
ttao.ActivityID,
aw.week_no,
case when ttao.endtime is null then '0' else '1' end
having
count(aw.week_no)<>9
order by
week_no asc
FOR XML PATH(''))as code
from
TT_Activity tta2
where tta2.PeriodID='2017'
Having looked at the code again and pulling it apart I've found the cause of the issue.
The language of the 2008 R2 server was set as British, while the language of the 2017 server was set as US-English.
This was causing the vw_AcademicWeeks view to create start and end dates of a week that were wrong, as such the formula string below was returning the incorrect date which was then not matching up.
TT_ActivityOccurrence TTAO ON (dateadd(dd, datediff(dd, 0, DATEADD(dd, - (DATEPART(dw, ttao.StartTime) - 1), ttao.StartTime)), 0)) = aw.ay_start
Solution: I had to add a WHERE clause to both SELECT queries.
I'm working on a SQL Server 2008 database.
I'm trying to write a SQL query to return a resultset like that :
date | turnover_centrale | turnover_public
+------------+----------------------+--------------------
| 2017-02-14 | 233.34 | 383.83
| 2017-03-14 | 142.81 | 166.8
| 2017-04-14 | 173.25 | 250.51
| 2017-05-14 | 186.96 | 245.08
| 2017-06-14 | 61.26 | 97.67
| 2017-07-14 | 262.98 | 356.16
| 2017-08-14 | 89.88 | 162.38
| 2017-09-14 | 250.32 | 381.47
| 2017-10-14 | 386.06 | 581.96
| Total | Result of the column | Result of the column
My SQL query is :
SELECT
CLC_DATE, CLC_PRIX_CENTRALE, CLC_PRIX_PUBLIC
FROM
##.dbo.CLIENTS_CONSO
WHERE
CF_USER = :ref
UNION ALL
SELECT
NULL, SUM(CLC_PRIX_CENTRALE), SUM(CLC_PRIX_PUBLIC)
FROM
##.dbo.CLIENTS_CONSO
The result of the last row is unfortunately false :(
So do you have a better solution ?
Thanks a lot!
Considering that each date only has one entry, perhaps:
WITH VTE AS (
SELECT CONVERT(date,[date]) AS [date], turnover_centrale, turnover_public
FROM (VALUES
('20170214',233.34,383.83),
('20170314',142.81,166.8 ),
('20170414',173.25,250.51),
('20170514',186.96,245.08),
('20170614',61.26 ,97.67 ),
('20170714',262.98,356.16),
('20170814',89.88 ,162.38),
('20170914',250.32,381.47),
('20171014',386.06,581.96)) V([date],turnover_centrale, turnover_public))
SELECT ISNULL(CONVERT(varchar(10),[date],120),'Total') AS [date],
SUM(turnover_centrale) AS turnover_centrale,
SUM(turnover_public) AS turnover_public
FROM VTE
GROUP BY [date] WITH ROLLUP;
Thanks a lot !
My table is build like that :
https://i.imgur.com/csXl8vl.png
<br>
CLC_DATE = the date of consumption, it's corresponding to each month in a year.
<br>
CLC_PRIX_PUBLIC = the turnover public
<br>
CLC_PRIX_CENTRALE = the turnover centrale
The WHERE clause is important, beacause its the gateway from the client and his consumnption
With your solution, it doesn't work.
I got 1 table which is dbo.Invoice. My current query now is able to select "SalesRef" that does not have invoice for "Mvt_Type" = '122'. However, I need to extend my query with PostDate field.
My problem is current query still display an SalesRef that does not have invoice for "Mvt_Type" = '122' with Postdate today( 8/8/2017). My expected result is it can only be display if no invoice was made more than 2 days after the Postdate. So, it suppose to display on 11/8/2017 or more.
Table dbo.Invoice
| PO_NUMBER | TYPE | MVT_TYPE | QUANTITY | SALESREF | DEBIT | POSTDATE |
|----------- |------ |---------- |---------- |---------- |------- |------------ |
| 10001001 | GR | 101 | 1000.00 | 5001 | S | 2017-01-08 |
| 10001001 | GR | 101 | 2000.00 | 5002 | S | 2017-02-08 |
| 10001001 | GR | 122 | 1000.00 | 5001 | H | 2017-01-08 |
| 10001001 | INV | 000 | 1000.00 | 5001 | S | 2017-01-08 |
| 10001001 | INV | 000 | 2000.00 | 5002 | S | 2017-02-08 |
| 10001001 | GR | 122 | 1500.00 | 5002 | H | 2017-02-08 |
| 10001001 | INV | 000 | 1000.00 | 5001 | H | 2017-01-08 |
Below is my current query :
SELECT *
FROM dbo.INVOICE i
WHERE MVT_TYPE = '122' AND SALESREF IS NOT NULL AND POSTDATE > CONVERT(VARCHAR(10), dateadd(day,2,getdate()),101)
AND NOT EXISTS (SELECT 1
FROM dbo.INVOICE
WHERE DEBIT = 'H' AND MVT_TYPE = '000' AND SALESREF = i.SALESREF )
Expected Result is same like below. But this time need to add PostDate.
| PO_NUMBER | TYPE | MVT_TYPE | QUANTITY | SALESREF | DEBIT | POSTDATE |
|----------- |------ |---------- |---------- |---------- |------- |------------ |
| 10001001 | GR | 122 | 1500.00 | 5002 | H | 2017-02-08 |
If PostDate is DATE or DATETIME, instead of casting you could use DATEDIFF function to get the days between two dates and do the INT comparison:
WHERE DATEDIFF(DAY, PostDate, GETDATE())>2
If PostDate is varchar, stored in the format shown in the OP:
SET LANGUAGE british
SELECT ....
WHERE DATEDIFF(DAY, CAST(PostDate as datetime), GETDATE())>2
EDIT: Apparently DATEDIFF will work if PostDate is VARCHAR data type as well
DECLARE #PostDate VARCHAR(50)
SET #PostDate='08-01-2017'
SELECT DATEDIFF(DAY, #PostDate, GETDATE()) -- GETDATE() is 08-08-2017
-- Returns 7
Having said this, it is a good practice to keep Dates and Times as proper data types. In your case, you could change the data type to DATE, if possible. Will speed up lookups
EDIT 2: Please note, SQL Server works with ISO 8601 Date Format, which is YYYY-MM-DD, but the dates in OP's example, even though as per OP refer to dates in August 2017, are given incorrectly (referring to Jan and Feb 2017) and are stored as varchar. For correct results, these need to be either converted to DATE/DATETIME data type, or reformatted with the correct ISO format.
EDIT 3: Showing an example of casting OP's date format into proper, ISO format before calling DATEDIFF:
SET LANGUAGE british
DECLARE #PostDate VARCHAR(50)
SET #PostDate='2017-01-08'
SELECT DATEDIFF(DAY, CAST(#PostDate AS DATETIME), GETDATE()) -- GETDATE() is 08-08-2017
-- Returns 7
And the WHERE clause would be as follows:
-- In the begining of the select statement
SET LANGUAGE british
SELECT *
FROM ...
WHERE DATEDIFF(DAY, CAST(PostDate as datetime), GETDATE())>2
Is the POSTDATE - date column? If no then you are comparing strings and the result is as expected as '2017-01-08' > '08/10/2017' ('2' > '0'). Most probably you just need to cast the POSTDATE. See the example:
select
case
when '2017-01-08' > CONVERT(VARCHAR(10), dateadd(day,2,getdate()),101) THEN 1
ELSE 0
end without_cast,
case
when CAST('2017-01-08' AS DATE) > CONVERT(VARCHAR(10), dateadd(day,2,getdate()),101) THEN 1
ELSE 0
end with_cast
So what you need is:
SELECT *
FROM dbo.INVOICE i
WHERE MVT_TYPE = '122' AND SALESREF IS NOT NULL AND CAST(POSTDATE AS DATE) > CONVERT(VARCHAR(10), dateadd(day,2,getdate()),101)
AND NOT EXISTS (SELECT 1
FROM dbo.INVOICE
WHERE DEBIT = 'H' AND MVT_TYPE = '000' AND SALESREF = i.SALESREF )
Your problem is that you store a date as a varchar.
To compare 2 dates correctly you should compare their DATE rappresentation, not strings.
So I suggest you to convert your varchar to date, i.e. instead of
CAST(POSTDATE AS DATE) > CONVERT(VARCHAR(10), dateadd(day,2,getdate()),101)
you should use DATEFROMPARTS ( left(POSTDATE, 4), right(POSTDATE, 2), substring(POSTDATE,6,2)) > dateadd(day,2,cast(getdate() as date));.
DATEFROMPARTS function is available starting with SQL Server 2012, let me know if you are on the earlier version and I'll rewrite my code
I've checked through here on how to use group by to count rows, but I think I am implementing it wrong, or missing something.
What I have:
Two columns with data. Machine_GroupID ( I extract the client name from this). The second column, fieldValue, contains the ship date of the Machine(I extract the year only from this).
SQL 2012 server
Example:
+------------------------------+-----------------------+
| Machine_GroupID(Client Name) | fieldValue(Ship Date) |
+------------------------------+-----------------------+
| Site1.clientA | 2015-05-07 |
| Site2.clientA | 2014-01-06 |
| Department.Site1.clientA | 2015-02-05 |
| Site1.clientB | 2014-03-04 |
| Department.Site1.ClientC | 2015-10-01 |
+------------------------------+-----------------------+
What I am trying to do:
I am trying to generate a report to show all of the workstations a client purchased in a certain year. This will end up being a report in reportviewer, or something useful to display the data to our Executive team.
Desired Report example:
Machines purchased in 2015
+---------+------------------------+
| ClientA | 2(Count of fieldValue) |
+---------+------------------------+
| ClientC | 1 |
+---------+------------------------+
Machines purchased in 2014
+---------+---+
| ClientA | 1 |
+---------+---+
| ClientB | 1 |
+---------+---+
My Code so far:
select count(*), reverse(left(reverse(Machine_GroupID) ,
charindex('.',reverse(Machine_GroupID))-1)) as Client ,
LEFT(fieldValue,4) AS "Ship Year"
from dbo.vSystemInfoManual
where fieldName = 'Ship Date'
group by fieldValue, Machine_GroupID
This code generates a table that looks like the following:
+---+----------+-----------+
| | Client | Ship Year |
+---+----------+-----------+
| 1 | ClientA | 2015 |
| 1 | ClientA | 2015 |
| 1 | ClientA | 2014 |
| 1 | ClientB | 2014 |
| 1 | ClientC | 2015 |
+---+----------+-----------+
Is there a change that I can make to my code to make this possible ? Am I trying to do too much with this query ? I am still learning SQL so any help is definitely appreciated! Thank you.
Your sample data doesn't really go along with the results you posted. Anyway, I would change the way you are getting the Client value by using the PARSENAME function:
SELECT PARSENAME(Machine_GroupID,1) Client,
LEFT(fieldValue,4) [Ship Year],
COUNT(*) N
FROM dbo.vSystemInfoManual
WHERE fieldName = 'Ship Date'
GROUP BY PARSENAME(Machine_GroupID,1),
LEFT(fieldValue,4);